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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
9 k9 c7 ~4 a! `+ i3 a) Q4 ~
4 {- b" B+ Y" U+ |+ w前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
. r7 {  d, f1 v6 A
+ T, k! Z. }/ r5 ~串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
( N  `9 @8 k6 k
4 u1 K! `/ H  M; O) u' }2 R  l1 X  h我们用 Keil 打开下面这个工程:+ M. @2 G* O! @1 }5 A: p2 C& R

8 l! L9 M  @3 ^7 L/ HSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
7 [* ?+ ~3 |5 X( X! Q* ]
  u8 C7 Y0 T; S0 d+ y. [这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。
9 n' e& J/ j* P8 A$ d6 O7 r% ]$ n; h# n2 U, U3 `( N: L+ [
20200605173429966.png

9 ~$ D) z# L! O9 I3 i
20200605173429770.png
+ ^+ p8 {# n3 z- Z3 d3 @

" w8 ^0 q+ r0 [9 N4 I$ \
& a5 S! l3 x" z, h2.UART 的初始化
6 O$ g5 F, r8 M5 u! ^: E, R  O/ f  T8 L9 a6 Y; n
我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
8 v0 ^# [, C) {) V5 G/ n3 y/ r. G1 L3 i0 S; E% Z
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
- W% S! q* C% K1 S" u- T. I- c5 l. P3 z8 e: t3 g
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
! l/ L/ L0 A; U6 d7 _! F" M3 X
8 i( x( ~( U7 E4 BHAL库里面对应每个硬件模块有两个函数 例如:+ @" \6 r7 b5 x4 u: B7 z+ ]
) k# w/ M% ^1 d" f
HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。$ r, v' H( w0 b/ a8 ^; v% v
& N' f$ g4 j, V  P% e0 V" z& H
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
8 x# ^( T# p  V) `' Y8 Z# I7 X
! z& F  ^8 D. c3 e+ @9 D& Y
20200605173429617.png
! i4 w# H+ V) {9 G/ b, F  V

6 j2 {$ h& |4 j6 G( F' E( r回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。9 L3 u- v) b' q  U9 u0 I: Z
4 P5 A5 q/ g3 w/ h7 ~5 q7 N
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。* M" J7 D% J" J0 @. t
! w1 D% N" p& U' E: P
20200605173429721.png
( y! {* r9 T5 D. U" P
# y7 Y( `+ O) g& K: S* t

! q/ }# a; Z3 Z" l) j' B$ F1 V4 Z
20200605173429793.png

+ t  _" W5 s$ M' P
- v8 {, a1 s7 T' p: g' S8 A1 z# H/ Q: n
3.熟悉 Handle

/ E, \: f: f5 `9 q  }
2 t+ Z' ]( a  g% z. u跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:) G8 G+ z1 n; O3 a

. s. G: A5 c# I: Z. G
20200605173429636.png
( N3 x1 @. i* x3 X. W& T
& ?0 I) R6 r& c
typedef struct __MY_TypeDef# H5 u! h; _) x5 m: w8 I& M
3 A6 y  o$ W; C/ f5 J+ C; X* k
{! `5 c0 B5 y! I' P( w0 W9 M5 f
    uint8_t    Var1;
$ M! `& d! M) X! j! G" j1 {7 j* G; B. F( K3 k, V
    uint8_t    Var2;9 E7 A' Y, P. t0 o- f( s5 u

0 R0 d6 R/ j& j" m, @4 Z7 @. [5 X    uint16_t   Var3;
2 w" m. H+ |/ l; `/ v) S7 }
2 n8 t; a4 l& k: i+ x    uint8_t*   Var4;
$ f, Z9 A) u; i" t+ }8 V8 B6 {  N  N) L
}MY_TypeDef;5 j+ f2 B  H$ \! H, y
) s' ~/ M( [  X& e* @
MY_TypeDef* MY_VAR;
7 G+ t. |* K+ X1 O3 G- e! R5 b( s/ t. i6 ~
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?' N4 P# Z8 [6 K- l# T4 p* n

) M' u0 t. f( p8 N! ^( gMY_VAR = (MY_TypeDef*) 0x20000018;
; w0 l) z/ K' {# O/ K8 t8 Y8 J- m
20200605173429681.png

, d4 g- _7 K4 q) N$ u1 E: P
: b: V! [/ G, u: W8 w) i. _  |需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而4 \# L' f+ Z5 A3 Y4 |5 B5 o3 }' }

1 D, Z) C( i8 ]* q$ i) ], Z* MY_VAR->Var4 的值是 0x02。3 s& s% \& B/ ?8 z" _' N, F) @* Y! ^

