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
# 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
! [+ 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- void DMA_Config(void)1 f* E8 ]5 @' |" f0 x1 M7 g' D
- {# c* J( B3 [( M
- DMA_InitTypeDef DMA_InitStructure;
6 |1 n8 U: O" Y% | - ! o, e0 g) i- O+ z6 q: ^
- /* Enable DMA clock */
+ S5 J4 d0 V0 j, j8 X - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
6 f) s. V3 f( H3 G - ! N$ d% {: O& ?/ Y9 N% K
- /* Reset DMA Stream registers (for debug purpose) */% e( t |$ s5 k
- DMA_DeInit(DMA2_Stream2);
4 z# U- h) {8 t4 J" X; G8 o - 9 ^* o* Z( o! a
- /* Check if the DMA Stream is disabled before enabling it./ }3 R' G3 |/ u" _8 x" M8 m" u# v
- Note that this step is useful when the same Stream is used multiple times:
; U, N9 ^* l* f9 |0 Q - enabled, then disabled then re-enabled... In this case, the DMA Stream disable
# C% S/ w6 z9 }3 B - 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
- not be possible to re-configure it before making sure that the Enable bit 0 h5 S8 G0 h0 Z* H
- has been cleared by hardware. If the Stream is used only once, this step might
" h6 n4 p# X( l4 z - be bypassed. */
. e4 c) r& E% C7 L9 w' Y - while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE)
( X: W* b# T3 q3 A, `! O - {+ P9 X1 W$ ]+ e0 j, T4 k) C
- }: P; o, E- W2 f/ M+ p _2 \' L6 i' W/ ]. w
- / Q, y p, \- r0 m' ~9 E% \
- /* Configure DMA Stream */5 X6 f1 M$ z. ]& ^1 J
- DMA_InitStructure.DMA_Channel = DMA_Channel_4; //DMA请求发出通道
5 f5 T( s7 P! g* k/ y0 \" n - DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;//配置外设地址
l- v# g$ b5 f1 L - DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)UART_Buffer;//配置存储器地址
* z7 a6 \/ _! b - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//传输方向配置4 B5 {" y( l" ~. }1 K; ~
- DMA_InitStructure.DMA_BufferSize = (uint32_t)32;//传输大小4 Z# R! T5 H' [
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
. k1 Y! o& q, p3 A* S! @! a2 M9 M( e - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//memory地址自增
9 f' x% ~3 o; V# N# f) \) h - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设地址数据单位
' T0 @5 \2 A, l3 ^/ {$ e - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//memory地址数据单位
3 ]6 J+ K0 u0 P4 Z1 S - DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA模式:正常模式
I* ^+ @6 ^" T3 N, w; P9 z - DMA_InitStructure.DMA_Priority = DMA_Priority_High;//优先级:高" Q, `$ t/ T. U$ V) q0 {
- DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式不使能.
) L4 W: C8 S1 }5 ^0 u* | - DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// FIFO 阈值选择
0 j8 z. Z$ h8 q5 G1 X - DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。
% Q0 w* Z" V2 e& m. }# n! C& v. J - DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式。. X p6 Z# f# X# q8 b% D9 T
- DMA_Init(DMA2_Stream2, &DMA_InitStructure);
. B( {& X' Z! Y: S! z" b - u( E& \4 p3 ]- |; R
- /* DMA Stream enable *// G; P2 o! V; B: q. b! B
- DMA_Cmd(DMA2_Stream2, ENABLE);- t2 o' z* \! l" I) g9 v2 l
- }
复制代码 - 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
; 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+ @
; 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
- 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- /*使能串口DMA接收*/$ x" l. Q5 B% C3 J
- 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
- /* Enable DMA Stream Transfer Complete interrupt */( y% S. p4 J% h) z
- DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);0 h: A) G2 d/ u2 x t! O
- # O, [: m# \ G7 P% t' M) L$ g; u$ Q
- /* Enable the DMA Stream IRQ Channel */( I S: t3 ]( [0 C: E2 b
- NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;$ e: v- Q5 C) {" R# x
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
; i8 h4 T! E9 X$ h) I: i - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
* i( L& x" v: {# C* i* U+ g - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
% b, k) i/ P) w$ J - 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
( 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- USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
; @/ V% E7 `# D3 L0 W1 L1 R6 p* G - ( o' e/ T( m0 G' R/ \+ H1 z; z) r+ x3 b7 q
- /* Enable the USARTx Interrupt */$ \- f$ G# f. U0 _8 K
- NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
7 K2 `: L/ Z7 P( R% l1 l- L - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
; v- b- t5 X2 T' C - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
`- q; t v; S8 M - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;) {% v! i% k( X9 f6 k Q
- 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
- void USART1_IRQHandler(void)
8 N8 P# J3 h$ l0 Q; S - {* C/ ?* n5 R" U2 f/ |$ A8 }
- uint8_t temp;
7 t& _ m$ A F0 K5 ^# x9 Q - if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) == SET)
5 z0 a- ^& w p' q3 z, | - {! `' c7 \6 V' O' a
- DealWith_UartData();+ ]6 X6 l1 {/ k
- // USART_ClearFlag(USART1, USART_FLAG_IDLE);
$ f/ O' Z5 ^. u- n+ m5 j R - temp = USART1->SR;
& j: z, Y$ }6 T. ] - temp = USART1->DR; //清USART_IT_IDLE标志
0 V1 C2 L* s! o! Z, a - }
9 m5 `+ |- k: k7 _ - }
复制代码
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- temp = USART1->SR;
' r' T* y0 R5 V1 f T; n0 \5 b1 v - 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
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
: 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) |
|
好诶!
学习