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

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

[复制链接]
STMCU小助手 发布时间:2022-4-16 21:00
01、概述, t. ?1 c! L% u  U6 _6 V' p, a
1 T' F! y! U0 D. {: [9 r3 Z, k, E
在串口讲解的文章中,示例代码采用中断方式接收和发送数据,中断的好处在于可以及时响应,快速接收到数据,但缺点也很明显,那就是频繁中断,接收1000个字节需要中断1000次,频繁中断就意味着会打断其他代码的执行,对一些应用场景是不允许的。这个时候,使用DMA+串口的组合就可以很好解决这个问题。8 ^4 c" J' M6 F. _$ s/ G4 B" Z
  o2 \; j+ d! v: _6 l
f6296bed737973b74944a69efd0c4d22.png
# q) b7 f3 ^* f- X9 x: Y% |- _2 |8 P0 A6 |8 k
DMA每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。
  Y0 ~% P1 n8 P% u2 w" `- g& j2 i: e; i9 u5 N2 b) i5 x: H
每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。4 |7 D* L: h# ]4 d* t5 _' m: d
1 r: k' u6 I: ^) V8 q8 S' e
我们使用USART1串口外设,从数据手册中可以查到,USART1的发送和接收都是支持DMA的,使用的是DMA2.
* {; J6 M; u5 v1 D' |
: h: W& C" d  K$ I 1451b55436b5ef4193f2954ce1c31990.png
! [+ W& m/ \5 K; g7 r6 @8 E8 N9 j' F8 b) I$ ?4 D" N
接下来我们循序渐进了解DMA在串口中的应用. b) d& D) V, ^3 T
; R: l, ?/ `& V  \1 x6 j
02、DMA接收4 S8 T) Z$ x# z+ e6 o: r1 r) v
我们先配置DMA,将DMA外设和串口联动起来。首先需要配置DMA。! \# W3 T, X3 K. {6 f# t

* K; {5 D4 j* l$ Q/ D3 T9 c
  1. void DMA_Config(void)1 f* E8 ]5 @' |" f0 x1 M7 g' D
  2. {# c* J( B3 [( M
  3.   DMA_InitTypeDef  DMA_InitStructure;
    6 |1 n8 U: O" Y% |
  4. ! o, e0 g) i- O+ z6 q: ^
  5.   /* Enable DMA clock */
    + S5 J4 d0 V0 j, j8 X
  6.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    6 f) s. V3 f( H3 G
  7. ! N$ d% {: O& ?/ Y9 N% K
  8.   /* Reset DMA Stream registers (for debug purpose) */% e( t  |$ s5 k
  9.   DMA_DeInit(DMA2_Stream2);
    4 z# U- h) {8 t4 J" X; G8 o
  10. 9 ^* o* Z( o! a
  11.   /* Check if the DMA Stream is disabled before enabling it./ }3 R' G3 |/ u" _8 x" M8 m" u# v
  12.      Note that this step is useful when the same Stream is used multiple times:
    ; U, N9 ^* l* f9 |0 Q
  13.      enabled, then disabled then re-enabled... In this case, the DMA Stream disable
    # C% S/ w6 z9 }3 B
  14.      will be effective only at the end of the ongoing data transfer and it will ) ^2 z# @3 w  O, ^2 ]7 L+ W  q
  15.      not be possible to re-configure it before making sure that the Enable bit 0 h5 S8 G0 h0 Z* H
  16.      has been cleared by hardware. If the Stream is used only once, this step might
    " h6 n4 p# X( l4 z
  17.      be bypassed. */
    . e4 c) r& E% C7 L9 w' Y
  18.   while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)
    ( X: W* b# T3 q3 A, `! O
  19.   {+ P9 X1 W$ ]+ e0 j, T4 k) C
  20.   }: P; o, E- W2 f/ M+ p  _2 \' L6 i' W/ ]. w
  21. / Q, y  p, \- r0 m' ~9 E% \
  22.   /* Configure DMA Stream */5 X6 f1 M$ z. ]& ^1 J
  23.   DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //DMA请求发出通道
    5 f5 T( s7 P! g* k/ y0 \" n
  24.   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址
      l- v# g$ b5 f1 L
  25.   DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址
    * z7 a6 \/ _! b
  26.   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置4 B5 {" y( l" ~. }1 K; ~
  27.   DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小4 Z# R! T5 H' [
  28.   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
    . k1 Y! o& q, p3 A* S! @! a2 M9 M( e
  29.   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增
    9 f' x% ~3 o; V# N# f) \) h
  30.   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位
    ' T0 @5 \2 A, l3 ^/ {$ e
  31.   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位
    3 ]6 J+ K0 u0 P4 Z1 S
  32.   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式
      I* ^+ @6 ^" T3 N, w; P9 z
  33.   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高" Q, `$ t/ T. U$ V) q0 {
  34.   DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.         
    ) L4 W: C8 S1 }5 ^0 u* |
  35.   DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择
    0 j8 z. Z$ h8 q5 G1 X
  36.   DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
    % Q0 w* Z" V2 e& m. }# n! C& v. J
  37.   DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。. X  p6 Z# f# X# q8 b% D9 T
  38.   DMA_Init(DMA2_Stream2, &DMA_InitStructure);
    . B( {& X' Z! Y: S! z" b
  39.   u( E& \4 p3 ]- |; R
  40.   /* DMA Stream enable *// G; P2 o! V; B: q. b! B
  41.   DMA_Cmd(DMA2_Stream2, ENABLE);- t2 o' z* \! l" I) g9 v2 l
  42. }
复制代码
- z, D: `. q4 z% }3 n5 L% O
" H9 `2 L5 i5 m" R
除了配置DMA外设外,我们还需要配置串口对应的DMA配置,在手册有一小章节讲解到。
0 d. L' Z, {. o% P: O) Y5 g$ {8 V+ g! C1 o9 y; U
e95e31ad6d72f10f21e1362a99a2a141.png ; g6 }4 A+ T5 P: O2 o2 |
& o) [7 I& G: U- M( l, V: G) L
需要配置的寄存器是USART_CR3寄存器。8 ]: G6 ^) l6 k; ]( v8 m3 q
) H% e3 ~8 n" m* a4 ?& l+ @
370187d3cf4066f5dbb23bd20d58a987.png ; F# P0 b$ ~: Y( t* Q- |, k- J( z  n: J

