你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】串口工作在 DMA 模式下有时接收异常

[复制链接]
STMCU小助手 发布时间:2022-2-14 21:13
前言& ]. H9 w* a) g$ w3 T# ?6 d- y
客户反馈在使用 STM32F205 的串口工作在 DMA 模式时,有时能够接收数据,有时完全没有数据,但如果换成中断模式来接
% i1 g/ ^( P9 g/ N/ d; z收又能 100%正常收到数据。, x' \3 l0 f9 Z: e
一 复现现象' e# B0 ?* U/ m$ G
问题背景
0 R6 \! O4 Z% p  x- q9 M与客户沟通,客户使用的是 STM32F2 标准库 V1.1.0,串口波特率为 1.408Mbps,不经过串口 RS232,直接连接主 CPU 和从 MCU(STM32F205)的串口发送和接收引脚,如下图所示:
' k3 g. g9 p0 m* }+ a
  L& H6 e- K2 ?: M1 {2 K L~F9~}F}TMRGAGZ(8)`)LX8.png ) n) k9 q, C& m+ W

( q5 y/ `( S, l( f. _
+ p7 L/ V, i; \8 i' ]  Q尝试重现问题8 P7 U9 S2 W8 g6 U/ k1 y" j
由于客户使用的是主从架构,实验采用两块 STM3220G-EVAL 评估板来重现现象。一块用来不间断发送串口数据,另一块采用串口 DMA 进行接收,直接通过杜邦线连接串口 PIN 脚并共地,不使用评估板上的 RS232 收发器。接收端使用STM32F2xx_StdPeriph_Examples\ USART\USART_TwoBoards 的示例代码。代码片段如下:: h) G$ E, K5 h  N7 {' T& `
  1. int main(void)# F  J8 k# v2 W8 p
  2. {
    & o5 ?5 _9 V! d0 k# F' W; }1 h
  3. ...
    : ?+ o) G/ j' a% r' _% V
  4. USART_Config();
      @" l; ~, q3 B
  5. ...
    ( v( t% D  w% u* h0 ~7 i+ S
  6. while (1)$ S6 O7 \+ N# S2 i
  7. {0 S/ ~% P" F+ W# d  b& j8 b8 F0 L
  8. /* Clear Buffers */8 R9 O& w* @3 U9 C0 R6 t) J  H
  9. Fill_Buffer(RxBuffer, TXBUFFERSIZE);
    - E4 q/ K7 E. u. [5 o
  10. Fill_Buffer(CmdBuffer, 2);
    $ k9 B& _0 f) Y) Z) f0 p) m) G
  11. 3 J6 O- [+ g3 G1 x7 L* J/ P
  12. DMA_DeInit(USARTx_RX_DMA_STREAM);
    9 s8 d3 S" T3 Z/ k  y
  13. DMA_InitStructure.DMA_Channel = USARTx_RX_DMA_CHANNEL;1 [5 }& j$ {# v
  14. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;. d+ e/ T! B) G4 \
  15. /************* USART will receive the the transaction data ****************/8 Y6 k( [' b" d9 K' u
  16. /* Transaction data (length defined by CmdBuffer[1] variable) */: t$ L3 `& m  h2 N; |
  17. DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RxBuffer;/ B' z5 J) M: {1 u
  18. DMA_InitStructure.DMA_BufferSize =10;// (uint16_t)CmdBuffer[1];
    2 u- }! w! [  i+ e3 a; d
  19. DMA_InitStructure.DMA_Mode =DMA_Mode_Normal;//DMA_Mode_Circular;9 _0 |1 H1 f% W
  20. DMA_Init(USARTx_RX_DMA_STREAM, &DMA_InitStructure);
    . f% G) J+ T3 J$ n5 D) s
  21. NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn;! f: o$ L3 u' D# `  S5 c  U
  22. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;. j8 g* G0 D% O7 U! Q1 M
  23. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;& }& q* i# q" \. t- S. a
  24. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;' ]" A3 s, B6 ~  a- K) t; j6 V5 C
  25. NVIC_Init(&NVIC_InitStructure);# S9 j7 F0 ^/ l' @3 V
  26. /* Enable DMA Stream Transfer Complete interrupt */4 g& z  J( I( i+ }
  27. DMA_ITConfig(USARTx_RX_DMA_STREAM, DMA_IT_TE|DMA_IT_DME|DMA_IT_FE, ENABLE);5 W  b1 K: Q. B' C
  28. /* Enable the DMA Stream */* P; q0 ?5 R$ R; I4 d' R8 |
  29. DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);! F3 o( m' q+ r# D/ K! a  c8 n
  30. /* Enable the USART Rx DMA requests */. V+ G, K9 f9 m5 I
  31. USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);
    9 N/ Y" h& z& Y% l9 r: d
  32. // USART_Cmd(USARTx, ENABLE);8 b; I" v% S4 f
  33. // while(SET ==USART_GetFlagStatus(USARTx,USART_FLAG_ORE))
    / y+ Z: I* X$ j' N' a% u- x
  34. // {1 w1 p! x6 \4 O! ]2 K+ i1 A  ^
  35. // Tmp =USART_ReceiveData(USARTx);" I7 ^) }7 k. ?$ S6 \3 G* a
  36. // }
      I- [) y% ~# D# [4 r- q
  37. while ((DMA_GetFlagStatus(USARTx_RX_DMA_STREAM, USARTx_RX_DMA_FLAG_TCIF) ==3 A$ P# h  V: R+ ]* q0 e0 s6 R
  38. RESET)
    4 Y$ ], O- q/ @& ?% i
  39. {
    , g: \& Q7 V0 t: R5 @7 }, B
  40. }4 Z% w2 o" x; f/ ?/ `
  41. /* Clear all DMA Streams flags */
    1 J  ?* }/ U- w0 v. k
  42. DMA_ClearFlag(USARTx_RX_DMA_STREAM, USARTx_RX_DMA_FLAG_HTIF |
      O$ |0 u4 z# I' y  O- Q- X; t
  43. USARTx_RX_DMA_FLAG_TCIF);
    , `0 E- x. K3 q, i: @

  44. 7 Q1 B6 u. b, P4 B6 \
  45. /* Disable the DMA Stream */4 d2 N7 @. |2 H  D
  46. DMA_Cmd(USARTx_RX_DMA_STREAM, DISABLE);# ?( s0 e6 w6 ~, @! x. F8 O- ~
  47. 2 |4 l( l6 X! S. o; x9 d2 ^1 C
  48. /* Disable the USART Rx DMA requests */
    3 Y# G. O9 l# V6 v5 ~- z3 R# {
  49. USART_DMACmd(USARTx, USART_DMAReq_Rx, DISABLE);; g  }- `5 q- G2 o  F* ^' D
  50. 7 |7 O' F8 k. u8 n1 H$ P7 [# o5 }
  51. //handle the RxBuffer data...
    + w1 V$ F1 w# D, ~
  52. //...
    ; i* p+ o9 c0 [
  53. }
    1 x7 z' y1 E# H
  54. }
    % n% n1 `& [6 Y3 ?
复制代码
USART_Config()函数如下:4 Z* [) N  j# n9 ~# ?4 L' V
  1. static void USART_Config(void)2 J; W' Y* l0 `
  2. {) h& ]; ~* [, |" _# ]# C8 f
  3. USART_InitTypeDef USART_InitStructure;
    0 V# }8 A% d1 i+ m/ @
  4. GPIO_InitTypeDef GPIO_InitStructure;  U, U( r- s* K& y/ V/ i# ^, N. G( c
  5. /* Peripheral Clock Enable -------------------------------------------------*/! J5 L# p% f8 n8 v5 F
  6. /* Enable GPIO clock */
    ; o3 b/ g* f7 q# q" j' Q
  7. RCC_AHB1PeriphClockCmd(USARTx_TX_GPIO_CLK | USARTx_RX_GPIO_CLK, ENABLE);
    0 O3 y5 i$ l6 K3 C/ ?3 l3 Y
  8. /* Enable USART clock */* t* i$ f$ }5 \1 z, h' C3 h5 \% t. H4 q
  9. USARTx_CLK_INIT(USARTx_CLK, ENABLE);
    ! V  D; X# g0 O' o7 g
  10. 7 x- G, n( P( S8 i
  11. /* Enable the DMA clock */+ F8 {" c( U+ n/ j# z/ d# ~
  12. RCC_AHB1PeriphClockCmd(USARTx_DMAx_CLK, ENABLE);
    * F& M7 X( a1 P+ |( Q

  13. ) v" O0 t2 q! U' K& Z
  14. /* USARTx GPIO configuration -----------------------------------------------*/
    * d2 e$ F5 u% r. g2 w. l
  15. /* Connect USART pins to AF7 */
    % l: t: }% R6 b! A$ q
  16. GPIO_PinAFConfig(USARTx_TX_GPIO_PORT, USARTx_TX_SOURCE, USARTx_TX_AF);+ t" f7 n. O7 b" T- d* f2 }
  17. GPIO_PinAFConfig(USARTx_RX_GPIO_PORT, USARTx_RX_SOURCE, USARTx_RX_AF);# S+ Z+ p) \+ `3 F

  18. & n/ ~4 {- G, M: Y5 D' J
  19. /* Configure USART Tx and Rx as alternate function push-pull */, C! [! P* `! F' _: a
  20. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;- E8 v( O& H. g8 C" Q( G* c
  21. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    : N3 i! E3 h2 Z7 `8 T' p' o1 t
  22. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;2 j0 o6 q% n) ?$ N% N1 I9 u, M
  23. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;7 f2 f, c7 I% \

  24. % @: _. s; }1 p# N$ z
  25. GPIO_InitStructure.GPIO_Pin = USARTx_TX_PIN;. q6 t: A1 W/ }* @
  26. GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStructure);
    + c- D* p8 ~4 R- T3 I

  27. * n" a& w& l7 s5 U! l- s$ F4 G
  28. GPIO_InitStructure.GPIO_Pin = USARTx_RX_PIN;
    4 q. n+ I% j, q! @) K
  29. GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStructure);
    ! E% @0 s7 e7 W" ]
  30. /* USARTx configuration ----------------------------------------------------*/
    8 }, r! D" K" c
  31. /* Enable the USART OverSampling by 8 *// O. x' w! ~/ _- v- `8 d3 f
  32. USART_OverSampling8Cmd(USARTx, ENABLE);
    & ^, v$ Z* |8 z
  33. 4 P) _1 [% [, I
  34. USART_InitStructure.USART_BaudRate = 1408000;//3750000;
    5 Q2 `& m: n7 x0 E
  35. USART_InitStructure.USART_WordLength = USART_WordLength_8b;
      k+ [1 K" p0 Z* I
  36. USART_InitStructure.USART_StopBits = USART_StopBits_1;
    4 `& s' e2 Y3 |; c
  37. /* When using Parity the word length must be configured to 9 bits */+ z- t$ l' F9 t
  38. USART_InitStructure.USART_Parity = USART_Parity_No;
    ' r! A$ h4 F" p; ]: y5 g/ d
  39. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    + G; r6 R' e8 t2 y( B; |2 C
  40. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;3 [2 U1 c1 S/ g' o. s% [
  41. USART_Init(USARTx, &USART_InitStructure);
    ) ~& f7 w4 M( \+ U7 K7 c
  42. /* Configure DMA controller to manage USART TX and RX DMA request ----------*/. Y0 C( o/ C9 t0 U6 S! v  ~
  43. DMA_InitStructure.DMA_PeripheralBaseAddr = USARTx_DR_ADDRESS;
    5 o7 E# U' p  N+ S) ~' k3 V
  44. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    3 W# a- _% J  |7 ~8 I
  45. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    6 s+ j8 j0 z2 Z2 J1 t6 h, Y
  46. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;2 a) l7 q, X% Z$ r4 |
  47. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;# c$ z) [! ^3 Y, ?. U9 b
  48. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;+ f; W' y5 ]9 f' F; H) [
  49. DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    2 V( `. S; z7 v& d" i+ M' M
  50. DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
    % n# p! u, r5 `/ j
  51. DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    - i& k; h/ A5 ?4 k& I- M; m9 ]
  52. DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;$ V/ j$ N5 z" @/ _: G0 R& U
  53. DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
      q" V" ?/ \  C/ N/ y: d3 F
  54. /* Here only the unchanged parameters of the DMA initialization structure are
    " V- X# M! g2 w( E  x
  55. configured. During the program operation, the DMA will be configured with8 {( f5 w) Q* \: n
  56. different parameters according to the operation phase */5 F$ o, H7 K4 \! M

  57. 6 z$ Q3 H; Q6 d. N* `, c
  58. /* Enable USART */
    8 B7 `& K9 p% _" `
  59. USART_Cmd(USARTx, ENABLE);& z- V$ B8 g; ?, I
  60. }
复制代码

0 j( b4 C  q+ ^  ]( u& q/ x8 c! V9 d按如上代码,有如下现象:
$ O9 i0 \6 |" B- L) j( Y" W1 代码不做修改,若先启动接收端 MCU 再启动发送端 MCU,接收端 MCU 的串口能正常接收。
9 @, o$ g3 P2 r2 代码不做修改,若先启动发送端 MCU 再启动接收端 MCU,接收端 MCU 的串口 100%接收异常。/ P: r! H: \. b  {) I. {$ k
3 修改发送端代码,改为发送端 MCU 串口每 1 秒间隔发送一次,则无论启动顺序如何,接收端 MCU 的串口都能正常。
( T+ t+ h1 P, z+ A
/ y& l" m# t8 r( r二 程序分析

( e7 {: q& [7 o7 a# M  h由上述代码可知,程序是先在 USART_Config()函数函数内初始化串口并使能,然后再在接下来的 main 函数的 while 循环内初始化 DMA 并使能。这个是标准库内附带的示例代码,咋一看没什么问题,但仔细一想,针对用户的使用场景,这里就会产生一个问题:由于用户的主 CPU 有可能在从 MCU 启动之前就已经有可能启动,那么在这种情况下,在初始化完串口并使能后,到 DMA 使能之前这段时间内,若主 CPU 向从 MCU 发送串口数据,从 MCU 是否能正确接收?( \; h0 ]" H" T& x
4 m' p7 {* @4 C
从上述测试代码的结果 2 可以得出,若在串口初始化并使能后到 DMA 使能之前有数据来,MCU 是不能接收的,经进一步调试,发现此时数据寄存器 USART_DR 存在一个数据,且在状态寄存器 USART_SR 中 ORE 值 1,由此可知,串口的接收寄
: h. P+ q( x: d" H3 Y  {6 E( R存器中已经接收到一个数据,但是后面的数据又来了,由于数据寄存器中的数据没有及时转移走(此时 DMA 还没有开启),从而导致后面的数据无法存入,所以产生了上溢错误(ORE),而一旦产生上溢错误后,就无法再触发 DAM 请求,及时之后再启动 DMA 也不行,无法触发 DMA 请求就无法将数据寄存器内的数据及时转移走,如此陷入死锁,这就是串口无法正常接收的原因。这时反观一下代码的结果 3,这又将做如何解释?
" D8 }1 Z! f; c0 ?- f, g$ [" d" j  i5 w; p6 R1 z/ U/ A4 i
仔细查看测试结果 3,发现这个发送端每 1 秒间隔发送一次,那么就会存在这个一个概率,这个发送的时间点是否刚好在接收端 MCU 的串口初始化并使能和 DMA 使能之间还是之后,这个时间窗口非常关键,如果刚好在时间窗,那么串口接收就不正常,如果在这个时间窗之后,串口接收就能正常。由于测试代码采用的是 1 秒间隔,对于 MCU 来说这个是非常大的时间长度,还是很小概率能碰中这个时间窗的,因此,测试结果看起来是都能正常,实际严格来说,还是存在刚好碰中的可能。如果间隔时间缩短,那个碰中的几率就增大。由此看来,这也就能解释测试结果 3 了,也能解释客户提到的有时正常有时不正常的现象了。
' k* l& K1 @4 O
  S: j* [# i" \+ C/ o- C6 P. R$ P4 r三 问题处理
( O* h0 D# a+ j1 r) v; O处理有两种方法,第一种方法是在使能 DMA 后,及时将数据寄存器 DR 中的数据清除掉,如下代码所示:- j$ \) r  R6 M# o& S
  1. ...
    - j# J5 N+ j) P- C! B* X) n
  2. /* Enable the DMA Stream */
    : r# \3 B. q  H
  3. DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);6 a* H: }# d" e! @% m7 r' j0 r1 x% }* a
  4. /* Enable the USART Rx DMA requests */
    6 X3 I, G1 {6 }5 Y- G# X5 f" C
  5. USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);
    5 c& `( D0 N& D' B  t8 E  P7 p) D# d2 I
  6. while(SET ==USART_GetFlagStatus(USARTx,USART_FLAG_ORE))
    4 x% g5 z9 o1 I, i1 l
  7. {
    0 W, j% p: c2 r" o1 v5 K/ D
  8. Tmp =USART_ReceiveData(USARTx);
    + z+ \. I. Y0 h5 B" L: e$ v( |. B
  9. }
    & e) w* h+ r2 `" x
  10. ...- |6 T5 I5 D7 C) F
复制代码

3 M! M4 g7 E( O7 F- i) ]% f" r* p  g3 B5 o
这里是使用读 DR 的方法来清除的,从参考手册中也提到使用这种方法来清除 ORE 标志:
9 E2 A1 \- X: |4 X- J1 t! J; R. ]# r) U; u& o; P2 c
YNFNU51MS8FI%{Z{XOKKZRA.png   U& U+ A& _) E+ E
! }# c5 o3 x' G9 f. J. R' z  \3 E
第一种方法类似于一种纠错措施,下面介绍另一种推荐的方法,如下代码所示:& r* w$ F6 z$ k6 k0 @
  1. ...
    $ ~& e2 ^9 a/ ?6 `& W; A- Q
  2. /* Enable the DMA Stream */
    9 B) G( B8 }+ |% b
  3. DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
    1 `6 O& m: ^$ r- Y  S5 _3 \
  4. /* Enable the USART Rx DMA requests */
    - N9 Z" T2 H8 ?5 i
  5. USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);
    1 O! U6 F4 t2 y% C
  6. USART_Cmd(USARTx, ENABLE);  C. k* e; l, k, k( J4 Z) w
  7. ...
复制代码

  I7 c! b' M; e6 Y5 X如上所示,可以先使能 DMA 再使能串口,这样就彻底不存在那个时间窗了,不管数据何时过来能能被 DAM 及时转走。这个是推荐的解决方法。* Y2 M# [- o" Y0 x
1 W1 M6 p1 |( d' C" G' i9 t2 T" [2 n
四 结论

* y; k: n: T* A, }% O4 k' O标准库中的示例代码一般来说只供参考,对于大部分情况来说都是能正常工作的,但偶尔也会出现不适用的情况,此时更需要我们针对问题进行思考分析,进一步找到原因才能解决问题。对于串口使用 DMA 来接收的情况,这里建议一定要先使能2 c* w2 t+ o* b: f+ `
DMA,最后使能串口,这样就能避免类似问题出现了。
4 q; n- q/ {8 ~8 o; i1 _: W, l- g- f4 {4 d7 i" k
赞 收藏 评论0 发布时间:2022-2-14 21:13

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版