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


    前言
    • 串口功能在单片机开发中,是比较常用的外设,熟练使用串口功能也是驱动开发必备的技能之一。
      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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 中断接收处理
    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);//
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 串口读缓存数据操作
    /*
    **串口读函数
    */
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 串口清缓存数据操作
    /*
    **串口缓存清除
    */
    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;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 调用
    /*
    **设备串口通信
    */
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

  • 相关阅读:
    Autobus 方法记录
    78 子集
    用c语言将文件中十六进制数据与二进制数据相互转换
    携职教育:这么备考中级经济师的,90%都考不过
    【RISC-V】Trap和Exception
    基于人工智能标记语言 (AIML)和任务型对话系统(Task)的深度智能对话机器人demo
    Yakit工具篇:子域名收集的配置和使用
    玉米社:百度竞价推广否词什么意思?在哪里查看?怎么添加?【全】
    『现学现忘』Git基础 — 17、Commit对象
    idea常用快捷键和插件
  • 原文地址:https://blog.csdn.net/weixin_43940932/article/details/128129428