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

【经验分享】STM32G4之UART

[复制链接]
STMCU小助手 发布时间:2022-7-5 22:17
一、UART原理
( V4 n$ S  R; N, x& BUART和USART:
9 l9 D: {2 l6 _3 XUART(universal asynchronous receiver and transmitter):通用异步收发器: S8 y  _! N- n8 ]
2 y0 b- [; U, h
USART(universal synchronous asynchronous receiver and transmitter):通用同步异步收发器/ x0 u. S* G8 r) Y! S) c/ J
5 {3 X2 \, e- x* h
STM32G4有三个串口,这里以串口1为例8 G* X1 l7 s4 {2 t  j% o% ~1 R' `: v
# s+ [! u) \+ A) _
`ZRTZ@1{_0HTR$PSM}_VZ@H.png 8 U0 D/ i5 k4 d! A- H
DL32BCMF[ERY[XKJS`XW~0F.png ! h, I4 `& J" O3 G+ w5 t
: |2 ~& i( S# ?, \( z; L; u
串口1的发送引脚是PA9,接收引脚是PA10
8 ~- [" T' _7 E- b) {$ i$ H- R0 c7 F) B4 G! |+ q9 W' d
{{YPUHIJ552)5W56FMJUQ`6.png
+ k7 b8 X2 B2 L+ y
. E' l+ O6 i6 c9 S$ `3 N/ z  t0 s数据接收过程:数据从RX先进入到接收移位寄存器,数据缓存到接收缓存寄存器,然后后进入到接收数据寄存器,最终供CPU或者DMA来进行读取该寄存器,完成数据接收
" v0 E. R% a) `8 e3 ^" x
+ H' }3 A. j8 Z数据发送过程:数据从CPU或者DMA传递过来,先进入发送数据寄存器,数据缓存到发送缓冲寄存器,后进入发送移位寄存器,最终通过TX发送出去6 G& v- j' \4 r3 p
  C: u7 Z7 z$ Z
`FGBC_ZJ6R_R)(]QI]M3@WI.png ' h, h9 P0 \, J) B

; q( X+ f. q) [" q  x* n+ e发送步骤:- T: z) ?+ ?  x5 t; t) t1 |; o- E" L
1.向CR1的UE位写1使能串口。% ^" X6 \' `9 J4 t
2.向CR1的M位定义字长。3 A/ Z* a' Y; s' }/ x% K
3.CR2的停止位数量进行编程。' s! ~9 l: d/ P5 Z  \8 I
4.对DMA进行配置。  ^; b2 F3 B- \4 e4 R& j
5.配置BRR寄存器的波特率。
1 u! ^$ g: D1 I! {3 B: J, {6.发送器对TE位置1,发送一个空闲帧作为第一次数据发送。; G3 t! {  U- V9 J) o8 `* t/ c
7.把要发送的数据写进USART_DR寄存器,硬件自动发送。TXE(DR数据是否为空标志)清零。, U' e8 O7 r4 P
8.在写入最后一个数据字后,等待TC=1,表示最后一个数据帧的传输结束。
5 ^$ `5 T  D* U! A7 @$ L6 T6 c9.如果USART_CR1寄存器中的TCIE位被置位,则会产生中断。
/ g4 ~2 q$ ]/ f$ `3 v5 G, E7 }, G+ k" H  n  f
36[{J$A8JC218Q0AQ4GR0.png
5 _/ u3 V1 }3 d2 d2 }
: z: d- q7 [/ {& {6 F/ s( I接收过程:
) w( h/ U8 H# ]. ~* l1.使能CR1寄存器中的UE位。4 `, ~7 ], m5 V& u; }2 e
2.修改CR1中的M位定义字长。9 u5 W' c) ~7 v/ K1 `4 p$ `
3.对CR2中的停止位数量进行编程。
( ]- y1 r. a" Q. R5 H4.配置DMA(如果需要)。( a! J  r/ B* k. F+ ~6 H1 V5 E* m
5.配置BRR选择波特率。
  N/ M7 R! y& G# t% j' k6.将CR1中的RE位置1.检测如下序列。检测序列表示开始接收1110X0X0X0000。因为发送前,先发送空闲帧,起始位又为高电平,所以,检测到从1→0的跳变后,就表示开始接收了。
