请选择 进入手机版 | 继续访问电脑版

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

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

[复制链接]
STMCU小助手 发布时间:2022-4-16 21:00
01、概述# r/ F% q7 \8 s) D
0 i# g) {0 w- K. `
在串口讲解的文章中,示例代码采用中断方式接收和发送数据,中断的好处在于可以及时响应,快速接收到数据,但缺点也很明显,那就是频繁中断,接收1000个字节需要中断1000次,频繁中断就意味着会打断其他代码的执行,对一些应用场景是不允许的。这个时候,使用DMA+串口的组合就可以很好解决这个问题。% o7 N* D/ ^9 m! H9 a) w% m

, H" M4 z* D9 }  D- u4 x) ^ f6296bed737973b74944a69efd0c4d22.png 2 p! [& F$ Y' q, I; T. x  z1 ~+ c
% F+ b" M- V$ P
DMA每个数据流有8个通道,每个通道映射到不同外设,这有利于针对不同的产品配置不同的DMA外设请求。
7 c* O7 B  O* M2 i8 t3 ]
) O# Z: ]4 y7 _* P1 @4 f( A1 f每个数据流只能配置为映射到一个通道,无法配置为映射到多个通道。即,与数据流不同,每个DMA控制器可以同时配置多个数据流(因为有仲裁器),但每个数据流不能同时配置多个通道(因为只有选择器)。; V) X" e0 d+ Q7 c$ I) m1 @8 H5 r
4 Q+ ~6 p' D) z4 t3 w* k
我们使用USART1串口外设,从数据手册中可以查到,USART1的发送和接收都是支持DMA的,使用的是DMA2., l' |) m/ ~1 R# @2 ~6 i9 ~/ d
$ j9 F" {  q& N
1451b55436b5ef4193f2954ce1c31990.png * R* [4 |' A3 m) b( I& o

( x/ H7 T: J' f$ M+ K( w0 z* M接下来我们循序渐进了解DMA在串口中的应用
" ~2 Y/ N) C; e4 D" s. ?/ f7 W: A! N! D& K0 G2 F
02、DMA接收/ Q1 O! O% v0 y$ d4 f6 z
我们先配置DMA,将DMA外设和串口联动起来。首先需要配置DMA。6 x- b, x8 Y* C

0 z7 H+ j. q* I7 U7 ^
  1. void DMA_Config(void)
    7 [+ b2 ^, O9 s7 T3 g! a5 g
  2. {" X6 K  a7 u4 y$ k/ Q
  3.   DMA_InitTypeDef  DMA_InitStructure;4 Z6 x( S; |1 }2 G7 X; i' I0 f

  4. ; i# x1 j% {2 f! p; A3 `9 j
  5.   /* Enable DMA clock */6 y3 U; q: b$ ?4 z+ G
  6.   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);' @1 M" C8 ~, k9 q

  7. # l7 @7 U' k8 F: ~: q
  8.   /* Reset DMA Stream registers (for debug purpose) */& n+ x; E# R2 B8 x) P% v- f
  9.   DMA_DeInit(DMA2_Stream2);! L& R4 G  N1 `) s5 p5 \
  10. ; M6 w$ P5 J+ r7 b
  11.   /* Check if the DMA Stream is disabled before enabling it.
    : {) A7 }+ ^( o: r* H% _
  12.      Note that this step is useful when the same Stream is used multiple times:
    : `5 L- P2 ~; j7 j: W7 |
  13.      enabled, then disabled then re-enabled... In this case, the DMA Stream disable
    . W' i( O9 j4 X( V% \& z6 L. M* N4 X
  14.      will be effective only at the end of the ongoing data transfer and it will 3 E2 g2 b' h  A4 D: E
  15.      not be possible to re-configure it before making sure that the Enable bit : R3 C8 G0 f* u. ^4 e
  16.      has been cleared by hardware. If the Stream is used only once, this step might , M) E6 P3 C) q9 F" }5 J' @
  17.      be bypassed. */7 P* ]; Z  Y, J( @, V
  18.   while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)
    6 H* m# ~1 @1 S. Q% l. U) p- C5 Z* E
  19.   {2 N; K7 Z  e" m, O$ P: v
  20.   }
    ' |9 w7 T( g9 m; H
  21. : X+ ]( J4 R- s6 w8 L% j
  22.   /* Configure DMA Stream */
    ) F' k( s0 b8 c" a5 J
  23.   DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //DMA请求发出通道$ o3 x8 z+ ~* `2 j1 Z
  24.   DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址/ l/ Z% A3 V0 J/ G/ x( j. k8 c9 w1 p2 o
  25.   DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址$ ^" q, T* _8 z3 u& Z( @  v& A% ~7 o& p
  26.   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置
    % k* `2 O+ n$ g/ O1 p  i/ V
  27.   DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小: G' q8 |7 v6 [  ~. B' _. H4 \
  28.   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变0 R4 s1 g0 I. U) ]
  29.   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增. c' q- n$ O0 ?% Q
  30.   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位8 k7 w& n+ c( c
  31.   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位
    ( [8 e6 q! V1 |2 ?5 i
  32.   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式
    1 q; J4 ~! j! g; R, m
  33.   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高
    : W( Q# k& A1 W5 `6 i
  34.   DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.          & o2 s) y$ c. m' L4 @( @' a
  35.   DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择7 G' E1 G9 ^& ~8 k/ e2 ~: `
  36.   DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
    8 s' q( y6 x; A9 j' I) [. t
  37.   DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
    & ~- w9 M2 ]/ q' A6 k
  38.   DMA_Init(DMA2_Stream2, &DMA_InitStructure); 7 N1 G! y, h5 ^
  39. # t. |6 V$ P2 a& {! H5 G# H3 W
  40.   /* DMA Stream enable */
    9 J7 C0 K: K  E
  41.   DMA_Cmd(DMA2_Stream2, ENABLE);  T: Y( E) O2 w/ m) S4 h( U8 ]
  42. }
