串口空闲中断+DMA实现不定长数据接收(基于GD32F103)

串口空闲中断+DMA的原理:

串口在接收数据过程中,不会触发中断,当触发串口空闲中断时,说明这个时候串口没有在收发数据了,可以视为收发完一帧数据。在这个过程中,DMA将串口接收数据寄存器的值直接“搬运”到指定的内存位置(可以是自定义的串口接收缓冲数组)。采用这种方式,可以减少中断次数,节约CPU资源。对比一下,如果采用串口接收中断,那么串口在接收到一个字节就触发一次中断,数据量大时,CPU要频繁处理中断,而采用串口空闲中断+DMA时,只需要触发一次中断。

下面给出代码。

bsp_usart.h文件编写函数声明以及引用外部变量。

#ifndef _BSP_USART_H
#define _BSP_USART_H
#include "gd32f10x.h"

/* 在main.c文件中定义的变量 */
extern uint8_t 		recv_buffer[1024];
extern uint16_t		recv_count;
extern uint8_t		recv_complete_flag;

/* 函数声明 */
void bsp_init_usart(void);
void usart_config(void);
void dma_config(void);

#endif

bsp_usart.c文件中编写串口及DMA初始化配置以及相关中断服务函数的代码(此处将中断服务函数直接定义到对应的外设初始化文件里了,可以自己选择位置,只需要保证中断服务函数能访问到相应变量)。

#include "bsp_usart.h"

/* 初始化串口 */
void bsp_init_usart(void)
{
	usart_config();
	dma_config();
}

/* 配置串口 */
void usart_config(void)
{
    /* 使能IO口时钟 */
    rcu_periph_clock_enable(RCU_GPIOA);

    /* 使能串口时钟 */
    rcu_periph_clock_enable(RCU_USART0);

    /* 初始化TX脚 */
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

    /* 初始化RX脚 */
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);

    /* 串口参数配置 */
    usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);						/* 波特率115200 */
    usart_word_length_set(USART0, USART_WL_8BIT);				/* 8位数据位 */
    usart_stop_bit_set(USART0, USART_STB_1BIT);					/* 1位停止位 */
    usart_parity_config(USART0, USART_PM_NONE);					/* 无校验位 */
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);	/* 无硬件流控制 */
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);	/* 无硬件流控制 */
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);			/* 使能接收 */
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);		/* 使能发送 */
	
	/* 使能串口 */
    usart_enable(USART0);
	
	/* 使能串口空闲中断 */
	nvic_irq_enable(USART0_IRQn, 2, 2);
	usart_interrupt_enable(USART0, USART_INT_IDLE);
	
	/* 使能串口DMA接收 */
	usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);
}

/* 配置DMA */
void dma_config(void)
{
	/* 定义DMA参数结构体变量 */
	dma_parameter_struct dma_init_struct;
	
	/* 使能相应DMA时钟 */
	rcu_periph_clock_enable(RCU_DMA0);
	
	/* 复位对应DMA通道 */
	dma_deinit(DMA0, DMA_CH4);
	
	/* 复位DMA参数结构体变量 */
	dma_struct_para_init(&dma_init_struct);
	
	/* 配置DMA结构体变量参数 */
	dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;			/* 外设到内存 */
	dma_init_struct.memory_addr = (uint32_t)recv_buffer;			/* 内存地址:接收数组缓冲的地址 */
	dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;		/* 内存地址自增 */
	dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;			/* 内存数据宽度8b */
	dma_init_struct.number = 1024;									/* 接收长度 此处可以通过宏定义设置值的大小,方便修改*/
	dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART0);	/* 外设地址:串口接收数据寄存器 */
	dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;		/* 外设地址不自增 */
	dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;		/* 外设数据宽度8b */
	dma_init_struct.priority = DMA_PRIORITY_HIGH;					/* 优先级:高 */
	
	/* 初始化DMA */
	dma_init(DMA0,DMA_CH4,&dma_init_struct);
	
	/* 不使用循环模式 */
	dma_circulation_disable(DMA0, DMA_CH4);
	/* 不使用内存到内存模式 */
	dma_memory_to_memory_disable(DMA0, DMA_CH4);
	
	/* 使能对应DMA通道 */
	dma_channel_enable(DMA0, DMA_CH4);
	
	/* 使能相应中断 */
	nvic_irq_enable(DMA0_Channel4_IRQn, 0, 0);
	dma_interrupt_enable(DMA0,DMA_CH4,DMA_INT_FTF);
}

/* 串口中断服务函数 */
void USART0_IRQHandler(void)
{
	if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) != RESET)
	{
		usart_data_receive(USART0);										/* 必须读寄存器清中断标志位 */
		recv_complete_flag = 1;											/* 接收完成标志置1 */
		recv_count = 1024 - dma_transfer_number_get(DMA0,DMA_CH4);		/* DMA设置的Number值-读取的DMA剩余值=接收数据个数 */
		recv_buffer[recv_count] = '\0';									/* 接收缓冲末尾赋值‘\0’ */
		dma_config();													/* 重启DMA,不重启会导致DMA计数不正确 */
	}
}

/* DMA中断服务函数 */
void DMA0_Channel4_IRQHandler(void)
{
	if(dma_interrupt_flag_get(DMA0, DMA_CH4, DMA_INT_FLAG_FTF) != RESET)
	{
		dma_interrupt_flag_clear(DMA0, DMA_CH4, DMA_INT_FLAG_FTF);
	}
}

当触发串口空闲中断时,在中断服务函数里面将接收完成标志位置位,主程序查询到这个标志位置位后,即可对数据进行相应的处理。

#include "gd32f10x.h"
#include "systick.h"
#include <stdio.h>
#include <string.h>
#include "main.h"
#include "bsp_usart.h"

uint8_t 	recv_buffer[1024];
uint16_t	recv_count;
uint8_t		recv_complete_flag;

int main(void)
{
	nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
    /* configure systick */
    systick_config();
	bsp_init_usart();

    while(1)
	{
		/* 等待数据传输完成 INTERRUPT */    
		if(recv_complete_flag)  // 数据接收完成            
		{                
			 recv_complete_flag = RESET; // 等待下次接收                
			 printf("recv_count:%d ",recv_count);                 
			 printf("Interrupt recv:%s\r\n",recv_buffer);  // 打印接收的数据                 
			 memset(recv_buffer,0,sizeof(recv_buffer));  // 清空数组                 
			 recv_count = 0;             
		}
    }
}

/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
    usart_data_transmit(USART0, (uint8_t)ch);
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));

    return ch;
}

main函数里查询接收完成标志位的值,查询到置位时打印数据。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值