GD32F303单片机的串口+DMA配置过程/DMA双通道切换/串口使用DMA发送数据

本文是GD开发板串口外设搭配DMA的一些基本配置操作。
1. 查阅用户手册可知,GD32F303有5个串口,其中前四个可以使用DMA搬运数据。
        关于有哪些,可以查看用户手册中DMA请求映射表;所有的手册都大同小异,找到就知道对应哪个dma通道了。
2. 下面是串口以及串口DMA配置部分代码:
        使用双通道切换模式,接收dma配置部分一定要开启循环模式。

void Uart_DMA_Init()
{
    	/*********************UART1   PA2 PA3*********************************************/
    #if 1
	rcu_periph_clock_enable(RCU_USART1);      			//开启USART1时钟
	rcu_periph_clock_enable(RCU_GPIOA);		  			//开启GPIOA时钟
	
	gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2);  		  //PA2(TX)配置为复用推挽输出
	gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3);  //PA3(RX)配置为浮空输入
	
	usart_deinit(USART1);					  			
    usart_baudrate_set(USART1, 115200);		  			
    usart_word_length_set(USART1, USART_WL_8BIT);
    usart_stop_bit_set(USART1, USART_STB_1BIT);
    usart_parity_config(USART1, USART_PM_NONE);
    usart_hardware_flow_rts_config(USART1, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART1, USART_CTS_DISABLE);		
		
    usart_transmit_config(USART1, USART_TRANSMIT_ENABLE);
    usart_receive_config(USART1, USART_RECEIVE_ENABLE); 	
	
	/* USART DMA 发送和接收使能 */
    usart_dma_transmit_config(USART1, USART_DENT_ENABLE|USART_DENR_ENABLE);	

	nvic_irq_enable(USART1_IRQn,1,0);	
//	usart_interrupt_enable(USART1,USART_INT_RBNE);	
    usart_interrupt_enable(USART1,USART_INT_IDLE);       //使能空闲中断
	usart_interrupt_flag_clear(USART1,USART_INT_FLAG_IDLE);//清除读数据缓冲区非空中断标志
    usart_enable(USART1); 	//使能USART1
    #endif

   //Uart1  RX   DMA0, DMA_CH5
   #if 1
	/* initialize DMA0 channe5(USART1 RX) */
	rcu_periph_clock_enable(RCU_DMA0);                        //开启DMA时钟
	dma_deinit(DMA0, DMA_CH5);
	DMA_InitStruct.direction = DMA_PERIPHERAL_TO_MEMORY;      //外设到内存
	DMA_InitStruct.periph_addr = (uint32_t)(&USART_DATA(USART1));
	DMA_InitStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;  //外设增量
	DMA_InitStruct.periph_width = DMA_MEMORY_WIDTH_8BIT;
	DMA_InitStruct.memory_addr = (uint32_t)uart1_1_rev_buf;
	DMA_InitStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;   //内存增量
	DMA_InitStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
	DMA_InitStruct.number = sizeof(uart1_1_rev_buf);;                               
	DMA_InitStruct.priority = DMA_PRIORITY_MEDIUM;
	dma_init(DMA0, DMA_CH5, &DMA_InitStruct);
	
	/* DMA循环模式配置,不使用循环模式 */
    dma_circulation_enable(DMA0, DMA_CH5);
	dma_memory_to_memory_disable(DMA0,DMA_CH5);	
	dma_channel_enable(DMA0, DMA_CH5);
	dma_interrupt_enable(DMA0,DMA_CH5,DMA_INT_FTF|DMA_INT_ERR);            //开启DMA传输完成中断标志
	/***开启DMA中断**/
	nvic_irq_enable(DMA0_Channel5_IRQn,1,0);
	
	//Uart1 TX 4G   DMA0, DMA_CH6
	dma_deinit(DMA0, DMA_CH6);									//USART1_TX
	/* initialize DMA0 channe6(Usart1_TX) */					
	DMA_InitStruct.direction = DMA_MEMORY_TO_PERIPHERAL;
	DMA_InitStruct.memory_addr = 0;
	DMA_InitStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	DMA_InitStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
	DMA_InitStruct.number = 0;
	DMA_InitStruct.periph_addr = (uint32_t)(&USART_DATA(USART1));
	DMA_InitStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	DMA_InitStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	DMA_InitStruct.priority = DMA_PRIORITY_MEDIUM;
	dma_init(DMA0, DMA_CH6, &DMA_InitStruct);
	dma_circulation_disable(DMA0, DMA_CH6);
	dma_channel_disable(DMA0, DMA_CH6);
    dma_interrupt_enable(DMA0,DMA_CH6,DMA_INT_FTF|DMA_INT_ERR);            //开启DMA传输完 
                                                                           //成中断标志
	/***开启DMA中断**/
	nvic_irq_enable(DMA0_Channel6_IRQn,1,0);
	#endif

}