. W& I' b9 o$ U* E  g( V7.接收到字符,RXNE(接收寄存器不为空)置1,表示移位寄存器内容传送到了RDR寄存器。
: y4 k/ f9 `  y! {8.如果RXNEIE置1,产生接收中断。
6 B5 m1 }- O4 f* c4 n9软件读取DR寄存器,RXNE自动清零,或者手动写入0清零。9 w" Y) k* {! r- E& l. {/ w

0 L, D" U" N" R+ m; _串口在一定条件下也会产生中断的
  u' a/ ^: ~1 j/ u2 s1 ]
2 i% E" T; T5 p( J Q0(E3%VOY0O~WNTP11OGHVJ.png : [3 g- Q% o; }
" |/ S7 Y6 G1 L& S: F. W
用得最多的是串口的接收中断3 R& Y& H& A4 i+ T/ N! q' u3 u

3 ]5 A+ p0 S0 H& c+ ^二、UART配置
0 D7 H" X6 D, k, t5 X1.IO引脚的配置
4 s  _  E7 A- P: d$ T; y8 `
2 r, ^9 u/ }2 j( j6 v3 }
AC7`T[_$(0Y{)}X6_O4XJ6K.png
7 K/ N# K3 `- B; b+ F1 k4 r8 x7 X- a% H+ A0 l! e5 M2 q7 T" G
先配置串口的引脚映射到复用功能(PA9–>USART_TX; PA10–>USART_RX),这里我们选择的是异步通信,这里一定要手动修改USART1的TX,RX。因为它默认的不是PA9和PA10引脚。
- H& @8 Y" R/ I* D# O
# S* ?3 V- s9 o! X3 Z. U. L设置IO的工作模式" Z+ Z6 j/ ?( c, q3 W6 v
1 o& v$ n, u, m: X2 p9 x. k3 u
5}6B3%6LA)03N1}V@~@N{%4.png ( ~4 t: s# b' q( _: R

& j. J! o3 V8 e' J这里设置的是复用开漏输出(官方案例),个人认为设置为复用推挽输出也是可以的。  X( ]: D0 x6 ?
1 ?, m2 F1 t: p  p6 n! D6 a9 _
2.串口相关配置
* X8 o+ E) a9 k, L主要是配置以下数据:
4 O+ z% T# _1 T) R: ?2 P1.配置波特率
$ I; a/ d) N& |4 G2.配置数据位,奇偶校验,停止位。- q, x# H: `7 ]# b. Q  z2 O: W$ o
3.数据传输方向。
7 a. \& E2 E! @4.过采样(8倍,16倍。)一般16倍7 F- d4 s7 k" e( S) w
5.串口的时钟使能。
: s7 ~  j; W; [: L. {3 M9 C7 `9 e* T  {% r* z( D, i! r
EF)SIK~7UYTSQAMPFL6L)JC.png
4 G. V; w" E* a  n5 B3 Z2 I2 x5 d& Z1 v) c( ~* o
3.串口中断配置
- I$ {; O2 M) V% j* R+ y
串口通信一般最简单的有两个中断:串口接收中断、串口发送中断。实际上应用中常用串口接收中断,这样可以在监测到有外部设备对本机通过串口发送数据时就可以马上进入中断服务函数,在服务函数中接收数据( b3 n0 f" e/ @$ J0 P# x9 V
" J) Y4 f+ c$ q6 ~2 A6 T$ H
`)[TMG_8DZW0J%ZN@U~A[48.png
7 u# U8 `9 c- C
# A! m$ m) C, z' c3 f+ r7 V$ t& v- _* a使能串口中断,设置中断分组,然后设置中断优先级
2 ^" v% `+ v( z, ~  y1 y0 Q8 i, i' N4 A1 t' C9 D% s' ^
小结:$ F- T' Q: Y* U/ I" X* i
# m. {0 p  i; Z1 u
QO[QCB_X7O7}NFD%$U``02N.png
% P' F8 o1 u6 M* F( l% e0 k
- `4 M: e' H' t8 B3 `% X2 y7 x三、UART常用函数. f- x, o3 {2 F1 ?2 T' y: T
1.串口句柄

7 @- p9 Y5 [& B- ^0 DSTM32CubeMX 定义一个结构体类型全局变量huart,为 UART_HandleTypeDef 结构体指针类型,俗称其为串口句柄,它的使用会贯穿整个串口程序
; ?  t  P4 z2 G" f4 s. \; k4 ~) f) \9 m, n+ a* l) D
  1. UART_HandleTypeDef huart1;
复制代码
; O# }: u- t0 M4 I
结构体 UART_HandleTypeDef:* `% \' q# z1 B+ A  O  s

& J0 Y; b! {) D  @7 E+ r. c
  1. typedef struct
    . a# K2 @) `0 n7 e6 p$ Q+ f
  2. {
    - {' o% E. [) X: Z
  3.         USART_TypeDef *Instance;
    3 K1 r. @: T  z9 g
  4.         UART_InitTypeDef Init;
    ' R3 T% n" k. }, s. F) G
  5.         uint8_t *pTxBuffPtr;, t0 O4 a( A1 ^' N2 U7 k# D, r
  6.         uint16_t TxXferSize;1 }; E! m; R+ s% z
  7.         uint16_t TxXferCount;4 K$ l0 u0 Q( E+ I% T6 R- h& o3 Z
  8.         uint8_t *pRxBuffPtr;" F- x% X7 x$ e  `3 ~
  9.         uint16_t RxXferSize;
    0 w* a: n  f, t& x4 V* I. }- f
  10.         uint16_t RxXferCount;! G9 _9 R7 X3 ]( T
  11.         DMA_HandleTypeDef *hdmatx;
    5 M& N; i& S, U: b% w
  12.         DMA_HandleTypeDef *hdmarx;
    : l5 l' k) D+ z6 w. k( a+ e
  13.         HAL_LockTypeDef Lock;- G3 b7 E+ L7 G5 V" c
  14.         __IO HAL_UART_StateTypeDef State;
    1 V  a, n4 l; c: D' I3 X# e: z- Z8 f
  15.         __IO uint32_t ErrorCode;' M) n- A; ^  |" h: R. @
  16. }UART_HandleTypeDef;
复制代码

5 C" s9 @9 |. S& D  s! b串口的初始化函数里面就是对该结构体成员变量进行初始化。
1 F1 N  n$ J" [+ E0 L& l# J
% u  d: t$ J. r1 H" C$ V+ D1 ` RBC%[W3IWJT0]8P_QYR8]PO.png 6 o/ a/ f; i$ ]6 J5 j0 x& W- @* M
) L) ]1 l" v$ h# j/ `" w
Instance 是 USART_TypeDef 结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址 HAL 库已经定义好了,如果是串口 1,取值为 USART1 即可。* _: g% Z! V; x3 F4 Y
! j1 S. Z1 O- m
Init 是 UART_InitTypeDef 结构体类型变量,它是用来设置串口的各个参数,包括波特率,停止位等* k4 `6 U- M" f/ F2 l

$ O; t8 G* O! I
  1. typedef struct
    6 u7 d" E: g3 g+ N8 y2 z. p
  2. {$ L" P3 ~% H: j* ^+ Q+ A6 x: A
  3.         uint32_t BaudRate; //波特率
    9 o) y5 m3 {) ?4 a
  4.         uint32_t WordLength; //字长
    , k: A! G  {4 B; D/ H
  5.         uint32_t StopBits; //停止位
    & z! a% n. x) O! }
  6.         uint32_t Parity; //奇偶校验
    2 u( ^4 Y$ V( y5 ^) j( |0 b  t
  7.         uint32_t Mode; //收/发模式设置) o8 b+ Q4 V: o( u; V
  8.         uint32_t HwFlowCtl; //硬件流设置* J) u: t. F  X% |; }0 S
  9.         uint32_t OverSampling; //过采样设置
    1 z) m, u5 u" U/ X$ g2 g
  10. }UART_InitTypeDef
复制代码

( C: S2 ]& h8 b$ gpTxBuffPtr, TxXferSize 和 TxXferCount 三个变量分别用来设置串口发送的数据缓存指针,发送的数据量和还剩余的要发送的数据量。接下来的三个变量 pRxBuffPtr, RxXferSize 和RxXferCount 则是用来设置接收的数据缓存指针,接收的最大数据量以及还剩余的要接收的数据量
) ~) @% \6 m: Y% }6 W1 n
9 {+ ~9 x8 h6 i% m2 ^0 x3 ^$ n0 Dhdmatx 和 hdmarx 是串口 DMA 相关的变量,指向 DMA 句柄
, J5 K4 \0 f3 o' ?" H$ c# b! A
( D3 \; L+ W. O& N5 H其他的三个变量就是一些 HAL 库处理过程状态标志位和串口通信的错误码
6 u& P- q  r; \, p9 m+ C. o' ^  R: m/ V9 E' X
小结:
  u7 V3 @3 ?9 T: H0 G0 I' [
* e5 \0 A8 O* y) v" Y 345@$G2G@_6FT{FSI[E@Q28.png
2 ]' w, I3 I6 a; w: t( P& s& j" p3 R
2.HAL_UART_Transmit
: Z. @6 Z' H+ L0 [通过该函数向串口寄存器 USART_DR 写入一个数据,当向该寄存器写数据的时候,串口就会自动发送
: F( Z0 o- n  H  B# A函数原型:0 X  X2 J1 \9 n3 k5 ~( I
6 y0 c  N# M2 Z( q
  1. HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
复制代码
4 ?& g; @8 J7 V+ y/ f
huart:串口句柄
9 T6 |; v0 P: y& R3 Q3 TpData : 发送数据的地址
) h9 G! x1 V% J( I. mSize :发送数据的大小& K9 K( B0 C& x! I( C
Timeout :超时时间. F. R  E: i1 j8 F5 \- b8 y* M$ S
& [: j5 g6 m: q9 ?2 h% I
W{VHBM~`@GB)8E1OC$W4D57.png
% ~: F9 ~* T" w  F- y; `. M! ^8 p5 X2 N3 J+ j% ]( `* y
eg:% I% h- X1 z1 Y) I- W# f
7 I1 ?' e* n, t$ v1 e/ E
  1. void Usart_Proc(void)
    " F) Q. W, z( y- c& h" H
  2. {
    5 o& l7 M3 B3 U& F7 a0 ]
  3.         if((uwTick - uwTick_Usart_Set_Point) < 500) return;- V/ y5 Z( ]! l% ~
  4.         uwTick_Usart_Set_Point = uwTick;1 ~6 }& L9 x8 y
  5.         & i# e, j( u) V* v4 m* \0 X# V
  6.         sprintf(str,"%4d : Hello,World.\r\n",counter);, G$ X2 ?- L4 T  n
  7.         HAL_UART_Transmit(&huart1,(unsigned char *)str,strlen(str),50);
    " j5 `4 H" C8 g: C/ d1 Q0 b
  8.         
    ' ?5 j9 m, v5 X! R% `5 i
  9.         if(++counter == 10000)7 [7 @& w# R: U/ L5 ]
  10.                 counter = 0;  z5 c" n" o# w4 D8 D
  11. }
