串口DMA
& q9 m2 g% L$ h0 bDMA利用好无疑会让串口使用起来更加高效,同时CPU还能处理自己的事情,但是DMA的使用却让代码变得更加的复杂,也使串口配置变得更加麻烦!麻烦???也许只是学习的方式不对,代码是变长了,但是思路清晰的话,也就是在原来串口加了点东西而已。本文让你轻轻松松学会串口DMA!定长数据传输与不定长数据传输都教会你!通过此文,希望能让更多人了解和会使用串口DMA,希望大家多多支持! 一、串口DMA的配置 本文针对串口2(USART2)如何进行DMA传输进行讲解,如果采用其他串口,注意要修改相应端口配置以及DMA通道。 1、串口的DMA请求映像 通过我之前的博文《 STM32 | DMA配置和使用如此简单(超详细)》可以知道,串口2(USART2)的接收(USART2_RX)和发送(USART2_TX)分别在DMA1控制器的通道6和通道7。下图为DMA1控制器的请求映像图。 8 } Q4 m' x$ S; J0 _
. a5 y( M" g4 Z6 Y
5 F. P: I; V- s' f/ _2、资源说明
0 X& ^+ _( |) A9 j6 q l6 x E8 ~( b( \
STM32F1中,USART2使用的是PA2(USART2_TX)和PA3(USART2_RX),为了方便阅读,使用到的资源列在下表。 外设 GPIO口 DMA请求映像通道 备注 USART2_TX PA2 DMA1通道7 RAM->USART2的数据传输 USART2_RX PA3 DMA1通道6 USART2->RAM的数据传输 3、DMA初始化配置 USART2的DMA配置在《 STM32 | DMA配置和使用如此简单(超详细)》中分别讲了库函数版和寄存器版两种配置,我这里直接搬用,不理解的可以看看《 STM32 | DMA配置和使用如此简单(超详细)》这篇文章。要注意的是库函数最大优势就是便于阅读,所以在DMA初始化配置中我们使用库函数。 首先,我们要先定义三个缓冲区(作全局定义),一个发送缓冲区,两个接收缓冲区,两个接收缓冲区是为了做双缓冲区,目的是为了防止后一次传输的数据覆盖前一次传输的数据,并且留出足够的时间让CPU处理缓冲区数据。双缓冲在串口DMA中有着很重要的意义并起着很大的作用! - //USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在头文件进行了宏定义,分别指USART2最大发送长度和最大接收长度: O: V; K8 g/ V( H; ]0 A' `$ C
- u8 USART2_TX_BUF[USART2_MAX_TX_LEN]; //发送缓冲,最大USART2_MAX_TX_LEN字节/ `2 b; l; _5 g
- u8 u1rxbuf[USART2_MAX_RX_LEN]; //发送数据缓冲区17 z( \2 g0 K1 M% ]7 ?. a1 Z
- u8 u2rxbuf[USART2_MAX_RX_LEN]; //发送数据缓冲区26 x& w: v7 c4 F$ `# V4 ?' J. J
- u8 witchbuf=0; //标记当前使用的是哪个缓冲区,0:使用u1rxbuf;1:使用u2rxbuf
% m A" }7 Z8 c, ^ - u8 USART2_TX_FLAG=0; //USART2发送标志,启动发送时置1+ t _3 F, X( Y4 c
- u8 USART2_RX_FLAG=0; //USART2接收标志,启动接收时置1/ H+ _' d t0 B( l( T V
复制代码要说明的是,实际上发送缓冲区可能用不上,因为我们要发送的数据内容和大小都不一定每次都是固定的,可以是变化的,我们只需重新指派新的发送缓冲区地址即可,但是在初始化中我们还是要指定一个发送缓冲区地址。
2 A, Z: @& y2 R- f( A& N下面是DMA1_USART2的初始化函数。 - void DMA1_USART2_Init(void)
) [- q$ @' h& W/ Z8 a - {; d, `# n) W! I( s4 Y+ F3 Z2 b7 r
- DMA_InitTypeDef DMA1_Init;
8 P4 L" z4 S- F - NVIC_InitTypeDef NVIC_InitStructure; z8 e7 e! |% B2 T2 ?5 _4 r! Z' r
- % x& [' a# D* w+ @" E4 s8 ?
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //使能DMA1时钟
! b% j) g# l% L& j/ w3 r% e5 b
: R/ \+ D2 `4 @8 q- E& N4 e) D/ p- z- //DMA_USART2_RX USART2->RAM的数据传输5 \4 J; S7 r0 E" T; q- [
- DMA_DeInit(DMA1_Channel6); //将DMA的通道6寄存器重设为缺省值 ( m# Q) }8 k, r; R. m9 m
- DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); //启动传输前装入实际RAM地址$ X$ C, ]5 f9 O2 }, _6 R# g
- DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf; //设置接收缓冲区首地址
4 q L$ s0 I# ^& [0 t. e - DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取到内存
8 [$ e5 c, K! j/ t! C - DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN; //DMA通道的DMA缓存的大小
1 B; v+ F6 j4 M" I9 K" B - DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
1 w) R/ r( E8 m N% \: c" Q4 p - DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增4 M4 ^* R; T8 w, d% u, Y/ w! F
- DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
" ^) Y0 Z0 l) u; d0 y; X - DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
9 N5 Y3 W; K0 R$ L! A+ Z - DMA1_Init.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
! Z! K$ I! h4 h8 q( ] - DMA1_Init.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级 / i( j6 a2 s1 X: a5 q9 \
- DMA1_Init.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
! t" ~ |: f& M -
" C7 P, @: m+ E* {4 t7 U - DMA_Init(DMA1_Channel6,&DMA1_Init); //对DMA通道6进行初始化
8 i( q: Q& h& U" h- @7 O8 t -
( f" j6 R! ~( r; m1 b4 C - //DMA_USART2_TX RAM->USART2的数据传输1 s a# w) Y6 C: L1 P% A, `( p
- DMA_DeInit(DMA1_Channel7); //将DMA的通道7寄存器重设为缺省值 0 f' N2 [2 r$ a! |. V+ C0 n9 C
- DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); //启动传输前装入实际RAM地址
" w$ S/ T* e: h/ X5 O - DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF; //设置发送缓冲区首地址3 V4 p$ Z; a# X& m/ \$ h) o
- DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存发送到外设
8 i) U" Y' [* E' l - DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN; //DMA通道的DMA缓存的大小
; W/ O$ N$ j) b: f" [ - DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
1 R( G1 r, C9 k! o - DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增; D9 W+ e1 n: x
- DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位1 U, E; u8 X X' P% l
- DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
" T4 y8 u0 [' C" c - DMA1_Init.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
, q" S1 E4 z! g4 Z+ ?) P - DMA1_Init.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级 6 @! E4 X7 m& Z9 h+ w- a4 a
- DMA1_Init.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
* E3 ~. V3 L6 v! C: I
- F9 `9 w$ p4 J- Y: B0 `" t- DMA_Init(DMA1_Channel7,&DMA1_Init); //对DMA通道7进行初始化
! C0 w9 `1 p- Y: M0 h -
6 f2 F1 ^" o0 ]3 ]( i8 A1 W. d - //DMA1通道6 NVIC 配置; C e8 M* |3 w# f4 m
- NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; //NVIC通道设置: i( y# V. p! C% F" ?
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级7 C" |4 N J: R! U: k' I* d
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
! U7 {4 }7 m6 }- t. i4 _2 R5 w: R# ?4 K - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
# L* e4 c) C6 E9 ?4 b) A( g( D6 Q( g. @ - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器/ E+ C' }) F1 z
-
8 i& C6 N5 T$ M - //DMA1通道7 NVIC 配置; M6 l: v# T$ Q7 f9 _* y2 F
- NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn; //NVIC通道设置/ b: M. @: F2 g2 F8 ~+ D2 Q& }$ O' w ~
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级2 @/ `1 v) C: O
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级
5 M9 H: }- o. r1 X. x! v - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
) d6 ^, L# Y& \# _) M$ Y - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
. {6 V" }3 i) C - ( E6 S4 T3 e( [) M- f) v7 ?' q
- DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); //开USART2 Rx DMA中断' c: ^' u. M# ~
- DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE); //开USART2 Tx DMA中断
3 R" B- A: F0 J
! {* |5 N% j7 H( s- DMA_Cmd(DMA1_Channel6,ENABLE); //使DMA通道6停止工作
7 M3 S' T6 \% [ - DMA_Cmd(DMA1_Channel7,DISABLE); //使DMA通道7停止工作
1 G/ R: N0 C# _7 H# j- ?; E -
7 r W/ P# _- p) F" r# ^ - USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); //开启串口DMA发送
- `0 V3 E* R9 u* } - USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //开启串口DMA接收, G& q% h) F/ W) D, w/ z2 X) T/ [
- }
7 |/ l5 D1 Q+ _/ b" t, ?
复制代码相应配置的讲解在《 STM32 | DMA配置和使用如此简单(超详细)》中讲解过,这里不再叙述,要注意的是,我们采用中断方式进行DMA传输。正常情况下,我们传输完数据要进行相应的处理,那我们怎么知道什么时候数据传输完成了呢?这时候就借助DMA传输完成中断。既然打开了中断,那一定要注意中断优先级,这里特别指出串口的中断优先级应低于串口DMA通道的中断优先级。 4、串口配置 前面已经完成了DMA的配置,而DMA是不能单独使用的,所以不要忘了配置要用到的串口USART2。 - void Initial_UART2(unsigned long baudrate)
& i! \* ~- ?( I; B7 m8 p$ a$ H - {
7 x9 c: S- C$ s5 e6 [* F - //GPIO端口设置9 Y R5 P- c6 E: _; \
- GPIO_InitTypeDef GPIO_InitStructure;
; e+ v* R+ f3 l6 O) R- R2 d# `# U0 A - USART_InitTypeDef USART_InitStructure;7 q- @% d' H/ ]6 h/ h
- NVIC_InitTypeDef NVIC_InitStructure; * l2 D- N/ `, ~/ j% N1 v3 ~
-
% x/ Q% }. n2 s0 e - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART2,GPIOA时钟
6 v; p1 }$ e1 o( u5 N q6 k5 f! B - + u- q j2 s l6 f
- //USART2_TX GPIOA.2初始化
3 X: d* Y+ d: _7 T4 E - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
8 z; b0 T$ K( J/ w. E: l4 G, Y - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出: K& ?/ Y8 k: r" e
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速率50MHz- o r. Y- |9 C! a3 Y) [: g" ]. D& v
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.2, j9 H6 G, G& c( F! d. l2 _# B
-
5 }* B: k* l( q4 i/ Y8 O& h - //USART2_RX GPIOA.3初始化( `+ ?" J4 d: F+ [
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA.3
0 K2 k @7 c6 s1 e% Q - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入! Y* o4 G/ q. ^; A E# Y
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.3
% _! A# v" w4 U4 a `! P! n @ b -
+ i9 u- J# o- p7 f - //USART 初始化设置$ ?' w& h+ Y2 \( E' Y
- USART_InitStructure.USART_BaudRate = baudrate; //串口波特率
1 ^% k7 k. Z9 _( k# q* Y( t( J - USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
3 f# _5 J- ]- X+ J0 _- y+ q - USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
2 j. t% n9 G2 W - USART_InitStructure.USART_Parity = USART_Parity_No ; //无奇偶校验位* @( G) O3 w& ^& @; p" z
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
; A3 \) E. Z. q" i2 }. `2 j - USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式4 i1 K3 |8 W4 f1 M9 l/ @# M5 O
- USART_Init(USART2, &USART_InitStructure); //初始化串口2
5 S- u/ j3 Y) [. e' e9 u) b -
* u% ?4 t( f7 g3 r1 U! U' j# q4 | - //中断开启设置
7 e+ C$ B/ q4 w# k2 w - USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); //开启检测串口空闲状态中断( F; }# O; }3 r) w: p0 x. M) R9 }7 S
- USART_ClearFlag(USART2,USART_FLAG_TC); //清除USART2标志位
9 ~; T' v& F% q' Z9 ~ -
& j8 p' S; v/ P3 v9 M7 v/ ?% p - USART_Cmd(USART2, ENABLE); //使能串口2 K0 N9 k* e) ^6 `) x3 e. o
- ; T. Z |" b6 P ]8 |% A
- NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //NVIC通道设置
% [' ~7 g' @$ p4 @2 C* z3 P - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8; //抢占优先级
0 u7 k- |) H$ O% j' E& s - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级
4 @& X7 i( h! o# U- j - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能& F8 ?; y; m) a( w4 l+ l+ }8 Q5 O
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
4 `" E- U c4 ]( e' a - / {& s4 [ p ^3 r
- DMA1_USART2_Init(); //DMA1_USART2初始化2 @) h Y# n& P9 [0 b
- }
, V$ X0 v* l; `+ J0 Q
& E X# A2 e. Y g! O
复制代码可以注意到,我这里定义的串口中断抢占优先级低于DMA中断的抢占优先级。细心点可以看到,我开启了串口空闲中断。为什么呢?因为通常情况下我们是不知道接收数据的长度的,这样我们是没有办法利用DMA传输完成标志位来判断是否完成接收,所以我们这里采用串口空闲中断来判断数据是否接收完成,接收完成了会进入串口空闲中断。串口配置的最后进行了串口2(USART2)DMA的初始化,这样直接初始化串口也直接包括了串口DMA的初始化,主函数初始化只需一步即可。 二、串口DMA的使用 。。。。。。论坛看起来太不舒服了,有的格式没办法设置,如果喜欢还是去CSDN博客看吧! ; j3 ]+ y, q R& Q
原文链接:http://blog.csdn.net/weixin_44524484/article/details/106029682 g1 G& ~8 J7 ` b; c9 Y. e7 i0 s
以下为原文目录,一步一步教会你串口DMA! - 串口DMA; }( `- |" w3 V5 e7 e
. W" {6 b; _, v6 Q/ \
- h' j: M# v3 i* A |