GD32F470之串口空闲中断+DMA篇

本文详细介绍了如何在STM32F470芯片上使用Keil进行C++编程,并通过取消MicroLIB支持来重定向printf函数。同时,结合串口2和DMA实现数据收发,包括USART配置、DMA通道设定、中断处理等关键步骤,以确保串口通信的稳定和高效。
摘要由CSDN通过智能技术生成

先申请,本栏目用的都是GD32F470芯片240M,软件用的是keil,编写用的是C++(其实和C没有区别).

C++格式下怎么使用printf

因为keil下使用C++,要把Micro LIB去掉 ,所以printf要重新重定向
在这里插入图片描述
看下面的代码,是很久之前 抄某一个大神的,

#pragma import(__use_no_semihosting)    

namespace std{   
    struct __FILE
      {
        int handle;
      };
 
    FILE __stdout;
    FILE __stdin;
    FILE __stderr;
   

 
    int fputc(int ch, std::FILE *f)
      {
					
					while(RESET == usart_flag_get(USART2, USART_FLAG_TBE));
						usart_data_transmit(USART2, (uint8_t)ch);
					return ch;

      }


   
    int ferror(FILE *stream)
      {
        /* Your implementation of ferror(). */
        return 0;
      }
 

   
    int fflush(FILE *f)
      {
        /* Your implementation of fflush(). */
        return 0;
      }
  
	extern "C" void  _sys_exit(int) 
	  {
        /* declared in <stdlib.h> */
       // abort();
        while(1);
      }
    
	extern "C" void _ttywrch(int ch) 
 	  {
		   
					while(RESET == usart_flag_get(USART2, USART_FLAG_TBE));
				usart_data_transmit(USART2, (uint8_t)ch);
					//return ch;
	            return ;
      } 
}

然后下面是usart的内容
直接上代码

new一个对象

Usart_class 	*Usart1_Obj 	= new Usart_class(USART2,115200);
Global_Usart1_Obj=Usart1_Obj;

IO配置

:我用的是串口2,然后配置成推挽,备用功能(复用功能),上拉。
具体的IO口配置看我的GPIO篇

void Usart_class::usart_io_init(void)
{
	
	if(__usart_periph == USART2)
	{
		rcu_periph_clock_enable(RCU_GPIOD);

		// connect port to USARTx_Tx,USARTx_Rx
		gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_8|GPIO_PIN_9);

		// configure USART Tx as alternate function push-pull */
		gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_8|GPIO_PIN_9);
		gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_8|GPIO_PIN_9);
	}
	
}

Usart配置:

在这里,开启空闲中断和使能时钟,配置dma和使能发送接收,最后开启串口

要注意的是,usart_deinit函数要放在usart_interrupt_enable函数前面,不然不工作。

void Usart_class::usart_init()
{
	if(__usart_periph == USART2)
	{
		rcu_periph_clock_enable(RCU_USART2);
		nvic_irq_enable(USART2_IRQn, 1, 1);
	}
	else
		return;
	
	usart_io_init();
	
	usart_deinit(__usart_periph); //取消初始化要放在中断usart_interrupt_enable前面
	

	usart_interrupt_enable(__usart_periph, USART_INT_IDLE);//空闲中断
	
	
	usart_baudrate_set(__usart_periph,__baudval);
	
	usart_receive_config(__usart_periph, USART_RECEIVE_ENABLE);
	usart_transmit_config(__usart_periph, USART_TRANSMIT_ENABLE);
	
	usart_dma_recv_config();
	
	usart_enable(__usart_periph);

}

DMA配置:

发送DMA配置:

注意DMA的通道即可,发送是通道3,最前面的通道对应函数
dma_channel_subperipheral_select(DMA0, DMA_CH3, DMA_SUBPERI4);
的最后一个参数DMA_SUBPERI4。

在这里插入图片描述

void Usart_class::usart_dma_Send_config(void)
{
		if(__usart_periph == USART2)
	{
		dma_single_data_parameter_struct dma_init_struct_send;
		
		// enable DMA0
		rcu_periph_clock_enable(RCU_DMA0);
		
	    dma_deinit(DMA0, DMA_CH3);
    dma_init_struct_send.direction = DMA_MEMORY_TO_PERIPH;
    dma_init_struct_send.memory0_addr = (uint32_t)usart_sendbuf;//类成员变量
    dma_init_struct_send.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
		dma_init_struct_send.periph_memory_width = DMA_MEMORY_WIDTH_8BIT;
    dma_init_struct_send.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct_send.number = Usart_SendLen;//类成员变量
    dma_init_struct_send.periph_addr = USART2_DATA_ADDRESS;
    dma_init_struct_send.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct_send.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH3, &dma_init_struct_send);
    /* configure DMA mode */
    dma_circulation_disable(DMA0, DMA_CH3);
    dma_channel_subperipheral_select(DMA0, DMA_CH3, DMA_SUBPERI4);
    /* enable DMA channel_3 */
    dma_channel_enable(DMA0, DMA_CH3);
	usart_dma_transmit_config(__usart_periph, USART_DENT_ENABLE);
		     /* wait DMA Channel transfer complete */

	//	while(RESET == dma_flag_get(DMA0, DMA_CH3, DMA_INTF_FTFIF));

	}

}

