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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
  F8 \5 f7 W/ _( S
  ^5 }3 w, @) [4 [% c8 ~前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
7 \( J/ Q5 P+ g) \- O9 H' O% ~" [! H7 D, ~4 N' e6 H' x
串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
/ P- M- b& E2 `/ H0 Q
/ V9 r/ J2 A+ G我们用 Keil 打开下面这个工程:  l: L) D5 q9 j  U1 o! y

8 a$ P3 E. P5 b+ \STM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx) D; d# K8 z& x$ I/ f& x* U) N
! v: h4 i4 H2 M9 E
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。7 Y4 C' x4 \3 P- t* W# ^2 ^% T

$ O$ X7 Y/ q* r# T3 N; ?
20200605173429966.png
) y, N/ P8 U  b1 d
20200605173429770.png

9 o2 L; g0 P7 ^) L& i) d# e0 J: \  B0 m8 Q
& V$ J2 z) \7 r. ]/ S! Y8 Z: N8 e* P
2.UART 的初始化
& D1 r5 @( V" Q& z5 P
2 b) \" z" q" j( n5 y- u我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
- C, a& {$ j7 i0 Q* Z$ l2 C4 V
) A% Z' ]+ S5 h' K' a. H我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
8 f4 Q, C6 g& \$ e0 M3 z5 e) ~
! I, r( F9 P: s7 V. DHAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
; M& T7 t4 P5 i7 h, q5 @3 ^% F0 r6 v* F2 t  {" g( i2 ^
HAL库里面对应每个硬件模块有两个函数 例如:
5 p' ]* v$ T  s
* p9 L! F( [/ Q) i5 i* O  WHAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
5 x- w9 n3 ?* Z0 q' d" X* v0 p# W& d& ?. i6 @
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
' U' J" m0 I. s2 [" D0 M9 t" E- Z% I3 x
20200605173429617.png

  N) B. ^3 c0 ?4 Z5 C. L$ E7 ~! t# ~, K& D
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
1 N% Z6 _8 r2 _/ c) M2 L' ?
: L- y" i- K3 e. O% ]: k! X这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。# w3 _& V9 k; F$ P$ t% n" P/ t4 s

$ K' l9 T) g7 K
20200605173429721.png
: Z- B' A2 ^! M% r/ C' Y) `

/ q, O" `0 x8 v; B, i
2 N% f" f& k/ o0 s6 M
20200605173429793.png

! q" @4 S9 b  w3 V& b% c8 {* `- |6 N. Y  @( |
9 i( s, E2 O7 }+ _6 D$ ]0 n- {
3.熟悉 Handle
! _) ^, e8 m  L3 h# w* F0 c

/ _, Y. k' F! B: R% L* `跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:
  l6 S) |: B& ?- C* U  e) Q8 w  U3 n$ W$ d) Y
20200605173429636.png
1 h% u1 P% u0 p1 }4 j
5 U- ?8 p6 M* P: g, B5 x9 P8 s
typedef struct __MY_TypeDef
- P4 }  L/ |1 k5 C! {: C3 A
4 R7 `* Z% ]" `( R- ]{
5 G. L% @' X" F: s8 k    uint8_t    Var1;6 M( @4 M5 I2 D3 V+ Q- R- B2 g

) T& u2 ^, n7 Q& B5 p/ ^. v    uint8_t    Var2;
- n! X6 |7 _7 e' g. T6 ^
9 n9 x, H! `  [8 k  E: y" C    uint16_t   Var3;* {; S5 |, Y. _# R: T3 V

2 {6 m& ]" G& X) x$ j3 g  K    uint8_t*   Var4;
/ ~5 \4 s" b% A( s5 X+ c. W' @7 h8 t  U
5 f2 s3 Z2 H* r}MY_TypeDef;6 h( w8 N  w7 f  k3 d* W

6 B0 I( L# F8 o# c. J9 YMY_TypeDef* MY_VAR;
1 a& u/ p/ A. _2 \  t9 F( J
. x4 @7 Q) _! X1 p0 iMY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?2 }4 n3 I* p4 {. g! L

& T- p% L% p" a' J* gMY_VAR = (MY_TypeDef*) 0x20000018;
! x# p7 [1 v2 X; n0 s  ]2 h" K1 u
( f9 n% {" U8 d2 [1 M) _* B# [( L6 k
20200605173429681.png
; k' `0 X, {. w2 a( U  ^
5 F  C% B7 |) X6 P, i) C4 D
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
7 u: m! {; B% V$ y1 p! r4 O
( m& C( ~) ~7 A+ Y- I8 t* MY_VAR->Var4 的值是 0x02。7 y( G# z$ L# P; i) C5 T/ L" ?( L

* ]( [2 R! \& f把 MY_VAR 指向另一个地址:# h) M" a1 v" u0 X" R4 O

  a" W& p  w# @1 H; \0 `MY_VAR = (MY_TypeDef*) 0x2000001C;! J$ f# c  ^* ]0 U- U
$ Q" I0 ]$ S% u+ S5 {
20200605173429700.png

: O- F/ T5 S2 e2 M; X9 E
" l- p" r7 f. @0 N7 I与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:7 M6 k. |! ]# h7 j; B
6 c+ M4 j' c5 I7 k& v5 }
20200605173429768.png
6 D/ ~4 g2 Y2 u) f3 H
0 U! S% e* i' e+ T
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:) t! r# ~7 b! p: Y0 t% ?6 Y
9 N, J/ f1 k- A# H% c+ ]
USART_TypeDef  *Instance;
6 O* U* u3 p- S$ o5 f1 G+ O5 z. v( t1 C2 I
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
0 j! Q/ U' i: S+ e) P! M& t  c. h1 x. z9 M
UART_InitTypeDef   Init;
9 {+ X, u- N- I0 H7 Z# O' d0 V1 Y7 @4 l! n8 C: Z8 Y
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。" [* q1 x8 l) N1 y! M) Z& d
, z8 ^' M3 A! Z7 g8 b5 @
UART_AdvFeatureInitTypeDef  AdvancedInit;
, F0 E( p1 |# n8 M) i3 G9 I- T' ]& F9 D2 Y- I* t
串口扩展功能初始化参数。当前未用到扩展功能。
" t& m/ ~3 X3 a" ]1 K2 \- R
2 f( z  i: B! W. s" N% X7 w使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:8 x2 O& H; o* P: _& f: E( |
' ?* [' a2 Y+ n( |
HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);! x$ u4 s; x7 w# Q3 @

& E, a& l8 V% |* ?& Q7 [&UartHandle 为 UART1 对应的 Handle 的首地址。9 G' ]" o3 ^9 R- h; d
0 k  @7 V0 C& N* h
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。% u! \( B+ |# s, N, G: n1 V

1 Z( \! `1 H1 [需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:% v5 C9 u$ ]) F" \& P

/ d, W" e* M/ n- D. C6 g$ YGPIO- G/ a( u4 [4 z
1 x7 T' a4 x" V. N, i
SYSTICK& H3 X  X: Z* Y% l. \
4 a3 o* ^& s, Y1 C
NVIC! U/ e7 P. w! j& `

! H! r: W6 d6 _5 C) G: NPWR5 `0 _. D: q7 ~! u
# z+ q. Q1 s+ d( i
RCC
6 A$ c$ N8 I% e2 u- f% O+ R) U. Z' a+ G8 s% v: {
FLASH.
9 d, R  ~0 u& Y, ^! N
: ]& Z9 t! y0 Z) N7 W) S: P) V1 r3 ^6 j% F0 I. L4 G
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版