|
前言 对于 STM32 串口的使用,确实很简单使用 STM32CubeMX 做好初始化,就可以直接使用了。 但是最近在某些产品上使用串口同时收发的时候,发现有时候串口会收不到数据了,但是发送正常,而且这个问题再数据量大的时候很容易出现,于是乎进行了好几天的问题测试……
* R+ | M) p4 Z9 a) }9 o, N 一、 HAL 库串口收发先简单回顾一下 STM32 HAL库串口收发是如何使用的。 2 P/ _0 Z1 P9 _" V/ j( R7 h* C$ d0 W
1.1 串口发送对于 STM32 来说,串口发送有3中方式: - 轮询发送;
- 中断发送;
- DMA发送;4 ]+ ^! r) q% \+ |. V+ G: i) v
在实际产品上,大部分项目中都用的是 轮询 方式发送,本次出现接收卡死的问题的产品也是采用的轮询发送,所以我简单的说明一下轮询发送,其他两种方式为 STM32 学习的基础问题,这里就不过多讨论。 发送相对简单,在 HAL 库实际都是使用HAL_UART_Transmit函数:
+ d$ i" r0 L& n4 M$ Z2 y0 l* E) Y" n4 F
8 }( h% _4 G0 d% R, |不管是哪个串口发送,都做了个简单的发送函数: , X1 U& s0 H6 s6 S) z6 d2 ?' {+ v8 U
' T# S H4 t I4 i( |- x
% [1 Y, M6 G! L发送没什么好说的,简单易用。 & D6 \ m1 X5 j9 ^8 N8 w8 o
1.2 串口接收和串口发送一样,串口接收有3中方式: - 轮询接收;
- 中断接收;
- DMA接收;
! z. ~, D$ r* p
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式。 那么对于本次出问题的产品,我采用的是 中断接收的方式。 但是相比较发送,在 HAL 库中 使用中断接收的方式就有点 “五花八门” 的感觉。
7 o" \% y$ S3 q; t3 ~; n/ w6 s, K 1.2.1 标准库接收在标准库的时候,我们经常这么用,在串口初始化的时候使用下面的语句使能中断: - //在串口初始化代码之后加上中断使能
2 C! U% j/ t6 B! a - USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
复制代码
; O/ P r9 \' O5 d. a& L如果需要自动判断一帧数据,我们再开启一下 IDLE 中断: - USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
复制代码 - T5 `. [. b" S% \- l: K
然后在对应的中断处理函数中,直接读取 DR 寄存器(STM32F103 而言为 DR,STM32L051 为RDR),然后当 IDLE 中断产生,也可以处理一下标志位: - //自己用的,用到了 USART_IT_IDLE 标志位,有时候不合适6 D. Z9 e3 p. J5 V
- void USART3_IRQHandler(void) //串口3中断服务程序
1 D, v9 ~9 D* d, y1 ^ - {
- i N% u+ H5 p- q/ V* y; m - u8 clear3=clear3; //消除编译器没有用到的提醒
l) \1 `* P2 Y& B& ^2 f - // u8 Res;( ~$ m3 _4 M. W/ Y' T2 O
- ; e! h/ [$ [& x9 D
- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) ; T0 c% @+ t* d0 B+ T7 A- r
- {. m* C( ~: C# h8 A$ D
- USART_Enocean_BUF[Enocean_Data++] = USART3->DR;, f: G/ G6 A" {( S9 Y8 Q% q
- USART3_RX_BUF[USART3_Data++] = USART_ReceiveData(USART3);9 p$ y8 K! H3 }; i* L
- // Res= USART_ReceiveData(USART3);! R' u U! B& G l2 Q& f3 f6 `
- // USART_SendData(USART1,Res);while(!(USART1->SR&USART_FLAG_TXE)); 2 } u# P! `% G9 b+ q- E& O7 M
- }
5 A4 W' d" n: a6 @7 y2 q; c - : Q% [2 W0 ]3 z: O
- else if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) % c0 o% O c% h5 v ^' W* F* A& M) U
- {; B; f3 Q! [+ a: T$ H! ]
- clear3=USART3->SR; //读SR寄存器 可以清空寄存器$ R6 c& O$ G/ Q5 i
- clear3=USART3->DR; //读DR寄存器(先读SR寄存器,再读DR,为了清除IDLE中断)
F: U9 W5 E, l; q6 R1 P - USART3_RX_STA=1; //标记接收到了一帧数据
" d% c3 R8 I; e - //USART3_Data=0;
- @% o' `; }- O! F+ z5 b/ G - } //enocean是不是读不到一帧数据,不用一帧数据测试一下 5 r R* s- k3 D! i1 h8 N
- }
复制代码
4 l( @7 G% y+ h% s! Y1.2.1 HAL库接收HAL库接收方式一在 HAL 库函数接收的时候,其实也可以使用标准库上一样的中断标志使能: - MX_LPUART1_UART_Init();7 N0 `/ y% T1 }
- __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
复制代码 6 n) |4 D" i# C" f! ^/ L% `
IDLE 中断使能: - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
复制代码 / ]. o1 G. w: i
这种方式的处理方式,可以和标准库差不多。 在 HAL 库中,外设的中断的入口函数都放在stm32l0xx_it.c 这个文件夹中( 以STM32L051 为例),在这个文件中可以找到和标准库一样的 中断入口函数,我们可以进行如下处理: - void USART2_IRQHandler(void)
: J' R% O% j) k# ?9 s - {1 w& ?* D7 k; K& _$ h4 S
- /* USER CODE BEGIN USART2_IRQn 0 */9 j6 H- d1 M+ X0 X+ I# K
e, G9 d: H+ B) u+ A- /* USER CODE END USART2_IRQn 0 */2 s7 \# T' c9 }
- HAL_UART_IRQHandler(&huart2);% `( Q$ Y% C5 q! R6 U6 g
- /* USER CODE BEGIN USART2_IRQn 1 */
. u; Q) ~& [6 L- i" j - if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) == SET){. D* U' c' A9 ?* R) z. p3 B
- // USART_Enocean_BUF[Enocean_Data++] = huart2.Instance->RDR
9 _ ?# {$ I2 m- Y. Y! { - // RXNE 数据处理,直接读取数据 ( `4 h3 s* w% R& K R* O
- }4 c& D l \% N8 b0 X
- if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))0 F/ G( a7 u6 P" H% W: v
- {! A/ ^. s# `; }2 m/ ^+ B
- __HAL_UART_CLEAR_IT(&huart2,UART_CLEAR_IDLEF); //Çå³ýÖжÏ
. E8 I: w9 l7 i2 p - // ReceiveState = 1;4 o0 @5 _) i6 i% m$ X# }/ y
- // IDLE 数据处理,一帧数据接收标志位置位
: P+ l; ~4 g1 ? - }$ I+ q/ r% `3 e! F9 ^
- }
复制代码
! I2 G% B. P$ _* O* \6 MHAL库接收方式二但是在于 HAL 中,还有一种比较常用的开启中断方法,不是直接使能中断,而是通过调用 HAL 库函数 HAL_UART_Receive_IT :
1 K# Q' T- V) R4 w4 ]
, `7 U$ g3 l. G2 s4 V
4 d5 T T n- [- i1 P$ |) F; \( Z1 ~! O6 J
一般的使用方式步骤: 3 y8 y6 }8 `- d
' |9 ~& p' G0 m1 f: }4 c/ D! x- O6 s. Z" C* ?0 f' t
2 ] G S# K) u( W9 Z9 V6 C
/ |( {7 R( ?- M8 T) R, h
, L& ~3 O% U- j, D' D& L对于本次需要说明的问题,就是使用了 HAL_UART_Receive_IT 函数导致的,下文我们会说明,这里列出了基本的使用步骤。 3 v, Z3 k* F0 W3 z! L
二、 收发同时串口卡死?
8 u; w) Q5 m) |0 t' d: V2.1 问题说明最近测试部反馈,产品有些时候的下行没反应,这里所说的下行,其实就是串口接收。 霹雳扒拉一大堆多余的省略 ... ... 只说几个重点: 出问题的最后现象就是串口发送正常,但是永远接收不到数据了,其他程序正常运行。 出问题只存在于串口又有接收,又有发送的产品上。 产品发送一般是周期性的,但是接收是随机的,无线信号串口接收,所以产品的出问题的情况也是随机的,但是数据量大起来肯定就会出现永远接收不到的问题。 6 n& f/ I+ V4 F" w. ~& H! Q
2.2 尝试的处理方式因为所有的一些都是按照正常流程设计的,按理来说实在是不知道为什么会这样,所以网上查询测试了好久,现在我把尝试的处理方式以及步骤记录说明一下:
; \0 Z6 [ ?7 z+ T+ Z+ a2.2.1 清除错误标志位在使用 HAL 库的时候,有4个错误 flag,如下图:
' E4 @6 i9 r" |; e' v3 Y
! d9 m6 ^$ T7 n8 |3 L3 Y9 u0 Y, H5 l H
# _/ k% W; z1 t' Z: \: n/ E
期初还以为是某些异常错误导致的,经过网上的的一些查询,刚开始是添加了清除错误标志位: - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_PE);//清标志: X7 b6 ?3 }% w1 U9 s+ u
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_FE);
! a% \9 l) p8 E5 |" `0 L Z8 ? - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_NE);
6 k4 E+ @, s/ n+ Y - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_ORE);
复制代码 ) V' ? u) S4 _. \9 D
在需要的地方加上错误标志位清除,我是在清除串口缓存中处理的: 0 Z# p3 Q# j, U0 s ^) S5 D' a
1 S0 l2 k; }: o) g) K( T- V
, J' r* Z9 F$ X j# N }. t2 c
' W8 C" l: T# U+ p: u" S/ r I2.2.2 串口溢出错误其实串口溢出错误在上面的已经清除过标志位,因为这个问题着实搞得我头有点大,所以看到网上前人的处理方式和上面直接清除不一样,还是试了一把。 这里简单说明一下,我还特意去看了下自己的 CubeMX 设置,在设置的时候 有一个 Overrun 错误标志位,平时我们设置都不一 定往下拉着看 = =!: + U, _' x0 m0 @( C* e
. i2 V/ i2 Y. ~. E" g0 s3 t5 W然后确定了开启了串口溢出错误检测以后,我根据网上的方式,加了一个HAL_UART_ErrorCallback函数,其实就类似于HAL_UART_RxCpltCallback 函数: # s+ d- A4 l$ o! R
5 I5 `; t: f; T0 ~7 W# V7 [) J" B' Y
: \# i7 i; ^. I
自己加了一个 出错处理函数,其实现在看来,当然也是没有用的。
4 l! c" x# @6 w5 t" S% h& h2.2.3 HAL库的半双工处理?折腾了好长一段时间,其实一开始就知道问题在于 同时收发会出问题的情况,那么继续上网找问题。 最终确定了一个问题就是: 我们都知道 STM32 串口是全双工的, STM32 HAL库在处理接收的时候会锁一下串口一会,导致变成某个短时间的“半双工”。 这个时候如果同时收发就会出现问题,最后解决的办法在这篇文章中看到了:STM32 F103串口同时收发出现死锁问题解决办法 问题在于我们使用的HAL_UART_Receive_IT函数中,有对串口加锁的操作: 2 L1 h, D5 y3 f/ T* M- r! D
0 C$ H: x L$ e9 k虽然在后面有解锁: ! \/ |% t5 D: l& ^
d4 }* b4 S+ Z1 X' V: M
6 o7 B% S1 H* J: F5 ^- |. @2 Q4 k1 y; g$ L# k
但是根据后期的解决方式来说,确实就是这个HAL_UART_Receive_IT函数的问题,最后使用的方式为,在产生一次中断以后开启的时候手动解锁: " S, k( E5 q# d7 ^+ |6 C, S
: }5 U- P2 |0 ~& O. m% U/ z. N, w! v: c5 F' P# w
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
6 @2 H$ Q$ \) i( g! z: F - {
, e9 d5 [! j3 n/ s - if(huart->Instance == LPUART1){1 G1 i" P' [4 {: _
- Enocean_Data++;, C, w' s/ @" V" h
- if(Enocean_Data > 98)Enocean_Data = 0;
6 w) `% G; f/ i: ?! I# A4 F - while(HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&USART_Enocean_BUF[Enocean_Data], 1) != HAL_OK){2 k$ e$ z0 @0 F$ _
- hlpuart1.RxState = HAL_UART_STATE_READY;9 C+ r/ f% D( a0 g
- __HAL_UNLOCK(&hlpuart1);
# ?. E$ P& x. H2 K% z! j - }0 Y1 L; p& t0 h) ]" m, ^9 H
- }6 t, u+ Q) L; k
- else if(huart->Instance==USART1)
9 c& a6 j5 p T( V! x# N - { " N/ B0 @1 ~8 \! @9 \' M- R+ C s
- }
h. _6 j0 f$ X% X - }
复制代码 # K- {/ F# f+ q% K6 Q
, ?# @, s n+ l- |; c
终于,串口不再卡死 , 成功! ?1 i% F' H3 a
: _+ J8 w; w" d. T/ f. u+ |
转载自:矜辰所致) T ~1 {8 M5 m# v5 ^6 N
如有侵权请联系删除) n+ A- J, A# Y {3 U3 k3 V, l
- c7 N! t7 m; I2 u$ T2 H2 i% d. ]; `! c- L2 E
|
直接使用HAL_UART_Transmit_IT,不会卡死,不过调用的时候要保证上一帧已经发送完成,不然会丢数据。
我觉得吧,一般情况使用HAL库收发函数要成对使用,接收和发送要么都轮询,要么都中断,要么都DMA,这样不用做特殊处理问题不大。
特殊情况下,比如RS485通信,收发不同时发生,轮询发+中断收也没问题。