一、什么是DMA?
DMA(Direct Memory Access,直接存储器访问)是一种数据传输方式,可以在不同的地址空间之间快速搬运数据,而无需CPU的直接参与。简单来说,DMA就像是一个“数据搬运工”,CPU只需要告诉它数据从哪里搬到哪里,然后DMA就会自动完成整个传输过程。
DMA可以用于外设与存储器之间的数据传输,比如从串口接收数据存入内存,或者从内存读取数据发送到外设;也可以用于存储器之间的数据搬运,比如将数据从一个内存区域复制到另一个区域。它的优势在于能够减少CPU的负担,提高数据传输效率,特别适用于大批量数据的高速传输。
在实际使用中,我们只需要正确配置DMA的参数,比如源地址、目标地址、数据长度等,然后启动使能DMA通道,数据就会自动完成搬运,无需CPU一直干预,大幅提升系统的运行效率。
●没有DMA参与的UART数据收发
在没有使用DMA的情况下,UART的数据发送和接收通常采用查询或中断方式。对于发送,CPU需要不断检测TXE(发送数据寄存器空)标志位,等待USART准备好后才能写入数据。这种方式不仅占用CPU资源,还可能导致较高的延迟。而在接收数据时,CPU同样需要不断轮询RXNE(接收数据寄存器非空)标志位,或者依赖接收中断,在数据到达时由中断服务程序进行读取。无论是查询还是中断方式,CPU都需要频繁介入数据的搬运,影响系统的实时性,特别是在数据量较大或速率较高的情况下,CPU负担会明显增加。
●有DMA参与的UART数据收发:核心配置好DMA控制器即可。
而使用DMA进行UART收发时,数据搬运的过程可以完全由DMA硬件完成。对于发送数据,CPU只需要将数据缓冲区地址和传输长度配置到DMA控制器,然后启动传输,DMA会自动将数据逐字节搬运到USART数据寄存器,无需CPU干预。接收数据时,DMA同样可以将USART接收到的数据直接存入指定缓冲区,CPU只需在数据传输完成后进行处理,而不需要时刻关注数据到达的情况。这种方式大幅降低了CPU的负担,提高了数据传输的效率,使得系统可以处理更高的数据速率,同时也减少了因中断或查询带来的性能损耗。
二、DMA的作用?
DMA的作用就是解决大量数据转移过度消耗CPU资源的问题,有了DMA得CPU可以更加专注的实用的的操作——计算、控制等。
DMA技术的出现,使得外围设备可以通过DMA控制器直接访问内存,与此同时,CPU可以继续执行程序。
DMA传输期间,DMA控制器接管了总线的控制权。在DMA传输结束后,DMA控制器将总线的控制权交给CPU。通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
三、DMA的传输方向
- 外设和存储器
- 存储器和外设的传输。
- 存储器和存储器间的传输。
四、DMA中断
每个通道都有 3 个事件标志(DMA 半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
五、DMA传输的模式
- 正常模式(DMA Mode Normal)
一次DMA数据传输完后,停止DMA传送,也就是只传输一次。要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA CNDTRx寄存器中重新写入传输数目。- 循环传输模式(DMA Mode Circular)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。主要用于处理循环缓冲区和连续的数据传输。- 指针增量模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。一般情况下存储器需要设置递增,而外设设置为保持常量。
六、DMA映射
以STM32F4开发板为例(不同型号映射不同)。DMA控制器包含了DMA1和DMA2,其中DMA1和DMA2均有8个数据流,每个数据流有8个通道。这里的通道可以理解为传输数据的一种管道。每个通道都有一个仲裁器,用于处理DMA 请求间的优先级。
如:UART使用DMA传输,就将DMA映射到下面的数据流通道中。
七、以串口为例,使用DMA1实现框图
(1)配置阶段:通过AHB从器件编程接口配置DMA控制器,设定源地址、目标地址、优先级等参数。
(2)请求阶段:数据源通过数据流请求通道向DMA控制器发送请求信号。
(3)传输阶段:DMA控制器根据优先级和仲裁结果,从源地址读取数据,经过FIFO缓冲区暂存,然后传输到目标外设的数据寄存器。
(4)处理阶段:外设接收到数据后进行进一步处理或发送。
八、DMA功能
1. 通道选择
每个数据流都与一个DMA请求相关联,此 DMA请求可以从8个可能的通道请求中选出。此选择由DMA_SxCR寄存器中的CHSEL[2:0] 位控制。芯片不同,DMA映射通道也不同。
2. 仲裁器(决定优先级,类似于NVIC控制器)
仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的8个DMA 数据流请求管理,并启动外设/存储器访问序列。
优先级管理分为软件配置和硬件配置:
软件:每个数据流优先级都可以在 DMA_SxCR寄存器中的PL[1:0] 位控制配置。分为四个级别:
硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流2的优先级高于数据流4。
3. FIFO直接模式和阈值突发模式
(1)FIFO简介
FIFO用于在源数据传输到目标之前临时存储这些数据。每个数据流都有一个独立的(总容量16字节)FIFO,FIFO临时存储数据最多为16字节,FIFO的存储阈值级别可由软件配置为1/4(4字节)、1/2(8字节)、3/4(12字节)或满(16字节)。
为了使能FIFO阈值级别,必须通过将DMA_SxFCR寄存器中的 DMDIS位置1来禁止直接模式。
(2)直接模式(相当于写多少个字节就输出多少个字节)
默认情况下,FIFO 以直接模式操作(将 DMA_SxFCR中的DMDIS位清0,不使用FIFO阈值级别 )。
在直接模式下,不使用FIFO 的阈值级别控制。每完成一次从外设到FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。当实现存储器到存储器传输时不得使用直接模式!!!
当在直接模式(禁止 FIFO)下将DMA配置为以存储器到外设模式传输数据时,DMA 会将一个数据从存储器预加载到内部 FIFO,从而确保一旦外设触发DMA请求时则立即传输数据。(为了避免 FIFO饱和,建议使用高优先级配置相应的数据流),该模式仅限以下方式的传输:
●源和目标传输宽度相等,并均由 DMA_SxCR中的 PSIZE[1:0] 位定义(MSIZE[1:0]位的状态是“无关”)
不可能进行突发传输(DMA_SxCB 中的 PBURST[1:0]和MBURST[1:0]位的状态是“无关”)。
(3)FIFO阈值突发模式
使能这种模式(将DMA_SxCR寄存器中的位EN置1)时,每次产生外设请求.数据流都会启动数据源到FIFO的传输。达到FIFO的阈值级别时,FIFO的内容移出并存储到目标中。
如果DMA_SxNDTR 寄存器达到零、外设请求传输终止(在使用DMA流控制器的情况下)或 DMA_SxCR寄存器中的EN位由软件清零,传输即会停止。
选择FIFO阈值(DMA_SxFCB寄存器的位 FTH[1:0] )和存储器突发大小(DMA_SxCR寄存器的MBURST[1:0] 位)时需要小心,FIFO阈值指向的内容必须与整数个存储器业发传输完全匹配。如果不是这样,当使能数据流时将生成一个FIFO错误(DMA_HISR或 DMA_LISR寄存器的标志FEIEx),然后将自动禁止数据流。允许的和禁止的配置在表41:FIFO阈值配置中介绍。
所有这些情况下,突发大小与数据大小的乘积不得超过FlFO容量大小。如果发生下列情况,会导致 DMA 传输结界出现不完整的冲突传输。如:31个byte,使用4节拍的1次突发,则最多突发7次,还剩3个byte(不完整的冲突传输)。这3个byte就使用单次传输模式进行传输。
4. 源、目标和传输模式
源传输和目标传输在整个4GB区域(地址在0x0000 0000和OxFFFF FFFF 之间)都可以寻址外设和存储器。
传输方向使用DMA_SxCR寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。表 37介绍了相应的源和目标地址。
只有DMA2控制器能够执行存储器到存储器的传输。
使用存储器到存储器模式时,不允许循环模式和直接模式。
使用示例:如果从UART的DR寄存器(外设)读取到buff[](存储器)中,则需要把DMA_SxCR寄存器的位DIR[1:0] 位写入00,并把UART的DR寄存器的地址放入DMA_SxPAR中,把buff[]的地址放入DMA_SxMOAR中,即可进行DMA传输。
5. 指针递增
根据DMA_SxCR寄存器中 PINC和 MINC位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。
使用示例:如果从UART的DR寄存器(外设)读取到buff[](存储器)中,则需要将buff[](存储器)设置为递增模式,UART的DR寄存器(外设)设置为固定模式。
通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。
如果使能了递增模式,则根据在DMA.SxCR寄存器PSIZE或 MSIZE位中编程旳数据宽度,下一次传输的地址将是前一次传输的地址递增1(对于字节)、2(对于半字)或4(对于字)。
为了优化封装操作,可以不管AHB 外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA.SxCB寄存器中的 PINCOS位用于将增量偏移大小与外设AHB端口或32位地址(此时地址递增4)上的数据大小对齐。PINCOS位仅对AHB外设端口有影响。
如果将PINCOS位置1,则不论 PSIZE值是多少,下一次传输的地址总是前一次传输的地址递增4(自动与32位地址对齐)。但是,AHB存储器端口不受此操作影响。
如果 AHB外设端口或AHB存储器端口分别请求突发事务,为了满足AMBA 协议(在固定地址模式下不允许突发事务),则需要将PINC或MINC位置1。
6. 流控制器
决定谁可以控制整个数据传输的终点。
控制要传输的数据数目的实体称为流控制器,此流控制器使用DMA_SxCR寄存器中的 PFCTRL位针对每个数据流独立配置。
DMA 控制器: 在这种情况下,要传输的数据项的数目在使能DMA数据流之前由软件编程到DMA_SxNDTR寄存器。
外设源或目标:当要传输的数据项的数目未知时属于这种情况。当所传输的是最后的数据时,外设通过硬件向DMA 控制器发出指示。仅限能够发出传输结束信号的外设支持此功能,也就是: SDIO。
九、示例实验
如何使用:一旦你配置好DMA通道并使能它,DMA会自动在源地址和目标地址之间传输数据。 你不需要在每次传输时手动调用数据操作。DMA的优势就在于它可以极大地减少CPU的负担,允许你在传输过程中继续执行其他任务。
实验一:串口使用DMA发送/接收数据
软件分析
流程:
(1)配置串口功能,并打开串口空闲中断,使能串口DMA发送和接收功能。
(2)配置DMA参数,如源地址、目标地址、大小、方向等。
(3)DMA发送和接收
① 使用DMA发送数据时,要先关闭发送通道(数据流),然后才能写入数据,写完以后使能通道就可以自动完成内容搬运。
② 使用DMA接收数据时,要先关闭接收通道,防止还没处理完,新的数据就又来了,处理完后再打开通道。
1. 寄存器版本(STM32F4)
发送数据代码如下,这里我们假设串口已经被配置好了,且该代码使用的是F4开发板。
void USART1_DMAT_Init (u32 sAddr,u32 rAddr,u32 num) // sAddr:源地址(发送方) rAddr:目标地址(接收方),num:数量
{
RCC->AHB1ENR|=(0x1 <<22); //1.打开DMA2时钟
DMA2_Stream7->CR &= ~(0x1<<0);//2.关闭数据流7
USART1->CR3 |=(0x1<< 7); //3.使能USART1_TX的DMA传输
DMA2_Stream7->FCR &= ~(0x1<<2);//4.使能直接模式
//5.配置CR
DMA2_Stream7->CR = 0;//整体清零
DMA2_Stream7->CR |=(0x4 <<25); //选择通道4
DMA2_Stream7->CR |=(0x2<<16); //高优先级
DMA2_Stream7->CR |=(0x1 <<10);//存储器地址递增 1byte
DMA2_Stream7->CR |=(0x1<<6);//存储器到外设方向
/*
*PSIZE=MSIZE =8bit = lbyte
*外设地址固定
*禁止循环模式
*DMA作为流控制器 */
DMA2_Stream7->NDTR = num;//6.设置传输项数
DMA2_Stream7->MOAR = sAddr;//7.设置源地址
DMA2_Stream7->PAR= rAddr;//8.设置目标地址
DMA2_Stream7->CR |=(0x1<<0);//9.使能数据流
}
//---------------------------------------以下为主函数main.c使用内容
u8 buff[20]="hello world";
void mian()
{
printf("Reset!!!\r\n");
USART1_DMAT_Init((u32)buff, (u32)&USART1->DR ,strlen(buff));
//printf("DMA!!!\r\n");
}
接收数据代码
void USART1_DMAR_Init (u32 sAddr,u32 rAddr,u32 num) // sAddr:源地址(发送方) rAddr:目标地址(接收方),num:数量
{
RCC->AHB1ENR|=(0x1 <<22); //1.打开DMA2时钟
DMA2_Stream2->CR &= ~(0x1<<0);//2.关闭数据流2
USART1->CR3 |=(0x1<< 6); //3.使能USART1_RX的DMA传输
DMA2_Stream2->FCR &= ~(0x1<<2);//4.使能直接模式
//5.配置CR
DMA2_Stream2->CR = 0;//整体清零
DMA2_Stream2->CR |=(0x5 <<25); //选择通道4
DMA2_Stream2->CR |=(0x2<<16); //高优先级
DMA2_Stream2->CR |=(0x1 <<10);//存储器地址递增 1byte
/*
*PSIZE=MSIZE =8bit = lbyte
*外设地址固定
*禁止循环模式
*DMA作为流控制器
*外设到存储器
*/
DMA2_Stream2->NDTR = num;//6.设置传输项数
DMA2_Stream2->PAR= sAddr;//7.设置源地址
DMA2_Stream2->MOAR = rAddr; //8.设置目标地址
DMA2_Stream2->CR |=(0x1<<0);//9.使能数据流
}
//---------------------------------------以下为主函数main.c使用内容
u8 buff[20]="0";
void mian()
{
printf("Reset!!!\r\n");
USART1_DMAT_Init((u32)&USART1->DR, (u32)buff, 20);
while(1){
if (DMA2->LISR &(0x1<<21)) //等待传输完成标志
{
DMA2->LIFCR |=(0x1 <<21); //清除传输完成中断
printf ( "RX:%s\r\n", buff);
DMA2_stream2->CR &=~(0x1<< 0); //关闭数据流
DMA2_stream2->NDTR =20; //重传传输项数
DMA2_stream2->CR |=(0x1 <<0);//使能数据流
}
}
}
分析: 发送时 为什么会出现下面这种情况呢?
答:因为DMA传输不需要CPU参与。当CPU打印完第一printf后,直接跳过USART1_DMAT_Init()函数,直接打印第二个printf,与DMA打印过程发生冲突。通俗来说:在CPU打印时,DMA也发生了打印。
2. 库函数版本(STM32F1)
功能:串口助手发送到开发板什么内容,就回显什么内容。注意:我们只需要配置好源地址和目标地址后,使能通道即可,DMA会自动去进行搬运数据。
usart.c
#include "usart.h"
#include "delay.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm32f10x.h"
#define USART_MAX_LEN 100
volatile uint16_t recv_length = 0; // 接收帧数据的长度
uint8_t DMA_USART1_RX_BUF[USART_MAX_LEN] = {0}; // 接收数据缓存
uint8_t DMA_USART1_TX_BUF[400]; // 发送数据缓存
void USART1_Config(u32 bound) // 同时配置接收和发送
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2 GPIO USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA.9
// USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA.10
USART_DeInit(USART1);
// 3 中断 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化VIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; // 嵌套通道为DMA1_Channel4_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级为 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4 + 3; // 响应优先级为 7
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 通道中断使能
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn; // 串口1发送中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5 + 3; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
// 4配置 USART设置
USART_InitStructure.USART_BaudRate = bound; // 串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
USART_Init(USART1, &USART_InitStructure); // 初始化串口1
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
USART_Cmd(USART1, ENABLE);
}
void USART_DMA_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* 开启DMA时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* 配置DMA1通道5(接收) */
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DMA_USART1_RX_BUF;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = USART_MAX_LEN;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
/* 配置DMA1通道4(发送) */
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMA_USART1_TX_BUF;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 0; // 发送时,BufferSize由实际数据大小决定
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
// 启动DMA接收通道
DMA_Cmd(DMA1_Channel5, ENABLE);
// 开启DMA发送完成中断
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);
// 启用USART接收和发送DMA请求
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}
void USART1_IRQHandler(void) // 串口1中断服务程序
{
/* 检查是否触发了空闲中断 (IDLE) */
if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) // 空闲中断触发
{
DMA_Cmd(DMA1_Channel5, DISABLE); /* 暂时关闭DMA接收,避免数据还未处理时进行错误的操作 */
// 获取接收到的数据长度,单位为字节
recv_length = USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);
USART_ClearITPendingBit(USART1, USART_IT_IDLE); /* 清除空闲中断标志 */
// 将接收到的数据复制到发送缓冲区并启动发送
DMA_USART1_Send(DMA_USART1_RX_BUF, recv_length);
// 重新设置DMA的计数器,确保下次DMA能够正确接收最大长度的数据
DMA_SetCurrDataCounter(DMA1_Channel5, USART_MAX_LEN);
DMA_Cmd(DMA1_Channel5, ENABLE); /* 重新启用DMA接收功能,继续接收数据 */
USART_ReceiveData(USART1); // 清除空闲中断标志位(此函数有清除标志位的作用)
}
/* 检查是否完成了串口数据发送 */
if (USART_GetFlagStatus(USART1, USART_IT_TXE) == RESET) // 判断发送完成标志位(TXE)
{
USART_ITConfig(USART1, USART_IT_TC, DISABLE); /* 禁用发送完成中断(TC中断),表示发送已经完成 */
}
}
void DMA1_Channel4_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_TC4))
{
DMA_ClearITPendingBit(DMA1_IT_TC4); // 清除传输完成中断标志位
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA1_Channel4->CNDTR = 0; // 清除数据长度
USART_ITConfig(USART1, USART_IT_TC, ENABLE); // 打开串口发送完成中断
}
}
void DMA_USART1_Send(uint8_t *data, u16 size) // 串口1DMA发送函数
{
// 禁用DMA通道
DMA_Cmd(DMA1_Channel4, DISABLE);
// 清除所有标志位
DMA_ClearFlag(DMA1_FLAG_TC4);
DMA_ClearFlag(DMA1_FLAG_HT4);
DMA_ClearFlag(DMA1_FLAG_TE4);
USART_ClearFlag(USART1, USART_FLAG_TC);
// 复制数据到发送缓冲区
memcpy(DMA_USART1_TX_BUF, data, size);
// 设置传输数据长度
DMA_SetCurrDataCounter(DMA1_Channel4, size);
// 使能DMA通道
DMA_Cmd(DMA1_Channel4, ENABLE);
}
main.c
#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
int main(void)
{
// 设置NVIC中断优先级分组为2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
// 初始化USART和DMA
USART1_Config(115200);
USART_DMA_Init();
DMA_USART1_Send("Hello World!\r\n", sizeof("Hello World!\r\n") - 1);
while (1)
{
}
}
上面程序的数据流走向:
接收数据流:
发送数据流:
实验二:存储器到存储器
要求:buff1 的内容复制给buff2 。
1. 寄存器版本(STM32F4)
实验使用数据流1通道0。
void DMA2_Stream1_CH0_Init(u32 sAddr,u32 rAddr,u32 num)
{
RCC->AHB1ENR |= (0x1 <<22);//1.打开DMA2时钟
DMA2_Stream1->CR &= ~(0x1<<0);//2.关闭数据流1
DMA2_Stream1->FCR |=(0x1<< 2);//3.禁止直接模式
DMA2_Stream1->FCR L= (0x3 <<0);//4.设置FIFo满容量
//5.配置CR
DMA2_Stream1->CR = 0;//整体清零
DMA2_Stream1->CR |=(0x2<<16); //高优先级
DMA2_Stream1->CR |=(0x1 <<10);//存储器2地址递增 1byte
DMA2_Stream1->CR |=(0x1 <<9);//存储器1地址递增 1byte
DMA2_Stream1->CR |=(0x2 <<6);//存储器到存储器方向
/*
*选择通道0
*禁止循环模式
*DMA作为流控制器 */
DMA2_Stream7->NDTR = num;//6.设置传输项数
DMA2_Stream7->PAR= sAddr;;//7.设置源地址
DMA2_Stream7->MOAR =rAddr//8.设置目标地址
DMA2_Stream7->CR |=(0x1<<0);//9.使能数据流
}
//---------------------------------------以下为主函数main.c使用内容
u8 buff[20]="Hello";
void mian()
{
printf("Reset!!!\r\n");
DMA2_Stream1_CH0_Init((u32)"Hello", (u32)buff, strlen("Hello"));
while(1){
if (DMA2->LISR &(0x1<< 11)) //等待传输完成标志
{
DMA2->LIFCR |=(0x1 <<11); //清除传输完成中断
printf ( "%s\r\n", buff);
}
}
}
2. 库函数版本(STM32F1)
dma.c
#include "dma.h"
#include "delay.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm32f10x.h"
#include "led.h"
void MyDMA_Init(uint32_t addrA, uint32_t addrB, uint16_t Size)
{
// 开启DMA1的时钟控制
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 清除所有标志位
DMA_ClearFlag(DMA1_FLAG_TC7);
DMA_ClearFlag(DMA1_FLAG_HT7);
DMA_ClearFlag(DMA1_FLAG_TE7);
DMA_InitTypeDef DMA_InitStruct;
DMA_StructInit(&DMA_InitStruct);
// 配置DMA
DMA_InitStruct.DMA_PeripheralBaseAddr = addrA; // 外设地址(源地址)
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 字节传输
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 地址自增
DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; // 最高优先级
DMA_InitStruct.DMA_MemoryBaseAddr = addrB; // 存储器的地址(目标地址)
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 字节传输
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 地址自增
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; // 单次传输
DMA_InitStruct.DMA_BufferSize = Size; // 传输计数器的大小
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; // 从外设到内存
DMA_InitStruct.DMA_M2M = DMA_M2M_Enable; // 软件触发
// 初始化DMA
DMA_Init(DMA1_Channel7, &DMA_InitStruct);
// 使能DMA,立即触发
DMA_Cmd(DMA1_Channel7, ENABLE);
}
usart.c
#include "stm32f10x.h"
#include "usart.h"
#include <stdio.h>
void USART_Config(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除USART2接收中断待处理位
}
}
/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */
#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
/* FILE 在 stdio.h里面定义. */
FILE __stdout;
/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口
其中串口可根据实际使用情况调整 */
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
main.c
#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "dma.h"
uint8_t Source[]="0123456789";
uint8_t Target[]="ABCDEFGHIJ";
int main(void)
{
// 设置NVIC中断优先级分组为2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化延时和LED
delay_init();
USART_Config(115200);
printf("\r\nbefore:\n");
printf("\r\nSource:%s\n",Source);
printf("\r\nTarget:%s\n",Target);
delay_ms(1000);
printf("\r\n---------------------------------------\n");
MyDMA_Init((uint32_t)Source,(uint32_t)Target,10); //数据转运
printf("\r\nafter:\n");
printf("\r\nSource:%s\n",Source);
printf("\r\nTarget:%s\n",Target);
printf("\r\n---------------------------------------\n");
while (1)
{
delay_ms(100);
}
}