看过我这篇博文《 STM32 | 基于NRF24L01串口透传(不定长数据无线串口双向传输) 》的应该就清楚我为什么要写DMA传输,最终目的就是为了让nRF24L01采用串口DMA+SPI DMA方式实现串口的透传,所以本文来讲讲DMA传输。还是老样子,我认真写好本文,希望更多人了解并且会用并且了解DMA传输,希望大家多多支持,可以参与评论或点个赞,谢谢! 那么就进入正题!
测试平台:STM32F103C8T6
库版本:官方库3.5版本
关键词:DMA
如果真的是刚刚接触单片机或嵌入式,肯定会问,因为按照难易程度:DMA>中断>查询。这里我提几个反问句回答。
其实我们要求的很简单,就是高效,传输数据的时候我们不在等待的时间浪费CPU资源,而且数据是一个字节一个字节传送的,接收的时候只要一个数据包最后一个字节数据接收到再处理即可,发送的时候让串口自己一个字节一个字节把数据发出去即可,不用在等待一个字节发送完再发下一字节数据这样。为了提高CPU使用效率,于是就使用DMA方式。
学习只有积跬步,才能至千里。可能废话有点多,当然这是写给还不知道DMA的同学看的。已经了解的,只想学怎么配置的可以直接跳转到后面的内容。
不太官方的理解:DMA只是个搬运工,帮助老板(CPU)搬运东西(数据),可以帮老板(CPU)把东西(数据)从家搬运到家门外(存储器→外设);可以帮老板(CPU)把到东西(数据)从家门外搬运到家里(外设→存储器);还可以帮老板(CPU)把东西(数据)从家里卧室1搬到卧室2(存储器→存储器)。
官方一点的表达:DMA,全称为:Direct Memory Access,即直接存储器访问。直接存储器存取( DMA )用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 干预,数据可以通过 DMA 快速地移动,这就节省了 CPU 的资源来做其他操作。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
STM32 最多有 2 个 DMA 控制器( DMA2 仅存在大容量产品中),12个独立的可配置的通道(请求), DMA1 有 7 个通道。DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。
小容量产品是指闪存存储器容量在16K至32K字节之间的微控制器。
中容量产品是指闪存存储器容量在64K至128K字节之间的微控制器。
大容量产品是指闪存存储器容量在256K至512K字节之间的微控制器。
互联型产品是指STM32F105xx和STM32F107xx微控制器。
后续要以串口2的接收举例,由以下内容可以知道USART2_RX请求在DMA1通道6。
从外设(TIMx[x=1、2 、3、4] 、ADC1 、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像。
外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。
从外设(TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,这意味着同时只能有一个请求有效。参见下图的DMA2请求映像。
外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭。
注意: DMA2控制器及相关请求仅存在于大容量产品和互联型产品中。
DMA2 请求映像
各个通道的DMA2请求一览
注意:ADC3、SDIO和TIM8的DMA请求只在大容量的产品中存在。
DMA寄存器的介绍主要参照参考手册,这里列出来方便大家阅读,使用DMA的时候可以使用库函数,方便阅读;也可直接配置寄存器,方便使用(快捷)。
注意: 在以下列举的所有寄存器中,所有与通道6和通道7相关的位,对DMA2都不适用,因为DMA2只 有5个通道。
数据位 |
|
---|---|
位31:28 | 保留,始终读为0。 |
位27,23,19,15,11,7,3 | TEIFx:通道x的传输错误标志(x = 1 … 7) (Channel x transfer error flag) 硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。 0:在通道x没有传输错误(TE); 1:在通道x发生了传输错误(TE)。 |
位26,22,18,14,10,6,2 | HTIFx:通道x的半传输标志(x = 1 … 7) (Channel x half transfer flag) 硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。 0:在通道x没有半传输事件(HT); 1:在通道x产生了半传输事件(HT)。 |
位25,21,17,13, 9,5,1 | TCIFx:通道x的传输完成标志(x = 1 … 7) (Channel x transfer complete flag) 硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。 0:在通道x没有传输完成事件(TC); 1:在通道x产生了传输完成事件(TC)。 |
位24,20,16,12, 8,4,0 | GIFx:通道x的全局中断标志(x = 1 … 7) (Channel x global interrupt flag) 硬件设置这些位。在DMA_IFCR寄存器的相应位写入’1’可以清除这里对应的标志位。 0:在通道x没有TE、HT或TC事件; 1:在通道x产生了TE、HT或TC事件。 |
数据位 |
|
---|---|
位31:28 | 保留,始终读为0。 |
位27,23,19,15,11,7,3 | CTEIFx:清除通道x的传输错误标志(x = 1 … 7) (Channel x transfer error clear) 这些位由软件设置和清除。 0:不起作用 1:清除DMA_ISR寄存器中的对应TEIF标志。 |
位26,22,18,14,10,6,2 | CHTIFx:清除通道x的半传输标志(x = 1 … 7) (Channel x half transfer clear) 这些位由软件设置和清除。 0:不起作用 1:清除DMA_ISR寄存器中的对应HTIF标志。 |
位25,21,17,13,9,5,1 | CTCIFx:清除通道x的传输完成标志(x = 1 … 7) (Channel x transfer complete clear) 这些位由软件设置和清除。 0:不起作用 1:清除DMA_ISR寄存器中的对应TCIF标志。 |
位24,20,16,12,8,4,0 | CGIFx:清除通道x的全局中断标志(x = 1 … 7) (Channel x global interrupt clear) 这些位由软件设置和清除。 0:不起作用 1:清除DMA_ISR寄存器中的对应的GIF、TEIF、HTIF和TCIF标志。 |
数据位 |
|
---|---|
位31:15 | 保留,始终读为0。 |
位14 | MEM2MEM:存储器到存储器模式 (Memory to memory mode) 该位由软件设置和清除。 0:非存储器到存储器模式; 1:启动存储器到存储器模式。 |
位13:12 | PL[1:0]:通道优先级 (Channel priority level) 这些位由软件设置和清除。 00:低 01:中 10:高 11:最高 |
位11:10 | MSIZE[1:0]:存储器数据宽度 (Memory size) 这些位由软件设置和清除。 00:8位 01:16位 10:32位 11:保留 |
位9:8 | PSIZE[1:0]:外设数据宽度 (Peripheral size) 这些位由软件设置和清除。 00:8位 01:16位 10:32位 11:保留 |
位7 | MINC:存储器地址增量模式 (Memory increment mode) 该位由软件设置和清除。 0:不执行存储器地址增量操作 1:执行存储器地址增量操作 |
位6 | PINC:外设地址增量模式 (Peripheral increment mode) 该位由软件设置和清除。 0:不执行外设地址增量操作 1:执行外设地址增量操作 |
位5 | CIRC:循环模式 (Circular mode) 该位由软件设置和清除。 0:不执行循环操作 1:执行循环操作 |
位4 | DIR:数据传输方向 (Data transfer direction) 该位由软件设置和清除。 0:从外设读 1:从存储器读 |
位3 | TEIE:允许传输错误中断 (Transfer error interrupt enable) 该位由软件设置和清除。 0:禁止TE中断 1:允许TE中断 |
位2 | HTIE:允许半传输中断 (Half transfer interrupt enable) 该位由软件设置和清除。 0:禁止HT中断 1:允许HT中断 |
位1 | TCIE:允许传输完成中断 (Transfer complete interrupt enable) 该位由软件设置和清除。 0:禁止TC中断 1:允许TC中断 |
位0 | EN:通道开启 (Channel enable) 该位由软件设置和清除。 0:通道不工作 1:通道开启 |
数据位 |
|
---|---|
位31:16 | 保留,始终读为0。 |
位15:0 | NDT[15:0]:数据传输数量 (Number of data to transfer) 数据传输数量为0至65535。这个寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入。 通道开启后该寄存器变为只读,指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。 数据传输结束后,寄存器的内容或者变为0;或者当该通道配置为自动重加载模式时,寄存器的内容将被自动重新加载为之前配置时的数值。 当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。 |
数据位 |
|
---|---|
位31:0 | PA[31:0]:外设地址 (Peripheral address) 外设数据寄存器的基地址,作为数据传输的源或目标。 当PSIZE=’01’(16位),不使用PA[0]位。操作自动地与半字地址对齐。 当PSIZE=’10’(32位),不使用PA[1:0]位。操作自动地与字地址对齐。 |
数据位 |
|
---|---|
位31:0 | MA[31:0]:存储器地址 存储器地址作为数据传输的源或目标。 当MSIZE=’01’(16位),不使用MA[0]位。操作自动地与半字地址对齐。 当MSIZE=’10’(32位),不使用MA[1:0]位。操作自动地与字地址对齐。 |
库函数比较好的就是便于查看,有的人会说库函数和寄存器学一个就好了,但是有时候你会发现库函数和寄存器结合起来使用的时候回比较方便。例如,你要修改一些配置的时候,寄存器会很方便,原本库函数需要几条代码才能搞定,而寄存器可能一条就解决了。所以,我建议使用DMA的时候可以库函数结合寄存器一起使用。在DMA配置的时候,使用 V3.5 库函数操作的话,我们只需要调用 DMA_Init() 函数就可以了。下面介绍一些主要的函数。
|
|
---|---|
DMA_DeInit | 将 DMA 的通道 x 寄存器重设为缺省值 |
DMA_Init 根据 | DMA_InitStruct 中指定的参数初始化 DMA 的通道 x 寄存器 |
DMA_StructInit | 把 DMA_InitStruct 中的每一个参数按缺省值填入 |
DMA_Cmd | 使能或者失能指定的通道 x |
DMA_ITConfig | 使能或者失能指定的通道 x 中断 |
DMA_GetCurrDataCounte | 返回当前 DMA 通道 x 剩余的待传输数据数目 |
DMA_GetFlagStatus | 检查指定的 DMA 通道 x 标志位设置与否 |
DMA_ClearFlag | 清除 DMA 通道 x 待处理标志位 |
DMA_GetITStatus | 检查指定的 DMA 通道 x 中断发生与否 |
DMA_ClearITPendingBit | 清除 DMA 通道 x 中断待处理标志位 |
|
|
---|---|
函数原形 | void DMA_DeInit(DMA_Channel_TypeDef* DMA_Channelx) |
功能描述 | 将 DMA 的通道 x 寄存器重设为缺省值 |
输入参数 | DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x |
输出参数 | 无 |
返回值 | 无 |
先决条件 | 无 |
被调用函数 | RCC_APBPeriphResetCmd() |
使用例子:
/* 将 DMA 的通道 6 寄存器重设为缺省值 */
DMA_DeInit(DMA_Channel6);
|
|
---|---|
函数原形 | void DMA_Init(DMA_Channel_TypeDef* DMA_Channelx, DMA_InitTypeDef* DMA_InitStruct) |
功能描述 | 根据 DMA_InitStruct 中指定的参数初始化 DMA 的通道 x 寄存器 |
输入参数 1 | DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x |
输入参数 2 | DMA_InitStruct:指向结构 DMA_InitTypeDef 的指针,包含了 DMA 通道 x 的配置信息 |
输出参数 | 无 |
返回值 | 无 |
先决条件 | 无 |
被调用函数 | 无 |
这里有必要介绍一下 DMA_InitTypeDef structure
DMA_InitTypeDef 定义于文件“stm32f10x_dma.h”:
typedef struct
{
u32 DMA_PeripheralBaseAddr;
u32 DMA_MemoryBaseAddr;
u32 DMA_DIR;
u32 DMA_BufferSize;
u32 DMA_PeripheralInc;
u32 DMA_MemoryInc;
u32 DMA_PeripheralDataSize;
u32 DMA_MemoryDataSize;
u32 DMA_Mode;
u32 DMA_Priority;
u32 DMA_M2M;
} DMA_InitTypeDef;
DMA_PeripheralBaseAddr:该参数用以定义 DMA 外设基地址
DMA_MemoryBaseAddr:该参数用以定义 DMA 内存基地址
DMA_DIR:规定外设是作为数据传输的目的地还是来源(数据传输方向),该参数的取值范围如下表。
|
|
---|---|
DMA_DIR_PeripheralDST | 外设作为数据传输的目的地 |
DMA_DIR_PeripheralSRC | 外设作为数据传输的来源 |
DMA_BufferSize:用以定义指定 DMA 通道的 DMA 缓存的大小,单位为数据单位。根据传输方向,数据单位等于结构中参数 DMA_PeripheralDataSize 或者参数 DMA_MemoryDataSize 的值。
DMA_PeripheralInc:用来设定外设地址寄存器递增与否。该参数的取值范围如下表。
|
|
---|---|
DMA_PeripheralInc_Enable | 外设地址寄存器递增 |
DMA_PeripheralInc_Disable | 外设地址寄存器不变 |
DMA_MemoryInc:用来设定内存地址寄存器递增与否。该参数的取值范围如下表。
|
|
---|---|
DMA_PeripheralInc_Enable | 内存地址寄存器递增 |
DMA_PeripheralInc_Disable | 内存地址寄存器不变 |
DMA_PeripheralDataSize:设定了外设数据宽度。该参数的取值范围如下表。
|
|
---|---|
DMA_PeripheralDataSize_Byte | 数据宽度为 8 位 |
DMA_PeripheralDataSize_HalfWord | 数据宽度为 16 位 |
DMA_PeripheralDataSize_Word | 数据宽度为 32 位 |
DMA_MemoryDataSize:设定了外设数据宽度。该参数的取值范围如下表。
|
|
---|---|
DMA_MemoryDataSize_Byte | 数据宽度为 8 位 |
DMA_MemoryDataSize_HalfWord | 数据宽度为 16 位 |
DMA_MemoryDataSize_Word | 数据宽度为 32 位 |
DMA_Mode:设置了 DMA 的工作模式。该参数的取值范围如下表。
|
|
---|---|
DMA_Mode_Circular | 工作在循环缓存模式 |
DMA_Mode_Normal | 工作在正常缓存模式 |
注意:当指定 DMA 通道数据传输配置为内存到内存时,不能使用循环缓存模式。
DMA_Priority:设定 DMA 通道 x 的软件优先级。该参数的取值范围如下表。
|
|
---|---|
DMA_Priority_VeryHigh DMA | 通道 x 拥有非常高优先级 |
DMA_Priority_High DMA | 通道 x 拥有高优先级 |
DMA_Priority_Medium DMA | 通道 x 拥有中优先级 |
DMA_Priority_Low DMA | 通道 x 拥有低优先级 |
DMA_M2M:使能 DMA 通道的内存到内存传输。该参数的取值范围如下表。
|
|
---|---|
DMA_M2M_Enable DMA | 通道 x 设置为内存到内存传输 |
DMA_M2M_Disable DMA | 通道 x 没有设置为内存到内存传输 |
|
|
---|---|
函数原形 | void DMA_Cmd(DMA_Channel_TypeDef* DMA_Channelx, FunctionalState NewState) |
功能描述 | 使能或者失能指定的通道 x |
输入参数 1 | DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x |
输入参数 2 | NewState:DMA 通道 x 的新状态。这个参数可以取:ENABLE 或者 DISABLE |
输出参数 | 无 |
返回值 | 无 |
先决条件 | 无 |
被调用函数 | 无 |
使用例子:
/* 使能DMA通道6 */
DMA_Cmd(DMA_Channel6, ENABLE);
|
|
---|---|
函数原形 | void DMA_ITConfig(DMA_Channel_TypeDef* DMA_Channelx, u32 DMA_IT, FunctionalState NewState) |
功能描述 | 使能或者失能指定的通道 x 中断 |
输入参数 1 | DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x |
输入参数 2 | DMA_IT:待使能或者失能的 DMA 中断源,使用操作符“ |
输入参数 3 | NewState:DMA 通道 x 中断的新状态。这个参数可以取:ENABLE 或者 DISABLE |
输出参数 | 无 |
返回值 | 无 |
先决条件 | 无 |
被调用函数 | 无 |
DMA_IT:使能或者失能 DMA 通道 x 的中断。可以取下表的一个或者多个取值的组合作为该参数的值。
|
|
---|---|
DMA_IT_TC | 传输完成中断屏蔽 |
DMA_IT_HT | 传输过半中断屏蔽 |
DMA_IT_TE | 传输错误中断屏蔽 |
使用例子:
/* 使能DMA通道6传输完成中断 */
DMA_ITConfig(DMA_Channel6, DMA_IT_TC, ENABLE);
下面是配置DMA通道x的过程(x代表通道号):
上述便是DMA初始化的配置过程,为什么前文说配置寄存器方便使用(快捷)?从上述过程可以发现实际只需配置四个寄存器即可完成DMA的配置。但是不好的地方就是不便于阅读,通常大家都要对照使用手册才知道什么位配置成‘0’或‘1’。这里为什么要将,有一个很重要的因素就是快。
先使能要使用的DMA时钟,这里使用DMA1,USART2_RX(即使用通道6)。
RCC->AHBENR |= 1<<0 ; //DMA1时钟使能
接着对照流程,配置寄存器方法如下(以通道6为例):
u8 u1rxbuf[100]; //接收数据缓冲区1
//DMA_USART2_RX USART2->RAM的数据传输
DMA1_Channel6->CPAR = (u32)(&USART2->DR) ; //1.设置外设寄存器地址,注意PSIZE
DMA1_Channel6->CMAR = (u32)u1rxbuf ; //2.设置数据存储器的地址,注意MSIZE
DMA1_Channel6->CNDTR = buffersize ; //3.设置要传输的数据量buffersize个
DMA1_Channel6->CCR |= 2<<12 ; //4.设置通道的优先级高(处于位13:12)
DMA1_Channel6->CCR &= ~( 1<<4 ) ; //5(1).设置数据传输的方向从外设读取到内存
DMA1_Channel6->CCR &= ~( 1<<5 ) ; //5(2).设置不循环
DMA1_Channel6->CCR &= ~( 1<<6 ) ; //5(3).不执行外设地址增量模式
DMA1_Channel6->CCR |= 1<<7 ; //5(4).存储器地址增量模式
DMA1_Channel6->CCR &= ~( 3<<8 ) ; //5(5).外设数据宽度8bi
DMA1_Channel6->CCR &= ~( 3<<10 ) ; //5(6).存储器数据宽度8bit
DMA1_Channel6->CCR &= ~( 1<<14 ) ; //5(7).非存储器到存储器模式
DMA1_Channel6->CCR |= 1<<1 ; //5(8).允许传输完成中断
DMA1_Channel6->CCR |= 1 << 0 ; //6.开启DMA通道6
因为上述允许传输完成中断,所以还要配置相应的NVIC,这里使用回库函数
NVIC_InitTypeDef NVIC_InitStructure;
//DMA1通道6 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; //NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
当然,使用串口DMA的话还需开启串口接收中断(通道6是串口2接收通道)
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //开启串口DMA接收
库函数最大的优势就是便于阅读。根据固件库手册可以写出以下配置。
要先使能要使用的DMA时钟,这里使用DMA1,USART2_RX(即使用通道6)。相应库函数前面有讲解,直接上代码(注释很清楚)。
DMA_InitTypeDef DMA1_Init;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //使能DMA1时钟
//DMA1通道6 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; //NVIC通道设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//DMA_USART2_RX USART2->RAM的数据传输
DMA_DeInit(DMA1_Channel6); //将DMA的通道6寄存器重设为缺省值
DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); //启动传输前装入实际RAM地址
DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf; //设置接收缓冲区首地址
DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取到内存
DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN; //DMA通道的DMA缓存的大小
DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA1_Init.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
DMA1_Init.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级
DMA1_Init.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel6,&DMA1_Init); //对DMA通道6进行初始化
DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); //开USART2 Rx DMA中断
DMA_Cmd(DMA1_Channel6,ENABLE); //使DMA通道6开始工作
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //开启串口DMA接收
查询方式可以不使能DMA中断,通过DMA_GetFlagStatus函数判断标志位来辨别是否传输完成或过半以及出错,然后关闭DMA通道,用DMA_SetCurrDataCounter函数重设缓存大小,完成相应操作后要记得清除标志位再使能DAM通道。如果使能了循环模式,会自动重装载计数。
DMA中断一般用于定长数据传输,以传输完成中断为例。
(1)当产生DMA传输完成中断后,清除中断标志位、传输完成标志位;
(2)关闭DMA通道;
(3)处理数据;
(4)重新设置DMA通道的DMA缓存的大小(可以省去);
(5)开启DMA通道
以串口为例,不定长数据传输的时候,可以通过串口空闲中断来判断传输是否完成(传输缓存大小要大于传输的数据大小),数据长度可以通过DMA_GetCurrDataCounter函数来计算,然后关闭DMA通道,重设DMA缓存的大小,再启用DMA通道。
设置两个缓冲区,设置一个缓冲区标志(用来指示当前处在哪个缓冲区),每完成一次传输就通过重新配置DMA_MemoryBaseAddr的缓冲区地址,下次传输数据就会保存到新的缓冲区中,可以通过自定义缓存区标志来判断和切换,这样可以避免缓冲区数据来不及处理就被覆盖的情况,也能为处理数据留出更多地时间(指到下次传输完成)。
串口DMA的配置以及使用请查看此文:《 STM32 | 串口DMA很难?其实就是如此简单!(超详细、附代码)》
其实DMA的配置并不难,按照上面的配置过程配置即可。在使用DMA的时候,如果启用了DMA传输中断,还要写相应的中断函数,在中断函数中切记要清除中断标志位。DMA只有在传输完成或传输过半或者传输出错才会产生中断(前提打开了中断),对于DMA定长数据传输的时候,建议使用中断,对于不定长数据,以串口为例,可以结合串口空闲中断来使用。因为中断不是本文内容,这里不做讲解。**还是要说一下的就是DMA通道一旦使能便开始传输数据,如果是接收数据,可以先使能DMA通道再等待数据接收;如果是发送数据,最好配置好发送缓存再使能DMA通道。**最后说明一下,本文为了后续讲解串口DMA和SPI DMA时候方便写下,所以对于各类的DMA运用还需大家结合实际运用。本文若有不足之处,欢迎指出,可在下方评论。