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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
" t5 C7 ^- a9 _' c) `4 c* \- r) q5 g; |. X. n& T3 x+ J
前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。
7 ]7 j$ [) `  N( F" U- e6 i; o5 ]
$ o9 A: ]' `* `& m/ D# d串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。: X% G0 G2 L5 {' n# n3 S

" u: B8 k( ?2 @% I# s: m3 j我们用 Keil 打开下面这个工程:
+ w6 N& A4 Z8 t3 K
: m' Z* W( g2 _" T+ SSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx* z) T+ ]; w9 o
, d7 r. H7 s* b
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。
* {8 \& M, T3 F; `- Z  b$ @& h1 ~; Q  F# U& ^0 U9 K
20200605173429966.png
) v, Q% ]9 W8 a! y
20200605173429770.png
* ~, z8 N3 F" F; x1 r

/ F4 [: d* v' A7 ]7 e1 g  K" D0 _. `$ Q6 `; t
2.UART 的初始化
  X! K9 ]% D7 k6 O8 F: J1 ^
7 S8 z; E5 t! S4 H% V我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?# k: a$ y4 L, R) V( o
- t6 }/ x6 b; N$ I
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。: }9 G3 L# c8 |# ?* V

: Q" D- b* P9 P2 tHAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。% W4 W% i1 Y2 T& g% _2 D

