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

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

[复制链接]
STMCU小助手 发布时间:2022-4-16 21:00
01、概述2 a4 \" L0 z: i/ o' |9 |9 j) A

2 g3 w1 _2 ?6 ^9 Z. }$ f* c在串口讲解的文章中,示例代码采用中断方式接收和发送数据,中断的好处在于可以及时响应,快速接收到数据,但缺点也很明显,那就是频繁中断,接收1000个字节需要中断1000次,频繁中断就意味着会打断其他代码的执行,对一些应用场景是不允许的。这个时候,使用DMA+串口的组合就可以很好解决这个问题。% a- i' z$ W" P/ }
$ t; R0 j% o0 u+ O1 L9 |& c- G' R
f6296bed737973b74944a69efd0c4d22.png ; g7 }8 i8 n& V0 O, c' i+ ~2 d
$ f" Z0 U) n+ o' x9 I+ O, R0 X
DMA每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。. `2 _" s1 ?0 b4 }2 [

+ K4 ^" a. N/ C3 s6 M, F2 S每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。
7 J8 {! }- j# B$ ]: I8 B' z3 h
* U1 T1 d" d( u* ~/ k! k0 J3 n我们使用USART1串口外设,从数据手册中可以查到,USART1的发送和接收都是支持DMA的,使用的是DMA2.
8 {  g  T9 Z8 J! _% H" D
- _! y2 l# f  e3 }( z4 ?1 i 1451b55436b5ef4193f2954ce1c31990.png
  i3 {$ S3 `, j; d2 p4 @" Q2 l4 D# U' E5 ^7 b/ p
接下来我们循序渐进了解DMA在串口中的应用
6 \# Z" O1 C0 r  K
0 k+ O* K8 {! Q% }3 M02、DMA接收5 @/ R& F) I* p5 B( h
我们先配置DMA,将DMA外设和串口联动起来。首先需要配置DMA。
& B/ P: C1 Y! V; H8 y. K" B) P/ ]9 t, e1 h1 G% ]
  1. void DMA_Config(void)
    2 O' {( n3 {) A; }
  2. {
    " s6 f2 \6 z) E1 i3 Y1 v6 b
  3.   DMA_InitTypeDef  DMA_InitStructure;
    ) ]# d! l% ]9 d' ^& k1 U$ \' q

  4. $ K9 O1 a/ v; L) F, }! e6 Y) p6 \
  5.   /* Enable DMA clock */* L% [9 f$ l4 y( a( A2 C7 k
  6.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    7 j0 o% [( O. K3 K* W. F
  7. 5 o$ x" u- S) K
  8.   /* Reset DMA Stream registers (for debug purpose) */
    , g5 S& ]. U7 ^, {5 `  F  b0 U& F
  9.   DMA_DeInit(DMA2_Stream2);- E  ]; Q$ d/ |1 y

  10. / K2 l# E! Z: Y( R' k( C
  11.   /* Check if the DMA Stream is disabled before enabling it.
    . d! X1 L. s7 b( H$ t
  12.      Note that this step is useful when the same Stream is used multiple times:
    3 Q" i0 P3 n# H
  13.      enabled, then disabled then re-enabled... In this case, the DMA Stream disable
    + k3 I2 G  W- m- ?
  14.      will be effective only at the end of the ongoing data transfer and it will
    4 c$ Y) Q7 _( T  _
  15.      not be possible to re-configure it before making sure that the Enable bit
    6 q2 }2 Y, e' n/ k
  16.      has been cleared by hardware. If the Stream is used only once, this step might 7 u6 U1 }. }2 k/ g& }+ L. ?# Z) f
  17.      be bypassed. */. e! t/ X; U- V! s: c
  18.   while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)( |  U: \. K. I+ I0 B& j
  19.   {
    * C% O! h1 R$ O& W9 k
  20.   }
    6 [* W2 R/ x& _2 v( i9 ]

  21.   p7 O4 W+ d/ G: I' c
  22.   /* Configure DMA Stream */% t" B$ B- r% b# @- H9 ~
  23.   DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //DMA请求发出通道
    1 w0 [8 e1 K6 p: E$ Y8 c! K
  24.   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址+ z' _" D  |3 \/ n* P+ s
  25.   DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址, V2 B9 B( y$ z& k" i
  26.   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置8 l1 ]5 Q, z0 j2 K$ v; V
  27.   DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小
    % z5 W; P. t1 a' w  D7 C+ ?
  28.   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变8 V- @% V8 Q! e9 w6 U
  29.   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增0 b$ s5 w) C  f/ W7 O
  30.   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位9 `* w% [. Q: z  w# g' Y
  31.   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位+ b& I' Z) i% P8 k9 F
  32.   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式
    & G! x# k! ]+ [' H' H
  33.   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高: j% ?3 u% l+ }
  34.   DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.          7 s( j5 E% _& U. L# E  O
  35.   DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择
    ) Z! V% j) f. A4 M% d9 B# P) B
  36.   DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。% v) y8 v6 j0 P+ r9 w( L3 y& ]& g
  37.   DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
    ) p3 h. R7 a& h# @+ y, `
  38.   DMA_Init(DMA2_Stream2, &DMA_InitStructure);
    # R5 E: d0 U# E- ?

  39. + N  O$ Y$ w: o/ D0 n
  40.   /* DMA Stream enable */" v* {7 ]! L. l* c
  41.   DMA_Cmd(DMA2_Stream2, ENABLE);& F5 t; k/ L3 R1 s
  42. }
复制代码

, ~. z2 S/ m2 ^; Y$ z8 V2 e: i) w6 C% f
除了配置DMA外设外,我们还需要配置串口对应的DMA配置,在手册有一小章节讲解到。8 P' ~6 A( _  f4 ?
& `# O7 n1 H1 i" @  z% g
e95e31ad6d72f10f21e1362a99a2a141.png
: Z- T! |2 v2 {: v/ H; f2 }; c! @( j3 L: d
需要配置的寄存器是USART_CR3寄存器。6 z' u4 a& c9 l- d; H3 i

+ ^: E9 X+ b% l: ~ 370187d3cf4066f5dbb23bd20d58a987.png
* L/ h- k5 i- {3 ^6 r) \1 j
. f4 K2 g/ u! f8 O* I* E我们可以通过配置USART_CR3寄存器的bit6和bit7使能串口发送和接收DMA。ST的标准外设库同样提供了对应的外设库。9 H+ R) N2 \2 _  @) z1 F! l# x" x

  U  O0 _/ V% \
  1. void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
复制代码
' X+ o5 r; B& s" l1 |6 r
通过上面接口可以配置串口的DMA配置如下:2 Z. J" [$ l2 E1 |
; x( Y! J) {  K: `+ E+ Z& \$ L
  1. /*使能串口DMA接收*/& t: D- l4 U4 K& J/ v9 O1 w: D5 D
  2. USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
