|
前言 对于 STM32 串口的使用,确实很简单使用 STM32CubeMX 做好初始化,就可以直接使用了。 但是最近在某些产品上使用串口同时收发的时候,发现有时候串口会收不到数据了,但是发送正常,而且这个问题再数据量大的时候很容易出现,于是乎进行了好几天的问题测试…… 0 L) M; A4 s" x9 [( g( I
一、 HAL 库串口收发先简单回顾一下 STM32 HAL库串口收发是如何使用的。
( u6 _0 E$ Y- b' `2 ] 1.1 串口发送对于 STM32 来说,串口发送有3中方式: - 轮询发送;
- 中断发送;
- DMA发送;
! w6 W1 h$ T& S6 Q0 m3 J
在实际产品上,大部分项目中都用的是 轮询 方式发送,本次出现接收卡死的问题的产品也是采用的轮询发送,所以我简单的说明一下轮询发送,其他两种方式为 STM32 学习的基础问题,这里就不过多讨论。 发送相对简单,在 HAL 库实际都是使用HAL_UART_Transmit函数:
+ X6 g4 ?& k& ], C! Y8 h+ A9 ^ Z/ Y& l' A
/ z& C- b7 p+ ?& m* _1 F# j不管是哪个串口发送,都做了个简单的发送函数: , J3 R6 C# _! V
! u0 @4 E# |/ g7 M$ N
# F3 a6 _3 g3 ?& Q- f发送没什么好说的,简单易用。
+ _) x- E# c# i8 `& l9 b7 G! \4 G 1.2 串口接收和串口发送一样,串口接收有3中方式: - 轮询接收;
- 中断接收;
- DMA接收;$ i+ c* g; {' T4 J0 _+ |) O
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式。 那么对于本次出问题的产品,我采用的是 中断接收的方式。 但是相比较发送,在 HAL 库中 使用中断接收的方式就有点 “五花八门” 的感觉。
6 @2 T0 l* o7 V9 @; c6 c 1.2.1 标准库接收在标准库的时候,我们经常这么用,在串口初始化的时候使用下面的语句使能中断: - //在串口初始化代码之后加上中断使能
7 h5 z. d7 |: d& H3 R# X - USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
复制代码 * y" D& h5 H/ ~. o
如果需要自动判断一帧数据,我们再开启一下 IDLE 中断: - USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
复制代码
3 I5 g$ x9 i! P# l6 ]然后在对应的中断处理函数中,直接读取 DR 寄存器(STM32F103 而言为 DR,STM32L051 为RDR),然后当 IDLE 中断产生,也可以处理一下标志位: - //自己用的,用到了 USART_IT_IDLE 标志位,有时候不合适% c4 h3 C2 ~& w- @0 n# R
- void USART3_IRQHandler(void) //串口3中断服务程序
5 R( E5 n1 f2 u - {
8 {" B! w6 C5 h7 M3 v) V; J6 j - u8 clear3=clear3; //消除编译器没有用到的提醒3 c; {0 A1 o; t. N {9 H6 a
- // u8 Res;. ^% G: F3 Z( }+ ~
- 7 y* ~' b5 H6 m% Y
- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) " t) s/ c* L" k# I
- {
' B! J! w' @4 t! [ - USART_Enocean_BUF[Enocean_Data++] = USART3->DR;* Q; ]/ f8 |9 T
- USART3_RX_BUF[USART3_Data++] = USART_ReceiveData(USART3);8 N3 [5 e6 L' x# g$ @! S4 [
- // Res= USART_ReceiveData(USART3);0 v1 m8 G5 b0 q. ~# Y
- // USART_SendData(USART1,Res);while(!(USART1->SR&USART_FLAG_TXE)); # V; ?9 p0 {+ i7 c3 G% G2 v
- }/ a W1 ]( y, |9 t
- ' _0 e8 E5 F4 o
- else if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
7 V( e6 X8 I4 E* h& [- s1 V( H - {
7 G. c8 `2 e$ {; y; o- w - clear3=USART3->SR; //读SR寄存器 可以清空寄存器' P* P2 h3 ~$ E
- clear3=USART3->DR; //读DR寄存器(先读SR寄存器,再读DR,为了清除IDLE中断)
; C; I1 }& ~7 J+ Z$ L0 z - USART3_RX_STA=1; //标记接收到了一帧数据! H M: V ^5 N. J1 u$ |
- //USART3_Data=0;
/ h* i4 v, |! K4 g - } //enocean是不是读不到一帧数据,不用一帧数据测试一下
# c4 l8 o4 O. M$ t+ R; C - }
复制代码 ( y; d$ S+ N7 G8 }* P2 Y) Q
1.2.1 HAL库接收HAL库接收方式一在 HAL 库函数接收的时候,其实也可以使用标准库上一样的中断标志使能: - MX_LPUART1_UART_Init();
; t% h$ ~4 t' C7 o+ c% E3 v - __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
复制代码 4 d, m% {8 ^4 x0 G( j
IDLE 中断使能: - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
复制代码 - I& z, f0 j* W
这种方式的处理方式,可以和标准库差不多。 在 HAL 库中,外设的中断的入口函数都放在stm32l0xx_it.c 这个文件夹中( 以STM32L051 为例),在这个文件中可以找到和标准库一样的 中断入口函数,我们可以进行如下处理: - void USART2_IRQHandler(void)' S* K& m9 ?# x* F) ]5 r
- {
2 y; B @7 y& r) ?! ` | - /* USER CODE BEGIN USART2_IRQn 0 */5 Z, v& m+ W2 `! V) o3 w" i
M Q; Q. I0 a) L' n* ]4 t5 y- /* USER CODE END USART2_IRQn 0 */
W+ I9 {2 {8 v& [6 H4 f' y - HAL_UART_IRQHandler(&huart2);
8 R* @7 p; ]/ L, p6 ?) E, d' Z - /* USER CODE BEGIN USART2_IRQn 1 */
; e+ d4 s0 E" X- _; ]/ s - if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) == SET){: v4 Y+ B: T, Z7 {
- // USART_Enocean_BUF[Enocean_Data++] = huart2.Instance->RDR / K& M, ?$ ~% \ W Z R
- // RXNE 数据处理,直接读取数据 . D& l$ X+ N3 r: N) }6 p: V0 s
- }& {2 ]: e& _" v
- if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))
/ ]: C/ j# I$ S5 Z: g - {; F: k: g' {+ l1 Y4 D+ y
- __HAL_UART_CLEAR_IT(&huart2,UART_CLEAR_IDLEF); //Çå³ýÖжÏ
O' I' T' }: i5 F8 j" R - // ReceiveState = 1;# R; _8 U; y2 D3 W" q* i6 I. n6 J8 K+ v
- // IDLE 数据处理,一帧数据接收标志位置位6 V# a- K( V% Q
- }; v( W8 |9 N2 V) d; v9 l. C
- }
复制代码
3 b* D" ^4 J* T2 @9 q% eHAL库接收方式二但是在于 HAL 中,还有一种比较常用的开启中断方法,不是直接使能中断,而是通过调用 HAL 库函数 HAL_UART_Receive_IT : / p" c9 N$ f# ~$ d- x
* z j9 g9 ?: r8 ?+ v
. c1 p, R- y: l: v
9 w( m) S0 g a) q E( y: a+ K$ Q一般的使用方式步骤:
6 J& |/ [# f' c% C/ B- }7 f' Y. q& S/ ^; j l! I
6 ]( K* D% j6 |( }( W: e
/ Z' Z* s! X } @, ?6 D8 U: }& Y0 e: m0 p" [& y
1 R' A) U% L, V' I
对于本次需要说明的问题,就是使用了 HAL_UART_Receive_IT 函数导致的,下文我们会说明,这里列出了基本的使用步骤。
5 k6 Y7 k v8 b 二、 收发同时串口卡死?/ b/ e' ], Z3 z/ A7 W
2.1 问题说明最近测试部反馈,产品有些时候的下行没反应,这里所说的下行,其实就是串口接收。 霹雳扒拉一大堆多余的省略 ... ... 只说几个重点: 出问题的最后现象就是串口发送正常,但是永远接收不到数据了,其他程序正常运行。 出问题只存在于串口又有接收,又有发送的产品上。 产品发送一般是周期性的,但是接收是随机的,无线信号串口接收,所以产品的出问题的情况也是随机的,但是数据量大起来肯定就会出现永远接收不到的问题。
0 y" q8 l- W/ v9 v" I, x0 m4 `9 S) ?2.2 尝试的处理方式因为所有的一些都是按照正常流程设计的,按理来说实在是不知道为什么会这样,所以网上查询测试了好久,现在我把尝试的处理方式以及步骤记录说明一下: $ ?" O1 ^+ s9 ^* Z, N$ y
2.2.1 清除错误标志位在使用 HAL 库的时候,有4个错误 flag,如下图:
( J7 M. H! c/ E: e, [) {! R3 ~
! H" X: o# @& T. i7 }% d7 p5 a
, F* Y* L6 E8 h( A3 X7 {( ~7 b
% g6 @$ B! K* V期初还以为是某些异常错误导致的,经过网上的的一些查询,刚开始是添加了清除错误标志位: - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_PE);//清标志
7 `" x' t! E2 n3 N) W6 U' x& R% C - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_FE);6 j- h# W+ V/ F: U+ T( O2 p! o
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_NE);
, Z% o" L8 {% R/ Y: _ ` - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_ORE);
复制代码 g- E" L3 }) i% r
在需要的地方加上错误标志位清除,我是在清除串口缓存中处理的:
! h8 X! c# I: A8 s9 W
" {& D v- s3 |; N3 F! k* U$ G$ {
% [, j5 S" z) o8 `7 r$ q" I4 @" R# i |# \* D1 p
6 h# w) m. w; V, r U8 B
2.2.2 串口溢出错误其实串口溢出错误在上面的已经清除过标志位,因为这个问题着实搞得我头有点大,所以看到网上前人的处理方式和上面直接清除不一样,还是试了一把。 这里简单说明一下,我还特意去看了下自己的 CubeMX 设置,在设置的时候 有一个 Overrun 错误标志位,平时我们设置都不一 定往下拉着看 = =!:
$ Y: c5 O2 F" z: o1 e1 u
' ]7 q% U) F4 \
然后确定了开启了串口溢出错误检测以后,我根据网上的方式,加了一个HAL_UART_ErrorCallback函数,其实就类似于HAL_UART_RxCpltCallback 函数:
; z1 ~7 Y0 p% s2 y' m! j+ i* |8 a7 h1 F* S& V _
& i4 ]. U; f: K; K" I
. C) w; G2 ]+ b自己加了一个 出错处理函数,其实现在看来,当然也是没有用的。
# F: M" K$ ]8 P3 W" H5 _2.2.3 HAL库的半双工处理?折腾了好长一段时间,其实一开始就知道问题在于 同时收发会出问题的情况,那么继续上网找问题。 最终确定了一个问题就是: 我们都知道 STM32 串口是全双工的, STM32 HAL库在处理接收的时候会锁一下串口一会,导致变成某个短时间的“半双工”。 这个时候如果同时收发就会出现问题,最后解决的办法在这篇文章中看到了:STM32 F103串口同时收发出现死锁问题解决办法 问题在于我们使用的HAL_UART_Receive_IT函数中,有对串口加锁的操作:
/ J. O3 E8 X, D6 j
5 ], X7 q# q" G; j4 l: s
虽然在后面有解锁:
5 T+ d, ~3 D! i8 T
6 E; O4 C$ N# ]; C' t) U( a$ c# V( |/ {* G6 Q
7 C u p A& C
但是根据后期的解决方式来说,确实就是这个HAL_UART_Receive_IT函数的问题,最后使用的方式为,在产生一次中断以后开启的时候手动解锁: 5 s4 B. [9 E9 }* m: H% C" h
" B; `7 }4 v+ K1 {3 s* C8 z+ o: W9 |" o/ I( K5 p
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart): n+ C2 _1 `" s( i" I
- {9 k& `, J: Y- Y2 \$ g
- if(huart->Instance == LPUART1){9 I1 ~) G, A5 \& O k
- Enocean_Data++;
* P; R% l. |- X' u0 O - if(Enocean_Data > 98)Enocean_Data = 0;' K& v& \" l) D- z
- while(HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&USART_Enocean_BUF[Enocean_Data], 1) != HAL_OK){
% ^# \" U3 z% C - hlpuart1.RxState = HAL_UART_STATE_READY;9 K, ~0 z, q! ?+ y) M, l
- __HAL_UNLOCK(&hlpuart1);
2 A. e8 S+ {) r0 X - }
# J- N' Y u. e - }2 Z, t5 M" b! p2 s3 y9 N( r/ c
- else if(huart->Instance==USART1)2 ^. D0 W; g/ t
- {
. d3 G3 M* S1 \; e5 ` - }% \( F' y& V9 O [
- }
复制代码
7 G. Q! b9 k2 K G8 J3 X3 E* }
. Z3 p% ?$ e2 m# l# E3 t f7 }终于,串口不再卡死 , 成功! " X8 P5 N J: ?% w* _' i+ n2 c
5 i A+ g- w0 l C+ q
转载自:矜辰所致
2 \! d( @$ R/ M* @7 F& N+ b如有侵权请联系删除
- [1 d, I2 x8 ? X% P
7 ~% E O ^+ ~, M' j) |+ n% L5 c f( q# r* c+ l
|
直接使用HAL_UART_Transmit_IT,不会卡死,不过调用的时候要保证上一帧已经发送完成,不然会丢数据。
我觉得吧,一般情况使用HAL库收发函数要成对使用,接收和发送要么都轮询,要么都中断,要么都DMA,这样不用做特殊处理问题不大。
特殊情况下,比如RS485通信,收发不同时发生,轮询发+中断收也没问题。