DMA(Direct Memory Access,直接存储器访问) ,DMA 传输是将数据从一个地址空间复制到另外一个地址空间。CPU 只负责初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。
一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
typedef struct
{
//指定DMAy信道的外设基址
uint32_t DMA_PeripheralBaseAddr;
//指定DMAy信道的内存基地址。
uint32_t DMA_MemoryBaseAddr;
//指定这个外设是作为数据传输的目的地还是数据传输的来源
uint32_t DMA_DIR;
//指定信道缓存的大小
uint32_t DMA_BufferSize;
//指定外设地址寄存器是否递增
uint32_t DMA_PeripheralInc;
//指定内存地址寄存器是否递增
uint32_t DMA_MemoryInc;
//指定外设数据宽度。
uint32_t DMA_PeripheralDataSize;
//指定内存数据宽度。
uint32_t DMA_MemoryDataSize;
//指定DMAy信道x的操作模式。
uint32_t DMA_Mode;
//指定DMAy信道x的软件优先级
uint32_t DMA_Priority;
//指定DMAy通道x是否将在内存到内存传输中使用
uint32_t DMA_M2M;
} DMA_InitTypeDef;
//外设作为数据传输的目的地
#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
//外设作为数据传输的来源
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
//递增
#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
//不递增
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
//递增
#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
//不递增
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
//一个字节(8位)
#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
//一个字(32位)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
//一个字节(8位)
#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
//一个字(32位)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
//循环模式
#define DMA_Mode_Circular ((uint32_t)0x00000020)
//普通模式
#define DMA_Mode_Normal ((uint32_t)0x00000000)
//很高
#define DMA_Priority_VeryHigh ((uint32_t)0x00003000)
//高
#define DMA_Priority_High ((uint32_t)0x00002000)
//中等
#define DMA_Priority_Medium ((uint32_t)0x00001000)
//低
#define DMA_Priority_Low ((uint32_t)0x00000000)
//是
#define DMA_M2M_Enable ((uint32_t)0x00004000)
//不是
#define DMA_M2M_Disable ((uint32_t)0x00000000)
根据以上步骤书写代码,因为分文件书写这样看着会有点麻烦,所以我就将示例写在一个文件中,望各位大佬见谅
说明:
USART1与蓝牙进行连接,用于输入数据
USART2与PC进行连接,用于显示输入的数据
#include
#include
#include
//缓冲区的大小
#define Buff_Size 1024
//此次接收结束的标志
volatile char rec_end_flag;
//接收二级缓存中的数量
volatile unsigned short count;
//一级缓存,即DMA直接搬运数据的目的地
volatile char usart1_recv_buff[Buff_Size];
/*二级缓存,即某次传输结束后将一级缓存的数据拷贝到此,
方便继续开启DMA使用一级缓存接收数据而数据不会被覆盖*/
volatile char recv_buff[Buff_Size];
//设置NVIC的优先级分组
void NVIC_config(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}
//初始化串口以及GPIO
void usart1_init(void)
{
//用于GPIO初始化的结构体
GPIO_InitTypeDef GPIO_InitStructure;
//用于串口初始化的结构体
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
//1. 初始化结构体
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
//应用NVIC结构体
NVIC_Init(&NVIC_InitStruct);
//2. 使能相应的串口以及对应的GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
//3. 配置GPIO结构体并应用
//初始化Usart1的Txd脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化Usart1的Rxd脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//应用GPIO结构体
GPIO_Init(GPIOA, &GPIO_InitStructure);
//4. 配置Usart结构体并应用
//初始化Usart1结构体
USART_InitStructure.USART_BaudRate = 9600;
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_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
//5. 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE)
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//6. 开启DMA接收
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
//7. 使能串口
USART_Cmd(USART1, ENABLE);
}
//使用Debug的显示
void usart2_init(void)
{
//用于GPIO初始化的结构体
GPIO_InitTypeDef GPIO_InitStructure;
//用于串口初始化的结构体
USART_InitTypeDef USART_InitStructure;
//开启该串口以及其对应GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
//初始化Usart2的Txd脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化Usart2的Rxd脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化Usart2结构体
USART_InitStructure.USART_BaudRate = 9600;
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_Tx | USART_Mode_Rx;
USART_Init(USART2, &USART_InitStructure);
//使能Usart1
USART_Cmd(USART2, ENABLE);
}
//初始化DMA
void DMA1_init(void)
{
DMA_InitTypeDef DMA_InitStructure;
//1.使能DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//将DMA的通道5的寄存器重设为缺省值
DMA_DeInit(DMA1_Channel5);
//2. 配置DMA结构体并应用
//设置DMA源地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & (USART1->DR);
//内存地址基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart1_recv_buff;
//数据传输方向,从外设读取发送到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1024; //DMA通道的DMA缓存的大小
//外设地址寄存器不递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//内存地址寄存器递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//外设数据宽度为8位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
//内存数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
//工作模式为正常模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//DMA通道 x拥有中等优先级
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
//此传输不是内存到内存传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//根据DMA_InitStruct中指定的参数初始化DMAy通道x。
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
//3. 使能DMA
DMA_Cmd(DMA1_Channel5, ENABLE);
}
//串口1中断函数
void USART1_IRQHandler(void)
{
//判断是否为空闲中断
if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET)
{
//数据接收完毕标志置1
rec_end_flag = 1;
//关闭DMA,准备重新配置
DMA_Cmd(DMA1_Channel5, DISABLE);
//clear DMA1 Channel5 global interrupt.
DMA_ClearITPendingBit(DMA1_IT_GL5);
//计算接收数据长度
count = 1024 - DMA_GetCurrDataCounter(DMA1_Channel5);
memcpy((void *)recv_buff, (void *)usart1_recv_buff, count);
//重新配置
DMA_SetCurrDataCounter(DMA1_Channel5, 1024);
DMA_Cmd(DMA1_Channel5, ENABLE);
//清除IDLE标志位
USART1->SR;
USART1->DR;
}
}
//串口发送一个字符(调试使用)
void usart_send_ch(USART_TypeDef *USARTx, char ch)
{
while (RESET == USART_GetFlagStatus(USARTx, USART_FLAG_TC))
;
USART_SendData(USARTx, ch);
}
//串口发送字符串(调试使用)
void usart_send_str(USART_TypeDef *USARTx, char *str, int length)
{
int i;
for (i = 0; i < length; i++)
{
usart_send_ch(USARTx, *(str + i));
}
//字符串末尾加换行\r\n
char enter_str[] = {'\r', '\n'};
for (i = 0; i < 2; i++)
{
usart_send_ch(USARTx, enter_str[i]);
}
}
int main()
{
// 1. 配置NVIC分组
NVIC_config();
// 2. 初始化串口以及GPIO
usart1_init();
//使用Debug的显示
usart2_init();
// 3. 初始化DMA
DMA1_init();
// 4. 编写串口中断函数
//见USART1_IRQHandler
while (1)
{
//如此次传输完成
if (rec_end_flag == 1)
{
//将二级缓存的数据发送到串口2进行显示输出
usart_send_str(USART2, (char *)recv_buff, count);
//处理完数据后将标志置0等待下次传输结束
rec_end_flag = 0;
}
}
}
36个字符加\r\n刚好38个字符,成功
接着又发送了长一点的数据,也没有任何问题
使用DMA的好处就是可以不用占用CPU的资源,另外CPU进入串口的空闲中断(USART_IT_IDLE)会比串口的接收中断(USART_IT_RXNE)的次数要少。而且使用空闲中断完美的解决了接收任意长度的数据,如若大佬们认为以上代码有任何Bug或错请在评论指出,我也会不断的完善。