复制代码
0 V! C" R) e! S# u, \% Y
  s0 _7 {/ H: X. h! ~; D
除了配置DMA外设外,我们还需要配置串口对应的DMA配置,在手册有一小章节讲解到。
" e5 E) L$ n2 l0 p' I: O
- x7 S, N9 j) g5 L- K0 p9 ^. z e95e31ad6d72f10f21e1362a99a2a141.png
- F+ }# ^. C' F* O% z5 w. _( Z
需要配置的寄存器是USART_CR3寄存器。1 z" d$ _: t9 t( y( w: r

. M) S0 c2 h4 Y! a# k 370187d3cf4066f5dbb23bd20d58a987.png
9 |( G3 P8 W0 y) b& _: y
- }8 G6 a  @9 ?2 c0 B我们可以通过配置USART_CR3寄存器的bit6和bit7使能串口发送和接收DMA。ST的标准外设库同样提供了对应的外设库。
+ v  A' M+ J9 S" l% x1 F5 Z) N) V' }) M
  1. void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
复制代码

% s9 B3 z3 N* d2 u通过上面接口可以配置串口的DMA配置如下:
9 `6 T" a+ S; A' m6 c/ x' E; s! s4 r4 N
  1. /*使能串口DMA接收*/6 v3 R. z* M0 t, u2 T4 C% ]  V
  2. USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
复制代码

6 I. i  V, J8 Y( }% w  v3 D1 l& ?% ]03、中断) @, [. {# V3 e- ?* T# {: e
我们使用DMA+串口解决了频繁中断的问题,但现在有一个问题,我们还需要及时将接收的数据信息通知CPU,以便达到数据的及时性。我们使用DMA和串口两个外设,他们都有自己的中断。
9 n! y' G) y9 ^- w! |1 W- ?3 z( j" a7 i3 |2 d' z( T) q
使用DMA中断,如下配置8 M, t/ X3 c# ?, u3 q7 g& E' e
. X& @4 N2 z- H. G7 ]
  1. /* Enable DMA Stream Transfer Complete interrupt */4 z* W6 \+ @9 R8 d+ h$ Y5 d0 [
  2. DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
    0 x+ z- Q; {. d0 s# o, u' R5 V9 }  J
  3. * {. {2 X7 A: w* Y) M( V
  4. /* Enable the DMA Stream IRQ Channel */1 q- L' p- M/ k# W# Z
  5. NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;
    : k4 n! A9 j- i- j: S4 w2 X
  6. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    3 J+ }% ]1 `" Q( q  E, J
  7. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;. b. A. U; D' M) o) o
  8. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;) K: r; e9 K8 l" t# v4 o$ ]
  9. NVIC_Init(&NVIC_InitStructure);
复制代码