这里的内存地址是 uint8_t usart_sendbuf[USART_MAX_LEN];
USART_MAX_LEN = 256

硬件地址是
在这里插入图片描述#define USART2_DATA_ADDRESS ((uint32_t)0x40004800 + 0x04)

发送的字节Usart_SendLen是不定的,需要你每次发送数据时对Usart_SendLen赋值,然后都要执行下面这一整段代码来配置dma,才能发送出去
usart_dma_transmit_config(__usart_periph, USART_DENT_ENABLE);
这句库函数代码就是使能发送

在这里插入图片描述

在这里插入图片描述

void usart_dma_transmit_config(uint32_t usart_periph, uint32_t dmacmd)
{
    uint32_t ctl = 0U;

    ctl = USART_CTL2(usart_periph);
    ctl &= ~USART_CTL2_DENT;
    ctl |= dmacmd;
    /* configure DMA transmission */
    USART_CTL2(usart_periph) = ctl;
}

接收DMA

注意要我代码的注释:
dma_init_struct_recv.number = USART_MAX_LEN; //使用宏,不像发送那样有几个才配置几个,是程序一开始就配置了,然后在接收中断那里接收完后再配置

这是能不断用DMA接收的原因。

//配合串口的空闲中断
void Usart_class::usart_dma_recv_config(void)
{
	dma_single_data_parameter_struct  dma_init_struct_recv;
	if(__usart_periph == USART2)
	{
			// enable DMA0
		rcu_periph_clock_enable(RCU_DMA0);
			dma_deinit(DMA0, DMA_CH1);
		dma_init_struct_recv.direction = DMA_PERIPH_TO_MEMORY;
		dma_init_struct_recv.memory0_addr = (uint32_t)usart_recebuf;
		dma_init_struct_recv.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
		dma_init_struct_recv.periph_memory_width = DMA_MEMORY_WIDTH_8BIT;
		dma_init_struct_recv.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
		dma_init_struct_recv.number = USART_MAX_LEN; //使用宏,不像发送那样有几个才配置几个,是程序一开始就配置了,然后在接收中断那里接收完后再配置
		dma_init_struct_recv.periph_addr = USART2_DATA_ADDRESS;
		dma_init_struct_recv.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	
		dma_init_struct_recv.priority = DMA_PRIORITY_ULTRA_HIGH;
		dma_single_data_mode_init(DMA0, DMA_CH1, &dma_init_struct_recv);
		/* configure DMA mode */
		dma_circulation_disable(DMA0, DMA_CH1);
		dma_channel_subperipheral_select(DMA0, DMA_CH1, DMA_SUBPERI4);
		/* enable DMA channel2 */
		dma_channel_enable(DMA0, DMA_CH1);
		usart_dma_receive_config(__usart_periph, USART_DENR_ENABLE);
	//	 while(RESET == dma_flag_get(DMA0, DMA_CH1, DMA_INTF_FTFIF));
	}

}

中断函数:

这里dma_transfer_number_get的意思是还剩下多少个没接收到,例如DMA配置256个字节,那么接收了10个,那么dma_transfer_number_get就返回256-10=246.
然后我再用256 - 246 =10,那么10就是我接收到的数据的个数。

然后把数据复制出来,最后重新配配置接收DMA。

这样,只有当空闲的时候(两个位的时间 间隙),才进中断.