复制代码

$ A" l/ Z  T  k03、中断& [: Q  |5 H) v, l8 F' b
我们使用DMA+串口解决了频繁中断的问题,但现在有一个问题,我们还需要及时将接收的数据信息通知CPU,以便达到数据的及时性。我们使用DMA和串口两个外设,他们都有自己的中断。, c" D/ i1 `9 \( A. W6 f& i- F
3 s! l* C5 w' j% ~6 ~
使用DMA中断,如下配置
# Q( L+ G3 Z9 n+ {( |) i; d( o( ?6 T. _- F2 D1 M
  1. /* Enable DMA Stream Transfer Complete interrupt */9 ]* [3 M0 X: s  K
  2. DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
    , @/ L( o# D: v; F2 m/ ~  Z0 \

  3. 7 n& i1 l- |6 J
  4. /* Enable the DMA Stream IRQ Channel */8 D9 V# _* G7 W/ _( D0 q
  5. NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;: U8 {3 G4 c/ m9 ]# C. ]8 e
  6. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;, B1 `. c3 |5 s8 }0 S( s- @
  7. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;- r; w, g' D1 w1 C. P
  8. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;+ M! {; k# y  N2 K5 }' z
  9. NVIC_Init(&NVIC_InitStructure);
复制代码
6 _& H8 F$ s" r6 N
当DMA接收完毕时,会产生中断通知CPU取数据。- e; y" R5 e$ b; K. ^3 W; B" x

5 W6 F# X3 Y& s$ V7 G但这有个明显的缺陷:串口接收一包数据,长度如果小于DMA的缓冲长度,那么久不能触发中断,只能等DMA接收满数据才会产生中断,如果下一包数据迟迟不来,那么这一包就不能被及时响应。
* p% B- u, F* |8 w. ~6 P- ]/ T+ V8 l/ U3 q
那么我们采用串口中断是一个不错的方案。串口提供了一个空闲中断,“似乎”就是为了DMA专门使用的。6 x/ P( w( n6 @" v0 a' R5 [
6 v6 [# U) g, r9 I
fe739d4d5996e64f2182421bfa815264.png
7 v6 q6 R! W$ q/ \+ {
1 ^# G! S' u  S* @3 d9 _当串口接收一包数据,接收完最后一个字节,没有数据接收时,会产生一个中断,这个时候,CPU就可以取数据。
3 {4 I8 \8 x9 h8 u' ?
, g: K% @( ?+ p; D& l串口的配置知识不再讲解,不太懂的同学请看《STM32串口详解》,串口空闲中断配置如下
! x$ }2 m$ w# B7 h! t# h$ I& R) O; _# l2 v2 ]( f, n2 M7 i
  1. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    + M9 k! x/ D( |" p' @$ J
  2. - z6 D3 ^+ B$ J3 d- U2 |  x& M; X7 U
  3. /* Enable the USARTx Interrupt */; ~& D! d! O1 ~8 M+ h
  4. NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    # y& ]% s# x/ L# n4 o
  5. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
    / i0 j/ M# {/ _
  6. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;7 X4 g2 l% U1 w+ P+ r
  7. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    ! X2 k# D( _1 M! q' Q6 r/ h
  8. NVIC_Init(&NVIC_InitStructure);
复制代码
' E8 }9 k" P7 |  L+ @$ L, {
串口中断代码如下" k( I% Q8 @3 }& l8 Q

9 I6 @& A/ ?/ m0 B3 p" F
  1. void USART1_IRQHandler(void)" U+ A4 G* V1 y* \% V7 V
  2. {) w) Z. K" z7 U9 I
  3.   uint8_t temp;" {3 b9 Z6 g. A' s* ^4 }8 C
  4.   if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET). N# S- c1 B  M8 }% }$ ?) @  C
  5.   {
    5 R" p3 F6 F1 J$ v  T. b# a8 l, v
  6.     DealWith_UartData();* J9 G+ P( V( f% {  x7 _4 V/ v& n. O
  7. //    USART_ClearFlag(USART1, USART_FLAG_IDLE);
    % o% r: A$ k8 h! q" `
  8.     temp = USART1->SR;  
    % q& Y2 p4 D! E+ N6 i- C
  9.     temp = USART1->DR; //清USART_IT_IDLE标志  
    % S! A  O- k$ O1 i
  10.   }1 N! J1 M/ ~1 G. S. u) n' y4 R5 t
  11. }
