65 STM32F0系列 串口DMA循环接收实验记录

针对STM32F030单片机在串口接收数据时遇到的中断冲突导致数据丢失问题,本文介绍了通过使用DMA方式来避免这一情况。详细阐述了配置DMA接收的要点,包括理解DMA的工作模式,选择适当的通道,以及在没有串口空闲中断的情况下,如何在while(1)循环中接收DMA数据。同时提供了具体的STM32F030串口及DMA初始化代码示例。

1.引言

        最近因为中断冲突问题,导致串口接收数据时随机丢失一两个字节。无奈串口的中断优先级不能是最高的,所以中断冲突问题明显存在,因此需要串口使用DMA方式来接收数据,从而规避串口接收数据丢失问题。

2.DMA配置经验

        首次使用dma,需要了解一下dma的知识。

        1.首先最重要要了解的一点就是,dma接收数据一定要接收到指定长度的数据,或指定长度的一半数据才能产生接收中断,如果数据没有达到一半或指定长度,就无法产生dma接收数据通知。

        一般解决这个问题是串口空闲中断IDE方式解决。也就是串口传完数据产生空闲中断后去读取DMA收到的数据,就能接收不定长度的数据了。

        然而现实很悲催,我所使用的cortex-m0芯片,居然没有串口空闲中断,为此需要另寻它法。所以就选择了在while(1)循环中接收DMA数据了。

        2.既然配置了串口的DMA接收,因此就不需要启动串口接收中断了,但串口的收发模式配置还是需要的。如下:

UART_InitStructure.UART_Mode		= UART_Mode_Rx | UART_Mode_Tx; //收发模式

        3.DMA模式选择

        DMA_Mode_Normal和DMA_Mode_Circular模式,指的是单次和多次的问题。如果是DMA_Mode_Normal模式,进行完一次DMA数据传输后,要启动第二次DMA传输,需先关闭该DMA通道,重新设置DMA传输数据个数,再开启DMA通道。而DMA_Mode_Circular模式则不需要,其会自动配置。

         下述代码中有这个体现,DMA_Mode_Normal模式下如果不重新配置,则收发就不正常。

        4.查看串口对应的DMA channel

        对于STM32F030单片机UART1引脚对应的DMA channel通道,可以看STM32F030参考手册,而不是芯片手册!!!!别找错文档了。然后查所使用的的引脚对应的通道选择就行。

在这里插入图片描述         我所使用的是USART1,并且没有重映射RX到其它引脚,因此是channel3,这个实际的时候要注意,别搞错了,当然搞错了也没关系,试一下就好了,花不了什么时间。

 

3.实验代码

#define UART1_RX_LEN		(64)
uint8_t tUart1_Rx[UART1_RX_LEN] = {0};


void UART1_Init(void)
{
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};
	UART_InitTypeDef UART_InitStructure = {0};

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);	//使能UART1,GPIOA时钟
	/////////////////////////////////////////////////////////////////
	//1.引脚初始化
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_0);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_0);

	GPIO_InitStructure.GPIO_Pin		= GPIO_Pin_6; //PA.9
	GPIO_InitStructure.GPIO_Speed	= GPIO_Speed_2MHz;
	GPIO_InitStructure.GPIO_Mode	= GPIO_Mode_AF_PP; //复用推挽输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOA.9
	
	GPIO_InitStructure.GPIO_Pin 	= GPIO_Pin_7;//PA10
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_2MHz;
	GPIO_InitStructure.GPIO_Mode	= GPIO_Mode_IPU;//浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOA.10 

	/////////////////////////////////////////////////////////////////
	//2.串口初始化
	UART_InitStructure.UART_BaudRate 	= 115200;//串口波特率
	UART_InitStructure.UART_WordLength	= UART_WordLength_8b;//字长为8位数据格式
	UART_InitStructure.UART_StopBits	= UART_StopBits_1;//一个停止位
	UART_InitStructure.UART_Parity		= UART_Parity_No;//无奇偶校验位
	UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;//无硬件数据流控制
	UART_InitStructure.UART_Mode		= UART_Mode_Rx | UART_Mode_Tx; //收发模式
	UART_Init(UART1, &UART_InitStructure); //初始化串口1
	UART_DMACmd(UART1, UART_DMAReq_EN, ENABLE); 
	UART_Cmd(UART1, ENABLE);

	//dma配置
    UART1_DMAInit();
}

void UART1_DMAInit(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	/////////////////////////////////////////////////////////////////
	//dma初始化 p -> m
	DMA_DeInit(DMA1_Channel3);	
	DMA_Cmd(DMA1_Channel3, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&UART1->RDR;//外设数据地址
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)tUart1_Rx;  //自己的接收buf
	DMA_InitStructure.DMA_BufferSize			= UART1_RX_LEN;  // 缓存大小
	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通道优先级
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable;// 内存地址递增
	DMA_InitStructure.DMA_PeripheralInc			= DMA_PeripheralInc_Disable;//外设基地址不需要递增
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte;
	DMA_Init(DMA1_Channel3,&DMA_InitStructure);
	DMA_Cmd(DMA1_Channel3, ENABLE); 
	/////////////////////////////////////////////////////////////////
}

