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

基于STM32的串口收发详解(HAL库)

[复制链接]
STMCU小助手 发布时间:2022-12-1 18:00
介绍
) \2 [# O# `; e  K串口(UART通用异步收发器,TTL)通讯是一种设备间的串行全双工通讯方式。由于UART是异步传输,没有传输同步时钟,为了保证数据的正确性,UART采用16倍数据波特率的时钟进行采样。因为它简便捷,因此大部分电子设备都支持该通讯方式工程师在调试设备时也经常使用该方式输出调试信息。6 V2 r2 _: c) r% P
本文详细的介绍如何来编写一个串口收发程序,我们采用常用的收发逻辑,发送直接编写函数进行实现,而接收使用中断进行完成。接收中断使用接收到一个字节和一帧数据两种中断触发方式。
0 ?; f, O" U) U$ K2 d* ?# c! V* X& r" H9 f. C: x
USART中断+ I. z$ B" G3 U) v# s/ i% s" a9 h
USART 有多个中断请求事件。
8 \' E, I3 p: I1 G$ w' C
' r/ |6 D% E* }! n# K 640 (3).png
9 h, \8 I( J) N, p3 X, i* ^$ m+ q; H0 R4 h% ^4 C. G
之所以介绍这个USART中断请求,是因为很多人在初学阶段,对串口怎么判断串口中断的状态不太了解,所以我这里重点来介绍一下。6 @3 R% c! {3 X. G4 C" I1 k
一般在我们开始和配置完串口中断后,进入串口中断处理程序的情况会有很多,我们也可以自己选择打开哪些串口中断情况。一般情况下,我们在接受时主要使用的中断事件标志是RXNE和IDLE。; w6 r9 j# V+ x& D' D. \" ]/ C
RXNE是接收中断,每接收一个字节都会出发这个中断,也是我们用的最频繁的中断请求。
/ |4 b! q; _8 @6 L1 b. _. uIDLE 是空闲中断,每接收完一帧数据,总线就会暂时空闲,就会触发这个中断。
$ H; `& u4 W9 v/ f8 E: s0 h: }$ m; g9 t
串口状态# j& u- u8 S8 i; z$ C
串口的状态可以通过状态寄存器 USART_SR 读取。USART_SR 的各位描述如下:
# N1 Q$ d6 R$ [" H. ]
3 I" N+ }4 z2 X- g2 t: Q! u 640 (2).png : d3 r: T4 w( [' f' {9 U8 J
+ W1 l) I( }, G7 B7 A
这里我们关注一下两个位,第 5、6 位 RXNE 和 TC。) e# i$ E6 D. ^3 y- x
RXNE(读数据寄存器非空),当该位被置 1 的时候,就是提示已经有数据被接收到了,且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以该位清零,也可以向该位写 0,直接清除。
# P" p; Q% H: j4 A2 ?TC(发送完成),当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。果设置了这个位的中断,则会产生中断。该位也有两种清零方式:$ F* f, u& g& J+ h0 U# b
1.读 USART_SR,USART_DR。
* ?, N4 E( c  g* y9 n1 z2.直接向该位写 0。7 i, u& r, w2 s* n% n
5 C2 w. R+ C7 {7 _
实例
! `6 A5 F% o8 q8 Z' \# Q0 E需求分析

$ F- V  P& X/ [& X  J, l本项目主要编写一个串口收发的实例。使用STM32F103C8T6充当MCU,在PC上使用串口调试助手充当上位机。每次PC向MCU下发一帧数据, MCU每接收一个字节数据,检查一下数据中是否有指令0x23,当接收到指令0x23的时候,MCU向上位机发送“PC”。当一帧数据接收完毕后,MCU向上位机发送“Receive a frame data”.
7 B- b* M" _* S) J
( M9 \, D- J3 Q! k串口初始化
4 B: ^4 a/ f* V) n3 R串口初始化的一般步骤可以总结为如下几个步骤:
) i! B6 S4 i% M0 }9 d: e1.串口时钟使能,GPIO 时钟使能。- B/ I1 g4 Q. b2 E
2.设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。
/ |1 G+ T7 a0 q3 f! [3.GPIO 初始化设置:要设置模式为复用功能。
) G' L) T& ^$ e3 L+ F4.串口参数初始化:设置波特率,字长,奇偶校验等参数。# T; ^# W0 e6 H1 l$ P4 G
5.开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。" T9 Z4 x4 w' A9 K+ B' e
6.使能串口。
7 P; F0 }+ g& y! t$ K5 b
! g8 ~# J. H# s* l! P
  1. #include "usart.h"0 I, A3 M1 D! @. t4 G; A
  2. #include <stdio.h>* L" S) W& C, E4 V6 h
  3. #include "stm32f1xx_hal.h
      O" v! A' i5 Y9 }
  4. UART_HandleTypeDef huart3, {! `  m0 k5 l% r$ M6 {7 Y% m0 Q
  5. void MX_USART3_UART_Init(void)* d+ f: K0 d& n
  6. ' z% R- @4 F$ O4 t+ Y
  7.   huart3.Instance = USART3;' i% b; W5 l0 C2 L' k4 P. O; \
  8.   huart3.Init.BaudRate = 115200;- X5 L) w: p, _( ^' g+ V
  9.   huart3.Init.WordLength = UART_WORDLENGTH_8B;
    0 O2 Y7 z% T5 A' o/ E# X
  10.   huart3.Init.StopBits = UART_STOPBITS_1;
    1 z1 X- J8 S- C( y, x1 v
  11.   huart3.Init.Parity = UART_PARITY_NONE;
    7 c5 ?0 p' j' F& K! Q# i
  12.   huart3.Init.Mode = UART_MODE_TX_RX;- k6 t, j7 E! M* n  G# j6 i
  13.   huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    % c- a& G4 Z6 q5 h
  14.   huart3.Init.OverSampling = UART_OVERSAMPLING_16;3 J0 S' }( J6 [4 T0 n
  15.   if (HAL_UART_Init(&huart3) != HAL_OK)
    # g: l; t' p9 a) @7 D7 h
  16.   {
    & j/ y. M" S" B) T) E: c
  17.     Error_Handler()
    + v  q( u! g8 @: b# b
  18.   __HAL_UART_ENABLE_IT(&huart3,UART_IT_RXNE);//接收中断使能* |# L9 E% m" S$ h7 r7 m
  19.   __HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);//空闲中断使能
    , P( O. a& }1 Z5 c/ J) e" G" J) K
  20. }) V' P1 ]3 X# ~& F  [

  21. - v: C% q( N1 @( v$ W. z3 ?
  22. void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)# S4 ~0 n9 ?; q: X$ K

  23. ( d) O, Z; {6 E4 p7 d0 [  N
  24.   GPIO_InitTypeDef GPIO_InitStruct = {0};- U- U( y: r$ D: H2 _, z/ d
  25.   if(uartHandle->Instance==USART3)
    6 z4 d. N0 P& V
  26.   {
    , [* ]' a0 m, Z" H/ y
  27.     __HAL_RCC_USART3_CLK_ENABLE()
    # J4 {; q% H( K. R0 ?1 \" Q9 i
  28.     __HAL_RCC_GPIOB_CLK_ENABLE();/ b3 `: n5 H0 l! Y1 N
  29.     /**USART3 GPIO Configuration6 H! G; q+ v; o1 b: P' N
  30.     PB10     ------> USART3_TX' t% a; m9 i$ e. W, x
  31.     PB11     ------> USART3_RX7 Z3 ]- R6 T2 M& j8 P
  32.     */
    2 K1 ~; @3 n; u! i
  33.     GPIO_InitStruct.Pin = GPIO_PIN_10;# j" r, E* u( u  o4 P- k) l; X9 R
  34.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    5 Z& j8 s9 h: Q: V/ f0 y
  35.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    2 H" Q( X% H: Y
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct)+ P0 g% N  F  V, a. w  u
  37.     GPIO_InitStruct.Pin = GPIO_PIN_11;
    # a! ^; h8 K; Y  ~" d1 Q
  38.     GPIO_InitStruct.Mode = GPIO_MODE_INPUT;6 M5 X: r  f+ p5 e
  39.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    3 L" r$ c/ z& C( Y9 Z5 }
  40.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    3 u+ D$ F; K" x' I
  41.     HAL_NVIC_SetPriority(USART3_IRQn, 5, 0);! q- D8 B, F3 T+ }
  42.     HAL_NVIC_EnableIRQ(USART3_IRQn);