5 D3 B) N  M* J9 _6 B我们可以通过配置USART_CR3寄存器的bit6和bit7使能串口发送和接收DMA。ST的标准外设库同样提供了对应的外设库。
' ~) m) A) O9 m; |$ N1 J- Q$ |4 P( l: m. {- t! q
  1. void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
复制代码
* l0 ^# p  o' l& r8 l
通过上面接口可以配置串口的DMA配置如下:
" ~' W! D3 _/ u" j2 Z- o" D
1 g! Z  O9 d3 O& U
  1. /*使能串口DMA接收*/$ x" l. Q5 B% C3 J
  2. USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
复制代码
0 p2 k4 r6 N4 i9 @5 G  c% D) r
03、中断
8 ~; s0 l4 n6 }% O& t: p6 `我们使用DMA+串口解决了频繁中断的问题,但现在有一个问题,我们还需要及时将接收的数据信息通知CPU,以便达到数据的及时性。我们使用DMA和串口两个外设,他们都有自己的中断。
# s; q  t0 s7 k6 w9 e7 K8 A: _) D$ S: m4 u5 m6 \6 m$ Y( K
使用DMA中断,如下配置/ K  L8 R  B5 t
/ V: e/ z6 T; B' }+ u! f. x
  1. /* Enable DMA Stream Transfer Complete interrupt */( y% S. p4 J% h) z
  2. DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);0 h: A) G2 d/ u2 x  t! O
  3. # O, [: m# \  G7 P% t' M) L$ g; u$ Q
  4. /* Enable the DMA Stream IRQ Channel */( I  S: t3 ]( [0 C: E2 b
  5. NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;$ e: v- Q5 C) {" R# x
  6. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    ; i8 h4 T! E9 X$ h) I: i
  7. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    * i( L& x" v: {# C* i* U+ g
  8. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    % b, k) i/ P) w$ J
  9. NVIC_Init(&NVIC_InitStructure);
复制代码
7 y6 {' D( \& g* V* V
当DMA接收完毕时,会产生中断通知CPU取数据。! w- n6 C  t. A" v

( P, m% u4 J! y  K但这有个明显的缺陷:串口接收一包数据,长度如果小于DMA的缓冲长度,那么久不能触发中断,只能等DMA接收满数据才会产生中断,如果下一包数据迟迟不来,那么这一包就不能被及时响应。9 k; ?+ o0 L4 f; ?' m1 U8 ~

& S& \- ~1 |% ^  ?$ J那么我们采用串口中断是一个不错的方案。串口提供了一个空闲中断,“似乎”就是为了DMA专门使用的。
* o* u7 C; s7 v3 z/ M- X
; C9 J/ |, T' I( I fe739d4d5996e64f2182421bfa815264.png
( g2 K7 J2 d$ @$ e
* V2 f$ u; K/ G" L, ]当串口接收一包数据,接收完最后一个字节,没有数据接收时,会产生一个中断,这个时候,CPU就可以取数据。
' V$ O9 A6 C) p6 T; x+ s6 ~4 D5 [
串口的配置知识不再讲解,不太懂的同学请看《STM32串口详解》,串口空闲中断配置如下
. V  @% z) U6 [. d6 M8 t
; `* t( ]- \. B2 G6 C
  1. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    ; @/ V% E7 `# D3 L0 W1 L1 R6 p* G
  2. ( o' e/ T( m0 G' R/ \+ H1 z; z) r+ x3 b7 q
  3. /* Enable the USARTx Interrupt */$ \- f$ G# f. U0 _8 K
  4. NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    7 K2 `: L/ Z7 P( R% l1 l- L
  5. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
    ; v- b- t5 X2 T' C
  6. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
      `- q; t  v; S8 M
  7. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;) {% v! i% k( X9 f6 k  Q
  8. NVIC_Init(&NVIC_InitStructure);
复制代码
# z4 Z1 d$ I! H5 e, ~8 t
串口中断代码如下, w! ]9 P, `' d( v3 g
1 D) w; q$ k& y$ `+ _* K
  1. void USART1_IRQHandler(void)
    8 N8 P# J3 h$ l0 Q; S
  2. {* C/ ?* n5 R" U2 f/ |$ A8 }
  3.   uint8_t temp;
    7 t& _  m$ A  F0 K5 ^# x9 Q
  4.   if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
    5 z0 a- ^& w  p' q3 z, |
  5.   {! `' c7 \6 V' O' a
  6.     DealWith_UartData();+ ]6 X6 l1 {/ k
  7. //    USART_ClearFlag(USART1, USART_FLAG_IDLE);
    $ f/ O' Z5 ^. u- n+ m5 j  R
  8.     temp = USART1->SR;  
    & j: z, Y$ }6 T. ]
  9.     temp = USART1->DR; //清USART_IT_IDLE标志  
    0 V1 C2 L* s! o! Z, a
  10.   }
    9 m5 `+ |- k: k7 _
  11. }
复制代码

7 I) O) W: y5 v1 t: I重点:这里有一个坑!!!
. i& s. R: n- b9 v- ~  _( ?5 c# s7 M9 R/ d, ~8 }$ N
清除空闲中断位的代码是
: ]6 f" c# ^- t
  g$ h' H/ F# j; i
  1. temp = USART1->SR;   
    ' r' T* y0 R5 V1 f  T; n0 \5 b1 v
  2. temp = USART1->DR; //清USART_IT_IDLE标志
复制代码
4 C% H$ L2 o; h6 e0 I
证据如下9 l0 w. {/ Z5 }( m, p: `

