DMA
简介
DMA(Direct Memory Access,直接存储器访问) ,DMA 传输是将数据从一个地址空间复制到另外一个地址空间。CPU 只负责初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。
原理
一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
- DMA请求
CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
- DMA响应
DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
- DMA传输
DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
- DMA结束
当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。
STM32标准库编程
DMA初始化结构体
结构体定义
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;
结构体参数取值
DMA_DIR
指定这个外设是作为数据传输的目的地还是数据传输的来源
//外设作为数据传输的目的地
#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
//外设作为数据传输的来源
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
DMA_PeripheralInc
指定外设地址寄存器是否递增
//递增
#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
//不递增
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
DMA_MemoryInc
指定内存地址寄存器是否递增
//递增
#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
//不递增
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
DMA_PeripheralDataSize
指定外设数据宽度。
//一个字节(8位)
#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
//一个字(32位)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
DMA_MemoryDataSize
指定内存数据宽度。
//一个字节(8位)
#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
//一个字(32位)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
DMA_Mode
指定DMAy信道x的操作模式。
//循环模式
#define DMA_Mode_Circular ((uint32_t)0x00000020)
//普通模式
#define DMA_Mode_Normal ((uint32_t)0x00000000)
DMA_Priority
指定DMAy信道x的软件优先级
//很高
#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)
DMA_M2M
指定DMAy通道x是否将在内存到内存传输中使用
//是
#define DMA_M2M_Enable ((uint32_t)0x00004000)
//不是
#define DMA_M2M_Disable ((uint32_t)0x00000000)
DMA串口编程的一般步骤
- 配置NVIC分组
- 初始化串口
- 使能相应的串口以及对应的GPIO时钟
- 配置NVIC结构体并应用
- 配置GPIO结构体并应用
- 配置Usart结构体并应用
- 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE)
- 开启DMA接收
- 使能串口
- 初始化DMA
- 使能相应的DMA时钟
- 配置DMA结构体并应用
- 使能DMA
- 编写串口中断函数
示例代码
根据以上步骤书写代码,因为分文件书写这样看着会有点麻烦,所以我就将示例写在一个文件中,望各位大佬见谅
说明:
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));
}
//字符串末尾加换行rn
char enter_str[] = {'r', 'n'};
for (i = 0; i < 2; i++)
{
usart_send_ch(USARTx, enter_str);
}
}
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个字符
PC端接收
36个字符加rn刚好38个字符,成功
接着又发送了长一点的数据,也没有任何问题
总结
使用DMA的好处就是可以不用占用CPU的资源,另外CPU进入串口的空闲中断(USART_IT_IDLE)会比串口的接收中断(USART_IT_RXNE)的次数要少。而且使用空闲中断完美的解决了接收任意长度的数据,如若大佬们认为以上代码有任何Bug或错请在评论指出,我也会不断的完善。
DMA
简介
DMA(Direct Memory Access,直接存储器访问) ,DMA 传输是将数据从一个地址空间复制到另外一个地址空间。CPU 只负责初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。
原理
一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
- DMA请求
CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
- DMA响应
DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
- DMA传输
DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
- DMA结束
当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。
STM32标准库编程
DMA初始化结构体
结构体定义
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;
结构体参数取值
DMA_DIR
指定这个外设是作为数据传输的目的地还是数据传输的来源
//外设作为数据传输的目的地
#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
//外设作为数据传输的来源
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
DMA_PeripheralInc
指定外设地址寄存器是否递增
//递增
#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
//不递增
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
DMA_MemoryInc
指定内存地址寄存器是否递增
//递增
#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
//不递增
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
DMA_PeripheralDataSize
指定外设数据宽度。
//一个字节(8位)
#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
//一个字(32位)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
DMA_MemoryDataSize
指定内存数据宽度。
//一个字节(8位)
#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
//一个字(32位)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
DMA_Mode
指定DMAy信道x的操作模式。
//循环模式
#define DMA_Mode_Circular ((uint32_t)0x00000020)
//普通模式
#define DMA_Mode_Normal ((uint32_t)0x00000000)
DMA_Priority
指定DMAy信道x的软件优先级
//很高
#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)
DMA_M2M
指定DMAy通道x是否将在内存到内存传输中使用
//是
#define DMA_M2M_Enable ((uint32_t)0x00004000)
//不是
#define DMA_M2M_Disable ((uint32_t)0x00000000)
DMA串口编程的一般步骤
- 配置NVIC分组
- 初始化串口
- 使能相应的串口以及对应的GPIO时钟
- 配置NVIC结构体并应用
- 配置GPIO结构体并应用
- 配置Usart结构体并应用
- 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE)
- 开启DMA接收
- 使能串口
- 初始化DMA
- 使能相应的DMA时钟
- 配置DMA结构体并应用
- 使能DMA
- 编写串口中断函数
示例代码
根据以上步骤书写代码,因为分文件书写这样看着会有点麻烦,所以我就将示例写在一个文件中,望各位大佬见谅
说明:
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));
}
//字符串末尾加换行rn
char enter_str[] = {'r', 'n'};
for (i = 0; i < 2; i++)
{
usart_send_ch(USARTx, enter_str);
}
}
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个字符
PC端接收
36个字符加rn刚好38个字符,成功
接着又发送了长一点的数据,也没有任何问题
总结
使用DMA的好处就是可以不用占用CPU的资源,另外CPU进入串口的空闲中断(USART_IT_IDLE)会比串口的接收中断(USART_IT_RXNE)的次数要少。而且使用空闲中断完美的解决了接收任意长度的数据,如若大佬们认为以上代码有任何Bug或错请在评论指出,我也会不断的完善。
举报