当前位置: 代码网 > it编程>编程语言>Asp.net > STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号

STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号

2024年08月02日 Asp.net 我要评论
使用STM32串口DMA空闲中断接收数功能实现串口解析航模遥控器sbus信号。本文进一步梳理一下HAL库串口空闲中断三种不同的使用方式,其中前两种使用DMA方式,最后一种使用HAL库自带的空闲中断机制。

一. 问题描述

能够点进这篇文章的小伙伴肯定是对stm32串口dma空闲中断接收数据感兴趣的啦,今天用这一功能实现串口解析航模遥控器sbus信号时,查阅了很多网友发布的文章(勤劳的搬运工~),包括自己之前写过一篇博客 stm32_hal库_cubemx串口dma通信(dma发送+dma空闲接收不定长数据)。本文进一步梳理一下hal库串口空闲中断三种不同的使用方式,其中前两种使用dma方式,最后一种使用hal库自带的空闲中断机制。

二. 方法一——使用hal_uart_receive_dma

最常见的方法就是使用hal_statustypedef hal_uart_receive_dma(uart_handletypedef *huart, uint8_t *pdata, uint16_t size)这个库函数,其使用方法类似于hal_statustypedef hal_uart_receive_it(uart_handletypedef *huart, uint8_t *pdata, uint16_t size),初始化时需要调用一次,然后每次在中断服务函数里面处理完数据后重新调用一次。

使用hal_uart_receive_it只需要打开串口接收中断,即hal_nvic_enableirq(uart5_irqn);

使用hal_uart_receive_dma空闲中断需要在初始化时打开串口空闲中断使能,调用方式为:__hal_uart_enable_it(&huart2, uart_it_idle)

此时dma中断可开可不开,开了也不用管,因为数据处理是在串口空闲中断中进行的。

1. 串口初始化代码:

void mx_usart2_uart_init(void)
{
	huart2.instance = usart2;
	huart2.init.baudrate = 100000;
	huart2.init.wordlength = uart_wordlength_9b;
	huart2.init.stopbits = uart_stopbits_1;
	huart2.init.parity = uart_parity_even;
	huart2.init.mode = uart_mode_tx_rx;
	huart2.init.hwflowctl = uart_hwcontrol_none;
	huart2.init.oversampling = uart_oversampling_16;
	if (hal_uart_init(&huart2) != hal_ok)
	{
		error_handler();
	}

	__hal_uart_enable_it(&huart2, uart_it_idle);		// 开启串口空闲中断,必须调用
	hal_uart_receive_dma(&huart2,usart2_rx_buf,usart_rec_len);		// 启动dma接收
}

void hal_uart_mspinit(uart_handletypedef* uarthandle)
{
	if(uarthandle->instance==usart2)
	{
		 __hal_rcc_usart2_clk_enable();
		 __hal_rcc_gpioa_clk_enable();
		 
		 gpio_initstruct.pin = gpio_pin_2|gpio_pin_3;
		 gpio_initstruct.mode = gpio_mode_af_pp;
		 gpio_initstruct.pull = gpio_nopull;
		 gpio_initstruct.speed = gpio_speed_freq_very_high;
		 gpio_initstruct.alternate = gpio_af7_usart2;
		 hal_gpio_init(gpioa, &gpio_initstruct);
		
		 hdma_usart2_rx.instance = dma1_stream5;
		 hdma_usart2_rx.init.channel = dma_channel_4;
		 hdma_usart2_rx.init.direction = dma_periph_to_memory;
		 hdma_usart2_rx.init.periphinc = dma_pinc_disable;
		 hdma_usart2_rx.init.meminc = dma_minc_enable;
		 hdma_usart2_rx.init.periphdataalignment = dma_pdataalign_byte;
		 hdma_usart2_rx.init.memdataalignment = dma_mdataalign_byte;
		 hdma_usart2_rx.init.mode = dma_normal;
		 hdma_usart2_rx.init.priority = dma_priority_medium;
		 hdma_usart2_rx.init.fifomode = dma_fifomode_disable;
		 if (hal_dma_init(&hdma_usart2_rx) != hal_ok)
		 {
		   error_handler();
		 }
		
		 __hal_linkdma(uarthandle,hdmarx,hdma_usart2_rx);
		
		 /* usart2 interrupt init */
		 hal_nvic_setpriority(usart2_irqn, 0, 0);
		 hal_nvic_enableirq(usart2_irqn);
	}
}