复制代码

2 }" T( C% i1 f! [' a3.HAL_UART_Receive_IT

( T5 u3 V. G& k作用是开启接收中断,同时设置接收的缓存区以及接收的数据量,并进入回调函数。8 a( H) P1 z$ z! d5 |* \
函数原型:
  1. HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
复制代码
* X) c: S. J& C
HAL_UART_Receive_IT函数是启动串口接收并且是中断接收,这个区别与普通串口接收函数:HAL_UART_Receive。HAL_UART_Receive_IT函数需要在初始化USART外设之后才能被调用
+ g& t% p/ o- f- @8 _9 O
+ ~+ @4 K% Q9 r* S+ I! y该函数有三个形参,第一个是USART句柄类型变量,第二个参数是接收区缓冲区,第三个是接收数目。
1 Q& R+ {8 V# K- [5 I0 D
4 D* e2 u- i% z5 J在使能了USART接收中断之后,stm32在每次接收到一个字节数据之后,就会自动运行USART1_IRQHandler函数一次,但不会每次都运行一次HAL_UART_RxCpltCallback函数。只有当接收到数据字节数与在HAL_UART_Receive_IT函数设定的接收数目相等时才会运行一次接收完成回调函数。
' p# v& `5 r4 p3 Q# p9 {2 v% t! _: d* L2 l; _! `
注:一定要在进入while前写一次,为了开启中断(回调函数里面也会写的)
  1. HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);