复制代码
9 }! s6 e6 o0 i  x
我们平时需要改的其实就是串口的一些参数配置。/ C! n% x$ M: K& D' \
BaudRate:波特率$ Q, |1 k! I' X1 t+ x
WordLength;:字长
$ A7 j3 u3 U8 x& l2 B, ~StopBits:停止位
  @+ Q& L* }  S' ]/ e. I  Y& FParity:奇偶校验
8 w8 Y! u. u$ Z. U2 d0 e5 l( X7 {Mode:收/发模式设置! J( ^( q- i3 y; K
HwFlowCtl:硬件流设置
4 ?% \: u, X, V0 eOverSampling:过采样设置% A' O. z- I8 \9 Q1 b/ L$ t
3 j1 [% Y9 D" Z$ M3 a+ r
串口发送
+ i4 b) |0 [9 e; ]串口发送这里使用的非中断发送方式。8 ?( I8 O* ]8 p' D. F  w, j/ G0 O) ]0 y8 L
+ @% [: X! O. |! Q. q8 L
  1. /*******************************************************************************
    - }2 l4 z, Y! [" z- Q
  2.   * @函数名称  USART_Send" K7 T! I  K! c5 F9 f1 l
  3.   * @函数说明   发送信息
    : {/ |2 W" v( I% E+ Y( Z2 t
  4.   * @输入参数   _UART:串口号' n: L3 e  [1 G) a' u. `8 O, i8 I
  5.       data:要发送的信息的首地址
    4 z% ?2 y6 `8 m% N' m
  6.       len:发送的长度; O% U; M& t, x; I8 k$ L
  7.   * @输出参数   无- l6 g* y" }$ J4 `8 b/ \# w; o
  8.   * @返回参数   无
    4 B, S/ r* l, n- s( R1 T2 G) P& [
  9. *******************************************************************************/
    6 i* q# u' b2 N* l9 I: L
  10. void USART_Send(USART_TypeDef *_UART,uint8_t *data,uint8_t len)7 V! e; K0 F& D' M" [
  11. {; i+ V: X3 [+ c3 O
  12.   for(int i;i<len;i++)
    1 @5 f7 e# g- T* j+ p/ R
  13.   {; g. n. I3 x; i+ K2 K/ g& u: s7 G; d
  14.     HAL_UART_Transmit(&huart3,&data<i>,1,1000);
    ' q  M6 n) @/ A' c
  15.   }, R# V5 ~. O6 t$ A! k% B
  16. }</i>
复制代码
" x9 ]& l0 p& w
主要使用的是HAL_UART_Transmit(&huart3,&Res,1,0Xffff);
/ A7 A3 z, v6 d/ k这是一个阻塞的发送函数,无需重复判断串口是否发送完成。发送每个字符,直到遇空字符才停止发送。其中第一个参数是串口号,第二个参数是要发送的数据起始地址,第三个是要发送的数据长度,第四个超时时间(超过此长度仍未发送成功则阻塞完毕,停止发送,函数执行完毕)。  s$ D- e: E7 _/ B
5 A, |2 v7 S$ o) G
串口接收7 _* V: t% K! s
这里串口接收使用的是中断的方式。2 ^! a8 S+ e/ L& k7 i
中断的类别在文章的最上边已经介绍过。我们在初始化时设定触发中断的类型。本文中设置的
, D" |  L( t- |) N* A2 S# B& i! k- }9 Q9 @& `
  1. __HAL_UART_ENABLE_IT(&huart3,UART_IT_RXNE);//接收中断使能
    5 q% ^" Q) m; I# m3 a# ^* k
  2. __HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);//空闲中断使能
复制代码

! [( [: L, E  u; t/ V+ a代表只有接收数据和空闲中断会触发。
- L0 I- a( M' _在stm32f1xx_it.c中有我们的串口中断处理函数。我们将这个函数进行重构。/ ~+ a! P9 J; m5 t

1 M) C& Y  w0 s" `, `) _
  1. void USART3_IRQHandler(void)6 L  O# K# g7 v' G4 \. O
  2. {
    ! k: R1 d5 `  h
  3.   uint8_t Res;
    / L3 R: a! a3 m# D9 Y0 P+ G* s
  4. if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_RXNE)!=RESET
    + j1 N" A7 K! W; i; k
  5. {
    6 ?0 t) U: i, E/ p
  6.   HAL_UART_Receive(&huart3,&Res,1,0Xffff); 1 ]  @( e) s$ k
  7. if(Res==0x23)  6 U8 x5 ?6 P& l; d8 W) K9 o- g
  8.   printf("PC");7 c8 T! a9 s; G
  9. }
    2 z- f2 ?$ j+ c3 s- n! ]/ R. Z
  10. else if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE)!=RESET)//空闲中断(代表这一帧数据传输完了)$ w8 X; g9 K4 }
  11. {% J. W* |% }, W. Q8 f4 o1 ]7 B
  12.   printf("Receive a frame data.");
    8 [( n2 p8 V. r
  13.   __HAL_UART_CLEAR_IDLEFLAG(&huart3);
    & P- N5 B, f3 p; S# }  Q4 T
  14. }
复制代码
, x3 ^, {6 T: x; _1 r
这里面的几个重点,我们来一一介绍。8 G% ?% N. E6 ~( @' I
首先是判断标志位,我们使用HAL库中的__HAL_UART_GET_FLAG()函数,里面有两个参数,前者是串口句柄,后者是具体哪个标志位。% C' ^0 E. ~9 P$ K/ x+ l
if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_RXNE)!=RESET)用来检测是否检测到有单个字节的中断。/ |: q! B0 P5 |; L' T2 ?
if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE)!=RESET)用来检测是否有空闲中断(代表这一帧数据传输完了)。0 f5 j; N. m5 y) s8 w& A9 |% e" p' j
3 U/ g( I) q1 o! ^; ^! E2 v; `
重定向printf和scanf6 z6 k7 l" Z3 Q
还有一点需要注意的,使用 fput 和 fgetc 函数达到重定向 C 语言标准库输入输出函数必须在 MDK 的工程选项把“Use MicroLIB”勾选上, MicoroLIB 是缺省 C 库的备选库,它对标准 C 库进行了高度优化使代码更少,占用更少资源
* b( V, T- j- O. u- o/ j为使用 printf、 scanf 函数需要在文件中包含 stdio.h 头文件。" M: B* x- A  c% W; u
  ^4 M- d: l/ g
  1. /**
    8 J; O$ _$ e4 j1 X$ N* l( T3 _- ?
  2.   * 函数功能: 重定向c库函数printf2 s. V& I, y/ r$ C4 H" o) T
  3.   * 输入参数: 无
    " M; w+ f7 ?1 L* w9 W1 ~
  4.   * 返 回 值: 无
    6 K6 K4 F7 h; z8 G/ Z
  5.   * 说    明:无" R$ m$ j0 w7 o7 L7 K& P
  6.   */
    - A$ z& ]" q# L! y2 ]5 t3 K  u
  7. int fputc(int ch, FILE *f)- _. H! I0 C  x2 ]6 s' b! p: u# H
  8. {' W% C8 J" A. C6 x3 w. u8 S& X
  9.   HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xffff);3 W2 N& b/ t. D2 m2 o4 h8 ~
  10.   return ch;
    2 E0 A- s/ ?. A# Y
  11. }
    ' i: J" K3 i: C

  12. 6 L7 t2 \! j) Z, e$ b3 c/ |  d8 j# `: N
  13. /**
    - u3 u  i/ w5 ~' z: V6 p/ s- M1 Z
  14.   * 函数功能: 重定向c库函数getchar,scanf
    5 p$ z  }$ x% i/ a9 t
  15.   * 输入参数: 无1 y/ a7 v1 R% N) @$ p
  16.   * 返 回 值: 无+ G2 k( j8 M! o$ t3 o
  17.   * 说    明:无) z0 A4 m5 E; s9 E4 N0 k' K
  18.   */+ G$ `' x0 ]9 W; g0 D
  19. int fgetc(FILE *f)9 u0 U+ h+ }$ {& s& K
  20. {. t  R/ l$ Q! E9 A4 f
  21.   uint8_t ch = 0;
    ( O9 g6 n3 `- O
  22.   HAL_UART_Receive(&huart3, &ch, 1, 0xffff);
    0 ]- d7 W4 `7 o! Y
  23.   return ch;; s4 z. v7 y/ y/ G! _5 ]
  24. }
复制代码
& t* Z# }; U! T  k1 w. P
效果
" i4 Z  }: R' P) F* v. K1.PC下发:11 22 33 44
7 l# }8 i' e: ?6 f  G+ C6 q- ?0 G/ W+ J; q* v
640 (1).png # z. |+ k! P: z1 q& e

/ C- m# J1 E" ]# V4 s- }7 K; f2.PC下发:12 23 34 45
' X- E* U! m' |( I
- s! g. q+ E, O# ` 640.png
3 J7 H( F6 {6 A* E3 d8 W# c( g! I5 A% N! R7 g
转载自: 跋扈洋
! q3 d, D, d# K7 }4 h
0 d) M0 p) g: E6 V, W, _6 A
收藏 评论0 发布时间:2022-12-1 18:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版