草庐IT

GD32实现串口空闲(IDLE)中断 + DMA机制接收数据

KAMI STUDIO 2023-10-04 原文

前言

  • 串口功能在单片机开发中,是比较常用的外设,熟练使用串口功能也是驱动开发必备的技能之一。
    DMA是一种CPU辅助手段,可以在CPU不参与的情况下,是做一些辅助CPU的事情,如通常的数据搬运。
    在没有DMA之前,数据读取时,需要CPU的处理,在多任务处理时,增加资源紧缺(CPU调度);
    引入DMA之后,数据可以直接先进入DMA中处理,然后通过相应的标志,在需要的时候去DMA拿去即可,这样就极大的减轻CPU负担,提高了CPU的利用效率,有更多的时间去处理其它的事情。

  • 本文讲的即是利用串口空闲(IDLE)中断 + DMA的机制来处理接收的数据。关于空闲的概念我在之前文章模拟串口收发驱动(采用IDLE信号机制),做了提及和介绍,也是在这根据这个概念在模拟情况下也引入这一机制,极大的提高的处理效率。

  • 本文是基于GD32F330芯片做的代码示范,其实STM32或其他ARM芯片也一样可以按照下面流程方式进行配置使用,都有实现过,故总结之。


正文(流程图 + 示例代码)


在使用DMA之前需要通过MCU手册了解到当前外设映射的所在DMA通道;
上图为GD32F330芯片的DMA请求映射关系图,可以看到下面示例的USART0接收(RX)即映射到DMA _CH2上面。

  • 框架流程图
收到空闲中断标志
接收数据帧标志置位,清除空闲中断标志
读取完后清除缓存以及数据帧标志
接收下一帧数据
初始化串口GPIO
初始化串口配置以及串口中断和DMA配置,使能空闲中断
中断中读取空闲中断标志
关闭DMA
读取串口缓存数据
重新设置DMA缓存大小,并使能DMA

  • 初始化配置
#define U1_RX_MAX_SIZE (150u)
unsigned char gb_uart1_rx_frame_flag = 0;//接收数据帧标志
unsigned char uart1_rx_buff[U1_RX_MAX_SIZE] = {0};//开辟的接收数据缓存区,按实际可能接收最大的数据长度来开辟即可。

/*
**串口1 GPIO初始化
*/
void gd32_uart1_gpio_init(void)
{
    /* enable GPIO clock */
    rcu_periph_clock_enable(RCU_GPIOA);
    /* connect port to USART0 tx */
    gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_9);
    /* connect port to USART0 rx */
    gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_10);
    /* configure USART tx as alternate function push-pull */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_9);
    /* configure USART rx as alternate function push-pull */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_10); 
}

/*
**串口参数配置
*/
void gd32_uart1_cfg_init(unsigned int baudrate)
{
	  nvic_irq_enable(USART0_IRQn,0, 1);
	
    /* enable USART clock */
    rcu_periph_clock_enable(RCU_USART0);
    /* configure USART */
    usart_deinit(USART0);
    usart_baudrate_set(USART0, baudrate);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);//打开串口接收功能
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);//打开串口发送功能
    usart_dma_receive_config(USART0, USART_DENR_ENABLE);//使能 DMA接收 功能
    usart_enable(USART0);//使能串口

    while (RESET == usart_flag_get(USART0, USART_FLAG_IDLE))
        ;
    usart_flag_clear(USART0, USART_FLAG_IDLE);//清除IDLE空闲标志,防止上电即误触发空闲。
    usart_interrupt_enable(USART0, USART_INT_IDLE);//使能IDLE空闲中断
}