4 S" s6 s3 P  l! F3 i) Y# YHAL库里面对应每个硬件模块有两个函数 例如:
8 u7 `1 z9 o4 _+ `
) _( O6 r  m- \6 r0 B0 QHAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。3 I7 `( m* ~0 P8 a1 Q, X( B
6 x, @! U4 m2 c" u5 x* x& q) C4 D4 W
HAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
4 S1 r2 I" j1 z. H# S# v( Y7 R! H/ d/ y, k6 s
20200605173429617.png

! u7 e3 R$ h& @$ j) I
4 L# m7 l) v, L; C! _% o0 l+ i' W, u回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。
0 ]+ }  S% \! g6 _& w5 W; ?" S8 T6 U* y- |7 F) I0 f
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。3 _% Y( {) F4 C( n) j
% l& c- b5 H" ^4 @2 ]/ E7 \' Y' ?
20200605173429721.png

) j: z$ U0 n( G! ^" s  D  \, f- w# t6 u
  @4 x3 h+ m0 z6 g1 E
20200605173429793.png
( n# G0 _2 U  c4 K

! ]  V6 x$ _; K" g: K$ n6 y: ]
, _* m8 ^2 l! p, N5 _+ d0 m7 Y) s3.熟悉 Handle

# p6 l2 r5 r$ N/ Y/ U
+ U7 f& C# b  }. [. o+ {" |9 y跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:$ E9 j8 f4 w0 }" R7 ^
7 [6 r, l2 m6 x) I" C2 S
20200605173429636.png

. y7 y5 N. Y- _* ]; X% u2 M9 u! t
8 S( N8 ]9 V1 ]9 _9 N9 F typedef struct __MY_TypeDef: c7 }, Q) M( B' ^  |. V$ }
2 I- y4 _: u# Q
{
6 X: o. }6 p* |6 c" r" }    uint8_t    Var1;
4 E2 M2 k6 h! u0 T- F( e3 }, \, @# a& K9 c% c
    uint8_t    Var2;
% K' Z) U$ V3 Y, e( r9 g  j( u+ L4 ~' @- i1 J8 X3 R  x" X
    uint16_t   Var3;: u3 d  C3 V# x9 r# m

% m& N5 s, N" j    uint8_t*   Var4;
4 D4 J* a7 \/ e: n. A; \2 M) t8 G) B/ ?! o/ E
}MY_TypeDef;7 X& k  I2 {1 G& K4 ?

1 W! R( `2 R  TMY_TypeDef* MY_VAR;
" a0 q' h3 H$ j1 b  N4 `$ u' f. p9 [- l6 Q4 C6 s7 h8 y1 v6 Q
MY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?$ I4 n- {# U( [' q

' ?7 ?, m* x* |) N1 }$ S0 h4 MMY_VAR = (MY_TypeDef*) 0x20000018;
# M8 \) n1 F: q- N5 p0 h( i5 f) y3 z
) c) {; m6 j0 \
20200605173429681.png

9 s1 v* p( n, R1 |! F$ O8 j' \3 b  S
需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
. V: ~1 E0 m( V9 Q( V
5 I2 F9 z9 h6 a. ?* MY_VAR->Var4 的值是 0x02。+ I: b2 ^# Z3 j, f4 ]& t1 ?3 g1 n8 C

4 c! q) [2 k/ D9 U把 MY_VAR 指向另一个地址:+ `  b+ s! r# H  ~
# ]  l0 G' E# D
MY_VAR = (MY_TypeDef*) 0x2000001C;, P/ x' [( e( [" g* P6 Y0 y/ ~

3 A5 l6 A+ }2 l8 E1 {6 g) a
20200605173429700.png
' M& A& Z2 _8 l! x4 ^0 y/ x# d6 a
4 ?2 J7 N: V0 _( U: s. {
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:
* ]% w8 j# Z2 b+ d$ g! p
* Q# W3 w, K! V7 [# A9 I9 B
20200605173429768.png
' Z" _6 ?8 n" b+ y3 A# l* V

1 |6 m: q" `( }我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:) S- `# R% w! w. g0 i" U+ Y( |
: Q' P  a7 d% D7 i
USART_TypeDef  *Instance;
" u) r! J3 `9 e6 Q! w5 i& `1 |, Z4 u4 ^( f7 q
USART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
2 B' O# ?* b6 w6 Q0 U! ]. C: o5 H5 _- B; T- S+ P. q
UART_InitTypeDef   Init;$ A3 K4 }0 {8 j3 y; e3 F
; U6 @# n/ Y, b' k7 m% k
在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。
. O" b# k7 ~; G0 S0 O- u# p
. C5 y# O( g, h" K$ {4 ?: J! P$ ZUART_AdvFeatureInitTypeDef  AdvancedInit;. t& i. J: f1 @. J1 G

( K" z7 t1 o7 ^& |  l串口扩展功能初始化参数。当前未用到扩展功能。: d' \) I2 f$ n+ i

6 C) Z; r8 |/ U, `使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:5 f' z* v# c5 r4 w+ @  r( c; ^" o+ e" ]

9 g" |9 |' Z1 JHAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);4 Q  e; b& I- `% O

- j8 Y# c7 n: G5 x# Y&UartHandle 为 UART1 对应的 Handle 的首地址。
2 s, G+ Q1 Y0 X
  V8 x* x1 j* D$ sHandle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
! S/ t: \1 q4 {& C8 c7 i
% ^  L0 i& D; C6 N需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:
2 ~1 V/ l% {$ f  F2 ^! I: w2 B( R2 w- P/ V' V$ J. o
GPIO
! [6 n' B1 |/ Z9 z% Q8 z7 ?* i% G
8 p8 ^. M, }# ?8 W" [- j6 \6 N3 USYSTICK/ G4 _3 Y- v2 Z. |

- v5 Q# ^9 K  [! L" y; K& o1 U$ NNVIC% N; `4 O% H* q* D* a" i

( p/ N' V! X- v# aPWR
, s5 d: k3 k# K' B3 l5 C, K# U" u! b: s0 r- w) @- }$ E
RCC
0 e3 y, z8 _# l" ?% \5 c+ n' s8 s" h7 ]$ e6 q* s
FLASH.
2 i' O4 w! u! a8 X* ?. K8 c1 ^+ P2 ?: I

. E; b1 P  n8 Q1 G7 w9 k
赞 收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

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