3. 使用串口空闲中断与DMA的协同工作机制传输最合适。
        一开始通过PC端串口助手测试发现串口dma接收回调函数打印不出我发给mcu的数据,但是dma的内存基地址我打印了出来数据,,后来结合串口空闲中断,使用dma快速搬运数据,再通过串口空闲中断将数据接收出来。
        dma的传输完成中断是 当你的基地址内存接满了,它才会进入dma接收完成中断服务函数中。所以刚开始我通过串口助手发的简单的少量数据压根没灌满内存地址的大小。
        以下是dma中断以及串口空闲中断的服务函数。使用的是双通道,单通道不切换通道即可。同时也有dma发送函数封装。

/* 串口1中断服务程序 双通道 */
void USART1_IRQHandler(void)
{
    if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE) != RESET) //接收数据空闲
    {
	    usart_interrupt_flag_clear(USART1,USART_INT_FLAG_IDLE);	/* 清除空闲中断标志位 */
        usart_data_receive(USART1);								/* 清除接收完成标志位 */
		
          /*
         双缓冲区模式时,需要将切换缓冲区放置在前面,处理接收数据放置在后面
        */
         //切换接收缓冲区 
		dma_channel_disable(DMA0, DMA_CH5);						/* 关闭DMA传输 */
		uint16_t recv_len = NET_BUF_SIZE - dma_transfer_number_get(DMA0,DMA_CH5); //获取实际接收数据字节数
		//DMA_CH5PADDR
		
		if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
		{
			
			Uart1Rev_CallBack(uart1_1_rev_buf, recv_len); //RX
			dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_2_rev_buf);
		}
		else
		{
			Uart1Rev_CallBack(uart1_2_rev_buf, recv_len); //RX
			dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_1_rev_buf);
		}
		dma_transfer_number_config(DMA0,DMA_CH5,NET_BUF_SIZE);
        dma_channel_enable(DMA0, DMA_CH5);		/* 开启DMA传输 */
		//数据处理接收
后面我将数据接收处理移到了上面的切换缓存通道的同时一起接收数据了。。。下面是刚开始写的,都可以实现。。但是下面的接收要注意上面的地址以及被更改了,所以接受的数据是上一个的缓存区
//		if(NULL != Uart1Rev_CallBack && recv_len > 0)
//        {
//			if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
//			{
		
//				Uart1Rev_CallBack(uart1_2_rev_buf, recv_len); //RX // 当前DMA指向uart1_1,说明上一帧在uart1_2,,因为上面已经切换了地址。程序到这里就对应也已经改变了。
//			}
//			else
//			{
//				Uart1Rev_CallBack(uart1_1_rev_buf, recv_len); //RX
//			} 
//        }
    }
}


///* 串口1中断服务程序  单通道的接收  */
//void USART1_IRQHandler(void)
//{
//    if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE) != RESET) //接收数据空闲
//    {
//	    usart_interrupt_flag_clear(USART1,USART_INT_FLAG_IDLE);	/* 清除空闲中断标志位 */
//        usart_data_receive(USART1);								/* 清除接收完成标志位 */
//		uint16_t recv_len = NET_BUF_SIZE - dma_transfer_number_get(DMA0,DMA_CH5);
//		if(NULL != Uart1Rev_CallBack && recv_len > 0)
//        {
//            Uart1Rev_CallBack(uart1_1_rev_buf, recv_len); //RX
//        }

//        dma_channel_disable(DMA0, DMA_CH5);						/* 关闭DMA传输 */
//		/* 重新设置DMA传输 */
//		dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_1_rev_buf);
//        dma_transfer_number_config(DMA0,DMA_CH5,NET_BUF_SIZE);
//        dma_channel_enable(DMA0, DMA_CH5);		/* 开启DMA传输 */
//    }
//}


//DMA的中断函数/