/*
**串口0 DMA(发送通道DMA_CH1,接收通道DMA_CH2)配置初始化
*/
void gd32_uart1_dma_init(void)
{
    dma_parameter_struct dma_init_struct;

    rcu_periph_clock_enable(RCU_DMA);

    /* deinitialize DMA channel2 (USART0 rx) */
    dma_deinit(DMA_CH2);
    dma_struct_para_init(&dma_init_struct);
    dma_init_struct.direction   = DMA_PERIPHERAL_TO_MEMORY;//数据是外设到内存(缓存)
    dma_init_struct.memory_addr = (uint32_t)uart1_rx_buff;//数据放置的内存(缓存)地址
    dma_init_struct.memory_inc  = DMA_MEMORY_INCREASE_ENABLE;//内存(缓存)地址增加开启
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;//内存(缓存)数据宽度是8bit,即1字节的存储。
    dma_init_struct.number       = U1_RX_MAX_SIZE;//开辟的内存(缓存)的大小
    dma_init_struct.periph_addr  = (uint32_t)&USART_RDATA(USART0);//数据来源的外设地址(串口接收寄存器)
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;//禁止外设地址增加
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;//外设宽度
    dma_init_struct.priority     = DMA_PRIORITY_ULTRA_HIGH;//DMA动作优先级(最高)
    dma_init(DMA_CH2, &dma_init_struct);
    /* configure DMA mode */
    dma_circulation_disable(DMA_CH2);//禁止DMA循环接收
	dma_memory_to_memory_disable(DMA_CH2);//关闭内存到内存方式。
    /* enable DMA channel2 */
    dma_channel_enable(DMA_CH2);//使能DMA通道。
}

/*
**串口1初始化
*/
void gd32_uart1_init(void)
{
    gd32_uart1_dma_init();
    gd32_uart1_gpio_init();
    gd32_uart1_cfg_init(115200); 
}

/*
**DMA读取接收的数据长度
*/
unsigned int uart1_dma_read(void)
{
    /*
    dma_transfer_number_get(DMA_CH2);是获取当前指针计数值,
    用内存缓冲区大小 - 此计数值 = 接收到的数据长度(这里单位为字节)。
    需要说明下在读取数据长度的时候需要先把接收DMA关闭,读取完了或者是数据处理完了在打开接收DMA,防止在处理的过程中有数据到来而出错。
    */
    return U1_RX_MAX_SIZE - (dma_transfer_number_get(DMA_CH2));
}


/*
**DMA重配置缓存大小,并使能DMA
*/
void uart1_dma_refcg(void)
{
    dma_transfer_number_config(DMA_CH2, U1_RX_MAX_SIZE); //重载缓存大小
    dma_channel_enable(DMA_CH2);
}

  • 中断接收处理
void USART0_IRQHandler(void)
{
    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) 
    {
        /* disable DMA and reconfigure */
        dma_channel_disable(DMA_CH2);	//关闭DMA,在没有读取该接收帧数据之前禁止DMA再接收数据。
         /* number of data received */
        gb_uart1_rx_frame_flag = 1;//接收数据帧标志置位       		
        /* clear IDLE flag */
        usart_interrupt_flag_clear(USART0, USART_INT_FLAG_IDLE);//

    }
}
  • 串口读缓存数据操作
/*
**串口读函数
*/
unsigned char *serial_read(const unsigned char port_num,unsigned int *const plen)
{
	switch(port_num)
	{
		case 0:
			if(gb_uart1_rx_frame_flag)
			{
				*plen = uart1_dma_read();//取数据长度	
				return uart1_rx_buff;//取数据指针
			}		
		break;
		case 1:	
		break;	
        defualt:
        break;	
	}
  *plen =0;
  return NULL;
}
  • 串口清缓存数据操作
/*
**串口缓存清除
*/
void serial_flush(const unsigned char port_num,unsigned int flush_sz)
{
    switch(port_num)
	{
		case 0:
            if(gb_uart1_rx_frame_flag)
            {
                for(unsigned int i=0;i<U1_RX_MAX_SIZE;i++)//U1_RX_MAX_SIZE<=flush_sz?U1_RX_MAX_SIZE:flush_sz
                {
                    uart1_rx_buff[i]=0x00;//清除缓存	
                }
                gb_uart1_rx_frame_flag=0;
                uart1_dma_refcg();//重配置DMA				
            }
		break;
		case 1:
		break;
        default:
        break;
	}
}

  • 调用
