你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F0单片机快速入门六 用库操作串口(UART)原来如此简单

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
2 J+ v1 {! i9 }' A
) U/ ], s! C9 k" [* I& T前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
5 Q4 J' j2 U/ E; g3 ^: T4 P
5 u: O9 J/ y3 Z% n$ t串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
7 g3 _3 Z) b& I2 G7 s7 y+ g7 p9 a7 Q
我们用 Keil 打开下面这个工程:# e% ~+ j$ Y* D1 \

: J5 H- F* l8 X, s# ?STM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
3 D( K+ q3 t; `
+ \- X5 F) `9 `2 F& _: D+ z& V/ _这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。* F. |7 F; q! D8 R( h

) ]  ?  w8 v" B3 J8 |. S0 y8 j3 H
20200605173429966.png

; K! S0 C+ j3 y. G0 P+ q0 ~
20200605173429770.png

* D7 M2 ~  ~" T+ B6 y* _/ L0 n4 p
$ V; K: j% ^& X! o7 r; Z5 b0 C  w# y2 B
2.UART 的初始化; ^" ^, r7 k9 e+ [7 V8 ^  N

2 o& g! Z" C6 u$ Q4 f我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
; x' w+ Z9 x- }5 [$ ~
5 J  O7 y  t* p9 n( G- u我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。1 |9 o* J/ f3 A! t0 ^/ [* C

7 l& q& U" _1 I( U, [1 H: oHAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。& L1 G9 \9 @  {4 K# K2 U

/ O  f3 \' K% H& mHAL库里面对应每个硬件模块有两个函数 例如:
. ^' J0 o) p5 }: T6 ]: y& Q4 C: h, C' d
HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。* V- Y0 p% l4 H( I! S& b( Q
2 a' S6 U7 _' c8 U
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。! W' U% R3 c8 w: Q. p1 `

. I( i: K! T3 ?* P& F
20200605173429617.png
+ p( p+ d5 s  u& V, H1 A$ J! x, C* O# [
* c& m8 W0 Z8 z: y- {( I$ k; U2 w! E
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
3 K9 \7 H" m5 Q) \& \1 N
! _( ]5 a1 V/ ?7 c2 z这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。
2 z% f, v* g6 r2 J; M: a1 Z1 ~. U! `/ N; G" j8 j+ O
20200605173429721.png
) e' m- t( n# u6 i3 [. Z6 q& z1 l6 l

. v4 r+ P$ @/ X7 S4 v0 M+ A+ t( d" x4 d1 u7 J1 x9 u* K& V8 D
20200605173429793.png
  g: a4 g7 I* e: x$ s, v% S. m( q

& ~3 o* z2 Z3 ]' J' d. h
/ h' Y& ~% \5 L+ Y/ I; ~7 G3.熟悉 Handle
! ?. ^0 S) E0 w2 w8 _
, \5 M7 w9 V- i- _$ n! [' i# r9 B- Q
跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:
# \7 P, M/ b0 W. X
9 V( j& e+ O' ~0 x- A* V, x
20200605173429636.png
3 p% m, e5 \1 G, O& L
6 N& m: Q4 ]# \. Q
typedef struct __MY_TypeDef
& F/ S4 ^1 h9 F* o" K1 H( i7 C- ~, Y3 m3 S
{( r; u' ]0 R5 F/ E* c
    uint8_t    Var1;
* r* Z2 E4 j2 y4 S" X
# p, c8 B8 o8 Q    uint8_t    Var2;
2 g6 G( \6 V$ o1 D: v
# p/ O5 N- _2 O) |    uint16_t   Var3;+ k% `) J. @; z# h$ L

/ D7 o4 Z. D. m( A, T& F" _! i* X  [    uint8_t*   Var4;
& ~: t. N0 N4 ?* R- n; U$ y; H2 `4 T' K9 ^; U; [+ e* i! M' j
}MY_TypeDef;
# w: B- {# \$ m, T
8 ~+ h% `$ @, N1 g3 NMY_TypeDef* MY_VAR;1 w! _& d- }) V3 Q2 s3 j

4 q' _. Y$ o+ d$ j" dMY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?3 n6 J6 O( v3 c. A( S
2 M4 V7 B4 Q3 o
MY_VAR = (MY_TypeDef*) 0x20000018;- Y9 B  p' O6 f7 b
* j5 B/ E+ H# C2 {7 k4 [5 v0 Q1 I
20200605173429681.png

) d0 |/ |6 K, h# M
5 X% D% n; ~1 h3 E' Q3 g+ N, T需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而- |+ L: R* a  h( J2 h, p# g
; f' V2 s1 y' t5 d, L" l5 e
* MY_VAR->Var4 的值是 0x02。* X  H5 Y* B( H: t9 Y' b
) }5 t, V8 l9 V% D8 `# I2 s
把 MY_VAR 指向另一个地址:
3 _+ K: @) R7 E, ~2 E3 Z
* h9 O4 h' Z) X  [: lMY_VAR = (MY_TypeDef*) 0x2000001C;
! m, z2 Q/ Y0 w
$ u/ S4 C8 u6 m6 T
20200605173429700.png
* M' y+ l" T  ?
" G# D7 z. E) }7 ]& I0 N& r
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:
) Z7 S2 P& K. }& ^$ A, v- F/ _2 n4 J4 u
20200605173429768.png
# X' @4 x* s0 Y6 A
5 s) A' K# t9 q4 p  G& n
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:$ u# P6 x  O! j
: l( W0 H) k  v! M
USART_TypeDef  *Instance;
5 E4 P1 `; a$ J6 ^4 ~. h
2 b9 r* h4 s# q% m8 g; B& U- bUSART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。- t* K. |# x* y% V0 ^

* [. I! K# x$ m5 z# XUART_InitTypeDef   Init;
& j9 [% J- P/ d2 B' F; b
- r5 w# @5 T5 i, T- u9 Q: g' J) L* a在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
/ \9 m9 b, \( e1 o; ~: a( G2 m" A0 N$ y( [( H
UART_AdvFeatureInitTypeDef  AdvancedInit;
% _4 u* p2 j1 B0 h
) A9 }$ U( K# B串口扩展功能初始化参数。当前未用到扩展功能。$ T/ R; a4 K$ G0 G
' k* {5 |  ~" f$ d. a4 M' G$ S
使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
+ ?  f5 r- A+ K1 Y  ^- \# r2 X8 U
HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);
  }+ G1 Q- f% I: A1 k
6 P( v8 f/ S4 Q5 E&UartHandle 为 UART1 对应的 Handle 的首地址。
- r) d& Y' G% e; i' h- i- ^
3 l0 I  ~7 M- v# z  b2 q: `Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。1 H: F" L' L6 W" @% U$ t& \5 ]4 _

% N/ U5 E; j8 e- y. n需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
/ j3 c8 X8 b( |# v9 n5 p  m. I7 p# P5 p
GPIO- _8 P9 B6 k+ G5 M( d$ P
  a, w) g& J( {6 X+ Y. a/ M( n
SYSTICK1 A% ~& f4 M8 e% s; F& Z

4 C2 z# `9 O, s, b7 a# |1 \) ZNVIC3 }9 T  D% l5 [& w. Y9 V

$ N; z. Q; {, G: ]PWR& D. P, A/ G: Q& ]+ z" \2 j+ {
7 C9 A" G, d7 `1 l
RCC
3 r8 a5 E' e: s2 \) r
+ f- p- N) f6 m+ _# u5 TFLASH.
; o/ z! N' @( A1 H3 b- |8 x( J. o, Q4 E% q
; k7 E& r& O9 Q8 N1 ^' C: H* S
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版