( o$ ^) n6 Z$ d; V把 MY_VAR 指向另一个地址:5 g; N" M/ j+ X& ?2 m) A

& ?7 L/ q" h; r: NMY_VAR = (MY_TypeDef*) 0x2000001C;& @2 P3 O2 _# p+ H* }! ?$ ^8 ]  R" R7 v" S
# Z8 e# D+ x. x7 S5 ?
20200605173429700.png
& d5 H( k. f/ A  ^8 ^% D, P( w

7 C9 E& S" D( d4 c$ ?/ H% H& ]与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:1 s/ _% P$ F! {6 g2 D

( a# v# B: q$ x
20200605173429768.png
8 Y3 I9 t. ~4 ^7 t8 t# l
- V. d6 u. y3 @2 R
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
$ m: W5 G  d  ^9 F- c, v7 v$ X
- X  {* X) N! u% F* a8 oUSART_TypeDef  *Instance;8 D* B* z: t2 B1 R9 z  E! t. F
6 f& _6 K2 c) |
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。: x' }1 P8 e& @" [4 i2 a3 N: H
! }4 x& @0 I& L, c3 |$ a
UART_InitTypeDef   Init;% e. E3 u. B: x7 O# o
9 \) `4 N2 W; G: Y) I; ]
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
8 X! ?8 J2 E! P3 a' \, g2 E$ ?) f2 q
UART_AdvFeatureInitTypeDef  AdvancedInit;$ \% c$ n; S$ E7 j

' @. [8 X+ E4 w9 n串口扩展功能初始化参数。当前未用到扩展功能。
* }+ H. O2 {% _/ V  M" Z2 x4 w+ z
# e( ^( b( R9 Y1 ?& l使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
: J9 [- z. j$ k  \& n7 O$ M* |
$ _8 h/ c; J/ [: m" A% U* oHAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);4 N% A/ }* ^9 E) B5 j. j9 x

  d) Y: b# x/ u7 F1 s2 h&UartHandle 为 UART1 对应的 Handle 的首地址。* t8 f! m. ?7 r
0 x& F: h/ d( S  t3 V8 j# l0 _
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。( I( J4 U( V: p: J1 Y& X

/ {4 w) W! c' i1 L4 o+ _8 l需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
; h0 H' p/ U" s7 d
) {# b) n! U- Z  aGPIO5 Y6 h' B2 r# Z/ Z/ z7 y# q  Q
) ~% c7 W- s# P* J& q
SYSTICK
2 }. ]$ O  k6 ?: V, l: K+ ^$ x+ m9 V+ ~: V% M- c' |
NVIC) q" E! f: ]) Z, o5 F/ I. s

3 E3 s7 Z( C% i) q  ^2 WPWR5 o* M" Y: p  [" T6 _# _+ U) g
' s& |# ]$ e& t2 y2 ?9 q
RCC
) {7 l- h# T7 s7 Q3 \# o" _; H$ B% e$ u' ]
FLASH.
) R- S/ B+ [0 l& g; L, B$ g1 M7 B: v- d5 F  p
: S( h6 [5 s1 N6 d
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版