/*
**设备串口通信
*/
unsigned char dev_com_task(xxr_un *const rxd_me)
{
	unsigned char ack_code = ACK_OK; //响应值
	unsigned char shk_inf[13] = {0};
	unsigned int dat_len = 0;
	const unsigned char *p = com_seial_read(&dat_len); //读取串口数据

	if (0 == dat_len || NULL == p)
		return 0; /*数据为空,直接返回*/

	(MAX_PPT_RX_UDAT_LEN + 9) < dat_len ? dat_len = (MAX_PPT_RX_UDAT_LEN + 9) : 0;
	for (unsigned short int i = 0; i < dat_len; i++)
	{
		rxd_me->buff[i] = *p++;
	}
	/*协议头判断*/
	if (xx_dev_recv_msg_header_is(rxd_me->frame.sync_header))
		goto __END_HANDLE;
	/*负载长度信息错误*/
	if (MAX_PPT_RX_UDAT_LEN < rxd_me->frame.len)
		goto __END_HANDLE;
	const unsigned char msg_ckv = (xx_msg_chksum(&rxd_me->buff[0], 8) + xx_msg_chksum(&rxd_me->frame.udat[0], rxd_me->frame.len)) & 0xFF;

	if (rxd_me->frame.chksum != msg_ckv)
		goto __END_HANDLE; /*非协议帧数据,直接结束处理*/

	switch (rxd_me->frame.cmd)
	{
	case EQ_SHK_SET_CMD: /*设备握手指令*/
		if (rxd_me->frame.udat[0] & 0x1)
		{
			/*SET THE COM IS CNNING STATUS*/
			dev_com_status_set(CNN_STATE);
			/*BATT VOLTAGE INFO*/
			union
			{
				float volt;
				struct
				{
					unsigned char dat[4];
				};
			} batt;
			batt.volt = BATT_VOLTAGE_CONVERT(gb_adc_value) + 0.7F;
			shk_inf[12] = batt.dat[0];
			shk_inf[11] = batt.dat[1];
			shk_inf[10] = batt.dat[2];
			shk_inf[9] = batt.dat[3];
			/*GET SN INFO*/
			dev_sn_code_read_from_flash(&shk_inf[4], DEV_SN_INF_LEN); //读取设备SN码
			/*GET VERSION INFO*/
			// V01.00.13
			shk_inf[3] = *(volatile unsigned int *)(GD32_FLASH_APP_FILE_INF_START_ADDR + 0x0A); // 13
			shk_inf[2] = *(volatile unsigned int *)(GD32_FLASH_APP_FILE_INF_START_ADDR + 0x09); // 00
			shk_inf[1] = *(volatile unsigned int *)(GD32_FLASH_APP_FILE_INF_START_ADDR + 0x08); // 01
			/*OK*/
			shk_inf[0] = 0x80; /*回复标志*/
			xx_dev_msg_make_and_send(rxd_me->frame.cmd, sizeof(shk_inf), (unsigned char *)&shk_inf[0], serial_write);
			goto __END_HANDLE;
		}
		else
		{
			dev_com_status_set(DISCNN_STATE);
			goto __ACK_HANDLE;
		}
		break;
	default:
		goto __END_HANDLE;
	}
__ACK_HANDLE: //设备回应
	xx_dev_msg_make_and_send(rxd_me->frame.cmd, sizeof(ack_code), &ack_code, serial_write);
__END_HANDLE: //清除串口接收数据缓存
	serial_flush(0);
	dat_len = 0;
	return 0;
}


有关GD32实现串口空闲(IDLE)中断 + DMA机制接收数据的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  5. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  6. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  7. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  8. ruby - ruby 乘法语句中星号中断语法前的空格 - 2

    在添加一些空格以使代码更具可读性时(与上面的代码对齐),我遇到了这个:classCdefx42endendm=C.new现在这将给出“错误数量的参数”:m.x*m.x这将给出“语法错误,意外的tSTAR,期待$end”:2/m.x*m.x这里的解析器到底发生了什么?我使用Ruby1.9.2和2.1.5进行了测试。 最佳答案 *用于运算符(42*42)和参数解包(myfun*[42,42])。当你这样做时:m.x*m.x2/m.x*m.xRuby将此解释为参数解包,而不是*运算符(即乘法)。如果您不熟悉它,参数解包(有时也称为“spl

  9. ruby - 在 ASP 页面上 Mechanize 中断 - 2

    require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

  10. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

随机推荐