摘要
DMA叫做直接内存访问,是不通过CPU进行对内存的操作的技术,可以增加效率
USART1是用于通信的串口,我们要做的就是将USART1传入的不定大小的数据用DMA传输到我们指定的数组中
芯片STM32F103C8T6 使用了USART1的USART_IT_IDLE
中断
做法
先用DMA获取USART输入的数据,这需要我们准备一个足够大的接收缓冲数组例如我定义了一个256字节的数组
接下来,我们通过DMA的DMA_GetCurrDataCounter()
函数获取剩余的计数量,来计算我们传入了多少个字节的数据:
传入数据量 = 256 - 剩余计数量
这个传入数据量也就是我们DMA接收到的数据大小,单位是字节,一个字节是8个bit位
然后我们可以向串口输出我们接收到的数据。
程序主要代码
#include <stm32f10x.h>
#include <string.h>
#include <stdlib.h>
#define Data_RX_SIZE 256
char buf[Data_RX_SIZE]; /*定义DMA接收缓存区*/
/*************************USART1******************************/
void uart_Init(uint32_t BaudRate)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /*设置中断优先级组*/
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); /* 开启串口1时钟 */
USART_InitStruct.USART_BaudRate = BaudRate;/* 比特率 */
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;/* 全双工模式 */
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);/* 串口配置初始化 */
GPIO_init_With_Mode(GPIOA,GPIO_Pin_9,GPIO_Mode_AF_PP); /* PA9 复用推挽输出*/
GPIO_init_With_Mode(GPIOA,GPIO_Pin_10,GPIO_Mode_IN_FLOATING); /*PA10 浮空输入*/
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);/* 中断配置初始化 */
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
/********************************************
*开启空闲检测中断,例如你向串口传入"Hello"
*当Hello传递完了之后才会触发这个中断
*而不是传个H触发一次,传个e再触发一次
********************************************/
USART_Cmd(USART1,ENABLE); /*使能USART*/
DMA1_init((uint32_t)(&(USART1->DR)),(uint32_t)buf,Data_RX_SIZE); /*自定义DMA1函数,初始化DMA1*/
}
/**************************DMA1********************************/
void DMA1_init(uint32_t AddrSend,uint32_t AddrReceive,uint16_t size)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); /*使能DMA1时钟*/
NVIC_InitTypeDef NVIC_InitStruct;
DMA_InitTypeDef DMA_InitStruct;
NVIC_Init(&NVIC_InitStruct);
/* DMA外设方面设置 */
DMA_InitStruct.DMA_PeripheralBaseAddr = AddrSend;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/* DMA内存方面设置 */
DMA_InitStruct.DMA_MemoryBaseAddr = AddrReceive;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_BufferSize = size; /*DMA缓冲区大小!*/
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; /*DMA传输方向,以外设为Srouce(源头)*/
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; /*禁止软件触发,因为我们用USART的接收触发方式,属于硬件触发方式,也就是非M2M方式*/
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; /*普通模式,需要手动去重设计数器*/
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; /*优先级,随便设置,反正也就一个DMA*/
DMA_Init(DMA1_Channel5, &DMA_InitStruct);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); /*USART的DMA使能,使用读取信息给DMA*/
DMA_Cmd(DMA1_Channel5,ENABLE); /*DMA1的通道5使能,在stm32f103中DMA1的通道五对应USART1的数据读取寄存器DR*/
}
/*********************USAER1中断***********************/
void USART1_IRQHandler()
{
if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET) //检查中断是否发生
{
volatile u8 temp;
temp=USART1->SR;
temp=USART1->DR; /*清除IDLE中断需要读取两个寄存器的信息*/
DMA_Cmd(DMA1_Channel5,DISABLE); /*关闭DMA传输,进行数据处理,数据处理完再开启*/
uint16_t length = Data_RX_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5); /*当前帧 数据长度*/
DMA_Show(length); /*展示DMA接收到的数据*/
DMA_SetCurrDataCounter(DMA1_Channel5,Data_RX_SIZE); /*重新给计数器赋值*/
DMA_Cmd(DMA1_Channel5,ENABLE); /*重新打开计数器*/
USART_ClearFlag(USART1,USART_FLAG_IDLE); /*清除串口空闲中断标志位*/
}
}
/************************DMA_Show()************************/
void DMA_Show(uint16_t len)
{
uart_cout("此时buf数组中接收到的值为:");
char * Temp_buf = (char*)malloc(len); /*开辟堆区空间,用于选择合适的输出范围*/
strncpy(Temp_buf,buf,len); /*将大小固定的数据写入*/
uart_cout(Temp_buf); /* 输出数据 */
uint16_t Remaining_Counts = DMA_GetCurrDataCounter(DMA1_Channel5); /*获取当前DMA缓存空间剩余*/
uart_cout_add_variable("DMA剩余空间大小(字节):",Remaining_Counts);
uint16_t Counts = Data_RX_SIZE - Remaining_Counts; /*获取数据大小*/
uart_cout_add_variable("接收到数据大小(字节):", Counts);
memset(Temp_buf,0,len); /*清除Temp_buf,防止前后传入的两次数据相互叠加*/
free(Temp_buf); /*释放堆区*/
Temp_buf = NULL; /* 将指针设置成NULL,避免悬挂指针*/
}
/************************main************************/
int main(){
uart_Init(115200); /*因为使用的中断,所以主函数里面只需要初始化就可以了*/
while(1);
}
程序次要代码
GPIO_init_With_Mode
void GPIO_init_With_Mode(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin , GPIOMode_TypeDef GPIO_Mode)
{
GPIO_InitTypeDef GPIO_InitStruct;
/*判断GPIO,不同的GPIO给不同的时钟*/
if(GPIOx == GPIOA)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
}
else if(GPIOx == GPIOB)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
}
else if(GPIOx == GPIOC)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
}
GPIO_InitStruct.GPIO_Mode = GPIO_Mode;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOx, &GPIO_InitStruct);
}
uart_cout
//传入数组指针,通过串口1输出数组
void uart_cout(const char* str)
{
unsigned int len = strlen(str);
delay_us(100);
for(int i = 0; i < len ; i++)
{
USART_SendData(USART1,str[i]);
delay_us(100); // 延迟用于等待串口输出信息
}
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
delay_us
void delay_us(uint16_t xus)
{
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // 使能 TIM1 时钟
TIM1->PSC = SystemCoreClock/ 1000000 - 1; // 设置预分频器的值,实现 1 秒内的计数次数
TIM1->ARR = xus * 2000 - 1; // 设置自动重载寄存器的值,实现指定的延迟时间
TIM1->CNT = 0; // 将计数器的值清零
TIM1->CR1 |= TIM_CR1_CEN; // 启动计数器
while ((TIM1->SR & TIM_SR_UIF) == 0); // 等待计数器溢出中断
TIM1->SR &= ~TIM_SR_UIF; // 清除计数器溢出标志位
TIM1->CR1 &= ~TIM_CR1_CEN; // 停止计数器
RCC->APB2ENR &= ~RCC_APB2ENR_TIM1EN; // 关闭 TIM1 的时钟
}
测试效果
![](https://i-blog.csdnimg.cn/blog_migrate/4df243f87ecda6e9cbc0c002f4bd21a2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4df7acc456d38879eb12755f8eda3cc5.png)
参考:
田野的小站