初始化中调用__hal_uart_enable_it(&huart2, uart_it_idle)开启空闲中断,hal_uart_receive_dma(&huart2,usart2_rx_buf,usart_rec_len)启动dma串口接收,usart2_rx_buf是接收缓冲区,usart_rec_len是定义的dma接收缓冲区长度,接收的数据不能超过这个长度。

2. 中断处理:

中断处理有两种方式,第一种是直接定义在void usart1_irqhandler(void)中,第二种是自定义一个函数,然后在void usart1_irqhandler(void)中调用。注意网上很多资源自定义了中断回调函数但代码又没贴全,读者可能以为是hal库自带的回调函数,结果因为没有在void usart1_irqhandler(void)中调用导致失败。

第一种形式——直接定义:

void usart2_irqhandler(void)
{
	uint32_t tmp_flag ;
	u8 len;
	u8 data[25];
	tmp_flag  = __hal_uart_get_flag(&huart2, uart_flag_idle);

	if( tmp_flag  != reset)
	{
		__hal_uart_clear_idleflag(&huart2);//清除标志位
		hal_uart_dmastop(&huart2); //停止dma接收,防止数据出错
	
		len = usart_rec_len - __hal_dma_get_counter(&hdma_usart2_rx);// 获取dma中传输的数据个数
		
		// 以下为用户数据处理,将数据拷贝出去
		if(len == 25)
		{
			memcpy(data,usart2_rx_buf,len);
			update_sbus(data);
		}
		hal_uart_receive_dma(&huart2,usart2_rx_buf,usart_rec_len);   //打开dma接收,数据存入rx_buffer数组中。
	}
	
	hal_uart_irqhandler(&huart2); //调用hal库中断处理公用函数
}

第二种形式——自定义回调函数:

void hal_uart_idlecpltcallback(uart_handletypedef *huart)
{
	uint32_t tmp_flag ;
	u8 len;
	u8 data[25];

	if (huart->instance == usart2)
	{
		tmp_flag  = __hal_uart_get_flag(huart, uart_flag_idle);
		if( tmp_flag  != reset)
		{
			__hal_uart_clear_idleflag(huart);//清除标志位
			hal_uart_dmastop(huart); //停止dma接收
		
			len = usart_rec_len - __hal_dma_get_counter(&hdma_usart2_rx);// 获取dma中传输的数据个数
			
			// 以下为用户数据处理,将数据拷贝出去
			if(len == 25)
			{
				memcpy(data,usart2_rx_buf,len);
				update_sbus(data);
			}
			hal_uart_receive_dma(huart,usart2_rx_buf,usart_rec_len);   //打开dma接收,数据存入rx_buffer数组中。
		}
	}
}

void usart2_irqhandler(void)
{
	hal_uart_idlecpltcallback(&huart2)
	hal_uart_irqhandler(&huart2); //调用hal库中断处理公用函数
}

再次提醒一下,hal库中并没有类似void hal_uart_idlecpltcallback(uart_handletypedef *huart)这样的回调函数,大家不要看到了以为是自带的回调函数而不在void usart2_irqhandler(void)中调用而导致失败。

中断处理中的函数说明:
__hal_uart_get_flag(huart, uart_flag_idle):返回中断标志位,产生空闲中断后会返回1;
__hal_uart_clear_idleflag(&huart2):清除空闲中断标志位,必须调用;
hal_uart_dmastop(&huart2):停止dma接收,防止数据处理出错,数据处理完成后重新打开;
__hal_dma_get_counter(&hdma_usart2_rx):返回dma中未传输的数据量,用缓冲区总长减去未传输数量就是已接收的数据长度;
hal_uart_receive_dma(huart,usart2_rx_buf,usart_rec_len):重新打开dma接收。

以上就能成功实现不定长数据的接受了,其实只要步骤对了还是很简单的。

三. 方法二——使用hal_uartex_receivetoidle_dma

第二种方式是使用hal_statustypedef hal_uartex_receivetoidle_dma(uart_handletypedef *huart, uint8_t *pdata, uint16_t size)这个库函数,并重定义void hal_uartex_rxeventcallback(uart_handletypedef *huart, uint16_t size)这个中断回调函数。

attention:__weak void hal_uartex_rxeventcallback(uart_handletypedef *huart, uint16_t size)是正儿八经的库函数(弱函数),不是自定义的,我们可以重定义它。

使用这个库函数,串口初始化代码中就不需要调用__hal_uart_enable_it(&huart2, uart_it_idle)了,因为hal_uartex_receivetoidle_dma函数内部已经打开了空闲中断,当然你加上也没事。

1. 串口初始化代码:

