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
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 \( _
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
- void DMA_Config(void)
) k: T2 @$ Z# \7 i - {; S E/ \! z! y# {0 D8 B
- DMA_InitTypeDef DMA_InitStructure;1 M! `" R/ Z1 a* q
+ w& l! H, i' W- Z; `, J, D- /* Enable DMA clock */
) M8 f2 S" h! o, L* ~ - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);# p g1 T& |5 l0 h. c
- ; |2 X2 P; p- H
- /* Reset DMA Stream registers (for debug purpose) */
$ r4 I+ P/ l- g3 Z! S) j9 \2 } - DMA_DeInit(DMA2_Stream2);0 t! o) e1 L6 F6 u: s5 }- B
9 ~- a" w( W. q- /* Check if the DMA Stream is disabled before enabling it.
k' i1 h' z4 r6 d' V - Note that this step is useful when the same Stream is used multiple times:
( r" |$ E. o& m0 m1 S; m - enabled, then disabled then re-enabled... In this case, the DMA Stream disable
/ `: [) X6 J& g% z6 M- ^ - will be effective only at the end of the ongoing data transfer and it will ! S; b0 j. |7 y
- not be possible to re-configure it before making sure that the Enable bit 7 Q# U5 o9 }$ n: F) ~2 n% Y1 C
- has been cleared by hardware. If the Stream is used only once, this step might
; S) m6 B$ @4 _/ B9 U2 `+ B - be bypassed. */
; @8 H) g* w- ?* q - while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)6 h: E b/ P2 C- R2 g( e5 @
- {6 c. D) ]/ Y6 A
- }
' V, k$ G" y0 ?; ^/ L- { - 0 ~3 J; K' d- f+ s0 }3 |
- /* Configure DMA Stream */
' K6 d5 e2 W- E @/ z9 h - DMA_InitStructure.DMA_Channel = DMA_Channel_4; //DMA请求发出通道- K3 {) U' P2 o* \/ F; U1 S
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址
F! L/ i0 m; G. S; t" _ - DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址
: O/ d5 Q- [! g - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置8 i* g0 L& o3 @( v9 `
- DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小) p% j: u( ^2 z9 o5 W% D" [
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
* ^& u7 c1 t9 e! X% w% L0 J1 q - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增
# q! k" \9 E) H1 L: y7 \ - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位, d5 N: c2 x" D8 M
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位4 ]/ ~2 G, k/ M6 r
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式6 X5 i! D9 E- r1 ?8 e
- DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高
+ n) Z( `0 p0 p9 b A8 J$ g - DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.
& j- F# D0 Z& f+ A - DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择1 t; {! q2 j# r" L# Y
- DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
- Y8 ]0 D/ @6 o+ n# O- O' R O - DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。0 I. b) ^7 k3 X* }3 \& U
- DMA_Init(DMA2_Stream2, &DMA_InitStructure); 3 q2 |6 `: W4 e0 } A( R
; p. o) _* p* X- /* DMA Stream enable */
* y3 W3 z9 }+ q# c% K - DMA_Cmd(DMA2_Stream2, ENABLE);8 L% s/ J6 h+ D, H% B
- }
复制代码
% 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
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
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- 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
- /*使能串口DMA接收*/
( c$ [- k0 _5 E& z! \. Q4 @ - 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- /* Enable DMA Stream Transfer Complete interrupt */. v+ G. U+ c7 k1 Y' H
- DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
2 E4 f$ l- Z$ W* V4 H - 2 _6 @) F) M4 _! t
- /* Enable the DMA Stream IRQ Channel */
x: \8 k0 Z0 b) W+ `- Z - NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;
+ N9 P& a2 q" I: A. |% l - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;: c2 f4 D7 b; M" o8 r: }7 s
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;6 T% M) n! a, {7 s1 S4 |
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;0 D. B' X, @# ^
- 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
, 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
- USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
# e" A1 e7 d6 @. `$ t
: @; a y- P! c% ?# t+ x- /* Enable the USARTx Interrupt */
8 }* q1 I4 {' ~$ T+ c - NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;1 L- F# u2 \: [; R" D! u
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;+ x# p! A/ g, D- T/ Q6 W
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;/ M0 N) x/ U2 f' V
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;4 }! Y x2 v3 \" {$ i
- 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
- void USART1_IRQHandler(void)1 u; R0 c4 ^3 v$ }- d: @' K
- {
- T) O4 l4 P) o/ U - uint8_t temp;
8 u$ s7 K% o2 I$ G8 } - if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
2 w2 d8 l) i0 ?% d$ `3 x6 j- V - {) X1 y& u0 b: S& ~, p# o3 e
- DealWith_UartData();
6 |- w. Q# }/ H" J% p: M. c6 y7 n - // USART_ClearFlag(USART1, USART_FLAG_IDLE);1 a. m0 y O% N, M
- temp = USART1->SR;
3 U/ ?* |$ X" e6 o - temp = USART1->DR; //清USART_IT_IDLE标志
3 }0 c% \# ]8 j$ `& r, N/ D; D5 i - }
/ \% L; p; N" R" L& j o - }
复制代码 / 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
- temp = USART1->SR; 1 Y5 L) n7 S6 [ `' X
- 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
, 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 ~
' 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; ` |
好诶!
学习