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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
% |" I+ M$ W, M5 d& k* E  G- T
0 y- Q4 K. f. E9 a4 i前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
  C8 v* G2 d" d6 S( \: a+ ^
' r; G5 w( U9 U3 m7 |  c1 h" z0 I8 _串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。1 \" r8 b  b4 i7 q
# g* u+ J5 x9 V' N2 S: H
我们用 Keil 打开下面这个工程:
+ O( o& v5 |) }; c5 {8 r
5 _& y- @: T, h( g+ }  y$ R  I  hSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx. b3 f4 q2 g, u& k

; Q. I2 C9 ?* E" Y" I9 p; E这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。
2 S. x& n) s3 K4 e8 ^1 B2 y
) U3 i. s6 {9 p4 a2 `4 @
20200605173429966.png
( t% E) A9 m$ w; L& i+ R, ]* Z% O
20200605173429770.png

6 o' E: w! f$ y# X3 t6 q/ [+ s8 A3 V  P

: }  w& ^' o; v2.UART 的初始化4 L2 A6 D, B) z( g( N

6 v! N) \, M) k/ ?. G我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
* \' t6 d# h8 w3 B
2 L, S) G6 a  C. Y0 f' I我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。; o  M0 i2 l0 b6 L& V0 I

1 P( }& n4 r- T' ]! xHAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
8 o* e1 F7 p. x2 ]& [# p& @4 p, d- p2 m, F) B7 y) s
HAL库里面对应每个硬件模块有两个函数 例如:9 l, P7 |, ^% W! P6 q& \: R2 N" f7 _
5 c% c) @7 m7 x' g4 o
HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
  w0 `3 z9 I: p3 J+ ]# D
* N) `$ Q3 s& A3 S% G0 ~, [HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。! s; ?, `+ _- l
8 B  j1 Z1 Y* h4 J+ A, I2 A, y6 x4 r' g
20200605173429617.png
7 H  `8 x) n' p6 q. F. t
4 K- X% e* z! _* A% K' G9 k
回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
) X+ }+ ]( A3 p$ H' F$ d5 {" s$ u, S, y7 x2 {7 ^& A+ A: @- i
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。
7 S$ E5 U# X& d) g1 `3 t; R4 V8 Q
& L: C( _+ K6 V& s' a' m0 c
20200605173429721.png
7 g' _% _  Q& I! j# ]5 S
* C, ?( d8 |" ?2 ^8 u
4 m% b' Z% h6 w) ]
20200605173429793.png

7 s3 ]9 |* e! d* O0 p) k- r$ T3 b9 g3 ?

3 `7 {" l+ i' [) S3.熟悉 Handle
* u4 }3 z! _+ C& R  x

$ C) n1 i7 e) ?' [% i' }: Q跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:9 a# k5 I) l1 S* ?; E

$ q* R9 w/ O: a6 y: @  e9 f
20200605173429636.png

. o* A; R+ P- E; C7 d+ I, y$ E9 n1 D) A4 k8 T4 Q; |8 }
typedef struct __MY_TypeDef
  w& i: Q+ e( o" M6 J
! R! c+ |0 `1 l1 \+ h5 a  z" F{
6 U+ C: k7 v$ ]    uint8_t    Var1;
. o, B$ a" x( o6 ]5 B* \9 t* s3 `" a
! ~# l# W3 F& [" D    uint8_t    Var2;
2 V, l# ?0 O% `6 h) h2 M/ e! N) T6 ^3 E; y7 k& d
    uint16_t   Var3;
: M3 X  S5 G/ R" W  N* Y; P- C" j
    uint8_t*   Var4;, X3 A- _8 u5 j

