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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
7 l( M7 C7 w/ ]& G: X8 l4 S& o+ ~
' N6 }, I' e/ {7 w前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
! ]4 r$ l- r' ]2 l6 o0 R: X& K% [' A' \7 Y4 w# V- O
串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
' J, v# q/ D5 c$ I; Y4 p+ k$ |* v  V- `4 w
我们用 Keil 打开下面这个工程:; c- G  W8 Y, y; w6 x

+ h9 ]1 M/ r3 U* n$ USTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx0 O; ~) R" P; }; y2 O) G

: H2 @5 I7 \; e, i+ f这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。
1 e8 {$ w# |  p, y5 t( V* M7 p; S3 q6 B5 ?7 p0 c% X% H0 [
20200605173429966.png
/ f+ ?7 X: r1 Y. L. e) K8 k8 \
20200605173429770.png
  y  n# ^8 K# G$ G4 F
3 Y% k- g) j" f* [9 D5 c/ J

' P& Q9 O9 [  w! C% k  ^( ?2.UART 的初始化+ G% M; h. \6 _, r

, A8 w, Z6 W; s9 f: ~, \我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
& J, ]- g0 ~' w2 H1 F* |: c3 n2 ?4 B" K+ V# c, r
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。" d9 p7 U4 [! M. v: W6 y9 w
+ D  t% v  j2 B; [
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。/ d' ~3 r8 H' w& @% n1 w
" F* _4 E- |8 ?4 d3 ~: a
HAL库里面对应每个硬件模块有两个函数 例如:: j/ K& S- i, b% o/ d! H% m
7 A6 q$ }) N( d
HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
- Z: m% H4 A5 P$ u2 f8 h' o9 g. P$ E, g& g" d
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
' A& A; ?5 b; H# S3 c( i7 G7 G8 R% m: ?( N$ `* Q' g- D7 J9 P# T! b
20200605173429617.png
1 l& Z0 V- r: e/ m. T9 i
2 U. e  C4 f, {$ ~) y
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。  w+ M! M* s5 S

' p3 r( z! U9 D3 F这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。, _9 |8 Y' R% E
! r8 B( F4 z9 k, N9 H$ D
20200605173429721.png

/ p; I3 g+ ?3 d8 u
1 g1 L9 K/ D3 @5 u$ [
: Y/ W5 q% j# o/ ?
20200605173429793.png

; y+ ^) f; S+ P: r
" @* C- N% O& W8 m. ?; o! N; A$ k8 R, Y- p- |2 B
3.熟悉 Handle
& U3 h& a9 T, G) Q% P) u% @

& f8 A# f; z  U跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:
% \2 @$ B1 ]" y6 T8 w
) y+ B5 K! K0 d; I
20200605173429636.png

. o7 F, r8 x& w2 D/ j  _7 m. h' N  |5 W
typedef struct __MY_TypeDef2 h- ]8 q& h# f! s% a: [' e5 _
+ e- M7 W& G& B% o; j4 B
{
6 ]2 p& A. J1 N, n, w& q    uint8_t    Var1;3 q$ ^4 N* _/ a
: Z0 ], h5 Z: d3 ]
    uint8_t    Var2;
7 X. V3 B9 q" z8 s5 U1 F1 q1 o% p% j- O6 j9 ~
    uint16_t   Var3;
+ y' M& {% w8 v1 M9 \4 t! w
; n0 R# |# u. c) p$ |! \5 Q    uint8_t*   Var4;6 d  u0 ?, S* W- u/ l1 c8 h

' R! }& o/ Q: R: T5 }" c* x}MY_TypeDef;
  V5 U; D% G3 V) I% w
9 u4 ?. H3 [' y" v' ZMY_TypeDef* MY_VAR;
" B" {/ V2 |) U5 D. Q6 d# D. @1 ~0 }7 @
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?2 ?+ G- _: h3 k) t3 t2 G9 ~

2 m- A6 n: T/ b! i% m7 cMY_VAR = (MY_TypeDef*) 0x20000018;8 W& R! m8 m$ Z% o. {

$ ~4 b2 j4 @* y! q
20200605173429681.png

. Y9 g: Y8 v2 t& F; r. s1 y0 v# `4 `' [) Y
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
& {9 f7 y# f$ D( Y8 N( Y$ ?3 ^* \2 x! A  y" K2 @
* MY_VAR->Var4 的值是 0x02。
3 c# R. `5 Y* {+ u! |6 t# Q: f" P/ M1 B6 k
把 MY_VAR 指向另一个地址:  r  F; ?' m+ a5 e. l6 I
4 e4 w+ o# M5 l! \
MY_VAR = (MY_TypeDef*) 0x2000001C;8 H$ H& t  {5 K  v! \& x

/ b! ?* n: S: V: [) J
20200605173429700.png
5 h: l. X% y" w4 N& h( J" Q- I
$ K0 K: q0 n9 S7 _, @
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:1 n% `* V. A3 C+ D* q) A) ^
9 j7 m3 E- D6 `/ b
20200605173429768.png

, }( _; y1 B% s2 C
! z# H8 ~3 u, x2 {2 F我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
. \- \7 \  U% c3 |4 C' w
) F: J& a4 a1 c  HUSART_TypeDef  *Instance;1 C! _- r) a! I" V/ I+ O
" b+ I$ l" w7 h( E, J6 u
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
7 E# B( _$ V1 Y1 m0 ^! m2 v  z7 H/ o9 x" `7 `9 @
UART_InitTypeDef   Init;
( F+ y( w5 P) X, c5 ^
* W% H' I8 I1 r0 \2 z, R; P! a- Q在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
  I+ P9 _6 U% G% C
3 m& ?2 s, Y2 `( F9 z; dUART_AdvFeatureInitTypeDef  AdvancedInit;8 F+ ?+ m* t& }0 K. l
" a$ j( h3 i- K1 P8 M
串口扩展功能初始化参数。当前未用到扩展功能。
& `. Z  a. l* U
+ J; f' Z0 C/ F6 N$ A" `使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:6 {; t, l- F# i; Y$ j
: L  k0 k) T5 T8 A% E
HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);
& f! Z. ]: D! \5 O5 r; K
! Z# f4 l( p) e, v  s/ E# a# z&UartHandle 为 UART1 对应的 Handle 的首地址。. G7 t9 p5 U8 w% ^! W7 }

& ]$ \- g: v+ N1 |; K+ c5 ?Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。1 y9 j. E, e  e

* I& D9 F4 W4 H4 h需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
; v, f! o+ d" \: V' |2 D0 h+ J& o3 K/ G
GPIO
6 |, F! f; ?+ K9 K) c6 c1 B4 D- V2 ~* e
SYSTICK' o) f% _1 Z. S& d
# u( p+ ^# e1 B# J/ V& M  d
NVIC
8 y: f5 ~! j( R) U6 i. n
+ W6 P1 W! ]$ B% r) TPWR
4 l. i9 C) Y' ]! I+ p) V* w" y3 |: V8 O- z& B& y; t
RCC
3 O2 |* l3 A$ Z+ q& J7 Z; N
; M2 B6 @% i) ?8 q  k! eFLASH.4 O, `! F, f, y0 C* V
1 f" I- l; P% E6 Y! d( ^, h, n
  |" k& k+ o" r
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

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