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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART+ F* z) ?1 t- {1 _

6 Q! s2 R' G2 G" f- D前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
. X+ C. ?9 A% ]8 k: v* ~  R2 f$ _( l+ E* c. ^2 E  u
串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。
5 I6 R; N. R& ^5 h1 y) {  O" I3 u: \' H1 x
我们用 Keil 打开下面这个工程:
/ p1 m- Q2 v4 `& N3 i
4 Z! `: `$ w2 g6 y: \STM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
: O- r$ f$ m  J8 t6 v' x  j" J% W" {8 Q' V
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。7 D1 P3 l. k/ t  s5 ]. K* C
9 M! P* \" z* ^. B( e$ N$ u) t
20200605173429966.png

5 H1 U) H% J7 g: h9 i
20200605173429770.png
3 P& }  ~8 B. O; v) p/ R

) w2 a( C! H3 h# t) |/ M* t  V! l. `& l/ N8 u0 r0 e( e
2.UART 的初始化1 p9 V- p+ W3 d. h

  o) n1 a5 s/ s' A我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?
. n1 n$ Z! H6 O6 Q( b- p1 g* e$ [5 ]
, N9 S8 ]5 ~# `. I我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
4 z! h/ ?6 L' m( ^( |/ [! s
% D9 R3 {8 X1 P; @& k. DHAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
( A+ `- b/ _- R* P$ l2 X. v
: U: W. E, ^2 JHAL库里面对应每个硬件模块有两个函数 例如:
! h2 J9 a' h8 z8 Y$ R
/ Y5 z  m0 r% i2 c. FHAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
+ \* t- u0 a5 C" C# m
2 B' I# ?5 W& }HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
! k  F4 G( N8 v3 W7 {  H
- o; V6 U3 c) Y6 W8 k# t
20200605173429617.png
4 }* Q! M2 W; H, i1 n" V

6 |5 M: F7 L* I+ B回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。) m' G; {4 ?2 [
" I3 u, F+ ]% G9 X8 ]0 R! P  W% X
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。) N+ J4 y( c- S: q
% E4 K& ]) D  ]) \
20200605173429721.png
5 ?8 }3 B; I! u, H  C
. v, d+ ~% X1 Q' [3 T& z. h! o5 }
- @! a# G! @9 F
20200605173429793.png
4 r3 k( r( F( L9 i& z
* J7 N- K* h  ~9 Y( S5 t

% Y# @8 }8 Q% h& z3.熟悉 Handle

9 C; ]1 e8 m! o$ C) @1 Z9 X& M7 _" @6 u5 y6 P1 F4 Y
跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:1 E# O' O5 V  S5 a* {# P3 D

/ C& \1 `6 g* q" K% P# w. @, ]* E
20200605173429636.png
2 Y2 w. u) a  n( b0 k7 K
; C; g+ d. k/ R8 p
typedef struct __MY_TypeDef/ ^# F9 g& F$ b/ J+ `

* J+ B0 X6 T2 U{7 n+ j, h3 t  i2 u- ^
    uint8_t    Var1;% R& n& z9 J. M' Z0 j
0 z1 {& \: `  i; E# D
    uint8_t    Var2;
, R! ]( _" z0 D6 x- H! ]2 ]% C, z, B& A$ d. \) v
    uint16_t   Var3;' }  P( D% i5 F# J' e5 r! l

$ ~, R' U' Y; W5 D  P/ j$ u; c    uint8_t*   Var4;
4 I" h3 o/ W. [! l8 k' R9 I9 {& B9 r" ^0 D1 R! K6 U
}MY_TypeDef;
* \9 G7 a5 V+ c$ m
$ d  G- c7 C* Q6 E& d/ tMY_TypeDef* MY_VAR;
* n0 z3 S/ ]9 n
; ^! m8 y$ c- E1 @) @/ d1 ~' j/ lMY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?
. T. h0 z' R2 E! {/ _0 u& u& `( A' K/ w! Z( v0 Q* D( k
MY_VAR = (MY_TypeDef*) 0x20000018;
9 V1 @0 k% m/ n4 J) n! e8 p! H' V: I! W
20200605173429681.png

4 B, v# J' U! z0 H6 ]
" w) u6 @, E4 V- K' X# T5 i需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而5 B; @& }6 \+ O# x/ E

6 y4 g1 G3 O4 U8 P7 l$ B* MY_VAR->Var4 的值是 0x02。
% L8 ?% l9 C& {5 R0 B; U' o
* E2 z$ j! V7 |0 o& }$ E# {5 q5 e/ x把 MY_VAR 指向另一个地址:
8 J5 K! ^! G. |
& t8 S% X/ z4 S! q3 nMY_VAR = (MY_TypeDef*) 0x2000001C;" _9 g' h3 o0 c# D

1 W3 [2 L. B% P! o8 Q
20200605173429700.png

; b' p. T2 z' e' t+ ?
5 Y5 Y9 e7 f2 l) f8 `) P7 v, _4 I与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:
( N6 ~& A* ^* M; L2 S" m8 w7 K, h, i$ x
20200605173429768.png
3 ]1 x' X" c/ J
7 t. O5 `) [& `. V" l* A
我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:
! |- O! g* @9 v1 r$ t8 b: r  d% F
USART_TypeDef  *Instance;2 F: f$ d8 }6 \- H) [
5 X4 L; i; f1 z  `$ s) _4 Y
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。% o8 l- s9 w' h6 l, e8 s" j1 `8 u

  W- f! M% v7 e. b1 g$ i+ iUART_InitTypeDef   Init;/ d$ s4 Z6 p. H+ |+ |1 |6 y1 A

" A( U4 ?; l$ ^: e( A; H& J在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。2 ^! `1 B& l: d+ H

$ }9 O1 r$ D, i# y+ R' L% `UART_AdvFeatureInitTypeDef  AdvancedInit;$ U0 l9 b/ C2 v; C! F

+ [/ o; N( q' f( Q# e6 a串口扩展功能初始化参数。当前未用到扩展功能。
% N+ u5 g9 p% s8 ]6 K; A
- I4 T! @3 q* m6 o2 a使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
; L/ X) I; Z+ |& e
' T2 u7 u& W4 i( JHAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);
, G: d& w1 v! z
5 Y$ R3 b9 M+ H&UartHandle 为 UART1 对应的 Handle 的首地址。
3 W; z9 F3 |, f. [3 \& x6 [
# r* d% O( a! F6 ^8 `Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
7 y- B$ e6 I) N! T7 G+ }" _
: B, r( _, l# o6 G3 f% y0 Q需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
- z3 _5 w2 q$ s+ z5 p& D$ O% a6 t( R" G7 y+ [8 [# ~
GPIO; W4 ^/ @! G# X

# E: k, @4 O5 w6 B' ^SYSTICK
; I7 k1 |, l, n) E# ?% i
# W) [8 i- I& B/ W. bNVIC9 c0 V5 I; A" _( r- @  v) f# {
0 S+ S# v+ r0 U0 r# ]7 b
PWR5 A3 `! o$ I2 ]& E% H& v. i) K  G
+ y0 {5 q1 d$ i0 N/ y. C
RCC) \: d; V9 a7 {8 B( T  c% m+ |
0 b% g% |, L5 f1 j# L* r9 s" R& z
FLASH.
$ n5 w/ x9 R2 j8 e; h. u% w4 N+ b

9 k7 P+ F; x8 ~3 p6 R4 z, O
赞 收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

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