1 F4 z' Q' D5 [9 F$ E4 M! I! u}MY_TypeDef;: H' z+ Z( N6 L+ B! r+ @$ C

) [8 s. h- H& H1 \$ M$ FMY_TypeDef* MY_VAR;# x3 G9 a& C2 \( E  q( \
6 W7 N5 ?, o/ L, j& I* s
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?
" U, S( u% h+ ~2 _8 I+ q% v# A5 u) R3 a4 A0 I8 J: F
MY_VAR = (MY_TypeDef*) 0x20000018;
# J% C0 e  M& K3 k0 c
4 O" {2 M, u- J
20200605173429681.png

/ z( R: C1 c8 x, E4 b4 N* z: }
, s( b0 ?6 r$ @5 ?6 M需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而7 a3 q) ~# b) s2 A

+ r; N5 R! Z8 p/ ^6 E* MY_VAR->Var4 的值是 0x02。
4 q8 c; J2 f7 b9 r5 i* k7 n0 R8 @& e3 H
把 MY_VAR 指向另一个地址:6 Y, X; ]6 Q6 ~( O& y

6 B1 G; h$ D8 X5 R. j2 ?# tMY_VAR = (MY_TypeDef*) 0x2000001C;
8 L! o0 c# Z1 Z+ y$ q! ]' r6 E$ O; V* Q7 X# P  Y- S3 l0 K7 _
20200605173429700.png
# m8 s4 i) R. T( I
9 s+ D3 A+ c, G0 d1 v" U/ D
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:' P& }4 f( Y  y/ X3 b
  W6 `( e9 j. K( _( e5 j
20200605173429768.png

* ~4 M- e$ Z; u5 j6 k
9 v) f! R  w$ i9 W& X4 H我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:& j( S: w% y* {+ K- R& O

- z. X8 ^* Q) [4 |1 O6 G. P, _USART_TypeDef  *Instance;
/ r( V! H4 i1 m5 E% h2 `2 u5 x1 o
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
% u$ D* q9 k6 `5 e, r' l* m# z3 A; l: w
UART_InitTypeDef   Init;  [2 ^' u: M) D+ Q! I  M
1 @2 s6 y( P' k6 G+ d! F$ u
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
: U& d- f2 v7 F7 ~) d/ Z6 Z5 G7 F4 u  V9 l& n2 Y
UART_AdvFeatureInitTypeDef  AdvancedInit;
% O; a3 \. }* Y0 r6 q2 \! O/ K. {, I8 F
串口扩展功能初始化参数。当前未用到扩展功能。; \- ^+ L$ f+ e( D
8 h5 M  e: s+ Z, O4 ?
使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:6 [4 a1 h4 l9 I  K8 x

3 B4 o; S0 Q* U, ~# ]7 ~) nHAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);; e2 l, z+ s8 z+ F2 d, v9 Y

3 _& D$ v6 a. U2 b, y8 _/ t, |&UartHandle 为 UART1 对应的 Handle 的首地址。
3 A2 {8 o0 j7 ?, y0 G6 ~& x7 N; W2 v* f
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
; N* I+ d" ^' u, I9 u1 W  Z& y% u- f. n' ~' V1 n* X) t& k
需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:' f$ i/ W& K9 T! {+ K9 D( `

: m$ |8 e* P/ ?3 V3 sGPIO
  ?9 \0 s# C7 v& H7 D
- I% _  \8 Q# vSYSTICK
0 Z  `) ^+ t  {
+ f9 R; q% v1 N- E4 N  PNVIC
7 A' h' w3 V7 Z8 Y' a5 g- o
1 P- t5 l$ X9 R2 Y, d; H) \PWR$ D& ~& k  A5 L: x

" ?1 d  e( |6 M: @5 x$ [1 k! M) [  JRCC
5 _. w) W1 d8 o) F! d. D" q4 [; B/ C8 K% R4 U" D  x
FLASH.
# o3 F! S$ Q- y5 S: @' g% f. p. D7 a) z# r, L: d0 r
8 K, d) G* `& g" H! A0 P. j1 \
收藏 评论0 发布时间:2021-11-25 15:00

举报

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