void mx_usart2_uart_init(void)
{
	huart2.instance = usart2;
	huart2.init.baudrate = 100000;
	huart2.init.wordlength = uart_wordlength_9b;
	huart2.init.stopbits = uart_stopbits_1;
	huart2.init.parity = uart_parity_none;
	huart2.init.mode = uart_mode_tx_rx;
	huart2.init.hwflowctl = uart_hwcontrol_none;
	huart2.init.oversampling = uart_oversampling_16;
	if (hal_uart_init(&huart2) != hal_ok)
	{
		error_handler();
	}

	//__hal_uart_enable_it(&huart2, uart_it_idle);// 开启串口空闲中断,可省略
	hal_uartex_receivetoidle_dma(&huart2,usart2_rx_buf,usart_rec_len);		// 使用串口dma空闲中断,会自动开启空空闲中断
}

void hal_uart_mspinit(uart_handletypedef* uarthandle)
{
	if(uarthandle->instance==usart2)
	{
		 __hal_rcc_usart2_clk_enable();
		 __hal_rcc_gpioa_clk_enable();
		 
		 gpio_initstruct.pin = gpio_pin_2|gpio_pin_3;
		 gpio_initstruct.mode = gpio_mode_af_pp;
		 gpio_initstruct.pull = gpio_nopull;
		 gpio_initstruct.speed = gpio_speed_freq_very_high;
		 gpio_initstruct.alternate = gpio_af7_usart2;
		 hal_gpio_init(gpioa, &gpio_initstruct);
		
		 hdma_usart2_rx.instance = dma1_stream5;
		 hdma_usart2_rx.init.channel = dma_channel_4;
		 hdma_usart2_rx.init.direction = dma_periph_to_memory;
		 hdma_usart2_rx.init.periphinc = dma_pinc_disable;
		 hdma_usart2_rx.init.meminc = dma_minc_enable;
		 hdma_usart2_rx.init.periphdataalignment = dma_pdataalign_byte;
		 hdma_usart2_rx.init.memdataalignment = dma_mdataalign_byte;
		 hdma_usart2_rx.init.mode = dma_normal;
		 hdma_usart2_rx.init.priority = dma_priority_medium;
		 hdma_usart2_rx.init.fifomode = dma_fifomode_disable;
		 if (hal_dma_init(&hdma_usart2_rx) != hal_ok)
		 {
		   error_handler();
		 }
		
		 __hal_linkdma(uarthandle,hdmarx,hdma_usart2_rx);
		
		 /* usart2 interrupt init */
		 hal_nvic_setpriority(usart2_irqn, 0, 0);
		 hal_nvic_enableirq(usart2_irqn);
	}
}

这里__hal_uart_enable_it(&huart2, uart_it_idle)是可省略的,hal_uartex_receivetoidle_dma(&huart2,usart2_rx_buf,usart_rec_len)启动dma串口空闲中断接收,usart2_rx_buf是接收缓冲区,usart_rec_len是定义的dma接收缓冲区长度,接收的数据不能超过这个长度。

2. 中断处理:

中断处理定义在void hal_uartex_rxeventcallback(uart_handletypedef *huart, uint16_t size)这个中断回调函数中。

void hal_uartex_rxeventcallback(uart_handletypedef *huart, uint16_t size)
{
	u8 len;
	u8 data[25];
	if(huart->instance == usart2)
	{
		hal_uart_dmastop(huart);
		len = usart_rec_len - __hal_dma_get_counter(&hdma_usart2_rx);// 获取dma中传输的数据个数
		if (usart2_rx_buf[0] == 0x0f && len == 25)	//接受完一帧数据
		{
			memcpy(data,usart2_rx_buf,len);
			update_sbus(data);
		}
		hal_uartex_receivetoidle_dma(&huart2,usart2_rx_buf,usart_rec_len);		// 再次开启dma空闲中断
	}
}	

void usart2_irqhandler(void)
{
	hal_uart_irqhandler(&huart2); //调用hal库中断处理公用函数
}

当然也可以像方法一种一样,中断逻辑处理直接定义在void usart2_irqhandler(void)中。

四. 方法三——使用hal_uartex_receivetoidle_it(不使用dma)

这种方法参考这篇文章: stm32 hal库串口空闲中断最新用法

使用方法:
1、在主函数中调用hal_uartex_receivetoidle_it()
2、在回调函数hal_uartex_rxeventcallback()中做中断逻辑处理。

这里我换用串口4,实现上位机发送,单片机原封不动返回数据。

1. 串口初始化代码:

