STM32串口DMA发送&接收(1.5Mbps波特率)机制

数据拷贝过程中不需要CPU干预,数据拷贝结束则通知CPU处理。

以115200bps波特率,1s传输11520字节,大约69us需响应一次中断,如波特率再提高,将消耗更多CPU资源

高波特率场景下,串口非常有必要使用DMA。

关键步骤:

初始化串口

使能串口DMA接收模式,使能串口空闲中断、配置DMA参数

使能DMA通道buf半满(传输一半数据)中断、buf溢满(传输数据完成)中断

处理流程步骤应该是这样:

第一步,DMA先将数据搬运到buf1,搬运完成通知CPU来拷贝buf1数据;

第二步,DMA将数据搬运到buf2,与CPU拷贝buf1数据不会冲突;

第三步,buf2数据搬运完成,通知CPU来拷贝buf2数据;

执行完第三步,DMA返回执行第一步,一直循环。

STM32F0系列DMA不支持双缓存(以具体型号为准)机制,但提供了一个buf"半满中断",即是数据搬运到buf大小的一半时,可以产生一个中断信号。基于这个机制,我们可以实现双缓存功能,只需将buf空间开辟大一点即可。

第一步,DMA将数据搬运完成buf的前一半时,产生“半满中断”,CPU来拷贝buf前半部分数据

第二步,DMA继续将数据搬运到buf的后半部分,与CPU拷贝buf前半部数据不会冲突

第三步,buf后半部分数据搬运完成,触发“溢满中断”,CPU来拷贝buf后半部分数据

执行完第三步,DMA返回执行第一步,一直循环

UART2 DMA模式接收配置代码如下,与其他外设使用DMA的配置基本一致,留意关键配置:

  • 串口接收,DMA通道工作模式设为连续模式
  • 使能DMA通道接收buf半满中断、溢满(传输完成)中断
  • 启动DMA通道前清空相关状态标识,防止首次传输错乱数据
void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
   
	DMA_InitTypeDef DMA_InitStructure;
    DMA_DeInit(DMA1_Channel5); 
    DMA_Cmd(DMA1_Channel5, DISABLE);
    DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART2->RDR);/* UART2接收数据地址 */
    DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; /* 接收buf */
    DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralSRC; 	/* 传输方向:外设->内存 */
    DMA_InitStructure.DMA_BufferSize 			= mem_size; /* 接收buf大小 */
    DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
    DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
    DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
    DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode 					= DMA_Mode_Circular; /* 连续模式 */
    DMA_InitStructure.DMA_Priority 				= DMA_Priority_VeryHigh; 
    DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
    DMA_Init(DMA1_Channel5, &DMA_InitStructure); 
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、溢满、错误中断 */
    DMA_ClearFlag(DMA1_IT_TC5);	/* 清除相关状态标识 */
    DMA_ClearFlag(DMA1_IT_HT5);
    DMA_Cmd(DMA1_Channel5, ENABLE); 
    }
// DMA 错误中断“DMA_IT_TE”,一般用于前期调试使用,用于检查DMA出现错误的次数,发布软件可以不使能该中断。

接收处理

接收数据大小

数据传输过程是随机的,数据大小也是不定的,存在几类情况:

  • 数据刚好是DMA接收buf的整数倍,这是理想的状态
  • 数据量小于DMA接收buf或者小于接收buf的一半,此时会触发串口空闲中断

因此,我们需根据“DMA通道buf大小”、“DMA通道buf剩余空间大小”、“上一次接收的总数据大小”来计算当前接收的数据大小。

/* 获取DMA通道接收buf剩余空间大小 */
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

DMA通道buf溢满场景计算

接收数据大小 = DMA通道buf大小 - 上一次接收的总数据大小

DMA通道buf溢满中断处理函数:

void uart_dmarx_done_isr(uint8_t uart_id)
{
   
  	uint16_t recv_size;
	
	recv_size = s_uart_dev[uart_id].dmarx_buf_size - s_uart_dev[uart_id].last_dmarx_size;
	fifo_write(&s_uart_dev[uart_id].rx_fifo, 
				   (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
	s_uart_dev[uart_id].last_dmarx_size = 0;
}

DMA通道buf半满场景计算

接收数据大小 = DMA通道接收总数据大小 - 上一次接收的总数据大小
DMA通道接收总数据大小 = DMA通道Buf的大小 - DMA通道buf剩余空间大小

DMA通道buf半满中断处理函数:

void uart_dmarx_half_done_isr(uint8_t uart_id)
{
   
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	if(uart_id == 0)
	{
   
	  	recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();
	}
	else if (uart_id == 1)
	{
   
		recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();
	}
	recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;
	
	fifo_write(&s_uart_dev[uart_id].rx_fifo, 
				   (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
	s_uart_dev[uart_id].last_dmarx_size = recv_total_size;/* 记录接收总数据大小 */
}

串口空闲中断场景计算

串口空闲中断场景的接收数据计算与“DMA通道buf半满场景”计算方式是一样的。

串口空闲中断处理函数:

void uart_dmarx_idle_isr(uint8_t uart_id)
{
   
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	if(uart_id == 0)
	{
   
	  	recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();
	}
	else if (uart_id == 1)
	{
   
		recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();
	}
	recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;
	s_UartTxRxCount[uart_id*2+1] += recv_size;
	fifo_write(&s_uart_dev[uart_id].rx_fifo, 
				   (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
	s_uart_dev[uart_id].last_dmarx_size = recv_total_size;
}
// 注:串口空闲中断处理函数,除了将数据拷贝到串口接收fifo中,还可以增加特殊处理,如作为串口数据传输完成标识、不定长度数据处理等等。
接收数据偏移地址

将有效数据拷贝到fifo中,除了需知道有效数据大小外,还需知道数据存储于DMA 接收buf的偏移地址

有效数据偏移地址只需记录上一次接收的总大小即可,在DMA通道buf全满中断处理函数将该值清零,因为下一次数据将从buf的开头存储。

在DMA通道buf溢满中断处理函数中将数据偏移地址清零:

void uart_dmarx_done_isr(uint8_t uart_id)
{
   
 	/* todo */
	s_uart_dev[uart_id].last_dmarx_size = 0;
}
应用读取串口数据方法

经过前面的处理步骤,已将串口数据拷贝至接收fifo,应用程序任务只需从fifo获取数据进行处理前提:处理效率必须大于DAM接收搬运数据的效率,否则导致数据丢失或者被覆盖处理。

串口DMA发送

基本流程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值