复制代码
  G9 e  D$ w7 k7 W# A+ b; d
重点:这里有一个坑!!!
/ N4 o* n: v' b- W& \. G4 }7 |3 k, M: s! d; J3 x
清除空闲中断位的代码是
4 Z: Z9 T3 k& Z6 `/ V
9 C6 w, P8 `& @! ~3 k+ z5 E8 g9 J
  1. temp = USART1->SR;   
    7 c, _5 I2 v0 G# L
  2. temp = USART1->DR; //清USART_IT_IDLE标志
复制代码

" f7 v8 r% h( {4 i% D, K8 ~证据如下# Y& h1 V- S+ p3 u

* I- u7 \1 `% D" a 33a2336da54482dfee049d60b3ffa5c4.png
6 E: J# L+ M6 l/ j6 A
+ N5 f% M8 j) ~7 D" [6 u8 c& [这一点很坑人,注意。
5 R0 q4 P$ J1 W3 S, l$ |/ f, e% S  o( e3 m% k  M; u3 _0 P
04、代码5 I4 ^* A; [& M# N9 L
DMA+串口接收的工程代码是开源的,Keil和IAR的工程都有
  V1 z& U8 x8 ~( k/ X% J/ i' ]) q, T  s5 s9 g
04d695e34eae02b5a48f69c91de7ad33.png ( b: O! O/ q* x
0 s5 g8 S$ G! `) A  {3 I! h
33-USART-DMA-Receive         DMA串口接收(没有使用中断)
3 y# z, h4 n! R/ K3 l
( q: F/ t/ M/ E34-USART-Receive-DMAInterrupt DMA串口接收(DMA中断)
4 ^5 P6 g9 c8 R1 ]0 N! N
- C- ~) `" z, y' }5 G! g8 q35-USART-DMA-Receive-Interrupt DMA串口接收(串口空闲中断)
/ w3 h  l$ w4 f; A, K' y; v. B8 W

- G5 Y8 }+ v  G3 \# @- `' t5 n
收藏 评论2 发布时间:2022-4-16 21:00

举报

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

好诶!

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

学习

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版