dma_USART_RX_NUM = dma_transfer_number_get(DMA0, DMA_CH1);
USART_RX_NUM = USART_MAX_LEN - dma_transfer_number_get(DMA0, DMA_CH1);
//空闲中断
extern "C" void USART2_IRQHandler(void)
{
			uint8_t rxbuffer_real[USART_MAX_LEN];
				dma_single_data_parameter_struct dma_init_struct;
				static uint16_t USART_RX_NUM=0,dma_USART_RX_NUM=0;;
	
	    if((RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_IDLE)) && 
       (RESET != usart_flag_get(USART2, USART_FLAG_IDLE)))
			{
				
				dma_flag_clear(DMA0, DMA_CH1, DMA_INTF_FTFIF);
				usart_data_receive(USART2);								/* 清除接收完成标志位 */
				dma_channel_disable(DMA0, DMA_CH1);						/* 关闭DMA传输 */
				
				dma_USART_RX_NUM = dma_transfer_number_get(DMA0, DMA_CH1);
				USART_RX_NUM = USART_MAX_LEN - dma_transfer_number_get(DMA0, DMA_CH1);
						
				mymemcpy(rxbuffer_real,Global_Usart1_Obj->usart_recebuf,USART_RX_NUM); /* 转存数据到待处理数据缓冲区*/
				printf("%s\r\n",rxbuffer_real);
				rxbuffer_real[USART_RX_NUM] = '\0';	/* 添加字符串结束符 */
						/* 重新设置DMA传输 */
				dma_deinit(DMA0, DMA_CH1);
        		dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
        		dma_init_struct.memory0_addr = (uint32_t)Global_Usart1_Obj->usart_recebuf;
        		dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
		        dma_init_struct.periph_memory_width = DMA_MEMORY_WIDTH_8BIT;
		        dma_init_struct.number = USART_MAX_LEN;
		        dma_init_struct.periph_addr = USART2_DATA_ADDRESS;
		        dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
		        dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
		        dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
		        dma_single_data_mode_init(DMA0, DMA_CH1, &dma_init_struct);
		        /* configure DMA mode */
		        dma_circulation_disable(DMA0, DMA_CH1);
		        dma_channel_subperipheral_select(DMA0, DMA_CH1, DMA_SUBPERI4);
		        /* enable DMA channel2 */
		        dma_channel_enable(DMA0, DMA_CH1);
				usart_dma_receive_config(USART2, USART_DENR_ENABLE);
				

			}	
}

注意:这里有一个小BUG:

就是当一次性接收的数据大于你的DMA配置的个数。
例如配置了接收10个,可是别人发了10个 以上的数据,那么
情况1:如果一直发10个以上的话,那么数据会错乱,最后一个数据会覆盖第一个数据
情况2:如果之后是10个以内,那么第一次会错乱,然后之后就会正常。

这个原因可能是DMA溢出错误了,我没有去处理的原因 。
有一种办法是,把 DMA的配置个数改得很大,那么就不容易出现,
还有一种就是新加个DMA溢出中断.(还没实现)

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GD32F470是一款基于ARM Cortex-M4内核的微控制器,支持DMA(直接内存访问)功能。DMA可以在不干扰CPU的情况下,直接将数据从外设传输到内存或者从内存传输到外设。这样可以大大提高数据传输的效率,减少CPU的负担。下面是GD32F470 DMA的一些介绍和示例代码: 1. GD32F470支持多个DMA通道,每个通道可以配置不同的外设和内存地址,以及传输数据的长度和方向等参数。 2. GD32F470DMA控制器支持循环传输和非循环传输两种模式,可以根据具体应用场景选择不同的模式。 3. GD32F470DMA控制器还支持中断和轮询两种传输方式,可以根据具体应用场景选择不同的方式。 下面是一个GD32F470 ADC2+DMA配置代码示例: ```c // 定义DMA传输数据的长度 #define BUFFER_SIZE 16 // 定义DMA传输的源地址和目的地址 uint16_t adc_value[BUFFER_SIZE]; uint32_t dma_dest_addr = (uint32_t)adc_value; uint32_t dma_src_addr = (uint32_t)&ADC_RDATA(ADC2); // 配置DMA通道0 dma_parameter_struct dma_init_struct; dma_deinit(DMA1, DMA_CH0); dma_struct_para_init(&dma_init_struct); dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; dma_init_struct.memory_addr = dma_dest_addr; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.number = BUFFER_SIZE; dma_init_struct.periph_addr = dma_src_addr; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA1, DMA_CH0, &dma_init_struct); // 配置ADC2 adc_parameter_struct adc_init_struct; adc_struct_para_init(&adc_init_struct); adc_init_struct.continuous_mode = ENABLE; adc_init_struct.data_alignment = ADC_DATAALIGN_RIGHT; adc_init_struct.external_trigger = ADC_EXTTRIG_REGULAR_NONE; adc_init_struct.scan_mode = ENABLE; adc_init_struct.channel_number = 1; adc_init(ADC2, &adc_init_struct); // 配置ADC2的DMA传输 adc_dma_mode_enable(ADC2); adc_dma_request_after_last_enable(ADC2); // 启动DMA传输 dma_channel_enable(DMA1, DMA_CH0); adc_software_trigger_enable(ADC2, ADC_REGULAR_CHANNEL); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值