GD32开发之UART+DMA接收不定长数据

**

GD32-UART接收不定长数据

**

简要说明

最近,公司项目用到国产芯片GD32做开发,刚好搞好了UART数据接收这一块。这部分内容由于一开始完全采用中断接收,后来测试发现对应用性能有影响,因此又对接收这部分进行大的改动,改成采用中断+DMA的方式接收数据,花了一些时间,今天在这里进行整理总结,分享给大家,若是哪里有错,希望大家能够指教改出。

平台说明

这里我们演示的芯片型号是GD32F450ZKT6,我们通过UART3的演示来对这部分内容进行简单的叙述。
(注:以下代码实现都是基于GD官方固件库开发)

方法

下面我们简单介绍如何通过UART接收不定长数据包的两种方法:

  • 接收中断(USART_INTEN_RBNEIE)+空闲中断(USART_INTEN_IDLEIE)
  • DMA+空闲中断(USART_INTEN_IDLEIE)

代码实现

不管使用上面说的哪一种方法,在操作uart前,我们都需要对uart进行初始化操作。
以uart3为例
首先是相关的宏定义

#define GD32_COM4                      UART3
#define GD32_COM4_CLK                  RCU_UART3

#define GD32_COM4_TX_PIN               GPIO_PIN_0	//PA0
#define GD32_COM4_RX_PIN               GPIO_PIN_1	//PA1

#define GD32_COM4_GPIO_PORT            GPIOA
#define GD32_COM4_GPIO_CLK             RCU_GPIOA
#define GD32_COM4_AF                  GPIO_AF_8

初始化操作如下

void gd_com_init(uint32_t com, uint32_t baudrate)
{
    //232
    rcu_periph_clock_enable(GD32_COM4_CLK);
    
    /* enable USART clock */
    rcu_periph_clock_enable(GD32_COM4_GPIO_CLK);
    
    /* connect port to USARTx_Tx */
    gpio_af_set(GD32_COM4_GPIO_PORT, GD32_COM4_AF, GD32_COM4_TX_PIN);
    
    /* connect port to USARTx_Rx */
    gpio_af_set(GD32_COM4_GPIO_PORT, GD32_COM4_AF, GD32_COM4_RX_PIN);
    
    /* configure USART Tx as alternate function push-pull */
    gpio_mode_set(GD32_COM4_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GD32_COM4_TX_PIN);
    gpio_output_options_set(GD32_COM4_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GD32_COM4_TX_PIN);
    
    /* configure USART Rx as alternate function push-pull */
    gpio_mode_set(GD32_COM4_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GD32_COM4_RX_PIN);
    gpio_output_options_set(GD32_COM4_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GD32_COM4_RX_PIN);
    /* USART configure */
    usart_deinit(com);
    //	usart_sample_bit_config(com, USART_OSB_1bit);
    usart_oversample_config(com, USART_OVSMOD_8);
    usart_baudrate_set(com,baudrate);			//波特率
    usart_parity_config(com, USART_PM_NONE);		//校验位:NONE
    usart_word_length_set(com, USART_WL_8BIT);		//数据位:8
    usart_stop_bit_set(com, USART_STB_1BIT);		//停止位:1
    usart_receive_config(com, USART_RECEIVE_ENABLE);
    usart_transmit_config(com, USART_TRANSMIT_ENABLE);
    usart_enable(com);
}

方法一

通过接收中断(USART_INTEN_RBNEIE)+空闲中断(USART_INTEN_IDLEIE)接收不定长数据

1. 首先,我们需要初始化UART3

gd32_com_init(GD32_COM4, MODBUS_BAUD_115200);

2. 接着,使能UART3的接收中断和空闲中断,打开UART3中断的使能的同时配置中断的优先级

/* Uart3中断使能 */
/* enable USART3 receive interrupt */
usart_interrupt_enable(GD32_COM4, USART_INTEN_RBNEIE);
/* enable USART3 IDLEIE interrupt */
usart_interrupt_enable(GD32_COM4, USART_INTEN_IDLEIE);
/* USART interrupt configuration */
nvic_irq_enable(UART3_IRQn, 1, 1);

3. 接下来就是中断函数的UART3_IRQ的实现

