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

【经验分享】STM32使用DMA接收串口数据

[复制链接]
STMCU小助手 发布时间:2022-4-16 21:00
01、概述( w, X' R( z. Q2 ]7 p2 B& h
7 X9 g5 @: |1 a1 r3 h2 A
在串口讲解的文章中,示例代码采用中断方式接收和发送数据,中断的好处在于可以及时响应,快速接收到数据,但缺点也很明显,那就是频繁中断,接收1000个字节需要中断1000次,频繁中断就意味着会打断其他代码的执行,对一些应用场景是不允许的。这个时候,使用DMA+串口的组合就可以很好解决这个问题。0 U1 s" m: R; q# [  H

" D0 k/ C0 r' V# v/ \$ d7 v  S f6296bed737973b74944a69efd0c4d22.png
2 t. R1 N& C: \6 W7 V
1 z% A; u7 R( C" W- o6 pDMA每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。; f) h# B: r( C. H" L
4 n7 Z0 e0 }% `
每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。
2 Z& k6 M5 [/ W2 Q
+ l" u  p; X# W  C我们使用USART1串口外设,从数据手册中可以查到,USART1的发送和接收都是支持DMA的,使用的是DMA2.' L* V/ `) k2 A+ b" I  j

; u# _0 A( l0 v$ Z9 \( _ 1451b55436b5ef4193f2954ce1c31990.png 5 i: k4 l5 z7 x: p7 Q% K
) q( w+ B% o) j3 y
接下来我们循序渐进了解DMA在串口中的应用
8 r2 Q# _& L9 B; r8 ]1 f* m/ z# k: S, F. Z
02、DMA接收
2 }$ X7 A: x# I0 c' F* V# ?& c我们先配置DMA,将DMA外设和串口联动起来。首先需要配置DMA。
( ?# |7 T+ c( R( s$ P% j4 i. M8 W4 w' s7 I+ c9 s# z8 R1 [/ O9 R
  1. void DMA_Config(void)
    ) k: T2 @$ Z# \7 i
  2. {; S  E/ \! z! y# {0 D8 B
  3.   DMA_InitTypeDef  DMA_InitStructure;1 M! `" R/ Z1 a* q

  4. + w& l! H, i' W- Z; `, J, D
  5.   /* Enable DMA clock */
    ) M8 f2 S" h! o, L* ~
  6.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);# p  g1 T& |5 l0 h. c
  7. ; |2 X2 P; p- H
  8.   /* Reset DMA Stream registers (for debug purpose) */
    $ r4 I+ P/ l- g3 Z! S) j9 \2 }
  9.   DMA_DeInit(DMA2_Stream2);0 t! o) e1 L6 F6 u: s5 }- B

  10. 9 ~- a" w( W. q
  11.   /* Check if the DMA Stream is disabled before enabling it.
      k' i1 h' z4 r6 d' V
  12.      Note that this step is useful when the same Stream is used multiple times:
    ( r" |$ E. o& m0 m1 S; m
  13.      enabled, then disabled then re-enabled... In this case, the DMA Stream disable
    / `: [) X6 J& g% z6 M- ^
  14.      will be effective only at the end of the ongoing data transfer and it will ! S; b0 j. |7 y
  15.      not be possible to re-configure it before making sure that the Enable bit 7 Q# U5 o9 }$ n: F) ~2 n% Y1 C
  16.      has been cleared by hardware. If the Stream is used only once, this step might
    ; S) m6 B$ @4 _/ B9 U2 `+ B
  17.      be bypassed. */
    ; @8 H) g* w- ?* q
  18.   while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)6 h: E  b/ P2 C- R2 g( e5 @
  19.   {6 c. D) ]/ Y6 A
  20.   }
    ' V, k$ G" y0 ?; ^/ L- {
  21. 0 ~3 J; K' d- f+ s0 }3 |
  22.   /* Configure DMA Stream */
    ' K6 d5 e2 W- E  @/ z9 h
  23.   DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //DMA请求发出通道- K3 {) U' P2 o* \/ F; U1 S
  24.   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址
      F! L/ i0 m; G. S; t" _
  25.   DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址
    : O/ d5 Q- [! g
  26.   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置8 i* g0 L& o3 @( v9 `
  27.   DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小) p% j: u( ^2 z9 o5 W% D" [
  28.   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
    * ^& u7 c1 t9 e! X% w% L0 J1 q
  29.   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增
    # q! k" \9 E) H1 L: y7 \
  30.   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位, d5 N: c2 x" D8 M
  31.   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位4 ]/ ~2 G, k/ M6 r
  32.   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式6 X5 i! D9 E- r1 ?8 e
  33.   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高
    + n) Z( `0 p0 p9 b  A8 J$ g
  34.   DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.         
    & j- F# D0 Z& f+ A
  35.   DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择1 t; {! q2 j# r" L# Y
  36.   DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
    - Y8 ]0 D/ @6 o+ n# O- O' R  O
  37.   DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。0 I. b) ^7 k3 X* }3 \& U
  38.   DMA_Init(DMA2_Stream2, &DMA_InitStructure); 3 q2 |6 `: W4 e0 }  A( R

  39. ; p. o) _* p* X
  40.   /* DMA Stream enable */
    * y3 W3 z9 }+ q# c% K
  41.   DMA_Cmd(DMA2_Stream2, ENABLE);8 L% s/ J6 h+ D, H% B
  42. }
复制代码

% y& a" r: P2 t. N! V6 w7 J$ t: O8 Y
2 O1 X) j( f3 W: ?) H2 k除了配置DMA外设外,我们还需要配置串口对应的DMA配置,在手册有一小章节讲解到。# g4 {/ M( C/ x
' Z+ g$ _- l% o
e95e31ad6d72f10f21e1362a99a2a141.png
5 f7 v6 X9 g1 L& i* `
. s' r8 R! j4 h" b6 f; _8 _$ l, y" J需要配置的寄存器是USART_CR3寄存器。
. z. Q, H  b8 S. r: F$ e  T
  }/ F' b; E+ M* X& O 370187d3cf4066f5dbb23bd20d58a987.png
