|
前言 对于 STM32 串口的使用,确实很简单使用 STM32CubeMX 做好初始化,就可以直接使用了。 但是最近在某些产品上使用串口同时收发的时候,发现有时候串口会收不到数据了,但是发送正常,而且这个问题再数据量大的时候很容易出现,于是乎进行了好几天的问题测试…… 2 @0 _7 p" o$ W; ~9 x
一、 HAL 库串口收发先简单回顾一下 STM32 HAL库串口收发是如何使用的。 ! u! d: {2 B' W" X, ^7 d7 M1 F
1.1 串口发送对于 STM32 来说,串口发送有3中方式: - 轮询发送;
- 中断发送;
- DMA发送;
- k+ z( X- [; H* b% O- p. H- X( x, R
在实际产品上,大部分项目中都用的是 轮询 方式发送,本次出现接收卡死的问题的产品也是采用的轮询发送,所以我简单的说明一下轮询发送,其他两种方式为 STM32 学习的基础问题,这里就不过多讨论。 发送相对简单,在 HAL 库实际都是使用HAL_UART_Transmit函数:
( } K9 H% t: @" [+ o5 E- O
: a2 O( M% z& w% R2 ^+ ?3 T- `" {- m }$ H
不管是哪个串口发送,都做了个简单的发送函数:
4 g* c5 Q/ [! l* |$ B I! {3 `) p/ h2 L* m" ^3 ]+ ~/ O' Q
8 `: {- s1 d: D: D* l/ k
发送没什么好说的,简单易用。
; p: p* X- Z/ [* y 1.2 串口接收和串口发送一样,串口接收有3中方式: - 轮询接收;
- 中断接收;
- DMA接收;& c1 ?) a3 g- g; a) P" L' ~3 Y
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式。 那么对于本次出问题的产品,我采用的是 中断接收的方式。 但是相比较发送,在 HAL 库中 使用中断接收的方式就有点 “五花八门” 的感觉。 " H. _, C* ?5 ~& @
1.2.1 标准库接收在标准库的时候,我们经常这么用,在串口初始化的时候使用下面的语句使能中断: - //在串口初始化代码之后加上中断使能
Z" m& f7 ?- g5 X( K2 m6 ] - USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
复制代码 4 g: Q4 N2 F2 `0 h( [8 p# U A( q
如果需要自动判断一帧数据,我们再开启一下 IDLE 中断: - USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
复制代码 # e' T; O! x" [- Y5 ~! @3 r3 T+ [
然后在对应的中断处理函数中,直接读取 DR 寄存器(STM32F103 而言为 DR,STM32L051 为RDR),然后当 IDLE 中断产生,也可以处理一下标志位: - //自己用的,用到了 USART_IT_IDLE 标志位,有时候不合适 t# F* o1 @$ q' s& D
- void USART3_IRQHandler(void) //串口3中断服务程序
( G4 J I1 s% S, h! V) K& g# l; S- ] - {* y! m+ \. k6 r
- u8 clear3=clear3; //消除编译器没有用到的提醒
1 _+ e# c; O4 z4 b+ T Q - // u8 Res;: \4 d' T* n, Y8 |
- 8 _/ M/ B: Z- O/ i; n
- if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) \& X$ a6 ^1 ]3 U4 ^: Q
- {& U' k: `: S6 z9 G; L' j' }" N
- USART_Enocean_BUF[Enocean_Data++] = USART3->DR;
0 D5 L( Z+ c- X" J9 }. E, S - USART3_RX_BUF[USART3_Data++] = USART_ReceiveData(USART3);* y9 S' d& n# [+ T4 V0 r7 v. I
- // Res= USART_ReceiveData(USART3);
4 i) f, V& ?- i {) o! f - // USART_SendData(USART1,Res);while(!(USART1->SR&USART_FLAG_TXE)); 0 |3 B% r6 ^4 t- F9 C( E+ W& s
- }
8 V \3 |* K" n- p# y3 S% Q" o - & [# \+ T5 a8 ?5 b+ M' I; K9 v
- else if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) ! f% K! ?: y2 C; G
- {
/ k6 M9 \% f; q* L) F% W - clear3=USART3->SR; //读SR寄存器 可以清空寄存器
" Q5 C9 U' X" a - clear3=USART3->DR; //读DR寄存器(先读SR寄存器,再读DR,为了清除IDLE中断)
+ C" G$ `; b1 V: l - USART3_RX_STA=1; //标记接收到了一帧数据
- Q0 F8 j4 N) Z; K7 w7 X/ H# R, i - //USART3_Data=0;7 r# n8 t/ x# _: T
- } //enocean是不是读不到一帧数据,不用一帧数据测试一下 1 g% ~+ B3 W9 W" f
- }
复制代码 5 V! ^, F4 p' m; f5 X+ A# b: }
1.2.1 HAL库接收HAL库接收方式一在 HAL 库函数接收的时候,其实也可以使用标准库上一样的中断标志使能: - MX_LPUART1_UART_Init();" r* Q/ j3 {8 ]
- __HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE);
复制代码 ( m7 N& c T9 }3 U0 C9 ]9 {
IDLE 中断使能: - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
复制代码 1 r. @/ Z) {4 ^% d7 H
这种方式的处理方式,可以和标准库差不多。 在 HAL 库中,外设的中断的入口函数都放在stm32l0xx_it.c 这个文件夹中( 以STM32L051 为例),在这个文件中可以找到和标准库一样的 中断入口函数,我们可以进行如下处理: - void USART2_IRQHandler(void)- U2 e/ Q: a; E) X: i% P$ g
- {: c# ~" L; W& Z$ ]
- /* USER CODE BEGIN USART2_IRQn 0 */
$ v7 j5 ~0 N/ s% o9 [
: b! R3 ]: j; J" J& N- /* USER CODE END USART2_IRQn 0 */5 G0 V8 n$ V, {7 T4 P" W
- HAL_UART_IRQHandler(&huart2);; Q* h5 X7 T9 I( u$ N& |
- /* USER CODE BEGIN USART2_IRQn 1 */! a2 ^; ]& Q: X5 B8 k0 o
- if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE) == SET){$ `0 d8 @, d, d! y
- // USART_Enocean_BUF[Enocean_Data++] = huart2.Instance->RDR - C h/ X" w. z( v
- // RXNE 数据处理,直接读取数据
1 N+ a2 q4 g3 C2 p! X- d - }9 q& H/ [8 `# G- q! E
- if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET))7 {1 \ o7 \. d5 b6 M; D, ^4 o
- {
7 j( U1 Y: F q/ D! _ - __HAL_UART_CLEAR_IT(&huart2,UART_CLEAR_IDLEF); //Çå³ýÖжÏ
; U9 q4 d% f- {$ ]+ F - // ReceiveState = 1;
! \. t L) c: o" n* O4 ^" L O4 [ - // IDLE 数据处理,一帧数据接收标志位置位
0 a' d, _( J; W& v* S+ m! Y - }) P, h* _. \' Z
- }
复制代码 9 n8 o' L' Z& H8 u( R* ?. e) t
HAL库接收方式二但是在于 HAL 中,还有一种比较常用的开启中断方法,不是直接使能中断,而是通过调用 HAL 库函数 HAL_UART_Receive_IT :
" Q9 V0 X/ }, K( g2 S2 N7 u
/ K+ d# N2 o* e5 \1 z3 D+ g3 I9 l T C+ m \- m& N
. o) z0 s# m. s) {一般的使用方式步骤: 8 L2 q# H+ I; b" b
2 ]( D' H n2 r7 ^
{* m0 B9 Y, V' N3 T/ [
6 m) j7 o8 U/ E
2 [" x- d+ j: r% s
+ S1 O F: R6 Q _% R1 B; g. G对于本次需要说明的问题,就是使用了 HAL_UART_Receive_IT 函数导致的,下文我们会说明,这里列出了基本的使用步骤。 , R: O0 K$ X7 x8 ?, T) S/ X
二、 收发同时串口卡死?
8 }' ]7 \: R- ?& `0 K3 ^2.1 问题说明最近测试部反馈,产品有些时候的下行没反应,这里所说的下行,其实就是串口接收。 霹雳扒拉一大堆多余的省略 ... ... 只说几个重点: 出问题的最后现象就是串口发送正常,但是永远接收不到数据了,其他程序正常运行。 出问题只存在于串口又有接收,又有发送的产品上。 产品发送一般是周期性的,但是接收是随机的,无线信号串口接收,所以产品的出问题的情况也是随机的,但是数据量大起来肯定就会出现永远接收不到的问题。
/ t' B. k! f- ]. m' ]9 Z( ]2.2 尝试的处理方式因为所有的一些都是按照正常流程设计的,按理来说实在是不知道为什么会这样,所以网上查询测试了好久,现在我把尝试的处理方式以及步骤记录说明一下: 8 T+ H9 W7 [/ Z4 J1 q c5 B4 e9 s
2.2.1 清除错误标志位在使用 HAL 库的时候,有4个错误 flag,如下图: 5 v; |/ \- N3 J- D4 M1 N& g/ \
; e6 d; a. Y2 D! J
* q! d6 F$ _, p' j0 U$ j" R* { u+ U8 {1 D, b* a0 ?( P& V$ V* E
期初还以为是某些异常错误导致的,经过网上的的一些查询,刚开始是添加了清除错误标志位: - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_PE);//清标志: J9 C" U' {( E! G
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_FE);
/ O( ]7 ^. d# w" E, R/ r& e, V - __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_NE);# w. }! \4 I+ {) w% f$ ^
- __HAL_UART_CLEAR_FLAG(&hlpuart1, UART_FLAG_ORE);
复制代码
( ^! Y2 K: j1 R0 ?7 Z, P2 V在需要的地方加上错误标志位清除,我是在清除串口缓存中处理的:
9 i3 w' J; j# v/ x& N1 y, J9 s; j' a5 i5 M( L7 ?7 e+ r: c
! T7 Z- }! q) ]9 U% C7 Y
# G2 R! w3 a1 `! S& k' o
+ d( M9 ~8 z7 c9 g' X* f# H/ {3 T2.2.2 串口溢出错误其实串口溢出错误在上面的已经清除过标志位,因为这个问题着实搞得我头有点大,所以看到网上前人的处理方式和上面直接清除不一样,还是试了一把。 这里简单说明一下,我还特意去看了下自己的 CubeMX 设置,在设置的时候 有一个 Overrun 错误标志位,平时我们设置都不一 定往下拉着看 = =!:
6 \) A& p% U% T) F2 U0 Y( z& \& ?
( S+ w( D2 x3 a. C% K9 n8 S然后确定了开启了串口溢出错误检测以后,我根据网上的方式,加了一个HAL_UART_ErrorCallback函数,其实就类似于HAL_UART_RxCpltCallback 函数: O& j/ `& d% K( x; L- H' Z/ v) ?
- M/ o% Q0 T" F. r3 ?
/ `* y4 p. Y- s6 A9 M' [
6 I$ ^4 t' X4 v: \- E自己加了一个 出错处理函数,其实现在看来,当然也是没有用的。 ; b4 D% e8 o! l& b% }) S7 |: a
2.2.3 HAL库的半双工处理?折腾了好长一段时间,其实一开始就知道问题在于 同时收发会出问题的情况,那么继续上网找问题。 最终确定了一个问题就是: 我们都知道 STM32 串口是全双工的, STM32 HAL库在处理接收的时候会锁一下串口一会,导致变成某个短时间的“半双工”。 这个时候如果同时收发就会出现问题,最后解决的办法在这篇文章中看到了:STM32 F103串口同时收发出现死锁问题解决办法 问题在于我们使用的HAL_UART_Receive_IT函数中,有对串口加锁的操作: $ X% P+ Z: v& U J- \+ X9 z
3 I; A/ g, c/ [. V- E$ @6 V1 m
虽然在后面有解锁:
3 }- F0 C3 i" I- @0 R' r3 @
7 v+ m* y8 _$ D. \+ E& D$ V( K5 U4 d% N v) ^/ L. X& H) l
" V- w( X* Z( g+ [5 f
但是根据后期的解决方式来说,确实就是这个HAL_UART_Receive_IT函数的问题,最后使用的方式为,在产生一次中断以后开启的时候手动解锁: / N3 z* K9 o) W) z( W0 ]1 u
! M0 B/ ?( g/ s7 c$ `6 q6 J# j
1 q1 f F ^2 t8 y" i1 `* w4 K- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)) u) w$ w( M' S# {% L" t6 ^ {
- {
% `/ w5 n4 C+ x# L, v - if(huart->Instance == LPUART1){- b+ }" i+ O1 A' Z; `0 r" E
- Enocean_Data++;" ?7 ~# S1 q( ~& f" c! P
- if(Enocean_Data > 98)Enocean_Data = 0;/ \% L7 f$ f3 f B
- while(HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&USART_Enocean_BUF[Enocean_Data], 1) != HAL_OK){9 F9 b' [9 b, n7 t% V! `; k
- hlpuart1.RxState = HAL_UART_STATE_READY;5 e$ p% \8 h3 z8 ~% S# Z$ j6 u: ]
- __HAL_UNLOCK(&hlpuart1);5 a% V" h$ `: Z- f( D$ W3 \
- }
1 ?: J$ S. u7 H0 I* @* `7 N$ O0 } - } _/ F( \% m: u6 G. n- P! q
- else if(huart->Instance==USART1)! q- ~: x, B- ?, w7 D- n
- {
9 l% }/ @( z. R3 k+ ?2 `% O$ [4 J - }7 l3 D1 N$ {/ z9 Y! ~& o" o/ R4 U
- }
复制代码 $ ~2 F+ R( ~# ^- H
7 U. A2 l- p: u) A! }# e) A) J+ R) `终于,串口不再卡死 , 成功!
8 U5 U% s- c2 z7 L0 X! R, h+ G: G2 f* D9 A+ Z1 W1 J5 p+ T2 w
转载自:矜辰所致2 c/ M% r: X6 r ?' i( }, H
如有侵权请联系删除# h- o" A$ i" p) s+ F
9 h$ o8 k! I0 ?3 T9 Z {; U$ A, }/ L1 I/ G- d! O( G! q* l
|
直接使用HAL_UART_Transmit_IT,不会卡死,不过调用的时候要保证上一帧已经发送完成,不然会丢数据。
我觉得吧,一般情况使用HAL库收发函数要成对使用,接收和发送要么都轮询,要么都中断,要么都DMA,这样不用做特殊处理问题不大。
特殊情况下,比如RS485通信,收发不同时发生,轮询发+中断收也没问题。