void DMA0_Channel5_IRQHandler(void) //UART1 RX 
{
	if(dma_interrupt_flag_get(DMA0,DMA_CH5,DMA_INT_FLAG_FTF) != RESET)
    {	
		dma_interrupt_flag_clear(DMA0,DMA_CH5,DMA_INT_FLAG_FTF); //清除传输完成标识位		
        if(NULL != Uart1Rev_CallBack)
        {
			if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
			{
				Uart1Rev_CallBack(uart1_1_rev_buf,NET_BUF_SIZE);
			}
			else{
			
				Uart1Rev_CallBack(uart1_2_rev_buf,NET_BUF_SIZE);
			}
        }
		//切换缓存区
		if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
		{
			dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_2_rev_buf);
		}
		else
		{
			dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_1_rev_buf);
		}
		dma_transfer_number_config(DMA0,DMA_CH5,NET_BUF_SIZE);
        dma_channel_enable(DMA0, DMA_CH5);		/* 开启DMA传输 */		
    }
	if(dma_interrupt_flag_get(DMA0,DMA_CH5,DMA_INT_FLAG_ERR) != RESET)
	{
		dma_interrupt_flag_clear(DMA0,DMA_CH5,DMA_INT_FLAG_ERR);
	}
}

/* uart1 tx 开始DMA传送 */
void UART1_DMA_Transmit(const uint8_t *data, uint16_t len)
{  
    dma_channel_disable(DMA0, DMA_CH6); // 先禁用通道,防止在配置时发生冲突
    dma_memory_address_config(DMA0, DMA_CH6, (uint32_t)data); // 配置发送数据的内存地址
    dma_transfer_number_config(DMA0, DMA_CH6, len); // 配置传输数据量
    dma_channel_enable(DMA0, DMA_CH6); // 启用DMA通道6开始发送
}

下面是串口 接受/发送 的回调函数:

void MCU_Uart_RevCallBack_Hook(MCU_Uart_Type uart, void (*UartRev_CallBack)(uint8_t *data, uint16_t len))
{
    switch(uart)
    {
        case Prot_Uart1:
            Uart1Rev_CallBack = UartRev_CallBack;
            break;

          case Prot_Uart2:
            Uart2Rev_CallBack = UartRev_CallBack;
            break;
        case Prot_Uart3:
            Uart3Rev_CallBack = UartRev_CallBack;
            break;
        case Prot_Uart5:
            Uart5Rev_CallBack = UartRev_CallBack;
            break;
        case Prot_Uart7:
             Uart7Rev_CallBack = UartRev_CallBack;
            break;

        default:
            break;
    }
}

void MCU_Uart_Send(MCU_Uart_Type uart, uint8_t *data, uint16_t len)
{
    switch(uart)
    {
        case Prot_Uart1:
            UART1_DMA_Transmit(data, len); //启动DMA开始发送
            break;

        case Prot_Uart2://开启中断发送            
            UART2_DMA_Transmit(data, len); //启动DMA开始发送
            break;
        case Prot_Uart3://开启中断发送
            UART3_DMA_Transmit(data, len); //启动DMA开始发送
            break;
//        case Prot_Uart5://开启中断发送
//            UART5_DMA_Transmit(data, len); //启动DMA开始发送
//            break;

        case Prot_Uart7://开启中断发送
          UART0_DMA_Transmit(data,len);//启动DMA开始发送
            break;
        default:
            break;
    }
}

以下是测试:使用串口助手给mcu发送数据。


/*
串口DEBUG接收中断
 */
#if 1
static void DEBUGRevData(uint8_t* data, uint16_t len)
{
	static uint16_t data_len = 0; // 当前接收的数据长度
	uint16_t i = 0;
#if 1//串口dma切换通道接收
	// 十六进制格式化输出
    printf("[UART] Received %d bytes:\n", len);
    for(uint16_t i=0; i<len; i++) {
        printf("%02X ", data[i]);
        if((i+1)%16 == 0) printf("\n");
    }
    printf("\n---------------------------------------------\n");
	printf("uart11_rev_buf %d byte = %s\n",len,uart1_1_rev_buf);
	printf("uart22_rev_buf %d byte = %s\n",len,uart1_2_rev_buf);
	
	    /* 双缓冲状态显示 */
    // 获取当前DMA存储器地址
    
    printf("[DMA Buffer Status]\n");
    printf("Active Buffer: %s\n", 
           (DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf) ? "Buffer2 (processing)" : "Buffer1 (processing)");
    printf("Buffer1 Ready: %s\n", 
           (DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_2_rev_buf) ? "Yes" : "No");
    printf("Buffer2 Ready: %s\n", 
           (DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf) ? "Yes" : "No");
#endif

//	for (i = 0; i < len; i++)
//    {
//        usartDebudg_recv_buf[data_len++] = data[i]; // 将接收到的数据存储到缓冲区
//	    // 检查是否接收到换行符
//		if (data[i] == '\n')
//		{
//			// 将接收到的数据以字符串形式输出
//			usartDebudg_recv_buf[data_len - 1] = '\0'; // 添加字符串结束符
//			printf("Received Data: %s\n", usartDebudg_recv_buf);
//			
//			printf("uart11_rev_buf = %s\n",uart1_1_rev_buf);
//			printf("uart22_rev_buf = %s\n",uart1_2_rev_buf);
//			// 清空接收缓冲区
//			data_len = 0;
//			memset(usartDebudg_recv_buf, 0, NET_BUF_SIZE);		
//		}
//	}	
}
#endif