复制代码
- r4 h5 @9 C. q! i2 e! L
4.HAL_UART_RxCpltCallback
8 f  N( t5 Z! ^0 M/ X! A+ Y/ R! ]/ S9 N) `
USART1_IRQHandler函数是USART1的中断服务函数,只要产生USART1相关的中断都会“自动的”运行一次该函数,该函数一般存放在stm32f1xx_it.c文件中,函数里边调用HAL_UART_IRQHandler函数。: T8 o" j: x3 F; H7 h* W! M

  a0 P* d8 Z0 |) ^& `8 Y' tHAL_UART_IRQHandler函数是HAL库函数定义在stm32f4xx_hal_uart.c文件中,该函数会判断到底是产生哪种串口中断:串口接收?串口发送?串口发送完成?7 V* p0 U) O$ Z, ?! r
1 z! D. u8 d. ^; M8 \! R* G9 j
D9I}YM4(2]NSQU1_Q{6YBRS.png ! h# [8 d2 A* }$ Q- p2 e' L

" K( Z9 m, X1 X0 ?& ~, {在判断到是接到串口数据并且数据数目达到设定值时就会调用接收完成回调函数HAL_UART_RxCpltCallback。这样我们在回调函数中实现我们的应用程序。# R) a2 X, l6 Y# S3 V  n

& ^! ^8 k, g% |+ Q3 m2 @: {当CPU向串口发送数据,会产生接收中断,然后进入中断服务函数,调用回调函数,回调函数才是我们进行中断处理的地方+ U8 b) Y" ~' x9 s9 w  w
% T+ V* h) g- s* I2 y7 }
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)# E0 `" y0 Q* @
  2. {
    . n( n' f! [; S7 ~
  3.         LED_Display(0xff);// 进入接收中断,led灯电平变化一次
    ! z5 N) D; Q+ ^3 J
  4.         HAL_Delay(200);
    - p* M7 ]( y, K9 z+ g" N
  5.         LED_Display(0x00);% {2 U9 B) G: H9 N6 k' B$ H
  6.         2 u& }" c6 i. |3 h) E$ B
  7.         HAL_UART_Receive_IT(&huart1, &rx_buffer,1);- I/ I/ p( s5 r. ?
  8.         LCD_DisplayStringLine(Line9,&rx_buffer);7 p, ]6 v/ r: O+ y, v
  9.         LCD_DisplayStringLine(Line8,"122");
    + T4 l- F! Y& [
  10. }
复制代码
& }" Q! K0 I; ^. E5 V. c$ Q

! R5 Y8 o/ U; }+ t- g8 u' I4 R' J4 F
3 f3 D# u6 o% S# h& k
, K5 n. z3 c: I( h8 Z
3 X6 Z, W& H  L6 B, A9 g$ X# E
赞 收藏 1 评论0 发布时间:2022-7-5 22:17

举报

0个回答

所属标签

相似技术帖

官网相关资源

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