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

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

[复制链接]
STMCU小助手 发布时间:2022-4-16 21:00
01、概述
0 c" l+ z. C, U% R9 V: Q6 K; C% r* U. J
在串口讲解的文章中,示例代码采用中断方式接收和发送数据,中断的好处在于可以及时响应,快速接收到数据,但缺点也很明显,那就是频繁中断,接收1000个字节需要中断1000次,频繁中断就意味着会打断其他代码的执行,对一些应用场景是不允许的。这个时候,使用DMA+串口的组合就可以很好解决这个问题。: b/ ^/ v9 H* P; a; w, ~  F

( G6 \5 x  O# A* O0 }& `' h% z2 d( C f6296bed737973b74944a69efd0c4d22.png " h+ |' e# Q  F  `! Q7 }" I

6 S( a6 j+ v5 V' K' ODMA每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。3 v  C; P+ b0 f: C% w  }

& Y+ r2 Q, G- W  B2 Q# F0 C每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。
8 h* j- l" L, x" h4 O8 k4 [& A* [; ^( E( v2 A0 M$ K( I2 X
我们使用USART1串口外设,从数据手册中可以查到,USART1的发送和接收都是支持DMA的,使用的是DMA2.
% [$ l& ^0 D! Z( T2 g/ f
% i  N; a5 z3 S+ ?$ w0 ~ 1451b55436b5ef4193f2954ce1c31990.png % _% z. B% r) q4 z9 Q
0 i% L9 @  G* Q+ P& Z# {
接下来我们循序渐进了解DMA在串口中的应用; [' Q+ f6 ~7 [% w  z8 E

: T; P8 ]9 \7 I0 E) W" M02、DMA接收; d$ Y5 ^5 J3 e  p! Q1 W. j1 W( |3 e
我们先配置DMA,将DMA外设和串口联动起来。首先需要配置DMA。
) J: Q$ Z: R8 {1 H7 o& {+ ?; ~7 T" r9 Y9 M
  1. void DMA_Config(void)* t5 L9 `( g0 E, W* d
  2. {% W  V5 |) q2 d8 }6 z: [
  3.   DMA_InitTypeDef  DMA_InitStructure;
    4 s9 A3 z- w2 j1 Z. |# A& W# ?8 W
  4. 8 g4 o$ L. t4 r5 R/ L; e# v
  5.   /* Enable DMA clock */
    ' U( L' Q4 {* M( E
  6.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    1 w+ |- h4 t1 T3 O! {
  7. . `+ P% L1 F4 O, ]/ n
  8.   /* Reset DMA Stream registers (for debug purpose) */! j1 W& H' Y3 S! X( {+ }
  9.   DMA_DeInit(DMA2_Stream2);$ e8 {7 L6 A& k  g, g0 \  _/ e

  10. & I# j% ]3 A; v6 ~$ Z2 G
  11.   /* Check if the DMA Stream is disabled before enabling it.7 B) j; G6 M& N4 G
  12.      Note that this step is useful when the same Stream is used multiple times:# I+ h9 {  \3 k1 N8 G1 G1 A
  13.      enabled, then disabled then re-enabled... In this case, the DMA Stream disable
    3 L' _" C9 k1 }- ]
  14.      will be effective only at the end of the ongoing data transfer and it will
    4 P- I: w  @/ n) ~  x$ Q
  15.      not be possible to re-configure it before making sure that the Enable bit
    4 P: L# N# ]6 o( A8 X3 y
  16.      has been cleared by hardware. If the Stream is used only once, this step might , w& d) \6 K3 J, @  R
  17.      be bypassed. */
    3 U. l0 }3 p( j# _
  18.   while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)
    ( f  R' ]: E, B% A( L# w% q6 B
  19.   {- N1 w& ~& V* r7 g
  20.   }( H) H& ?/ U+ g$ D) A5 m. }
  21. - L3 d8 L6 x: n4 E' }
  22.   /* Configure DMA Stream */, ^, a8 d; q; w2 I
  23.   DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //DMA请求发出通道% e* n4 I7 f& i8 o3 O8 Z# O
  24.   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址
    & [3 b, G7 ~& i+ D" j* B9 U
  25.   DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址0 x+ l9 ^0 V' y
  26.   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置% A+ m. D+ V7 z9 l  M
  27.   DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小
    * [) r6 G) A/ c9 O
  28.   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
    " J, ], d) C- [, s6 ]
  29.   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增
    & d( E. i2 o# [; r) ^
  30.   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位
    0 z$ Z  b. S6 x- r& C
  31.   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位3 G% }9 i% F: F9 d3 w  J
  32.   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式
    0 z7 C0 T( M$ w0 B0 y- t% t( l
  33.   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高
    " P# C# h1 R9 w) _8 b& F& d
  34.   DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.          * ~0 I" r9 j$ M* U9 f0 a
  35.   DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择0 l3 Q4 R: {6 i$ d
  36.   DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
    ; _2 S* ^! `6 X  T% a( N0 I
  37.   DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。. g' S& K" v; t
  38.   DMA_Init(DMA2_Stream2, &DMA_InitStructure);
    . M  C- o. e& P! z- \2 v1 T
  39. 1 y: o/ K/ }: H- d9 z9 f7 c
  40.   /* DMA Stream enable */
    8 ~6 O/ F; e0 v) |7 c) d4 ^
  41.   DMA_Cmd(DMA2_Stream2, ENABLE);: {; a3 e0 F0 O7 y. T3 G! e
  42. }
复制代码
$ U1 D1 y4 M' D- k) J

: \$ }" H# e; t+ _除了配置DMA外设外,我们还需要配置串口对应的DMA配置,在手册有一小章节讲解到。! M9 R# G! Y; W8 k/ Q

( z* Q2 k) p! L$ N8 X e95e31ad6d72f10f21e1362a99a2a141.png
9 g$ A8 F; k& y, s+ [# L" ~
/ A6 L. C, l) s0 T- E1 g. Z需要配置的寄存器是USART_CR3寄存器。7 ?* Z4 M* [& R' L. _, x; `: v

# K, `/ a, p: E0 Z 370187d3cf4066f5dbb23bd20d58a987.png / L, ?! y* |) P% A6 H

9 a7 Q7 ]0 o. z; P6 S' Y+ U0 H我们可以通过配置USART_CR3寄存器的bit6和bit7使能串口发送和接收DMA。ST的标准外设库同样提供了对应的外设库。% e3 d* |3 |4 f% c4 ]* V

1 A2 G2 @1 {& c  ?: a
  1. void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
复制代码

% N1 L+ n! {) I通过上面接口可以配置串口的DMA配置如下:
% a' p# }8 S, N4 ~' O  e' p) N, Q, z
  1. /*使能串口DMA接收*/! E1 x& W7 r3 D& {; s2 ^$ }( a" K
  2. USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
复制代码
7 e1 z# K, d* ~$ d! y
03、中断0 D" E8 c. a& e( k; b
我们使用DMA+串口解决了频繁中断的问题,但现在有一个问题,我们还需要及时将接收的数据信息通知CPU,以便达到数据的及时性。我们使用DMA和串口两个外设,他们都有自己的中断。2 p( u9 w) F9 l( d" Y0 ?" Z

4 v) D! v, ?0 {$ R8 x使用DMA中断,如下配置3 a+ A# [3 s% ]+ d0 g
  y3 x9 ^2 Y6 G, S3 e7 B* q, L
  1. /* Enable DMA Stream Transfer Complete interrupt */
    0 ^2 v& o2 R( o  C, c1 Q0 ^
  2. DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);2 j5 C. h' C6 a: O& ]9 D8 E

  3. : n# [, e2 n9 S2 V; I. D4 V0 m
  4. /* Enable the DMA Stream IRQ Channel */8 i5 L) e/ r2 Y8 x3 T: H
  5. NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;
    - Z$ O, }2 L. z& K2 W5 t
  6. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;3 |; q$ i5 y3 n6 a
  7. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;1 M5 R; P7 T) ?% r: S
  8. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;% P  p  G, F/ }+ u( f
  9. NVIC_Init(&NVIC_InitStructure);
复制代码

0 G/ A, {( x3 S/ ?( i当DMA接收完毕时,会产生中断通知CPU取数据。4 R. i( _  _1 ~6 U* s3 T+ a
) l" d& U. e/ I$ r
但这有个明显的缺陷:串口接收一包数据,长度如果小于DMA的缓冲长度,那么久不能触发中断,只能等DMA接收满数据才会产生中断,如果下一包数据迟迟不来,那么这一包就不能被及时响应。
. }/ r' ^$ l) E' Z: K* W! K
2 G- y( X( L- \% R% e那么我们采用串口中断是一个不错的方案。串口提供了一个空闲中断,“似乎”就是为了DMA专门使用的。& U% P. J% v, m; T# z
5 Q4 x- I" S. e# y2 i; M( d
fe739d4d5996e64f2182421bfa815264.png
- R4 }9 \& B5 a7 h6 j, A" Y0 f- R7 x7 I0 ~9 A/ D7 K
当串口接收一包数据,接收完最后一个字节,没有数据接收时,会产生一个中断,这个时候,CPU就可以取数据。
' J: f) @9 L  F( w! J5 u- }3 C. j& x
串口的配置知识不再讲解,不太懂的同学请看《STM32串口详解》,串口空闲中断配置如下
) a/ b! r7 T5 Y, J9 {4 ]- k4 N* e* W, \
  1. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);# B8 J! g$ t" n. W  A' q

  2. . _- ?5 b" j. J$ p; S$ c  H
  3. /* Enable the USARTx Interrupt */
    8 b4 E% {2 U3 F9 ~: p+ G
  4. NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    % u" W4 i2 ~4 r$ V% U; I1 w% E
  5. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;) U( T" g  r6 U' N* [/ T  g. p
  6. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    1 ~# v5 K" r- u% E) Z
  7. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;: |8 ]& f& T/ }- g  ^% Q# L0 [
  8. NVIC_Init(&NVIC_InitStructure);
复制代码
$ f; i6 O7 e/ z/ [6 y
串口中断代码如下
: X+ x& m  R/ r6 A2 B; _! n$ k0 @7 t* K1 Y5 [9 @
  1. void USART1_IRQHandler(void)2 H2 p, @5 m! O) W# Q
  2. {! J# `- n1 E0 o' Q, I' x
  3.   uint8_t temp;; k6 U: j9 U" ?5 `! Q
  4.   if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
    $ ?  Y" H. X) w0 L9 l4 i
  5.   {; [# L. U' z- f7 }) ?
  6.     DealWith_UartData();1 [& V$ ~+ ]! B- Y! ]1 w
  7. //    USART_ClearFlag(USART1, USART_FLAG_IDLE);
    - l- s, z) F# ]3 L
  8.     temp = USART1->SR;  ' V2 s6 Y( K6 C/ z
  9.     temp = USART1->DR; //清USART_IT_IDLE标志  1 _9 S' W+ [' p8 K7 P
  10.   }
    0 X4 z4 W7 S& A! \, a
  11. }
复制代码

& Y" J6 A0 U) \! N0 u重点:这里有一个坑!!!, w& [  q3 o/ t& e9 l4 Y( E

) z5 P( H2 A) |清除空闲中断位的代码是8 R0 ?9 z% K2 D( p9 }7 w% w0 Z- m

* v8 I, v0 Y" @" j# |# m
  1. temp = USART1->SR;   
    & |7 B; k: p! @- g8 ~! S/ j/ ~
  2. temp = USART1->DR; //清USART_IT_IDLE标志
复制代码
* H# B% a9 E6 d5 j
证据如下+ u" k* L1 e) j7 d8 X6 p( ^) C" D
$ l/ O: y! [, j# u
33a2336da54482dfee049d60b3ffa5c4.png
* U4 |0 y( `' r! l$ I3 ?* Q$ W  x' |& A! y
这一点很坑人,注意。
  S# i3 i9 a0 i% J% q/ Q; V7 \' {8 \! `' r0 F
04、代码; o* O& ~" x+ T$ a
DMA+串口接收的工程代码是开源的,Keil和IAR的工程都有
. B8 u$ @; U& ?( ^. L: }; }. X; }4 U0 q3 o( N/ e) V8 g5 _
04d695e34eae02b5a48f69c91de7ad33.png
, C% ]5 ?+ h; m! f: D' b5 x+ g. k$ Z' d9 R
33-USART-DMA-Receive         DMA串口接收(没有使用中断)
2 U% j) L. K# r7 O8 Y% R* Z0 C
2 X3 G" s: B% d& I" H( \: U, {34-USART-Receive-DMAInterrupt DMA串口接收(DMA中断)5 J' t9 F! h1 n" E* `9 X
$ U7 E! E5 b) w: p+ D- t; J
35-USART-DMA-Receive-Interrupt DMA串口接收(串口空闲中断)
& A9 ]& |" r1 I) X. d$ X# F; G$ A7 x' t5 f( G8 e3 t3 L7 o
- x6 [, {% R& }5 Z
收藏 评论2 发布时间:2022-4-16 21:00

举报

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

好诶!

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

学习

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版