|
前言 对于 STM32 串口的使用,确实很简单使用 STM32CubeMX 做好初始化,就可以直接使用了。 但是最近在某些产品上使用串口同时收发的时候,发现有时候串口会收不到数据了,但是发送正常,而且这个问题再数据量大的时候很容易出现,于是乎进行了好几天的问题测试…… & @( r4 |9 R1 w) w6 O
一、 HAL 库串口收发先简单回顾一下 STM32 HAL库串口收发是如何使用的。
+ } {6 d. n4 W( p+ g5 D 1.1 串口发送对于 STM32 来说,串口发送有3中方式: - 轮询发送;
- 中断发送;
- DMA发送;; \* c/ I2 r% v2 Z( t, f
在实际产品上,大部分项目中都用的是 轮询 方式发送,本次出现接收卡死的问题的产品也是采用的轮询发送,所以我简单的说明一下轮询发送,其他两种方式为 STM32 学习的基础问题,这里就不过多讨论。 发送相对简单,在 HAL 库实际都是使用HAL_UART_Transmit函数:
' k% s& v6 n' r' H) B5 ^. L$ V9 |
- Q" k0 u0 k, P
不管是哪个串口发送,都做了个简单的发送函数: % O, g7 Z) Y+ S! s8 ?
( l8 Z- ^; n/ s {% e0 c& ~, F1 m% I) ] c; H
发送没什么好说的,简单易用。 ' ? W7 i, p2 h
1.2 串口接收和串口发送一样,串口接收有3中方式: - 轮询接收;
- 中断接收;
- DMA接收;
0 n0 T0 ]1 a" ~, ?: \
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式。 那么对于本次出问题的产品,我采用的是 中断接收的方式。 但是相比较发送,在 HAL 库中 使用中断接收的方式就有点 “五花八门” 的感觉。 M7 `' [/ Y$ U' n' D8 j
1.2.1 标准库接收在标准库的时候,我们经常这么用,在串口初始化的时候使用下面的语句使能中断: - //在串口初始化代码之后加上中断使能" h0 \( b" T5 p( v
- USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
复制代码 ( N" ~7 l" O" n% q' ~
如果需要自动判断一帧数据,我们再开启一下 IDLE 中断: - USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
复制代码
; ~- n3 ~ j4 ~! j然后在对应的中断处理函数中,直接读取 DR 寄存器(STM32F103 而言为 DR,STM32L051 为RDR),然后当 IDLE 中断产生,也可以处理一下标志位: - //自己用的,用到了 USART_IT_IDLE 标志位,有时候不合适
7 I W% a/ F, B- G - void USART3_IRQHandler(void) //串口3中断服务程序% c% l* D! c ^$ r- S
- {
. P; e: P# I; ^6 z7 ^% x$ r - u8 clear3=clear3; //消除编译器没有用到的提醒
, a2 ?4 R- Q L( g8 H; `7 k - // u8 Res;$ ~, f" t! f* k: d
- " s2 z: E" W+ g1 A
- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) 7 a5 w6 U/ I. c9 a5 t9 M
- {
3 ]. {+ U& D9 [, M5 |4 O+ o - USART_Enocean_BUF[Enocean_Data++] = USART3->DR;
+ t/ q4 l- n2 a7 F3 G- K - USART3_RX_BUF[USART3_Data++] = USART_ReceiveData(USART3);4 B/ B6 c/ p7 O$ W, e5 k
- // Res= USART_ReceiveData(USART3);
$ k3 ~& q8 U# L3 d8 x5 [ - // USART_SendData(USART1,Res);while(!(USART1->SR&USART_FLAG_TXE)); + s5 r. @7 i" o9 ^
- }/ U: o6 w0 d( q" X1 O5 q
, y7 s/ a& k- I& s9 K. q+ [. O- else if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) 0 \6 l2 f5 m! a- T4 j" V1 h
- {: \* _% T1 m% @. c
- clear3=USART3->SR; //读SR寄存器 可以清空寄存器9 |. D2 t' u9 ~. I/ n7 U2 ^) l. L
- clear3=USART3->DR; //读DR寄存器(先读SR寄存器,再读DR,为了清除IDLE中断) 2 f: x5 ?, [1 n9 C1 x
- USART3_RX_STA=1; //标记接收到了一帧数据( `. K @3 s3 z8 Z6 X. r+ i" L8 q
- //USART3_Data=0;) V" u* Y& g! r0 H
- } //enocean是不是读不到一帧数据,不用一帧数据测试一下 $ z" o o4 n1 T- T6 q; X0 a; W/ j% P+ s
- }
复制代码
% E! N' ?' k. q1 [/ j: Y) b1.2.1 HAL库接收HAL库接收方式一在 HAL 库函数接收的时候,其实也可以使用标准库上一样的中断标志使能: - MX_LPUART1_UART_Init();1 t3 o3 k8 l4 m
- __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
复制代码 : v; L5 v( f& R* v& }1 ^
IDLE 中断使能: - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
复制代码 ; ?1 a* h3 ?' m( |
这种方式的处理方式,可以和标准库差不多。 在 HAL 库中,外设的中断的入口函数都放在stm32l0xx_it.c 这个文件夹中( 以STM32L051 为例),在这个文件中可以找到和标准库一样的 中断入口函数,我们可以进行如下处理: - void USART2_IRQHandler(void). V# U- _% Y; S8 K5 a$ ?! ^+ g; c/ u& o
- {/ l; d7 B o, d6 G8 N" S
- /* USER CODE BEGIN USART2_IRQn 0 */
+ F$ r5 L9 E8 H4 ^ - $ i; o/ f, Y2 P3 c, B
- /* USER CODE END USART2_IRQn 0 */
# S5 D$ b9 C; z7 Q3 d( `/ ~; | - HAL_UART_IRQHandler(&huart2);
! W% s$ I3 L3 t- O% A0 j4 V6 G* E" O - /* USER CODE BEGIN USART2_IRQn 1 */
2 q# T& M- D" X4 E- C - if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) == SET){& x0 Y9 a9 [: i0 [$ c% L
- // USART_Enocean_BUF[Enocean_Data++] = huart2.Instance->RDR
/ \) D! W9 r2 F6 N2 D - // RXNE 数据处理,直接读取数据
' ~1 D7 G9 ~: U n. Z0 L9 X - }* ~, l) O8 [. \9 b. k- v5 b
- if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))
3 v' O9 m5 ?: n0 }, Z$ T6 T - {
, ~" L! R" y. H2 |( t2 Z - __HAL_UART_CLEAR_IT(&huart2,UART_CLEAR_IDLEF); //Çå³ýÖÐ¶Ï 0 T: s( z( \1 g: m
- // ReceiveState = 1;
2 b( P5 ~6 O$ W1 X - // IDLE 数据处理,一帧数据接收标志位置位/ Y# i( G. p+ _" P4 v+ B
- }( ^/ i% M8 g. t( b
- }
复制代码
- @# A+ l' x# C; f" s/ [HAL库接收方式二但是在于 HAL 中,还有一种比较常用的开启中断方法,不是直接使能中断,而是通过调用 HAL 库函数 HAL_UART_Receive_IT :
3 M. t! k3 |1 f$ M! W! c! n/ c
5 S* `# d8 W( z. j+ N2 x2 o8 i
* D$ j+ Z2 d8 `- W# F( P" q0 O, b* ~0 i0 i7 r
一般的使用方式步骤:
! Z( F; S$ P+ @2 P- N
- l) Q" H# P" ~/ M5 c* K
u+ Q) e; D4 `* o7 o( d1 `' Q/ g+ G J/ H
5 \& j3 l; {' [+ B4 r, Y# K7 K5 U: d; d5 b- k2 ?3 n# ]
对于本次需要说明的问题,就是使用了 HAL_UART_Receive_IT 函数导致的,下文我们会说明,这里列出了基本的使用步骤。 8 K( E A& h% g- J
二、 收发同时串口卡死?
) \+ ~% g; C* w9 [" J2.1 问题说明最近测试部反馈,产品有些时候的下行没反应,这里所说的下行,其实就是串口接收。 霹雳扒拉一大堆多余的省略 ... ... 只说几个重点: 出问题的最后现象就是串口发送正常,但是永远接收不到数据了,其他程序正常运行。 出问题只存在于串口又有接收,又有发送的产品上。 产品发送一般是周期性的,但是接收是随机的,无线信号串口接收,所以产品的出问题的情况也是随机的,但是数据量大起来肯定就会出现永远接收不到的问题。 9 A1 Q7 e: K. G5 c* C* X0 r ~
2.2 尝试的处理方式因为所有的一些都是按照正常流程设计的,按理来说实在是不知道为什么会这样,所以网上查询测试了好久,现在我把尝试的处理方式以及步骤记录说明一下: 4 W( x4 Q+ M( z, G5 s5 y$ q
2.2.1 清除错误标志位在使用 HAL 库的时候,有4个错误 flag,如下图:
" X; ?) {" G( w( b [0 h) o. b: |2 t* c; l
F" m+ N$ N% D" F; M7 H! p$ p% |6 r% L$ c8 H
期初还以为是某些异常错误导致的,经过网上的的一些查询,刚开始是添加了清除错误标志位: - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_PE);//清标志
' e" {1 E. E4 q2 [3 J% S: ~ - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_FE);
3 r* R, l ~1 c: x3 \* O - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_NE);
& t/ A& _8 }* y3 W% Z7 j - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_ORE);
复制代码 S5 P+ A( n. {$ ~
在需要的地方加上错误标志位清除,我是在清除串口缓存中处理的:
3 n; \2 ]$ Y5 {/ E4 G* H) Z G2 k: r. z: b, J2 Q
- _; p$ B& R V4 X3 S6 b2 f9 q6 A2 g: x! ~, K- k& X2 n+ R
, n' Q9 \% y2 O/ B9 U! k; x4 _! N: _2.2.2 串口溢出错误其实串口溢出错误在上面的已经清除过标志位,因为这个问题着实搞得我头有点大,所以看到网上前人的处理方式和上面直接清除不一样,还是试了一把。 这里简单说明一下,我还特意去看了下自己的 CubeMX 设置,在设置的时候 有一个 Overrun 错误标志位,平时我们设置都不一 定往下拉着看 = =!:
: _4 c# {/ N: F, e
5 @. Q/ n* E- k; i+ }! |
然后确定了开启了串口溢出错误检测以后,我根据网上的方式,加了一个HAL_UART_ErrorCallback函数,其实就类似于HAL_UART_RxCpltCallback 函数: 5 F, P6 Q' l" t8 a4 u; o, H: I/ R
- m" p/ p& F9 g9 p
& K. R7 A1 |( w# ~+ b
( w3 Y6 ^9 K3 S% I自己加了一个 出错处理函数,其实现在看来,当然也是没有用的。
# k1 r; [9 Q( x2.2.3 HAL库的半双工处理?折腾了好长一段时间,其实一开始就知道问题在于 同时收发会出问题的情况,那么继续上网找问题。 最终确定了一个问题就是: 我们都知道 STM32 串口是全双工的, STM32 HAL库在处理接收的时候会锁一下串口一会,导致变成某个短时间的“半双工”。 这个时候如果同时收发就会出现问题,最后解决的办法在这篇文章中看到了:STM32 F103串口同时收发出现死锁问题解决办法 问题在于我们使用的HAL_UART_Receive_IT函数中,有对串口加锁的操作:
" C& b: x2 }7 n
& U/ @) d' }4 H5 A
虽然在后面有解锁: ) P. X n" r1 {1 q, F v
; x' {) k' X2 M. a# a
2 s' c) u4 `8 N- j
% h x. ]* o' [( u/ J7 W但是根据后期的解决方式来说,确实就是这个HAL_UART_Receive_IT函数的问题,最后使用的方式为,在产生一次中断以后开启的时候手动解锁: ; H/ M2 l9 _8 R9 @; N
/ W" @' }3 \0 h. a _# m6 \
$ N% S( [( v6 l6 Y3 F- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
* y6 t0 N, Z8 r- F - {
5 ]! O$ l( Z. e$ N/ _ - if(huart->Instance == LPUART1){0 h1 {' [2 p7 q" M8 Y3 y3 P
- Enocean_Data++;+ _$ \9 U- X1 ` C
- if(Enocean_Data > 98)Enocean_Data = 0;
: G. I/ t" A( t; L1 b4 K - while(HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&USART_Enocean_BUF[Enocean_Data], 1) != HAL_OK){
; l" u3 z* R3 n* t4 N* T, [ - hlpuart1.RxState = HAL_UART_STATE_READY;! q( b% D7 p" H0 D1 w3 M( n6 B
- __HAL_UNLOCK(&hlpuart1);& N) l' l, D0 G: Y
- }
1 r7 \$ J% t; K7 R - }
3 w6 |+ T6 `4 b) ` - else if(huart->Instance==USART1), r) b# w5 P& L0 {9 O
- {
8 A6 x0 O4 ], m - }
' W' Z. ]5 O$ ^; L - }
复制代码
6 g; J, S/ t5 {3 N: c9 @
- Y s( L" T7 z, f终于,串口不再卡死 , 成功! / H# O3 c, G; _9 V* l
; I; O. k' R1 F9 |/ |3 A
转载自:矜辰所致4 R, h5 l+ o6 x- K7 I2 b$ m! h
如有侵权请联系删除; K# p( E% h0 j* u7 O
" E( h- n, s5 s) V% N1 u. F
& o0 o) W% w+ K: ] |
直接使用HAL_UART_Transmit_IT,不会卡死,不过调用的时候要保证上一帧已经发送完成,不然会丢数据。
我觉得吧,一般情况使用HAL库收发函数要成对使用,接收和发送要么都轮询,要么都中断,要么都DMA,这样不用做特殊处理问题不大。
特殊情况下,比如RS485通信,收发不同时发生,轮询发+中断收也没问题。