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
" 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 ~
% _% 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
- void DMA_Config(void)* t5 L9 `( g0 E, W* d
- {% W V5 |) q2 d8 }6 z: [
- DMA_InitTypeDef DMA_InitStructure;
4 s9 A3 z- w2 j1 Z. |# A& W# ?8 W - 8 g4 o$ L. t4 r5 R/ L; e# v
- /* Enable DMA clock */
' U( L' Q4 {* M( E - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
1 w+ |- h4 t1 T3 O! { - . `+ P% L1 F4 O, ]/ n
- /* Reset DMA Stream registers (for debug purpose) */! j1 W& H' Y3 S! X( {+ }
- DMA_DeInit(DMA2_Stream2);$ e8 {7 L6 A& k g, g0 \ _/ e
& I# j% ]3 A; v6 ~$ Z2 G- /* Check if the DMA Stream is disabled before enabling it.7 B) j; G6 M& N4 G
- Note that this step is useful when the same Stream is used multiple times:# I+ h9 { \3 k1 N8 G1 G1 A
- enabled, then disabled then re-enabled... In this case, the DMA Stream disable
3 L' _" C9 k1 }- ] - will be effective only at the end of the ongoing data transfer and it will
4 P- I: w @/ n) ~ x$ Q - not be possible to re-configure it before making sure that the Enable bit
4 P: L# N# ]6 o( A8 X3 y - has been cleared by hardware. If the Stream is used only once, this step might , w& d) \6 K3 J, @ R
- be bypassed. */
3 U. l0 }3 p( j# _ - while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)
( f R' ]: E, B% A( L# w% q6 B - {- N1 w& ~& V* r7 g
- }( H) H& ?/ U+ g$ D) A5 m. }
- - L3 d8 L6 x: n4 E' }
- /* Configure DMA Stream */, ^, a8 d; q; w2 I
- DMA_InitStructure.DMA_Channel = DMA_Channel_4; //DMA请求发出通道% e* n4 I7 f& i8 o3 O8 Z# O
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址
& [3 b, G7 ~& i+ D" j* B9 U - DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址0 x+ l9 ^0 V' y
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置% A+ m. D+ V7 z9 l M
- DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小
* [) r6 G) A/ c9 O - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
" J, ], d) C- [, s6 ] - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增
& d( E. i2 o# [; r) ^ - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位
0 z$ Z b. S6 x- r& C - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位3 G% }9 i% F: F9 d3 w J
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式
0 z7 C0 T( M$ w0 B0 y- t% t( l - DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高
" P# C# h1 R9 w) _8 b& F& d - DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能. * ~0 I" r9 j$ M* U9 f0 a
- DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择0 l3 Q4 R: {6 i$ d
- DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
; _2 S* ^! `6 X T% a( N0 I - DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。. g' S& K" v; t
- DMA_Init(DMA2_Stream2, &DMA_InitStructure);
. M C- o. e& P! z- \2 v1 T - 1 y: o/ K/ }: H- d9 z9 f7 c
- /* DMA Stream enable */
8 ~6 O/ F; e0 v) |7 c) d4 ^ - DMA_Cmd(DMA2_Stream2, ENABLE);: {; a3 e0 F0 O7 y. T3 G! e
- }
复制代码 $ 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
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
/ 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- 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
- /*使能串口DMA接收*/! E1 x& W7 r3 D& {; s2 ^$ }( a" K
- 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
- /* Enable DMA Stream Transfer Complete interrupt */
0 ^2 v& o2 R( o C, c1 Q0 ^ - DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);2 j5 C. h' C6 a: O& ]9 D8 E
: n# [, e2 n9 S2 V; I. D4 V0 m- /* Enable the DMA Stream IRQ Channel */8 i5 L) e/ r2 Y8 x3 T: H
- NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;
- Z$ O, }2 L. z& K2 W5 t - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;3 |; q$ i5 y3 n6 a
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;1 M5 R; P7 T) ?% r: S
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;% P p G, F/ }+ u( f
- 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
- 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, \
- USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);# B8 J! g$ t" n. W A' q
. _- ?5 b" j. J$ p; S$ c H- /* Enable the USARTx Interrupt */
8 b4 E% {2 U3 F9 ~: p+ G - NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
% u" W4 i2 ~4 r$ V% U; I1 w% E - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;) U( T" g r6 U' N* [/ T g. p
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
1 ~# v5 K" r- u% E) Z - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;: |8 ]& f& T/ }- g ^% Q# L0 [
- 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 @
- void USART1_IRQHandler(void)2 H2 p, @5 m! O) W# Q
- {! J# `- n1 E0 o' Q, I' x
- uint8_t temp;; k6 U: j9 U" ?5 `! Q
- if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
$ ? Y" H. X) w0 L9 l4 i - {; [# L. U' z- f7 }) ?
- DealWith_UartData();1 [& V$ ~+ ]! B- Y! ]1 w
- // USART_ClearFlag(USART1, USART_FLAG_IDLE);
- l- s, z) F# ]3 L - temp = USART1->SR; ' V2 s6 Y( K6 C/ z
- temp = USART1->DR; //清USART_IT_IDLE标志 1 _9 S' W+ [' p8 K7 P
- }
0 X4 z4 W7 S& A! \, a - }
复制代码
& 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- temp = USART1->SR;
& |7 B; k: p! @- g8 ~! S/ j/ ~ - 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
* 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 _
, 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
|
好诶!
学习