void mx_uart4_init(void)
{
	huart4.instance = uart4;
	huart4.init.baudrate = 115200;
	huart4.init.wordlength = uart_wordlength_8b;
	huart4.init.stopbits = uart_stopbits_1;
	huart4.init.parity = uart_parity_none;
	huart4.init.mode = uart_mode_tx_rx;
	huart4.init.hwflowctl = uart_hwcontrol_none;
	huart4.init.oversampling = uart_oversampling_16;
	if (hal_uart_init(&huart4) != hal_ok)
	{
		error_handler();
	}
	// hal_uartex_receivetoidle_it同时开启空闲中断,开启接收
	hal_uartex_receivetoidle_it(&huart4,usart4_rx_buf,usart_rec_len);
}

void hal_uart_mspinit(uart_handletypedef* uarthandle)
{
	if(uarthandle->instance==uart4)
	{
		 __hal_rcc_uart4_clk_enable();
	    __hal_rcc_gpioc_clk_enable();
	  
	    gpio_initstruct.pin = gpio_pin_10|gpio_pin_11;
	    gpio_initstruct.mode = gpio_mode_af_pp;
	    gpio_initstruct.pull = gpio_pullup;
	    gpio_initstruct.speed = gpio_speed_freq_very_high;
	    gpio_initstruct.alternate = gpio_af8_uart4;
	    hal_gpio_init(gpioc, &gpio_initstruct);
	
	    hal_nvic_setpriority(uart4_irqn, 0, 0);
	    hal_nvic_enableirq(uart4_irqn);
	}
}

可以看到这里是没有使用dma的,初始化中调用了hal_uartex_receivetoidle_it(&huart4,usart4_rx_buf,usart_rec_len)来开启串口4的空闲中断,usart4_rx_buf是接收缓冲区,usart_rec_len是缓冲区的长度。这里可以看下库中hal_uartex_receivetoidle_it的定义和提示:

在这里插入图片描述

框1的地方说,这个函数用于在中断模式下接收一定量数据,直到指定的数据长度被接收到或者空闲中断产生。也就是说,如果usart_rec_len定义很小,还没到产生空闲中断,接收就会完成。

框2的地方,即是打开串口空闲中断。所以不需要额外再去开启空闲中断了。

2. 中断处理:

中断处理定义在void hal_uartex_rxeventcallback(uart_handletypedef *huart, uint16_t size)这个中断回调函数中。注意到,和使用dma传输时是同一个回调函数。

void hal_uartex_rxeventcallback(uart_handletypedef *huart, uint16_t size)
{
	u8 len=0;
	u8 data[25];
	u8 uart4_len=0;
	u8 uart4_data[50];
	if(huart->instance == usart2)
	{
		hal_uart_dmastop(huart);
		len = usart_rec_len - __hal_dma_get_counter(&hdma_usart2_rx);// 获取dma中传输的数据个数
		if (usart2_rx_buf[0] == 0x0f && len == 25)	//接受完一帧数据
		{
			memcpy(data,usart2_rx_buf,len);
			update_sbus(data);
		}
		hal_uartex_receivetoidle_dma(&huart2,usart2_rx_buf,usart_rec_len);		// 再次开启dma空闲中断
	}
	
	if(huart->instance == uart4)
	{		
		while (usart4_rx_buf[uart4_len] != '\0')	uart4_len++;
		memcpy(uart4_data,usart4_rx_buf,uart4_len);
		hal_uart_transmit(&huart4,uart4_data,uart4_len,0xff);
		
		// 调用printf是使用串口1
		printf("data: %s\r\n", uart4_data);	
		printf("data length: %d\r\n", uart4_len);	
		
		memset(usart4_rx_buf, 0, usart_rec_len); 
		memset(uart4_data, 0, sizeof(uart4_data)); 

		hal_uartex_receivetoidle_it(&huart4,usart4_rx_buf,usart_rec_len);
	}
}	

void usart2_irqhandler(void)
{
	hal_uart_irqhandler(&huart2); //调用hal库中断处理公用函数
}

void uart4_irqhandler(void)
{
	hal_uart_irqhandler(&huart4); //调用hal库中断处理公用函数
}

回调函数中就是读取usart4_rx_buf缓冲区的数据和数据长度,然后做相应处理,这里只是简单的原数据发送回去,如果是sbsu信号或modbus或其他带协议帧的数据,则可根据帧头帧尾、数据长度、校验位等做进一步处理。

最终实现的效果就是上位机发送什么,单片机返回什么,如图:

在这里插入图片描述

五. 总结

以上三种方式都能实现串口空闲中断接收不定长数据,适合用于处理不定长数据接收、减少cpu负担,相比较而言,方法三不使用dma,使用上更简洁。方法一、二使用dma,好处是可以减少cpu负担。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com