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

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

[复制链接]
STMCU小助手 发布时间:2021-11-25 15:00
1.从 GPIO 到 UART
4 ~  J' ?4 b. O  D2 F1 {6 D4 b: K+ F1 m0 h2 A
前面几节我们讲了MCU如何启动,如何用翻转IO引脚,以及用按键去触发中断。接下来我们介绍的也是最常用的一个模块,串口(UART)。: H2 E5 s! A7 }, t2 S: d
+ s& z) P8 w3 t$ a2 @
串口可以说是最古老,而且生命力最强的一种通信接口了。RS485总线更是久经考验。虽然串口早已经从大多数PC的标配中去掉了,但是嵌入式系统跟上位PC机通信用的最多的应该还是通过串口转USB吧。0 T( h" I! n" x% X& V/ k

1 I* h4 C% B$ n8 m* x! t3 S8 }: |* B我们用 Keil 打开下面这个工程:- y  a0 d2 f& M" y" t

; X7 U4 s9 r9 z% A/ M$ kSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\Examples\UART\UART_TwoBoards_ComPolling\MDK-ARM\Project.uvprojx
: S1 I. P% D" W# c! K: {6 `. V3 n0 P: v. ~
这个代码配置串口为 9600,8 N 1,我们把代码编译下载后,可以通过 UART to USB 转换器连接到 PC 的 USB 口,在PC端用串口观察MCU发送的数据。( s) [8 X+ a0 x* o" Q; Z
4 ?: S/ |/ d; _
20200605173429966.png
; ?! C$ D& Z) w- J# o8 x4 |
20200605173429770.png

( a( L$ |, I. ?0 f+ p8 A
% {* J  s+ d1 r6 }0 \2 [" o
3 ^$ U5 |  u2 v0 E! T2.UART 的初始化+ o# M1 e- M* K1 A1 @
7 a& f) t/ J" Z, ?8 E2 b
我们看一下代码,串口参数的设置是在主程序里完成的,还有一部分是在stm32f0xx_hal_msp.c 里完成的。为什么要这么费事儿,而不把初始化代码全放在一个主程序里完成呢?6 S: w/ x+ S2 I7 w
# r' w4 A* ~- e; X
我们要慢慢体会这样做带来的好处。我们调用一个驱动时,这个驱动难免会跟底层硬件打交道,比如串口驱动,它最终是利用用户选择的某一个串口模块,和与此模块连接的收发引脚进行数据收发的。
+ b# q! \9 c; @# S4 U/ {- b1 W2 F2 j/ s2 k2 n( e, }0 d
HAL(Hardware Abstract Layer) 把跟具体硬件细节相关的代码单独剥离了出来,并在Cube库中引入了 MSP(MCU Support Package) 的概念, 具体的硬件细节交给用户在这里面配置。
6 L$ G' h0 p5 U# W# w5 C  m/ c8 x6 a2 C9 K8 m
HAL库里面对应每个硬件模块有两个函数 例如:
7 X$ t: @. |. N5 a6 {' t
( a9 S3 K0 d6 _HAL_UART_Init( )  功能上的描述:设置收发模式、奇偶校验位、停止位数等等(与芯片无关)。
: r5 v, j) Y5 O' w! Y; ^
! h. ]9 L) O* [, Q# S) nHAL_UART_MspInit( )  硬件的描述: IO初始化,不同芯片,不同引脚设置不同。
2 O/ i( p2 D. U. d) o5 I
5 O% S% u4 z; L
20200605173429617.png
1 s/ x. c8 z$ D+ L4 W- h" u

  J+ [) q4 m* h" K  d4 M! z回到程序,我们要使用串口时要调用驱动层的初始化函数 HAL_UART_Init( ),这个初始化函数回过头来调用了 HAL_UART_MspInit( ) 这个函数来完成 UART 时钟和收发引脚时钟的使能,以及收发引脚的配置。之后初始化函数继续进行 UART 端口的参数配置。5 H! W' [3 O# \6 P) F7 s" a# d. Y
3 J0 W% b* N2 ?
这样做的一个好处就是使驱动层的初始化函数与硬件无关。一般我们做好一块板子后,所用的串口和引脚也就固定下来了,在 HAL_UART_MspInit( ) 里配置一次就好了,之后不需要频繁的改变这些代码。, |6 t) ^7 _) I5 y
& k, m  X% W% S0 x
20200605173429721.png