4 p, Y; d1 K( w" s/ E) e' `5 ~当DMA接收完毕时,会产生中断通知CPU取数据。
/ K& O( g- P' ~( Y$ [
( `4 c! b0 M. O$ J但这有个明显的缺陷:串口接收一包数据,长度如果小于DMA的缓冲长度,那么久不能触发中断,只能等DMA接收满数据才会产生中断,如果下一包数据迟迟不来,那么这一包就不能被及时响应。
9 J+ z1 g" H$ B" \% ~! d" ?" _3 k* |
那么我们采用串口中断是一个不错的方案。串口提供了一个空闲中断,“似乎”就是为了DMA专门使用的。
% v3 j  r( w) V& P2 m5 X3 T) `! a% }. x- |% L
fe739d4d5996e64f2182421bfa815264.png / |1 B  a- N3 c# E

; F1 z+ y  c6 l( T  ^) M当串口接收一包数据,接收完最后一个字节,没有数据接收时,会产生一个中断,这个时候,CPU就可以取数据。
8 t1 H4 P$ b  Q" J* w2 p1 ?/ F9 s, a: ?7 V' Q
串口的配置知识不再讲解,不太懂的同学请看《STM32串口详解》,串口空闲中断配置如下
. }! F$ ?3 h4 y# E8 s# G( e3 S
6 s2 B3 e* |. X1 W, |! \! T
  1. USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);/ z' F) f; V: c+ a2 P3 J
  2. 8 A: E5 ~0 x- x5 Y$ m
  3. /* Enable the USARTx Interrupt */0 E# `. [  U. y
  4. NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    , }+ I, Q4 f0 w8 Q3 j% Y
  5. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;, D* P3 ^+ N4 }+ D
  6. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    ! Z0 P, C* J2 M( D
  7. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;. n. W/ b- _' E  s) t( j# n
  8. NVIC_Init(&NVIC_InitStructure);
复制代码

6 }3 S5 ~0 H( u; V' d% m串口中断代码如下
: A% |1 F' h) {5 w6 d
1 e' y9 p. b# Y% M; {% v
  1. void USART1_IRQHandler(void)# z+ X) V7 i4 W
  2. {- i( Z) y8 [8 Z
  3.   uint8_t temp;' A, k9 j& L. z: b
  4.   if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
    ! [# E) H% n" E3 }/ D
  5.   {; A% }0 M# a# w2 l7 B# u
  6.     DealWith_UartData();' F% m: z& X. d, l1 h& [
  7. //    USART_ClearFlag(USART1, USART_FLAG_IDLE);" d; e# W) K( E, _
  8.     temp = USART1->SR;  
    ) z& j. i9 o% }9 E- g5 `
  9.     temp = USART1->DR; //清USART_IT_IDLE标志  $ w3 V1 n. t! |# H
  10.   }/ Z" W( x1 t; [' F
  11. }
复制代码
/ V7 [+ J: d% n3 c" I  ?
重点:这里有一个坑!!!' T- R# ]& a% D  L

) L! I" Z$ F% x7 h9 L, R2 q清除空闲中断位的代码是
' g/ w; R* e8 f7 N/ Q: ^5 m' P3 V  S, w. A
  1. temp = USART1->SR;   
    * e% m3 B+ e0 |* K9 {, S
  2. temp = USART1->DR; //清USART_IT_IDLE标志
复制代码
9 z2 J7 C$ i; w8 r: i2 s
证据如下
0 ?. ~  R* E+ z4 S2 c  `4 Q0 L8 _3 w1 ^3 G5 {" Z
33a2336da54482dfee049d60b3ffa5c4.png " H1 q: P5 P- W: Z- @
3 W# C7 d8 b; ~7 a7 B5 z* e
这一点很坑人,注意。& i0 O* A2 t) I$ r0 X' _
& m, y$ ~  G3 ]$ }$ _  D7 H
04、代码7 B9 N) Q* X8 I. K* s) @
DMA+串口接收的工程代码是开源的,Keil和IAR的工程都有
8 G' l. |* l, q% \
! t* q0 X$ U$ j4 ]( n& J& [; | 04d695e34eae02b5a48f69c91de7ad33.png " s0 N+ q, ]! i" ?4 _# L
0 K' a$ n/ [* [' i( m  f, Y
33-USART-DMA-Receive         DMA串口接收(没有使用中断)
! s6 ?% n) ?& O2 X& ^0 o9 E4 H* a% q" {, D3 n% W( ~
34-USART-Receive-DMAInterrupt DMA串口接收(DMA中断)9 Q$ G& h, w: J7 i
  Z8 ^. [, ^% g/ O7 V  }
35-USART-DMA-Receive-Interrupt DMA串口接收(串口空闲中断), I' \5 Y, ^0 Z

2 v, g7 Y( E0 ^) z4 x
" a  o* W: D  _! V$ X; b( d
收藏 评论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 手机版