void UART3_IRQHandler(void)
{
    //若接收到一个字节
	if(RESET != usart_flag_get(GD32_COM4, USART_FLAG_RBNE))	
	{
        g_ucRecvBuf_FIFO[MODBUS_UART_PORT3][g_cxRecvCnt_FIFO[MODBUS_UART_PORT3]++] = usart_data_receive(GD32_COM4);
	}
    //若接收检测到空闲帧中断
	else if(RESET != usart_flag_get(GD32_COM4, USART_FLAG_IDLEF))
	{
        usart_data_receive(GD32_COM4);
   }
}

在这里我们对接收中断和空闲帧中断做一个简要的说明
关于这部分,用户手册上对于这两个中断对应的标志位说明是这样描述的
在这里插入图片描述
简单来说,RBNE接收中断,就是只要读数据缓冲区接收到数据时,就会触发中断,而空闲中断IDLEF则是需要完整地接收到一帧数据(多个字节),才会去触发对应的中断。不管是接收中断还是空闲中断,在触发中断进入中断函数后,我们都需要在中断函数中去清除对应的标志位,才能正常进入等待下一次中断的触发。
以下是整个过程的流程图说明
在这里插入图片描述
以上是通过UART接收中断(RBNE)+空闲中断(IDLEF)来接收不定长数据的方法,从上述我们可以看到,这种方法需要频繁地进入中断来接收一帧数据中的每个字节,假如说一帧数据有8个字节,则接到到这帧完整地数据,前前后后会进入9次中断函数,在频繁的数据传输中,这对CPU的运行速率是一个很大的负担,在个别应用上,这种方法是不适用的。
下面,我们介绍下另外一种方法来接收不定长的UART数据

方法二

DMA+空闲中断(USART_INTEN_IDLEIE)接收不定长数据

这里我们先简单介绍下DMA的作用,这里同样直接把用户手册的内容挂上来
在这里插入图片描述
通过对DMA功能的大概了解,我们可以先想到这么一种思路,在串口检测到有数据输入的时候,让DMA直接往我们定义好的存储器中搬收到的UART数据,在这个过程中,CPU还是在处理自己的事,最后,在一帧数据传输完成后,通过中断告诉主任务一帧数据已经传输完毕,主任务再对接收到的数据进行相应的处理。整个过程下来,程序代码只会触发一次中断,就是DMA搬移完数据后通知CPU的那一次中断,对比前一种方法,每接收到一个字节就需要进中断把数据读到自定的的FIFO中,第二种方法不论一帧数据的数据量有多大,我们都只会触发一次中断。

以下是代码实现

1. 同样,首先,我们第一步仍然需要初始化UART3

gd32_com_init(GD32_COM4, MODBUS_BAUD_115200);

2. 接着,这里我们只需要使能UART3的空闲中断,打开UART3中断的使能的同时配置中断的优先级

/* enable USART3 IDLEIE interrupt */
usart_interrupt_enable(GD32_COM4, USART_INTEN_IDLEIE);
/* USART interrupt configuration */
nvic_irq_enable(UART3_IRQn, 1, 1);

3. DMA的初始化配置
在对DMA进行初始化配置前,我们需要通过翻看手册确认我们需要使用DMA的哪个通道。
在这里插入图片描述
接着进行相关的配置操作

usart_dma_config(GD32_COM4);
void usart_dma_config(uint32_t com){
    dma_single_data_parameter_struct dma_init_struct;
	/* enable DMA0 */
    rcu_periph_clock_enable(RCU_DMA0);
    /* deinitialize DMA channel */
    dma_deinit(DMA0, DMA_CH1);
    
    dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
    //存储器地址
    dma_init_struct.memory0_addr = (uint32_t)&g_ucRecvBuf_FIFO[MODBUS_UART_PORT2][0];
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct.number = 4;
    dma_init_struct.periph_addr = USART2_DATA_ADDRESS;
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
    dma_single_data_mode_init(DMA0, DMA_CH1, dma_init_struct);
    
    /* configure DMA mode */
    //	dma_circulation_disable(DMA0, DMA_CH1);
    dma_circulation_enable(DMA0, DMA_CH1);	//循环模式
    dma_channel_subperipheral_select(DMA0, DMA_CH1, DMA_SUBPERI4);
    
    //放在使能通道前,使能通道后不可写
    dma_transfer_number_config(DMA0, DMA_CH1, MODBUS_ASCII_FRAME_SIZE);
    //使能通道
    dma_channel_enable(DMA0, DMA_CH1);
    
    usart_dma_receive_config(USART2, USART_DENR_ENABLE);
    }

