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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART2 b8 I: J8 F& x/ w6 s9 s

) s1 @& J/ C2 J) Y前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。5 |6 y8 }5 q& g' Q( @! w6 p

/ f2 Z! o1 e' S/ j3 T* I; l. @串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
3 u! M, L% y' ~; W. g3 F5 e' \, u# @, z" p
我们用 Keil 打开下面这个工程:
6 {) R, L* n2 f3 }0 P$ C  {* L8 h
" H( v/ o7 x" {) CSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
7 A, \- S+ u9 @+ f/ Y  r/ e5 ^. }+ T  X8 B4 g- q: `
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。
) ?. {( W! R3 I5 e: V' y' D  d* L8 _! t9 v1 Y
20200605173429966.png
/ @  _4 Y- |; s9 J) Y) T
20200605173429770.png
1 G# P7 B, I2 P9 j7 e
4 R4 G2 @! ^/ S" b) S8 U

* c7 H8 _) q& h2.UART 的初始化
# K' C* f0 i+ V. Q, ?* p
- q0 o, c+ \/ ]$ d我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?3 N8 w% E# A  C* S: ~! S6 a+ D) \
" i4 ~. x& c% K, l$ b5 ]. q
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
/ }' A" k8 b/ p4 [+ K5 J0 f' @) [" P( j( F4 v
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。' g$ m- e' A, @& Z. |. B% p
0 p6 [$ N  X6 a# {6 h
HAL库里面对应每个硬件模块有两个函数 例如:
. g( [/ v7 ~- I. j+ K, z
4 {( P# Z% a4 fHAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。# B8 M2 O# I, O, S% v! W! I

* p$ e  K" p& b+ ]6 I5 n9 ?HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
  v: F' A/ {6 X9 \% }% H  h6 f8 [! C# A. ]( b- e
20200605173429617.png
0 j/ _' z8 {7 I. n- j" g9 S

4 l2 h. v2 N+ r' ?$ h) @  d回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
0 p- @, f. ~6 W% E, G
9 H' Y3 l0 @6 n+ u% b! d这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。6 ~0 @. ~" x( w4 R% @- J

9 M+ Z( N/ e" }! O
20200605173429721.png

+ G* |  u+ O1 d+ ~4 ^% e& r9 U8 b6 [3 A' P
5 O$ r# ^' c6 B1 \( q
20200605173429793.png
* |3 B* {7 ~, J  i& p  W7 n

* m7 w) d; n. J7 w9 `2 t  G- h3 W0 y
3.熟悉 Handle
$ p7 C. x: h8 j6 O9 W2 p) S
# D  K0 y( _; E- v$ H2 T; E
跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:1 t) ^! i. ~  {* b; s- d% K( c
! u7 F7 @+ r" g
20200605173429636.png
) A+ c6 E6 ]4 ?' M, K0 V& B

6 e. y* Y! w. W, r5 g& k& B typedef struct __MY_TypeDef
; R7 a, k7 x( w/ M$ y, V# K2 ]# ]: l- h& d
{: f6 R% K0 p$ k: l$ H
    uint8_t    Var1;
4 K9 @. r' o; U4 w: I4 T, V2 g! k) |( q; \7 Q
    uint8_t    Var2;
# r# d6 l: @' |1 L
" N9 s' P' U( f, H/ f    uint16_t   Var3;
. I9 _& x4 m0 X  i2 R. c) `5 b6 F- L& X# \1 s6 F
    uint8_t*   Var4;, L( \- b- r" s: d1 d  Q
. Z: W4 x8 ]  J8 A$ [
}MY_TypeDef;1 z5 v& U! \* W: c

0 f% ^$ ~& [3 N) W/ @MY_TypeDef* MY_VAR;
7 \2 M/ t' V4 d) N) j7 T% T0 L( i1 l3 d3 M
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?
/ K2 ^/ U9 J8 m
. t! U3 ?/ I3 z6 Z# G- bMY_VAR = (MY_TypeDef*) 0x20000018;* S. ?1 H, {, ^4 T% r2 y
! ]8 y5 k0 l. E" ?
20200605173429681.png
1 i3 D2 ^# `) Z
4 ]% g2 I  Y- @/ E" H+ {9 h
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而0 l" W6 [' o+ I4 u

4 B5 g% y  G& z. r0 B; C* MY_VAR->Var4 的值是 0x02。
9 U/ i/ r. r9 j1 T! H) {
/ l8 m7 x, I( N0 h2 x; b5 B把 MY_VAR 指向另一个地址:( Q: s/ T( v+ k/ z6 [5 {% ]
7 a  v! o8 [" I4 K0 W  y1 K. c0 n
MY_VAR = (MY_TypeDef*) 0x2000001C;
/ S% }  ], O' V; x, w3 \
0 K! W* b' }4 H6 |
20200605173429700.png
. y# k- N  Q5 u" B: ]! g2 X+ k
$ `( y% ^! c/ k% {1 u8 J1 {
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:4 M1 i' ^3 Q! S; k$ B" \
' {' N8 ?0 d. [5 \. e
20200605173429768.png
1 n. b' i0 ?& ?

. ]4 N7 G: R0 P# V/ b  G! g7 a我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
4 i- N9 ~2 Z& \3 U7 ~% _3 Y$ z6 \9 S% h& b) M
USART_TypeDef  *Instance;
1 R8 t+ F7 A1 [2 x4 v, \4 p3 }( C, z/ z
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。, P: i0 p: B4 ?2 k( D

- w! f2 X/ g. v+ ^; i$ vUART_InitTypeDef   Init;# R0 I+ D! e* m5 {* b6 j
( X3 f* w, S  E6 A+ F' y$ k# i0 x
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
* f9 h2 D6 H+ S  H8 n
& R5 k, C1 E( T8 [/ IUART_AdvFeatureInitTypeDef  AdvancedInit;' a9 y0 Q4 d# B1 f# t& h- O+ C
: o. F5 g" D4 C, q; F/ m2 Z  y
串口扩展功能初始化参数。当前未用到扩展功能。
% ~  {$ E( O% }1 P# m, U
# p3 u, `( T: k6 ~5 m' q, e使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:' ?8 c5 p0 V2 _/ Z6 W! _& I: N& S

/ n7 l- m1 e% x9 HHAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);/ `( p9 @( m6 q8 Y% k

2 C9 a) n) G$ Y% H. m3 L; o4 v' W&UartHandle 为 UART1 对应的 Handle 的首地址。+ H; {: d  F$ l( l: I
' ~) n" }4 w' i/ l
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
" z! S& q0 n* a1 L+ b7 s' [% J, Q
! P& b# J/ W7 F需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:( ]' }" e/ Z6 r4 I9 f& q+ |
$ v" U7 [4 b' y, ~2 g, R
GPIO
3 ]0 V4 Q1 j, A( {$ d7 C! D. b' |) ~9 e5 X- Y
SYSTICK
2 o6 G' L( N- |5 g* B, Z( I/ x8 S2 r. v6 U8 L7 Y
NVIC
5 {$ K  c( J5 H% R$ a  R4 J8 p& L% C) y7 u
PWR8 u+ ^8 x0 W1 B* I7 S
( t! T3 s4 j. E2 G; F, Y! f
RCC
! Y2 b- g9 k3 T+ A: _/ w* i
+ i8 l) J7 P7 x+ r6 l+ wFLASH.- Q$ Q8 W' n) ^! ~. `) W8 ^

: J2 ^# k8 P1 j1 O+ i. b' O: l- o
& M( }3 A0 F$ ]6 L2 U6 O
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

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