请选择 进入手机版 | 继续访问电脑版

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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART% J# R$ S0 |4 Y

! J1 C( N; G+ O; g前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
3 r) V1 `0 M8 |6 S2 C& V8 H2 j
& Q# U0 h0 P, H/ @9 a串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
/ j( p0 Y: d, |0 t/ T8 F9 F0 i3 t9 k3 H7 J+ F: i4 n% D& B- U
我们用 Keil 打开下面这个工程:/ R) u/ _) w7 @) @) i
7 h" Q' Z( Y. c5 n& {* E
STM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
2 F- P) f6 _& o# ^$ F# v! `
  }' s" G* `9 W3 A+ L这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。3 Q" d+ R7 b9 X4 w4 d

& u: n6 E% }1 i
20200605173429966.png

; E" F8 ?+ C2 P/ _
20200605173429770.png
1 m% H" d& I8 m6 x6 D9 w/ g
& Z& G2 J& ^- w) Z7 k: m5 {

# c' ?9 K' R( g! G4 E% T2.UART 的初始化
. m/ q5 L; _: `2 r! v4 A6 |) h9 s  W# I; L: j' T( l* f7 @. X
我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?  V3 m! z3 i; J$ k. ^, ~9 ]
$ S( k4 m  |& {
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。8 b3 ]& |% V" F1 T- w; j
/ Q* G8 |5 U; F( w: F: U+ i
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
  t: u# x7 ?( d6 j6 s
5 s/ V6 }8 ?4 G7 h  |3 WHAL库里面对应每个硬件模块有两个函数 例如:) P' ]2 t; N3 {5 k

$ s. |5 J. X1 L, jHAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
: k! U( x5 K* @. [8 ~$ J/ ~  ?2 N3 _1 l. \
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
% Y: I8 H. {; o+ c% K1 t0 I& i( Q& m/ x( O* o) F
20200605173429617.png
7 x" Z; H7 n; a9 A/ i# ?# I7 ~
: C, v: w3 p- G! I; L0 _
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
3 `. b  i# H, ]1 v9 n/ B5 i2 Q  W* ?# x5 C: ~
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。% q9 U/ N3 c2 G0 g& q$ P

2 Y3 t  l5 Y1 x$ H+ V# L7 H3 D4 }1 w
20200605173429721.png

) T0 X  B$ K: T
/ b; C% x4 v. r  p5 z' ~& a- R! C( U& S+ r
20200605173429793.png
1 s) S, L& m6 v/ q; T

) p4 n1 M! E3 ~) V. t% @! ?5 q/ O* }0 j5 w( y
3.熟悉 Handle
+ t. }3 v  [6 d, {* d" `
7 {2 _7 L8 ]( x- D; ?, Q7 f, u+ G
跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:# A5 N7 {0 s1 x! {& t. f  [

# W, \2 c: @+ \: f6 G
20200605173429636.png

1 R* r8 ?" ~% S6 E& I" L: F  j. j4 b7 p' W
typedef struct __MY_TypeDef
9 f) ^9 l' K- n2 e  p  s; }% _
{
/ d. l( {% L# U4 D    uint8_t    Var1;! x  \' H& }9 J% D( D

% }+ R, _1 T/ y) e$ C5 r7 ?/ e    uint8_t    Var2;
0 R' `% q+ O' [6 v, E  ^9 u* `' T, \- b/ R$ P' u* c
    uint16_t   Var3;  M8 b7 J& O- c2 p: z' ^
' z" ~" J2 |2 v  p  i% o- V" b
    uint8_t*   Var4;
* c' ?! l- V- n8 B$ Y
1 ]( E# h( {7 Z}MY_TypeDef;
' c6 D. i4 H- _' p1 e7 @9 j8 Z/ S/ P8 O" c1 I2 u- ~3 T1 x
MY_TypeDef* MY_VAR;$ ^$ U- I( @' E2 O% Z
& s" e; V; G( A3 D
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?3 g; @/ D2 T3 q4 x. T- e

5 b2 F2 H4 e* ^+ n. K( BMY_VAR = (MY_TypeDef*) 0x20000018;
3 J7 I8 }2 d9 H' b* P2 C) g' U/ L
/ f. o1 T* O) A4 n
20200605173429681.png
- \' S9 ?2 Q& `/ T" }2 V
& ]  {6 R8 F6 W/ u! z) m
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
6 j" h8 A; U) M3 g2 o% n, Z( x! i: A, N2 ?2 c% ]) v& l* X
* MY_VAR->Var4 的值是 0x02。
9 r7 r3 G. l1 B: {3 |, A( k+ _, o9 ]$ f1 X; q1 D! b
把 MY_VAR 指向另一个地址:% @* O+ k& E; s3 O- u  y3 }- @, f

* B& W6 A7 U: Z* mMY_VAR = (MY_TypeDef*) 0x2000001C;
( a3 ]% N8 s- @' V# ~. Y2 e* n% B8 B) `8 m3 _: J6 g
20200605173429700.png
5 F6 w( t0 j5 p# Z+ M4 |8 d
3 g2 l2 }: i, m3 L- `, d( ^, o
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:. ]2 o& x) u) m( ]2 F8 J

0 y1 `- B7 \) _! ^$ I
20200605173429768.png

6 r" V. s: F5 P+ f( z3 T, H3 ]2 N2 ?1 n; k: G( M" m
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
; R/ @& {5 s+ j. u
; `4 t2 U* C& L- ?% ]: w- |4 [6 T. gUSART_TypeDef  *Instance;3 R2 G0 I% T4 r2 |/ D
: X  \7 H. X/ V% S7 w! a+ U: C
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
1 D" Q9 w  g4 W* n1 g: m+ m& ]: _5 X8 H9 l
UART_InitTypeDef   Init;
: X+ N# [/ r' y
- F- U; Y- ?6 u$ P+ @在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
& X$ o8 H; J$ F% @; D+ D
* ], E; H5 |4 G& F& D4 NUART_AdvFeatureInitTypeDef  AdvancedInit;
/ [8 G  y8 V# ~, `6 `! w5 W, C! ^! p  h
串口扩展功能初始化参数。当前未用到扩展功能。
' e0 y0 E/ f3 z1 V7 G: O$ a* [# \# o2 `4 h) Y; H' K
使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
" B: j0 J, s' B
2 t4 V. [9 n. E- `HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);+ ?; b+ X0 l/ {! x

# V# n+ f; U0 {' M$ r&UartHandle 为 UART1 对应的 Handle 的首地址。
, S9 @0 I8 r, i( d: ^( E6 Z, R: K: h, q+ L
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
0 G: k; ^& P+ r* D7 g) P6 ?( V! z+ C5 v* Z/ d0 }' m
需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:6 l3 S/ ~$ D! x' l4 Q2 C) o% f8 D
7 }. E  I1 U! W& q; y
GPIO7 R) B0 t3 M& _
. @: R$ T) Y& `$ v
SYSTICK* W/ u# H- W  J7 O2 ?: A3 U
4 Y$ j2 J& J$ k9 E4 @/ B
NVIC9 x  S) c& M. k6 c, x/ y

. J0 u7 L& T) u2 h& b1 X* Y. WPWR
. V# i* {& {  T$ v! _: @$ w4 I7 ]
RCC3 i4 g4 X4 G0 n+ S. M- G

, n; N. D/ y# v  z% v  _0 s* b* _FLASH.
+ l. D2 }6 h: `7 N; U# P: \- ]" ~( P; j4 h$ X5 n( U
, P' [( d' f3 p! F& I$ ^3 _/ G
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版