1 ?8 l( i" C( _% z8 `% i
1 u  ~% F+ P; s" R: v' K6 P我们可以通过配置USART_CR3寄存器的bit6和bit7使能串口发送和接收DMA。ST的标准外设库同样提供了对应的外设库。
1 E4 C. }' c5 M" O2 p
$ r- m1 {0 W) L( U* Z# B
  1. void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
复制代码

* K; D  K: M0 y! C% R通过上面接口可以配置串口的DMA配置如下:
0 l5 }) a  v1 R. l2 r" _2 {* N. J/ L$ T/ z
  1. /*使能串口DMA接收*/
    ( c$ [- k0 _5 E& z! \. Q4 @
  2. USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
复制代码

6 k1 w0 c$ O- I1 |1 J03、中断
$ d: U9 W" A) {- `" ~3 h我们使用DMA+串口解决了频繁中断的问题,但现在有一个问题,我们还需要及时将接收的数据信息通知CPU,以便达到数据的及时性。我们使用DMA和串口两个外设,他们都有自己的中断。
0 @1 |1 _/ ]: U4 q' v9 y
5 a  I! Y2 y! t) }. V( q使用DMA中断,如下配置
$ N. ?8 Q1 h( ^" R  W7 `  F4 Y
3 ~3 h9 p2 ]1 N- V, ~" m
  1. /* Enable DMA Stream Transfer Complete interrupt */. v+ G. U+ c7 k1 Y' H
  2. DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
    2 E4 f$ l- Z$ W* V4 H
  3. 2 _6 @) F) M4 _! t
  4. /* Enable the DMA Stream IRQ Channel */
      x: \8 k0 Z0 b) W+ `- Z
  5. NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;
    + N9 P& a2 q" I: A. |% l
  6. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;: c2 f4 D7 b; M" o8 r: }7 s
  7. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;6 T% M) n! a, {7 s1 S4 |
  8. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;0 D. B' X, @# ^
  9. NVIC_Init(&NVIC_InitStructure);
复制代码

1 x& V1 S; l8 Z- @当DMA接收完毕时,会产生中断通知CPU取数据。5 D$ _6 d- P* `: u* p9 w4 |1 f' M& }
. ]- D8 g. ]0 [" s
但这有个明显的缺陷:串口接收一包数据,长度如果小于DMA的缓冲长度,那么久不能触发中断,只能等DMA接收满数据才会产生中断,如果下一包数据迟迟不来,那么这一包就不能被及时响应。
. ?' j7 y% z/ e3 D8 b; X; }- l
. G3 {) z: L5 L, {+ l那么我们采用串口中断是一个不错的方案。串口提供了一个空闲中断,“似乎”就是为了DMA专门使用的。$ V! \. H& n0 f3 V# m* s, I

6 h% c2 P9 b/ y; X fe739d4d5996e64f2182421bfa815264.png
, X8 @9 I6 k/ @7 b7 e
. n" N% }8 ~0 ~. M2 E* u0 D  F; _当串口接收一包数据,接收完最后一个字节,没有数据接收时,会产生一个中断,这个时候,CPU就可以取数据。+ y+ _: u, ~  }) j/ A* W  n

; t- d3 C* ?# P串口的配置知识不再讲解,不太懂的同学请看《STM32串口详解》,串口空闲中断配置如下! ]# u# ?) E  q8 T
5 y: |  @" I/ Z
  1. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    # e" A1 e7 d6 @. `$ t

  2. : @; a  y- P! c% ?# t+ x
  3. /* Enable the USARTx Interrupt */
    8 }* q1 I4 {' ~$ T+ c
  4. NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;1 L- F# u2 \: [; R" D! u
  5. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;+ x# p! A/ g, D- T/ Q6 W
  6. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;/ M0 N) x/ U2 f' V
  7. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;4 }! Y  x2 v3 \" {$ i
  8. NVIC_Init(&NVIC_InitStructure);