/*
串口1 DMA发送完成中断
 */
 #if 1
static void NetTranCompleteCallback(void)
{
	printf("\n");
    tx_busy= 0;
	printf("tx_busy = %d\n",tx_busy);
}
#endif
void CallBack_init(void)
{
	XYMCU_Uart_DMATranCallBack_Hook(Prot_Uart7,NetTranCompleteCallback); //串口dma发送完成回调
	XYMCU_Uart_RevCallBack_Hook(Prot_Uart1,DEBUGRevData);    //串口空闲回调
	
}


int main()
{
    //由于测试的功能较多同时我使用的并不是裸机测试,是rtos中测试,所有主函数测试函数可能不通,只是一个测试流程和方向。具体需要自己修改可能。。更主要的是外设的配置部分
    systick_config(); // 初始化systick计时器
    Uart_DMA_Init()
	CallBack_init();
    uint16_t tx_busy = 1;
    while(1)
    {
        //串口dma发送  这里是测试dma发送功能,当使用串口发出去后,回调函数会立马tx_busy  = 0。
		tx_busy = 1;
		printf("tx_busy = %d\n",tx_busy);
		MCU_Uart_Send(Prot_Uart7,(uint8_t *)tx_buff,sizeof(tx_buff));
        delay(1000); 
    }
}

### 关于GD32F103RCT6单片机串口3的配置使用 #### USART3初始化设置 对于GD32F103RCT6而言,USART3位于APB1总线上,默认情况下其时钟源来自PCLK1。为了使能USART3的功能并完成初步设定,需先通过RCC控制器开启对应外设时钟,并指定GPIO端口模式为复用推挽输出形式[^1]。 ```c /* 开启USART3及时钟 */ rcu_periph_clock_enable(RCU_USART3); rcu_periph_clock_enable(RCU_GPIOA); /* GPIO初始化结构体定义 */ gpio_init_type gpio_init_struct; void usart_gpio_config(void){ /* 复位PA8, PA9引脚 */ gpio_pin_reset(GPIOA, GPIO_PIN_8 | GPIO_PIN_9); /* 设置PA8(TX), PA9(RX)为复用推挽输出 */ gpio_init_struct.gpio_mode = GPIO_MODE_AF_PP; gpio_init_struct.gpio_out_speed = GPIO_OSPEED_50MHZ; /* 初始化PA8(PA9同理)*/ gpio_init(GPIOA, &gpio_init_struct); } ``` #### 配置波特率及其他参数 接着是对通信速率(即波特率)、字长、停止位等属性的具体规定。这里采用标准异步半双工传输协议,数据帧由起始位、8个数据位构成,不带校验位且仅有一个终止位[^2]。 ```c usart_init_type usart_init_struct; void usart_parameter_config(void){ /* 波特率为115200bps */ usart_init_struct.baud_rate = 115200U; /* 字符长度为8位 */ usart_init_struct.word_length = USART_WL_8BIT; /* 奇偶校验关闭 */ usart_init_struct.stop_bits = USART_STPB_1; /* 禁用硬件流控制 */ usart_init_struct.hardWARE_flow_control = USART_HFC_DISABLE; /* 发送/接收使能 */ usart_init_struct.transmit_enable = USART_TE_ENABLE; usart_init_struct.receive_enable = USART_RE_ENABLE; /* 应用于USART3实例化对象上 */ usart_init(USART3,&usart_init_struct); } ``` #### 中断服务程序设计 当希望利用中断机制处理接收到的数据包时,则还需注册相应的ISR入口地址至向量表内,并编写具体的响应逻辑[^4]。 ```c extern "C" void USART3_IRQHandler(void){ if(RESET != usart_interrupt_flag_get(USART3, USART_INT_FLAG_RBNE)){ char ch = usart_data_receive(USART3); // 用户自定义业务流程... usart_interrupt_flag_clear(USART3, USART_INT_FLAG_RBNE); } } // 启动接收中断请求 usart_interrupt_enable(USART3, USART_INT_RBNE); NVIC_EnableIRQ(USART3_IRQn); ``` 以上便是针对GD32F103RCT6型号MCU中USART3接口的一般性介绍及其基础应用示范。值得注意的是实际项目里可能还会涉及到更复杂的场景比如DMA批量传送或是LIN/LPUART低功耗特性支持等问题,在具体实践中应参照官方手册进一步深入研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值