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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART- q  Y9 S& M9 |# ]
! A% @9 m4 _- w) S& r
前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
& r" [# t& T6 ]) f. y
5 C( u* z! s. u+ }串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
! ~7 F( p, Y* Z5 `& z+ n
; Z! a8 S8 Z+ A4 `. w我们用 Keil 打开下面这个工程:
' J; v+ {$ X* M0 M5 @. K
  G1 K& k8 d/ m3 E  v- m9 vSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx0 k0 r* B8 y6 z0 D- A4 W0 f
" I. `2 n% C% A1 A+ n
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。
5 F! C% Y, H" Z0 ]5 d1 w. M' _
  t" A0 i8 H  s$ @2 G: j; U
20200605173429966.png

) P( a3 q4 [* o/ I! J7 L, P
20200605173429770.png
4 }8 t- n2 A5 Y5 s9 H

; p: _' r& D5 g: `6 [
- s5 ~1 S; q$ _8 W2.UART 的初始化$ p6 x8 R2 P- ^7 Q$ F

) M: x" d8 Q$ ~0 H& Z) a; E我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?# |" D# K3 H$ ]& N% i) ^% f
# i; e7 x7 z  Q* V1 {) i2 s' Y
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
( Q! H) b+ R' E" K" O$ J6 H$ k& b' D7 a- W9 S5 g$ i
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。! F% |1 ^( K; j% B& m" ^& A

5 w. n/ b2 u. mHAL库里面对应每个硬件模块有两个函数 例如:
+ g8 P' z3 D- ^+ b$ i9 k8 V( q- Z8 I4 O
HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。/ O( a1 q" W( a/ e& i- X% r. y
, e" b! e# N$ Z% k( N
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。5 ?4 d, E: c$ A) J  t

, |. ^4 f5 ~  j
20200605173429617.png
8 h5 n$ P: M  c$ c" G+ U
$ [. F: t3 Y& s
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
6 A, B2 G% Y9 p$ y) q
8 O; Y- v# J0 m2 ?' Q+ T9 ?0 [这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。  e/ Y, K) X, Z* [

, f$ u2 I9 c: H9 R8 Q- o$ `
20200605173429721.png

9 D2 i- U8 D6 W4 c6 j/ E/ [6 v. j" A6 W! y) ?6 E5 x7 F

, }# H& M, D4 U# w
20200605173429793.png

. b& M  x. {' m! w$ U7 i* q( y; h, \* A9 a2 ~" @$ H

9 t3 c# Z+ A/ I" O9 c6 A( `3.熟悉 Handle

, R: j2 }* V) p+ f5 w+ J2 C1 G# y. \" ?5 @7 B
跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:0 ~, s) D) R! S! I2 ]0 _

  h) O; x/ D0 j% [" X2 A7 j
20200605173429636.png
7 |0 P( u' ]. N) e- s* A

6 S7 X: J3 m. O# a# W5 y typedef struct __MY_TypeDef
) I6 N. K. O# p" r
* C) l) [$ s& }7 t; R+ L{
; N1 r% k+ @) Z  X- S- y/ S6 m    uint8_t    Var1;
) ?4 g" ?' T: G! N% r" U' v% e$ x3 s2 D0 `; r5 o; H  \2 @9 C
    uint8_t    Var2;
" o- u* [7 d0 C$ f! q8 w. F$ o* q) N/ L; T- r+ Z
    uint16_t   Var3;
! ^' T& z3 X9 O7 S) H
7 ~1 X3 V6 Z8 n    uint8_t*   Var4;* m' c8 ~% C8 G& |$ f
# e) Z) [% @' u" J
}MY_TypeDef;
$ J) O$ g7 M& f& f( F" ?0 G, f& p0 J$ p4 H
MY_TypeDef* MY_VAR;
" h3 M- ^# L1 g; ]
. H& ^7 a. w3 E- DMY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?3 n$ W# e& r! {  m4 u' o

2 k( ]  F2 ^7 @- nMY_VAR = (MY_TypeDef*) 0x20000018;
0 ?% C6 b; Q. f
, b- Y4 T. x$ e' s  Z% j! c0 U
20200605173429681.png

: a& H  r) }, _: k/ p3 k8 `5 W. i, c  L9 O3 _
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
5 |* _  r  X; X" \1 U) \1 ]7 P5 v" L' b( x5 A, P8 A& p
* MY_VAR->Var4 的值是 0x02。$ `& L( y2 D4 W+ R( H8 Z/ q  d. L

" O+ y" v# k  b把 MY_VAR 指向另一个地址:
  O9 v5 i+ x5 O4 n# }. H) A
0 t" E0 ?# E; AMY_VAR = (MY_TypeDef*) 0x2000001C;  N$ m4 F4 C( z# H2 U7 a9 ~
: t' C4 f% N3 n
20200605173429700.png
/ Q% p- d# Y9 b

+ n1 t  k9 |- A3 }, O9 G与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:
' [  P. L+ h3 p6 c* ^! \. P( ], E+ g+ m" x6 B7 n7 q  S# {
20200605173429768.png

( p# q6 e- a# I5 J4 U2 u+ }" d" t* o2 X! z$ ?
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
+ ~$ }  C% m4 N, J# ]7 n4 I" W+ H4 q1 E" L0 }4 n" j
USART_TypeDef  *Instance;- D, X, ^8 T5 m' b7 T1 T% r

( I, O: q/ `; w( U$ \USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
: d& Z) k0 `/ K2 s! Q1 K# X& a) q+ i( q* v/ U7 i& f6 q  y5 J
UART_InitTypeDef   Init;' |/ ?! B4 @2 p# G9 y! _

, z0 ^4 v7 U! W在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。0 }  ?7 a7 g! M" [! l" K
, P! A$ D  j2 b- u' @  y
UART_AdvFeatureInitTypeDef  AdvancedInit;
, I: z: T  L3 t# }$ X1 i( ^
( z, p. z5 `0 s6 |* \7 k/ U串口扩展功能初始化参数。当前未用到扩展功能。% q, |# D) Z3 s; n% Y

& `* @  Y  K* r7 W2 r- B9 w使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
; {8 B- U- y+ k+ O5 Y4 J
7 d7 a% w7 N9 ?HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);8 E" c2 X8 T( Z  k+ S
, b8 n0 x3 S. m! c; b  S2 q5 \
&UartHandle 为 UART1 对应的 Handle 的首地址。
% E6 E1 ?6 N2 |" s) J
1 H; o9 w2 F# w& I' V$ _  ~) vHandle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。, o# Q$ Y; P% L
% `" x) b* \* d* U" X& s# C
需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
$ V5 J, \/ W5 h  d# B# q
% {1 Q4 s4 Y, z. @GPIO
' ^/ n2 Z. k& |5 _7 e! @# t/ ?/ \/ a! a; O
SYSTICK
- W8 E5 p4 u" T2 c
0 i/ }. G+ S2 V" n: {NVIC; M9 P. U! F0 v  }4 ?# e

0 d/ r0 R! E0 ]& YPWR+ V" @1 v, A8 H8 a

& Y" M2 X& h8 k* V" u) R) IRCC
* i, r5 c, `6 Q
* m  i  C" R  m) [FLASH.  [2 D3 F" V6 f5 B0 L9 ^

& I+ Y/ i: ]! O, r' l
, N; }- J( V2 t, D" E: U
赞 收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

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