//因为该单片机没有串口空闲中断
//所以只能使用poll接收dma数据
//该函数放在while(1)中即可
void UART1_DMAChannel3_rcvTask(void)
{
	uint16_t rxSize = 0;

	//获取当前接收到的数据大小
	rxSize = UART1_RX_LEN - DMA_GetCurrDataCounter(DMA1_Channel3);
	//如果数据大于0,说明开始接收数据了
	if(rxSize>0)
	{
		//休眠10ms,是因为让串口dma继续接收数据
		//根据115200波特率计算,10ms足够传输1024字节大小的数据
		//所以delay 10ms基本满足单次数据的收
		delay_ms(50);
		DMA_Cmd (DMA1_Channel3,DISABLE);//重新配置DMA时,需要先disableDMA

		//接收完成,再获取一次实际的数据长度
		rxSize = UART1_RX_LEN - DMA_GetCurrDataCounter(DMA1_Channel3);

		//接收数据,写入buf
		BleClient_RcvDataBufIn(tUart1_Rx, rxSize);
		
		memset(tUart1_Rx,0,UART1_RX_LEN);    //清空数组
		DMA_Init(DMA1_Channel3, &DMA_InitStructure); //重新配置一次DMA
		DMA_Cmd (DMA1_Channel3,ENABLE); 
	}
}

over!

### 3.1 配置串口DMA循环接收模式 在STM32中,使用串口DMA循环接收模式可以实现高效、连续的数据接收。该模式下,DMA会在预设的缓冲区内循环写入数据,适用于需要持续接收数据的应用场景。 在配置DMA时,需要启用循环模式(Circular Mode),这样DMA会在缓冲区填满后自动从头开始覆盖写入[^1]。具体配置包括设置DMA方向为外设到内存、选择合适的DMA通道和流、配置缓冲区大小等。 以下是一个典型的DMA配置代码示例: ```c // 初始化DMA结构体 DMA_InitTypeDef DMA_InitStruct; // 设置DMA通道 DMA_InitStruct.DMA_Channel = DMA_Channel_4; // 设置DMA方向为外设到内存 DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; // 设置DMA缓冲区大小 DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE; // 设置DMA外设地址(串口数据寄存器) DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // 设置DMA内存地址(接收缓冲区) DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)rx_buffer; // 启用DMA循环模式 DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 设置DMA外设数据大小为字节 DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 设置DMA内存数据大小为字节 DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 设置DMA优先级为中等 DMA_InitStruct.DMA_Priority = DMA_Priority_Medium; // 禁用内存到内存传输 DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 禁用外设增量模式(外设地址固定) DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 初始化DMA通道 DMA_Init(DMA1_Stream5, &DMA_InitStruct); // 启用DMA通道 DMA_Cmd(DMA1_Stream5, ENABLE); ``` ### 3.2 串口配置 在使用DMA进行串口接收时,需要正确配置串口收发模式。串口接收功能必须启用,并且DMA请求也需要被激活。在STM32F0系列中,可以按如下方式配置串口: ```c // 配置串口参数 UART_InitTypeDef UART_InitStruct; // 设置波特率 UART_InitStruct.UART_BaudRate = 115200; // 设置数据位为8位 UART_InitStruct.UART_WordLength = UART_WordLength_8b; // 设置停止位为1位 UART_InitStruct.UART_StopBits = UART_StopBits_1; // 设置校验位为无 UART_InitStruct.UART_Parity = UART_Parity_No; // 设置模式为收发双工 UART_InitStruct.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; // 设置硬件流控制为无 UART_InitStruct.UART_HardwareFlowControl = UART_HardwareFlowControl_None; // 初始化串口 UART_Init(USART1, &UART_InitStruct); // 启用串口DMA接收请求 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); // 启用串口 UART_Cmd(USART1, ENABLE); ``` ### 3.3 数据处理与读取 在DMA循环接收模式下,应用程序需要定期检查DMA缓冲区的状态,以获取新接收数据。可以通过计算DMA当前传输的地址偏移量来判断接收了多少数据STM32提供了一个寄存器用于读取DMA剩余传输数量(`DMA_GetCurrDataCounter()`)[^1]。 例如,可以使用以下逻辑来获取当前接收数据量: ```c // 获取DMA当前剩余传输数量 uint16_t remaining = DMA_GetCurrDataCounter(DMA1_Stream5); // 计算已接收数据量 uint16_t received = BUFFER_SIZE - remaining; ``` 通过定期读取并处理这些数据,可以实现高效的串口通信。需要注意的是,由于DMA缓冲区是循环使用的,因此必须确保数据处理速度足够快,以避免数据被覆盖。 ### 3.4 中断与错误处理 虽然DMA循环接收模式通常不需要启用串口接收中断,但仍然建议启用DMA传输完成中断或半传输中断,以便在数据到达一定量时及时处理[^1]。此外,串口通信过程中可能会发生错误(如帧错误、溢出错误等),应在代码中添加相应的错误处理机制。 例如,可以在DMA中断服务函数中添加数据处理逻辑: ```c void DMA1_Stream5_IRQHandler(void) { // 检查DMA传输完成标志 if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5)) { // 处理接收到的数据 ProcessReceivedData(); // 清除中断标志 DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5); } } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值