复制代码
" M" }4 A, n8 K/ e( V* a( D
串口中断代码如下9 d9 t: X0 P6 G% m( l7 U! g" M7 W2 D
( z) W1 W" z* r% C. f
  1. void USART1_IRQHandler(void)1 u; R0 c4 ^3 v$ }- d: @' K
  2. {
    - T) O4 l4 P) o/ U
  3.   uint8_t temp;
    8 u$ s7 K% o2 I$ G8 }
  4.   if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
    2 w2 d8 l) i0 ?% d$ `3 x6 j- V
  5.   {) X1 y& u0 b: S& ~, p# o3 e
  6.     DealWith_UartData();
    6 |- w. Q# }/ H" J% p: M. c6 y7 n
  7. //    USART_ClearFlag(USART1, USART_FLAG_IDLE);1 a. m0 y  O% N, M
  8.     temp = USART1->SR;  
    3 U/ ?* |$ X" e6 o
  9.     temp = USART1->DR; //清USART_IT_IDLE标志  
    3 }0 c% \# ]8 j$ `& r, N/ D; D5 i
  10.   }
    / \% L; p; N" R" L& j  o
  11. }
复制代码
/ U$ ]; \  \7 @/ U" W
重点:这里有一个坑!!!/ E& ~% F$ h7 b- {  s8 n% V2 R4 Z3 h

; }0 k, W' a$ o清除空闲中断位的代码是
6 j  r) U+ n3 P$ H+ _  b0 ?# e1 [+ t5 w4 A; f, P
  1. temp = USART1->SR;   1 Y5 L) n7 S6 [  `' X
  2. temp = USART1->DR; //清USART_IT_IDLE标志
复制代码
8 o) U% O0 r7 x6 w& I
证据如下* e! m1 J+ x  J* }2 d0 O4 U8 O  |* Q

; ?9 W# l: D: @. h! I9 [& S 33a2336da54482dfee049d60b3ffa5c4.png
, E  g; Y4 g; F/ a8 a, b% ?6 c4 X+ s  V" {( p
这一点很坑人,注意。
: j- C* A# a2 @& X, ~. ?2 t% C9 @! V; |. T7 P* K* E: W
04、代码
: L2 y4 W3 P+ n5 m4 |# u* ZDMA+串口接收的工程代码是开源的,Keil和IAR的工程都有1 S" [0 c" h' T) a: W

$ |# L7 z, ?6 H4 ~ 04d695e34eae02b5a48f69c91de7ad33.png
' p1 j1 p% Z: Z( W( m8 M% f$ |8 j  L; w
33-USART-DMA-Receive         DMA串口接收(没有使用中断). V2 y3 |7 P' B# ?, w

3 H7 W! Q; X8 f/ r6 |& B34-USART-Receive-DMAInterrupt DMA串口接收(DMA中断)
$ R6 }7 H; G8 X* q- ]+ J' I; y9 ~/ u5 ]6 D0 V
35-USART-DMA-Receive-Interrupt DMA串口接收(串口空闲中断)
" }# C4 ^$ B: d! s0 U8 t2 F
! b, k; _. Y( z  L5 ?4 p2 G  y
9 @  n' l+ j- r! H3 H3 z; `
收藏 评论2 发布时间:2022-4-16 21:00

举报

2个回答
鹿森 回答时间:2022-4-16 22:04:08

好诶!

晨风65015 回答时间:2024-7-8 09:12:21

学习

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版