|
前言 对于 STM32 串口的使用,确实很简单使用 STM32CubeMX 做好初始化,就可以直接使用了。 但是最近在某些产品上使用串口同时收发的时候,发现有时候串口会收不到数据了,但是发送正常,而且这个问题再数据量大的时候很容易出现,于是乎进行了好几天的问题测试…… ; @+ u; v$ r8 m' ~) e7 c1 n2 O {1 \
一、 HAL 库串口收发先简单回顾一下 STM32 HAL库串口收发是如何使用的。
a9 R7 a6 F& t3 z6 \+ w% | 1.1 串口发送对于 STM32 来说,串口发送有3中方式: - 轮询发送;
- 中断发送;
- DMA发送;) `/ h: k: V+ X* G, B
在实际产品上,大部分项目中都用的是 轮询 方式发送,本次出现接收卡死的问题的产品也是采用的轮询发送,所以我简单的说明一下轮询发送,其他两种方式为 STM32 学习的基础问题,这里就不过多讨论。 发送相对简单,在 HAL 库实际都是使用HAL_UART_Transmit函数:
8 s$ {( A- i1 {2 o9 A! V: \( y5 M2 o2 Z
7 q8 h, S, V; i* T1 |
- T4 V3 s0 _ m9 r不管是哪个串口发送,都做了个简单的发送函数:
1 ~- ^. w* z- q9 ~6 z" v* |5 V8 J! c# J
7 b0 d* u( p3 j% O1 S1 U' x, H
发送没什么好说的,简单易用。 4 q" N2 i6 I/ r+ k, n/ H7 v- d/ n. o
1.2 串口接收和串口发送一样,串口接收有3中方式: - 轮询接收;
- 中断接收;
- DMA接收;
+ x K2 G G' M- g) B: C: T- l
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式。 那么对于本次出问题的产品,我采用的是 中断接收的方式。 但是相比较发送,在 HAL 库中 使用中断接收的方式就有点 “五花八门” 的感觉。
7 ]1 k( Y8 g. [ 1.2.1 标准库接收在标准库的时候,我们经常这么用,在串口初始化的时候使用下面的语句使能中断: - //在串口初始化代码之后加上中断使能8 {# O! l$ J+ `1 `
- USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
复制代码
! I7 C' _1 Z4 B) W( G: o2 V e2 D如果需要自动判断一帧数据,我们再开启一下 IDLE 中断: - USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
复制代码
% v' w {8 y, D8 m; ]然后在对应的中断处理函数中,直接读取 DR 寄存器(STM32F103 而言为 DR,STM32L051 为RDR),然后当 IDLE 中断产生,也可以处理一下标志位: - //自己用的,用到了 USART_IT_IDLE 标志位,有时候不合适
4 [) Y7 L# X9 J0 {! p& m% |' u6 ? - void USART3_IRQHandler(void) //串口3中断服务程序" `. `, T w% E
- {2 O9 e% O4 c* M5 U, b9 l
- u8 clear3=clear3; //消除编译器没有用到的提醒
6 @7 C: _, i' f, z - // u8 Res;' N/ r0 G m R2 k* Z
: j5 @! \- S% S0 o; A- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
& M( q q }9 R" }% \7 | - {
' x h; [# _% ^; ]4 i3 r- o, ~ - USART_Enocean_BUF[Enocean_Data++] = USART3->DR;
; I+ d4 w! ~% K. B' r1 q0 e. m1 y - USART3_RX_BUF[USART3_Data++] = USART_ReceiveData(USART3);5 v( `, N0 k0 T; p& Z+ o
- // Res= USART_ReceiveData(USART3);
9 M# t+ g9 H3 f! [! r - // USART_SendData(USART1,Res);while(!(USART1->SR&USART_FLAG_TXE));
; B5 W$ ^" P) L9 H - }
. T. Y8 K0 a; u, { - & y' i- q+ l" @+ `# f
- else if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
8 U# \: J& p' p: h. { - {
) L4 U- W# _! \! I" T' \' y% t( L - clear3=USART3->SR; //读SR寄存器 可以清空寄存器& N0 [0 j; T8 E7 y
- clear3=USART3->DR; //读DR寄存器(先读SR寄存器,再读DR,为了清除IDLE中断) / }4 Y+ z- ~0 A4 H
- USART3_RX_STA=1; //标记接收到了一帧数据
& U2 R l' d( `6 p9 \5 y. v, U" N - //USART3_Data=0;" s2 I6 {; B0 V) h3 P
- } //enocean是不是读不到一帧数据,不用一帧数据测试一下
( g$ h* ]- n* O9 M0 B* ^ - }
复制代码
( X5 k+ u$ q4 d ]# S1.2.1 HAL库接收HAL库接收方式一在 HAL 库函数接收的时候,其实也可以使用标准库上一样的中断标志使能: - MX_LPUART1_UART_Init();- Y) s2 [" j4 |3 n6 X5 x+ p: U5 D
- __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
复制代码
. z; x0 H; _; v* MIDLE 中断使能: - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
复制代码
) i$ n9 K( u& V- J2 Q+ }这种方式的处理方式,可以和标准库差不多。 在 HAL 库中,外设的中断的入口函数都放在stm32l0xx_it.c 这个文件夹中( 以STM32L051 为例),在这个文件中可以找到和标准库一样的 中断入口函数,我们可以进行如下处理: - void USART2_IRQHandler(void); O! H) I7 t- |5 a6 l, H; W
- {$ Q6 C0 X4 G: Y6 c5 C
- /* USER CODE BEGIN USART2_IRQn 0 */
' @" G3 `" J" w - & g% k; ] }( M$ Y' r
- /* USER CODE END USART2_IRQn 0 */, ^' h9 F$ J% Q
- HAL_UART_IRQHandler(&huart2);/ @+ o7 `; x1 `! S
- /* USER CODE BEGIN USART2_IRQn 1 */
8 _1 C$ k j& k/ s/ J - if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) == SET){( @# i _& ^- O( d, J
- // USART_Enocean_BUF[Enocean_Data++] = huart2.Instance->RDR
5 O* v+ O# {0 D6 ?1 q - // RXNE 数据处理,直接读取数据 % Y+ W4 h# c% C4 V
- }/ H* P5 k7 g S( p e- K6 f
- if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))
5 k- O5 l, V, v+ f% X: j - {
4 [- _3 W* w6 ]* [8 W - __HAL_UART_CLEAR_IT(&huart2,UART_CLEAR_IDLEF); //Çå³ýÖжÏ
( m, J5 P9 A& z) ?0 L - // ReceiveState = 1;
* ]5 z* w" _$ ?& F" H# B) u1 Z - // IDLE 数据处理,一帧数据接收标志位置位$ m- S& H: n( ]0 p7 i
- }
* B! b. h. {! N - }
复制代码 . J# o2 T/ z0 f* r" T
HAL库接收方式二但是在于 HAL 中,还有一种比较常用的开启中断方法,不是直接使能中断,而是通过调用 HAL 库函数 HAL_UART_Receive_IT : 8 f7 t( t5 h7 ~# ^ o
* {3 p# _" [3 ^$ z, z. F# H0 v% Q) p9 x4 r# r) H. ]
2 g" u3 Y( v6 Q0 J
一般的使用方式步骤:
- O$ N) {7 _9 b- Y5 n: O c* n2 ^$ e3 Q0 l; ~
1 Y# ~- y9 W3 K5 Z9 a3 F0 a! N# e9 e4 ?# |
; z$ }9 ]; i) p* W2 O& B3 N: D; P- c! K3 w
对于本次需要说明的问题,就是使用了 HAL_UART_Receive_IT 函数导致的,下文我们会说明,这里列出了基本的使用步骤。
/ |; ]7 ]) N+ D g8 y( _0 L" m 二、 收发同时串口卡死?
7 t* X3 x' x% M, N+ _2.1 问题说明最近测试部反馈,产品有些时候的下行没反应,这里所说的下行,其实就是串口接收。 霹雳扒拉一大堆多余的省略 ... ... 只说几个重点: 出问题的最后现象就是串口发送正常,但是永远接收不到数据了,其他程序正常运行。 出问题只存在于串口又有接收,又有发送的产品上。 产品发送一般是周期性的,但是接收是随机的,无线信号串口接收,所以产品的出问题的情况也是随机的,但是数据量大起来肯定就会出现永远接收不到的问题。 [ l3 j% w# y( I6 f
2.2 尝试的处理方式因为所有的一些都是按照正常流程设计的,按理来说实在是不知道为什么会这样,所以网上查询测试了好久,现在我把尝试的处理方式以及步骤记录说明一下:
. y, E$ I/ c8 g2.2.1 清除错误标志位在使用 HAL 库的时候,有4个错误 flag,如下图:
: n; Y% Q$ a7 l: d* e7 h7 x
) U' \) j) p: J4 h5 g. X( V) c
2 T! O; {6 ^2 l) Q/ }: |; K; X( q* y
1 J4 }1 A5 W8 E期初还以为是某些异常错误导致的,经过网上的的一些查询,刚开始是添加了清除错误标志位: - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_PE);//清标志
: r9 `5 T6 W! |8 q - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_FE);
; A! w# x6 S9 P" S - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_NE);; p$ W! P# K' k" B' m
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_ORE);
复制代码
, G" Q5 @; k: f6 F在需要的地方加上错误标志位清除,我是在清除串口缓存中处理的:
% m& K# E! ^3 M) K. \
3 ]7 n- `8 c3 _7 \: c0 `# |+ s4 }1 ^+ l# M1 ]
6 j A' ~% e& B! w7 x' W* V5 C8 `/ }" M% r' X- }/ r! q
2.2.2 串口溢出错误其实串口溢出错误在上面的已经清除过标志位,因为这个问题着实搞得我头有点大,所以看到网上前人的处理方式和上面直接清除不一样,还是试了一把。 这里简单说明一下,我还特意去看了下自己的 CubeMX 设置,在设置的时候 有一个 Overrun 错误标志位,平时我们设置都不一 定往下拉着看 = =!:
2 z! p: h y$ K; T! Z
4 t* P2 ~. m3 V然后确定了开启了串口溢出错误检测以后,我根据网上的方式,加了一个HAL_UART_ErrorCallback函数,其实就类似于HAL_UART_RxCpltCallback 函数: N$ `0 q6 _' q
' z, q) I: o. J) {
% J- o$ _4 z% c& N0 ^' A9 S
, ?/ E: y. I! X7 Z. u0 z* F0 r. F自己加了一个 出错处理函数,其实现在看来,当然也是没有用的。 . s! ~! m# C ?8 @; m
2.2.3 HAL库的半双工处理?折腾了好长一段时间,其实一开始就知道问题在于 同时收发会出问题的情况,那么继续上网找问题。 最终确定了一个问题就是: 我们都知道 STM32 串口是全双工的, STM32 HAL库在处理接收的时候会锁一下串口一会,导致变成某个短时间的“半双工”。 这个时候如果同时收发就会出现问题,最后解决的办法在这篇文章中看到了:STM32 F103串口同时收发出现死锁问题解决办法 问题在于我们使用的HAL_UART_Receive_IT函数中,有对串口加锁的操作:
5 D' a* _! g0 D. t6 n! R' E$ x/ t
; p' m( Q0 s* r虽然在后面有解锁: 6 u& b; q: B/ x7 Q3 v' t
, U; s7 x% r4 j/ |4 {! F6 \: X$ ?/ U$ k2 u% e
8 V# M: v+ j4 g1 F
但是根据后期的解决方式来说,确实就是这个HAL_UART_Receive_IT函数的问题,最后使用的方式为,在产生一次中断以后开启的时候手动解锁: # a5 s* D8 ]' h+ y
' h( c$ j+ j6 |/ J# u6 H$ P7 V5 O+ v
6 }) _; [6 J1 {* H/ `- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)" R. S- ]+ x( S& c3 _8 y
- {
: ]& u/ _) @9 f( j - if(huart->Instance == LPUART1){" ~; x( ~- D3 u( M/ b' E1 S
- Enocean_Data++;
" ?5 }, Z5 |7 D4 a- \ - if(Enocean_Data > 98)Enocean_Data = 0;
7 y" Z# a: z f, Y* j - while(HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&USART_Enocean_BUF[Enocean_Data], 1) != HAL_OK){ C [1 Z9 J6 g2 G
- hlpuart1.RxState = HAL_UART_STATE_READY;! Y2 B7 I; T1 p7 ?- w5 W
- __HAL_UNLOCK(&hlpuart1);
* }* C0 u( d0 _( s: W. @ - }! c/ Q7 [8 ~9 a2 G0 {2 I' o* K2 M; j
- }
) Q4 k9 {- L9 r+ y- N9 @' X - else if(huart->Instance==USART1)
. [% R& f# U7 f7 y- G& G: J2 ^ - {
& f/ p: R8 F) c! l( O - }/ V5 T! D, N$ _5 t1 O+ {0 ^) N
- }
复制代码
, ^, y% R+ T" v% b, N8 q) y: m. o
- U3 o+ V1 Q" _终于,串口不再卡死 , 成功! - S6 ]2 @8 Q6 a% ]
$ Q; F* |/ x) i& I1 Z" g
转载自:矜辰所致
: `4 c- V: D% @如有侵权请联系删除
: {. O; F& \& u4 m9 G% ~& P% H$ w: ^8 v% W# @( z1 x& y
2 v5 d& U. d! X7 Q% X2 B+ x
|
直接使用HAL_UART_Transmit_IT,不会卡死,不过调用的时候要保证上一帧已经发送完成,不然会丢数据。
我觉得吧,一般情况使用HAL库收发函数要成对使用,接收和发送要么都轮询,要么都中断,要么都DMA,这样不用做特殊处理问题不大。
特殊情况下,比如RS485通信,收发不同时发生,轮询发+中断收也没问题。