* s. q- E6 B9 R* C) |& _4 F6 z. }. `7 r, P 33a2336da54482dfee049d60b3ffa5c4.png
3 R" b! k" X9 }2 _. M
$ `2 q  X* l8 k, h# z6 V这一点很坑人,注意。
. c- S: N6 s$ G3 [6 i8 e& I8 E, \0 P- I. j3 T' G, ]* Y8 E  S: f0 [) `
04、代码; [$ U5 J- u0 q5 M
DMA+串口接收的工程代码是开源的,Keil和IAR的工程都有
$ C* N3 P; x$ g
. w6 {! n6 P) w4 |$ d% A; L 04d695e34eae02b5a48f69c91de7ad33.png : B; O9 M0 D3 ^7 ]

6 N$ I+ Y+ S$ c33-USART-DMA-Receive         DMA串口接收(没有使用中断)
) i* x% H8 |* J# o. r: a$ s! a# Q6 S5 n  `9 g1 U* [7 r
34-USART-Receive-DMAInterrupt DMA串口接收(DMA中断)+ `0 \: l7 F+ M: h' q/ d
! h. ?; f* w4 q& [1 _' w; a4 ?4 }
35-USART-DMA-Receive-Interrupt DMA串口接收(串口空闲中断)
* D( _$ R; n) U( }
! ?5 M0 z# t! a9 k+ P" k7 Z: l" {& y) |
收藏 评论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 手机版