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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART9 a/ ^4 g1 o+ b* n

) F' j2 Q+ B* @2 |前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。& J9 Y$ r. b" q/ a! k

) i2 D2 u0 t9 U# M$ N$ P7 e5 e串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
4 C# t, C8 r5 x! H. R6 H  l5 w+ A
% f  V+ @" D. f9 _2 ~我们用 Keil 打开下面这个工程:7 A+ J3 b, n; U

6 g3 t0 d3 T' w' b+ DSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
! l  g& ?  F" v4 t( N4 J- \, k8 k4 c5 {, P& ]
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。( I6 h2 F  A7 e( R. L7 @+ r
, O6 y6 j1 O% ^: Z  t7 \$ D) {
20200605173429966.png

, k( U3 Q4 j% G" k% v6 S" a7 U
20200605173429770.png

/ P% G! E" f! A; [. }) R; w3 Q
6 r1 S& N( f/ ~- I. @. z" u
, O' Z6 z2 F6 w7 y) H8 E2.UART 的初始化
; b2 L: S7 A+ J
3 v. @0 F& n6 C* |我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
  Y3 |) _- ^; O, W7 B
# z5 p3 ^( L1 T我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
3 C* f1 G9 v# O% G6 q
3 U( p) L- B! ~; vHAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
, D1 t  E7 f! Y# Z: d  o
9 N4 ?5 r. C! p0 _: XHAL库里面对应每个硬件模块有两个函数 例如:
+ L( U: i4 m& C; j5 W& i8 q# G4 K. P/ W' ^$ i# m/ F
HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
, N5 h1 Y" S+ D6 m' y3 v
) I) H$ Q, x/ o5 x) a2 ~HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
1 O- U; m5 z2 H8 k- V) C/ o" x  k! M7 t
20200605173429617.png
2 ?7 {, c2 @" i" W

! y* Z. ?1 W6 E0 A0 G5 W2 y回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。  b' F( e+ I3 l! Z& W9 ^" G8 ~
; C5 d" a0 n. U, g' l0 t
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。
* A- F' h4 g! t6 w9 A3 z- m
4 k. A% e$ ?9 W0 D$ a4 X
20200605173429721.png

+ U2 c  B1 f$ Y5 f
( b5 Y: Q; r  Y; a
4 _% i/ G- T. O4 ?( h
20200605173429793.png

1 ?2 z/ }' e9 H" j7 b. a; F# _
2 s0 x/ c! z+ y1 V3 x5 H2 i: ^2 U- Y' \( Q" x* b
3.熟悉 Handle

# o2 n# Y$ n5 t' r9 s0 x- [
2 K/ v% B' X! T' M" N跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:
; e  e1 y/ K8 {. V8 X/ @1 i2 _/ a; ~5 a8 ]! F3 H9 u
20200605173429636.png
; ^- L% f- r* ~1 Q+ O1 L; Q3 V
0 ]5 P( H+ w7 @: G8 G7 N6 N
typedef struct __MY_TypeDef
2 C5 O6 R+ J; x+ _6 e. D' i( P$ f4 S4 ^
{* {' Z: f, j! m% i) c; M
    uint8_t    Var1;
& t4 {' k% }' V& y3 {6 L8 M& p/ P% I0 H: n/ B/ s0 D
    uint8_t    Var2;. n+ V1 V# j& j3 Y9 o. S/ D$ f

9 B! B# W: I0 o& b    uint16_t   Var3;
" a/ ^; M- i, c/ z6 E# [: [, @0 f
& X8 b2 L' x+ d  D    uint8_t*   Var4;( f) \* d7 K! }  I$ S+ w

1 C! }% {6 z# e) x' ?' Y}MY_TypeDef;; s; u9 _9 l+ |3 I
, o, _+ d9 G. }+ [3 @, t4 u
MY_TypeDef* MY_VAR;  V- G+ `/ \2 e# V/ A

4 N2 G4 g1 a0 ^+ PMY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?
: T6 Z% o5 R$ ^! ?; K( z+ O6 C' N8 ^2 p/ P- {' s. E
MY_VAR = (MY_TypeDef*) 0x20000018;
0 D  {  \) U: j( a* i6 p# B' m- O, R# Y( U! T" H+ r1 ~
20200605173429681.png
$ a8 v; d$ g2 a0 b9 s6 @! f& p6 A& u

- U( G- [1 N0 S需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而, {0 d/ g# o; _0 ~7 y

/ m2 A! I# Y0 F* MY_VAR->Var4 的值是 0x02。. l& l( X! i2 O4 }4 A% l
  h' @  V  Q" y( q! H4 i( [6 N
把 MY_VAR 指向另一个地址:
3 k* Q) F$ {' O
8 r* G4 H" V: h% d- aMY_VAR = (MY_TypeDef*) 0x2000001C;  ~1 J; a% ^& {0 k6 Q, Y3 |: |
0 ~0 n. m2 n& f( i6 J% B2 A0 j+ U
20200605173429700.png
) e% J  F7 H9 z  W
. y) `, [; _' }; P9 M9 t
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:/ @4 b) Y/ f6 b% n) r0 ?4 K
; S. A$ Y; E' c9 e  W
20200605173429768.png
) N8 x4 P6 N: @. v$ l

% x: l' R7 [: j/ d& E7 _我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:0 s0 h; i( b6 d( M$ E

3 H, }' w! C  }! N! eUSART_TypeDef  *Instance;
, F7 ]- |# N  q5 Z& f
: F; T/ J8 t9 h" \USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。8 i6 {  Z5 r$ K3 v
+ J# r, X3 C! K. [* F
UART_InitTypeDef   Init;1 L2 ~: u: O0 B
/ o; d( Z9 }2 ~3 T4 |- ]* Q5 N4 y
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。6 T! v$ S' J: A, i9 R- s

. _; V' a6 s1 WUART_AdvFeatureInitTypeDef  AdvancedInit;2 z) [$ J5 m# N; P* B+ ?  ?
' K1 R7 A+ @% _2 O) D7 b
串口扩展功能初始化参数。当前未用到扩展功能。& l  n# X& q: E6 X

5 E( g2 J' A' w: I. ]' [使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
* g2 v# @, j' ]. x% }
# `  }- }; @* o0 F  gHAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);
7 _' ~7 R& d2 b5 ^7 C0 p6 x& L% c  I9 m( S  C5 l' m+ L
&UartHandle 为 UART1 对应的 Handle 的首地址。* l! T9 m4 F% `) h# }

3 E) x5 V3 A9 {3 K$ rHandle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。7 }3 ~+ t) |$ Y5 F& @: L# A; s7 h
! \* f. k4 N/ y
需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:5 b* Z  |+ k7 A' W5 {

/ V3 c1 d& W" ?- ?% eGPIO7 L2 ]$ u/ ]& u6 {' w
( z1 J3 y6 h# N& C
SYSTICK/ Z; ?1 E2 L. T' r# y
$ y/ H2 y# E2 l# D0 o. S
NVIC
! @' q. ]! }2 m9 l
3 H3 F4 d& @( O2 ?PWR+ v% t2 \+ |+ t4 F3 ?% `" E
2 m7 J1 b( Y7 D& b
RCC6 h% I8 z; {( Y  u0 `' C$ ?
- v: U1 n( k' F% m1 x0 g
FLASH.
' K& W1 q4 Z; K. M- k7 {, R9 M6 g: r% V  A
2 Q% f0 I0 N" G2 g
收藏 评论0 发布时间:2021-11-25 15:00

举报

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