对于uart DMA的初始化,大致可按照用户手册上的说明来配置
在这里插入图片描述
通过对dma_single_data_parameter_struct 结构体变量的配置,按需求设置好DMA的源地址(UART3_DATA),目的地址(自定义FIFO地址),以及一次传输的字节数等后,使能DMA通道,最后打开串口的DMA接收模式,这样,我们就可以在UART接收中正常使用DMA功能了。
在DMA的配置函数中,我们还调用了这样一个函数:

//放在使能通道前,使能通道后不可写
dma_transfer_number_config(DMA0, DMA_CH1, MODBUS_ASCII_FRAME_SIZE);

这里是对通道计数寄存器的配置,我们需要先配置好这个寄存器,在接收到一帧数据后再去读这个寄存器的值,来获取接收到的数据帧的大小。
在这里插入图片描述
在这里插入图片描述
4. 最后就是中断函数的UART3_IRQ的实现

void UART3_IRQHandler(void){
    //DMA接收,只触发空闲中断
    if(RESET != usart_flag_get(GD32_COM4, USART_FLAG_IDLEF))
    {
        Receive_DataPack(GD32_COM4);
        usart_data_receive(GD32_COM4);  //清楚标志位
        OSSemPost(g_pRecvSEM[MODBUS_UART_PORT3]);
    }
}

UART3_IRQ中断函数主要做了三件事,调用了Receive_DataPack函数,清除标志位并通知主任务已接收到一帧数据。
Receive_DataPack函数

void Receive_DataPack(uint32_t com){
    dma_single_data_parameter_struct dma_init_struct;
    /* 关闭DMA,防止干扰 */
    dma_channel_disable(DMA0, DMA_CH2);    
    /* 清DMA标志位 */
    dma_flag_clear(DMA0, DMA_CH2, DMA_INTF_FTFIF);
    
    /* 获取接收到的数据长度,单位:字节 */
    g_cxRecvCnt_FIFO[MODBUS_UART_PORT3] = MODBUS_ASCII_FRAME_SIZE - dma_transfer_number_get(DMA0, DMA_CH2);
   
    dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
    //设置存储器地址
    dma_init_struct.memory0_addr = (uint32_t)&g_ucRecvBuf_FIFO[MODBUS_UART_PORT3][0];
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct.number = 4;
    dma_init_struct.periph_addr = UART3_DATA_ADDRESS;
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH2, dma_init_struct);
    
    /* 重新赋值计数值 */
    dma_transfer_number_config(DMA0, DMA_CH2, MODBUS_ASCII_FRAME_SIZE);
    
    /* 重新打开DMA */
    dma_channel_enable(DMA0, DMA_CH2);
}

Receive_DataPack函数主要做了两件事,一是通过计算对应通道计数寄存器值的变化来得到接收到的数据的长度;二则是重新进行DMA的配置(主要是DMA目的地址的配置)。
以下是对上述整个流程的简要说明:
在这里插入图片描述
对于这部分的介绍大致就介绍到这里,如果哪里有误,欢迎大家指出!

利用STM32 HAL库实现串口DMA发送和不定数据接收的方法如下: 1. 串口DMA发送: 首先,需要初始化串口和DMA相关的参数。通过HAL_UART_Init()函数初始化串口,设置波特率、数据位、停止位等参数。然后使用HAL_UART_Transmit_DMA()函数启动DMA发送,将发送数据缓冲区的指针和数据度传入该函数。 2. 不定数据接收: 在接收数据时,我们可以使用DMA模式配合中断来实现不定度的数据接收。首先,需要初始化串口和DMA相关的参数,与串口DMA发送相同。然后,使用HAL_UART_Receive_DMA()函数启动DMA接收,将接收数据存放到接收缓冲区中。 在接收数据的过程中,可以通过中断方式来判断数据是否接收完成。在中断处理函数中,可以读取接收数据缓冲区的数据,并根据接收到的数据进行处理。在处理完数据之后,可以继续启动DMA接收,以进行下一次的数据接收。 需要注意的是,在中断处理函数中,需要判断DMA接收是否完成,可以通过检查DMA接收状态寄存器的标志位来判断。如果DMA接收完成,则可以执行相应的操作,比如解析接收到的数据。 总结: 利用STM32 HAL库,可以方便地实现串口DMA发送和不定数据接收。通过初始化相关参数,并启动串口DMA发送和接收,可以实现高效的数据传输。在中断处理函数中,可以对接收到的数据进行处理,并根据需要继续启动DMA接收。这种方法适用于需要在接收端实时处理不定度的数据的场景。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Top0_0lll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值