DMA理解:
直接内存存取(Direct Memory Access):在数据传输过程中不需要通过CPU的干预,控制总线并直接与外设和内存进行通信,给CPU减轻负担,以及提升数据的传输速度。
两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道用来管理外设对存储器访问的请求,也可以理解为,通道是数据的来源以及去向。
仲裁器:
咋听起来很高级,其实就是协调各个DMA请求的优先级
软件: 每个通道优先权可以再DMA_CCRx寄存器中设置,有4个等级
最高优先级
高优先级
中等优先级
低优先级
硬件:如果有两个请求相同的软件优先级,则较低编号通道优于较高编号优先权,2通道优于4通道。
DMA特性:
1:12个独立可配置通道:DMA1有7个通道,DMA2有5个通道
2:每个通道直接连接专用硬件DMA请求,每个通道同样支持软件触发,这些功能通过软件配置
3:独立数据源和目标数据区的传输宽度(字节,半字,全字),模拟打包和拆包过程,源目标地址必须按数据传输宽度对齐。
4:支持循环的缓冲区管理
5:每个通道都有3个事件标志(DMA半传输,DMA传输完成,DMA传输出错),每个事件标志逻辑成为一个单独的中断请求
6:闪存,SRAM,外设的SRAM,APB1,APB2外设均可作为访问源和目标
数据的大小单位
在计算机科学和嵌入式系统中,字、半字、全字 是指数据的大小单位
字(Word):字指得是处理器能够一次处理的数据大小,取决于处理器架构和位数,在32位处理器中,一个字通常是32位(4字节)大小,64位(8字节)大小。字是处理能够处理的最大数据单元。
半字(Half - Word):半字是字的一半大小,半字用来表示处理器能够读取或写入最小数据单元
全字(Double Word):全字是字的两倍大小,全字用于表示比一个字更大的数据单元
事件标志
DMA控制器中,每个通道都会有多个事件标志位,其中包括以下三个主要的事件标志:
1:传输完成(Transfer Complete):当DMA通道数据传输完成时,会触发完成事件,表示DMA已经成功完成一次数据传输操作
2:半传输(Half Transfer):这个标志表示DMA通道已经传输数据缓冲区的一半,在双缓冲区模式下,当DMA传输一半的数据时,会产生这个中间状态的事件
3:传输错误(Transfer Error):如果发生DMA传输错误,比如传输过程中出现了总线错误或外设错误等,会触发传输错误事件,这标志表示DMA传输过程中发生了错误。
DMA通道数量
最大达到65535
DMA优点:
1:提高效率:
CPU不需要介入数据传输过程,通过DMA可以并行处理其他任务,提高系统效率。
2:减少CPU负载:
对于大容量数据传输,使用DMA可以减少CPU的负载,让CPU处理更重要的任务
3:快速数据传输(仲裁器)可选择:
3.1:最高优先级
3.2:高优先级
3.3:中等优先级
3.4:低优先级
4:传输模式:
4.1:单次传输模式:
单次传输模式下,DMA只进行一次数据传输操作,传输完成后立即停止
4.2:循环传输模式:
循环模式下,DMAke已反复执行数据传输,不断重复数据传输操作
4.3:自动请求传输模式:
外设可以通过某种方式自动触发DMA数据传输,不需要CPU干预
4.4:多块传输模式:
在多块传输模式下,DMA将数据分成多个块进行传输
5:灵活性 :
5.1:外设到内存
5.2:内存到外设
5.3:内存到内存
5.4:外设到外设
我们今天的代码是外设(上位机)通过USART用DMA传输数据到Falsh中
以下图片可以看到是不经过CPU数据传输
USART1实现发送接收,这里需要记住DMA通道,等会得用到DMA的通道实现USART1接收和发数据的中断
1:DMA初始化
void DMA_Config(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
}
2:理解
#define SECTORSIZE (1024*2)
#define RX_ARRAY 2
#define RXBUFFERSIZE (1024+128)
#define TXBUFFERSIZE 1024
uint32_t rx_array_cnt; //缓冲区
uint8_t RxBuffer [RX_ARRAY][RXBUFFERSIZE];//数据大小//设置DMA数据传输的缓冲区大小为 RXBUFFERSIZE,即需要传输的数据量
1:DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
//禁止存储器到存储器的传输模式
2:DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//设置DMA工作模式作为普通模式,即非循环模式,普通模式下,DMA只传输一次数据后就停止。
3:DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//设置数据传输方向,为外设到寄存器,(外设是源地址。存储器是目的地址)
4:DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//设置DMA传输的优先级为高优先级,决定了在有多个DMA请求时DMA的处理顺序。
5:DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//允许存储器地址自增功能,每次传输完成之后,存储器地址会自动增加,编译连续存储数据
6:DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//配置DMA数据传输的外设基地址为USART1寄存器DR,USART1是外设,DR是数据寄存器,作为数据传输的源地址
7:DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
//禁止外设地址自增,因为USART1的DR寄存器地址是固定的,不需要自增
8:DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//设置存储器数据的大小为字节
9:DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
//设置DMA数据传输的存储器基地址为RxBuffer数据,这里的RxBuffer是存储数据的缓冲器。
10:DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
//设置外设数据的大小为字节
11:DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
//使用上述配置初始化DMA1的第五个通道
12:DMA_Init(DMA1_Channel5,&DMA_InitStructure);
//将DMA1的第5个通道所有配置重置为默认值
13:DMA_DeInit(DMA1_Channel5);
//清除 DMA 通道 x 中断待处理标志位
14:DMA_ClearITPendingBit(DMA1_IT_TC5);
//使能或者失能指定的通道 x 中断
15:DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE);
3:初始化USART1
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
USART_Init(USART1,&USART_InitStructure);
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
USART_Cmd(USART1, ENABLE);
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
}
4:普通io口理解:
1:打开普通IO脚时钟,复用时钟,串口时钟,因为普通io脚复用成USART模式所以需要打开三个时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1, ENABLE);
2:复用推挽输出 GPIO_Mode_AF_PP
USART一般都是配置复用功能,这样引脚就能兼容USART功能,推挽输出模式允许引脚既能提供高电平输出,也能提供低电平输出,这是为了适应USART的输出模式,以正确的传输数据
3:浮空输入 GPIO_Mode_IN_FLOATING
对于USART的接收线(RX),设置为浮空输入模式是因为UASRT接收到的数据是外部设备发送的,这种模式引脚不会被内部上拉或下拉,而是接收外部设备发送的数据信号
以上配置引脚是为了确保引脚正确和USART相连工作,推挽输出适用于USART输出数据,浮空输入适用于USART接收数据。
5:串口理解:
//波特率
USART_InitStructure.USART_BaudRate = 115200;
//设置数据长度为8位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//设置停止位为1位
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;
//发送DMA请求
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
//接收DMA请求
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
USART_Init(USART1, &USART_InitStructure);//空闲总线中断
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
//接收中断
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
7:NVIC优先级
void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x2;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x2;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
8:DMA中断
void DMA1_Channel5_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC5) != RESET)
{
DMA_ClearITPendingBit(DMA1_IT_TC5);
DMA_Cmd(DMA1_Channel5, DISABLE);
rx_array_cnt++;
rx_array_cnt = rx_array_cnt%RX_ARRAY;
memset(RxBuffer[rx_array_cnt],0,RXBUFFERSIZE);
DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, ENABLE);
}
if(DMA_GetITStatus(DMA1_IT_TC4) != RESET)
{
DMA_ClearITPendingBit(DMA1_IT_TC4);
}
}
void DMA1_Channel5_IRQHandler(void)
{
// 检查 DMA1 通道 5 传输完成中断标志位
if(DMA_GetITStatus(DMA1_IT_TC5) != RESET)
{
// 清除 DMA1 传输完成中断标志位
DMA_ClearITPendingBit(DMA1_IT_TC5);
// 关闭 DMA1 通道 5
DMA_Cmd(DMA1_Channel5, DISABLE);
// 增加接收缓冲区计数器并取模,确保在接收缓冲区数组范围内
rx_array_cnt++;
rx_array_cnt = rx_array_cnt % RX_ARRAY;
// 将当前接收缓冲区清零
memset(RxBuffer[rx_array_cnt], 0, RXBUFFERSIZE);
// 重新配置 DMA1 通道 5 的 DMA 参数,包括缓冲区大小、内存基地址等
DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
// 再次使能 DMA1 通道 5,准备下一次传输。
DMA_Cmd(DMA1_Channel5, ENABLE);
}
// 检查 DMA1_IT_TC4 传输完成中断标志位
if(DMA_GetITStatus(DMA1_IT_TC4) != RESET)
{
// 清除 DMA1_IT_TC4 传输完成中断标志位
DMA_ClearITPendingBit(DMA1_IT_TC4);
}
}
9:USART中断
void USART1_IRQHandler(void)
{
uint32_t id = 0;
uint8_t FIFO_Level = 0,i = 0;
uint16_t cnt=0;
if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
{
USART_ClearFlag(USART1, USART_FLAG_ORE);
}
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
cnt = RXBUFFERSIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
rx_array_cnt++;
rx_array_cnt = rx_array_cnt%RX_ARRAY;
memset(RxBuffer[rx_array_cnt],0,RXBUFFERSIZE);
DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel5, ENABLE);
uart_com_rcv((uint8_t *)RxBuffer[(rx_array_cnt-1)%RX_ARRAY],cnt);
}
}
void USART1_IRQHandler(void)
{
// 定义变量
uint32_t id = 0;
uint8_t FIFO_Level = 0, i = 0;
uint16_t cnt = 0;// 检查 USART1 接收寄存器溢出标志
if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
{
// 如果溢出标志位为非零,则清除溢出标志位
USART_ClearFlag(USART1, USART_FLAG_ORE);
}// 检查 USART1 空闲中断标志位
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
// 读取 USART1 数据寄存器,但没有使用返回值
USART1->DR;
// 清除 USART1 空闲中断标志位
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
// 禁止 DMA1 通道 5
DMA_Cmd(DMA1_Channel5, DISABLE);
// 计算接收数据的数量
cnt = RXBUFFERSIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
// 增加接收缓冲区计数器并取模,确保在接收缓冲区数组范围内
rx_array_cnt++;
rx_array_cnt = rx_array_cnt % RX_ARRAY;
// 将接收缓冲区 RxBuffer[rx_array_cnt] 清零
memset(RxBuffer[rx_array_cnt], 0, RXBUFFERSIZE);
// 重新配置 DMA1 通道 5 的参数,包括缓冲区大小和内存基地址
DMA_InitStructure.DMA_BufferSize = RXBUFFERSIZE;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer[rx_array_cnt];
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
// 重新使能 DMA1 通道 5,准备下一次传输
DMA_Cmd(DMA1_Channel5, ENABLE);// 执行 uart_com_rcv 函数,处理接收到的数据
uart_com_rcv((uint8_t *)RxBuffer[(rx_array_cnt - 1) % RX_ARRAY], cnt);
}
}
10:USART发送
void usart_send(uint8_t *data,uint16_t len)
{
int i = 0;
uint8_t *p = NULL;
memset(TxBuffer,0,TXBUFFERSIZE);
memcpy(TxBuffer,data,len);
p = TxBuffer;
for(i=0; i<len;i++,p++)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART1->DR = ((*p) & 0xFF);
}
}