/ i" c- {- m( C8 V9 w$ y2 |7 L, @( n
2 K6 S, }1 E7 w% l+ P
20200605173429793.png

( n0 \! `3 {+ l  D2 k2 F6 ?
; z3 H; M# r* E1 v
+ }& R0 g- ], s6 ~9 Z# S3.熟悉 Handle

, V! t# ^0 Z9 |$ d' I( K; N1 A
9 e' h( L  G7 [" b3 l- y' w跟 GPIO 的初始化有所不同,在UART这个模块引入了 Handle 这一概念。在看 Handle 之前我们先熟悉一下在驱动里经常用到的结构体及其指针的用法:( z. y1 @3 g: A0 d9 b

7 X0 M7 X1 F) L  d
20200605173429636.png

" b1 }% d! Q' z/ h
' _. d1 X3 s, L/ W7 a( p4 \ typedef struct __MY_TypeDef5 ?- n5 R3 V+ S% A
0 r" g. m% F5 ~- U# v: \7 H
{
$ A  P2 t* r+ |) l' l: j$ L' T, ?    uint8_t    Var1;
! t6 q! O5 y9 R/ R7 e8 o( y, O
7 X% Z8 }* z3 Z+ t( }1 I& ]$ u    uint8_t    Var2;
8 j  q4 ~8 S) z" T5 ~. B
/ w4 c- E& ?2 `  u: R    uint16_t   Var3;0 F0 ?+ C4 ~# ~% e

: g4 a1 h, N+ Y    uint8_t*   Var4;
4 @0 `& V4 Q* W
- b( s( k" T0 i& r2 w}MY_TypeDef;
( ^# p& K: e2 C9 @# k& M! N6 V6 ?. _! s0 J1 ]& ^
MY_TypeDef* MY_VAR;1 J; ]5 O+ B+ [

1 @: q- S# a! m3 W* wMY_VAR 是一个 MY_TypeDef 类型的指针,我们看看把它指向不同的地址时会发生什么?
( L8 a6 `0 Q6 b+ S% A' M3 E- \1 w. X* X4 ^
MY_VAR = (MY_TypeDef*) 0x20000018;
! V- R( P% Q0 n* A. L& |* Z2 |4 y
1 M; V: o; C# r! D
20200605173429681.png

9 O; h, r: T4 L( r+ A0 b9 l9 v5 R" R" K
# P: P3 N, Q  F. Q1 ^4 w需要注意 MY_VAR->Var4 是个字节型指针变量,这个变量本身占用4个字节,它的值是 0x20000018, 而
; N+ M: n; I, w8 V8 Q( U) p2 J3 o( F  e( ^8 I4 {2 e
* MY_VAR->Var4 的值是 0x02。5 V' d" \; a4 d  y0 l% ~
8 R6 ~/ n( t+ Y$ A% R) p5 F
把 MY_VAR 指向另一个地址:
/ N, A* I! K1 b$ n# q9 _3 o4 f' T; N, }! o- M. h, c/ k4 s0 b
MY_VAR = (MY_TypeDef*) 0x2000001C;
. y% G( ^& r5 P) u- S- Z2 T" i% m7 W2 H. z8 r; I( c
20200605173429700.png

9 K( z9 Q  Z) ]- m2 @: F( S- x4 V& \% d
与此类似,对于串口模块,驱动定义了一个结构体类型 UART_HandleTypeDef,我们可以用这个类型定义多个结构体,并通过把串口模块寄存器区的起始地址付给一个结构体,使该结构体和串口之间建立起联系:2 v3 }+ @$ y4 g5 ~
* K$ F: m9 z# t) |6 Z3 W/ \
20200605173429768.png

; e2 @- Y7 M7 ~  f7 r
1 |: z- G, t- p6 V: ?7 x我们运行的当前程序操作串口的方式为查询(polling)方式,结构体中和DMA,中断方式相关的内容可以先忽略,只需要关注结构体中下面这些成员即可:- i) h7 ^! w- }+ ?
' I" y% i% I6 Z- k8 [
USART_TypeDef  *Instance;# V  Y6 q7 h- F: s6 p) z

1 @9 }/ ^% I2 p1 _& fUSART_TypeDef 类型的指针,需要指向欲操作的串口寄存器区起始地址。以把此 Handle 和该串口建立起联系。
$ K0 E: R" V) x  ~3 p- W1 n4 Z. S, `' r- s; _9 B
UART_InitTypeDef   Init;
' ~$ S7 X9 @- B8 T$ u& W0 _; j
& P' h: n$ s% S: `: _在调用初始化函数前,需要把初始化参数如 波特率,是否奇偶校验等写入此结构体。: v+ }& R7 h- c

6 [( \+ z" K- y1 H- T( Z) f# fUART_AdvFeatureInitTypeDef  AdvancedInit;3 u2 o3 l9 @3 {  n

- _  }& {# Y$ b8 ?# n串口扩展功能初始化参数。当前未用到扩展功能。& E! [. R! V% p/ y9 e
4 g8 ~/ S6 {: s7 A2 b! c
使用 Handle 的好处是,我们操作某个模块时,把这个模块对应的 Handle 的首地址传给驱动函数就行了。此函数通过 Handle 就可以找到所有需要的东西。如:
  N& d% s) |* V; G; U  O6 O9 ^+ n6 l' f  {+ O
HAL_UART_Transmit(&UartHandle, (uint8_t*)aTxBuffer, TXBUFFERSIZE, 5000);
) R" |$ J; t  e! K" M0 ?* U1 r% R1 D) Z. B5 J
&UartHandle 为 UART1 对应的 Handle 的首地址。
- F$ E' j7 A( {; x; I0 D5 D4 X: m" k" F- h
Handle 除了保存自己对应模块的参数信息,还保存缓冲数据,以及当前工作状态。它可以保证各模块之间互不干扰,在代码执行过程中被打断,恢复后又可以正确继续执行。这样也便于把驱动集成到操作系统中。在以后的中断方式和 DMA 操作模式中,我们可以更深刻的体会到这种方法的优点。在理解了串口模块的工作方式后,理解其它模块就非常容易了。
4 S% B! S6 a8 A; R% y6 V8 }3 |& U; y' W' B* l3 {
需要提到的是,在M0芯片内,有一些共享的或系统级的硬件模块不使用 Handle 的方式来处理:: ^: a; `' J, `( [/ e, N9 K

. A/ B* L+ r6 i6 g8 X" }GPIO
" l, }# s* I6 r5 u0 J/ g* k1 `& H6 a; x, e4 M
SYSTICK
/ O0 E1 x% \+ h7 w* j- R' S. Y
NVIC
0 S( _1 w; b* Z; E* m% N# K  o
+ K$ s& u7 Q$ e1 [1 @; b- A5 L7 G. HPWR8 @! Q  @% H0 M0 }  {

9 \. c( @- b7 _+ xRCC
# M2 D% k. G4 T9 W# j: r! u) S$ I- ]+ C, B3 ?2 }
FLASH.& j8 q5 d8 o1 [- c

+ l) R' {( e  r5 j( `+ }
  I4 @2 T! n* H& P& S
收藏 评论0 发布时间:2021-11-25 15:00

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版