GD32实现串口空闲(IDLE)中断 + DMA机制接收数据
创始人
2024-03-06 15:44:04
0次

前言

  • 串口功能在单片机开发中,是比较常用的外设,熟练使用串口功能也是驱动开发必备的技能之一。
    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;iuart1_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.13shk_inf[3] = *(volatile unsigned int *)(GD32_FLASH_APP_FILE_INF_START_ADDR + 0x0A); // 13shk_inf[2] = *(volatile unsigned int *)(GD32_FLASH_APP_FILE_INF_START_ADDR + 0x09); // 00shk_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;
}

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...