关于CH32V307使用串口+DMA发送与接收的配置和一点见解 |
您所在的位置:网站首页 › 串口数据的接收与处理方法是 › 关于CH32V307使用串口+DMA发送与接收的配置和一点见解 |
笔者之前一直好奇DMA可以在不耗费CPU的资源下转运数据,那相比于不开启DMA的串口发送和接收到底有啥优势,快又快多少呢,于是近期用沁恒一款芯片做了测试。(这里我想爆赞一下沁恒的芯片,便宜好用,截止目前为止,个人使用下来发现库函数代码和STM32绝大多数兼容,只有少数寄存器名字略有差别,最关键的是沁恒官网社区服务很好,有问必答,而且常常发送一个配置好了的例程过来,非常人性化) 好的,咱们废话不多说,由于笔者水平也极其有限,若文章出现错误还请多大佬评论区指出,万分感谢! 下面是使用USART2配置DMA发送和接收的初始化代码: uint8_t TxBuffer2[USART2_BUFLEN_TX] = {0}; uint8_t RxBuffer2[USART2_BUFLEN_RX] = {0}; uint8_t USART2_Revnum =0; DMA_InitTypeDef DMA_InitStructure_USART2_TX; DMA_InitTypeDef DMA_InitStructure_USART2_RX; void USART2_DMAConfig(uint32_t baudtate) { GPIO_InitTypeDef GPIO_InitStructure = {0}; USART_InitTypeDef USART_InitStructure = {0}; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 , ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //DMA config DMA_DeInit(DMA1_Channel7); DMA_InitStructure_USART2_TX.DMA_PeripheralBaseAddr = (u32)(&USART2->DATAR); /* USART2->DATAR:0x40004404 */ DMA_InitStructure_USART2_TX.DMA_MemoryBaseAddr = (u32)&TxBuffer2; DMA_InitStructure_USART2_TX.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure_USART2_TX.DMA_BufferSize = USART2_BUFLEN_TX; DMA_InitStructure_USART2_TX.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure_USART2_TX.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure_USART2_TX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure_USART2_TX.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure_USART2_TX.DMA_Mode = DMA_Mode_Normal; //单次模式 DMA_InitStructure_USART2_TX.DMA_Priority = DMA_Priority_VeryHigh; DMA_InitStructure_USART2_TX.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel7, &DMA_InitStructure_USART2_TX); DMA_DeInit(DMA1_Channel6); DMA_InitStructure_USART2_RX.DMA_PeripheralBaseAddr = (u32)(&USART2->DATAR); DMA_InitStructure_USART2_RX.DMA_MemoryBaseAddr = (u32)RxBuffer2; DMA_InitStructure_USART2_RX.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure_USART2_RX.DMA_BufferSize = USART2_BUFLEN_RX; DMA_InitStructure_USART2_RX.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure_USART2_RX.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure_USART2_RX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure_USART2_RX.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure_USART2_RX.DMA_Mode = DMA_Mode_Normal; //单次模式 DMA_InitStructure_USART2_RX.DMA_Priority = DMA_Priority_VeryHigh; DMA_InitStructure_USART2_RX.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel6, &DMA_InitStructure_USART2_RX); //USART2 config /* USART2 TX-->A.2 RX-->A.3 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = baudtate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART2, &USART_InitStructure); USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); USART_Cmd(USART2, ENABLE);//先使能串口 再使能中断 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_Cmd(DMA1_Channel7, ENABLE); /* USART2 Tx */ DMA_Cmd(DMA1_Channel6, ENABLE); /* USART2 Rx */ USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);//使能接收转运 USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);//使能发送转运 } //备注:DMA通道使能和发送接收请求使能都要开启,缺一不可 //每个外设对应的DMA转运通道是固定的,具体可以查看芯片手册
下面给出USART发送测试代码: void USART2_DMA_SendByte(uint8_t send_dat) { TxBuffer2[0] = send_dat; DMA_DeInit(DMA1_Channel7); //初始化 // DMA_Cmd(DMA1_Channel7, DISABLE); //先失能DMA DMA_InitStructure_USART2_TX.DMA_BufferSize=1; //发送的数据量大小 ==>> 单个字节 DMA_Init(DMA1_Channel7, &DMA_InitStructure_USART2_TX); DMA_Cmd(DMA1_Channel7, ENABLE); //再使能 DMA while(DMA_GetFlagStatus(DMA1_FLAG_TC7) == RESET) {;}; } void USART2_DMA_SendBuff(uint8_t *buf, uint8_t len) { memcpy(TxBuffer2,buf,len*sizeof(unsigned char)); DMA_DeInit(DMA1_Channel7); //初始化 // DMA_Cmd(DMA1_Channel7, DISABLE); //先失能DMA DMA_InitStructure_USART2_TX.DMA_MemoryBaseAddr = (u32)TxBuffer2; DMA_InitStructure_USART2_TX.DMA_BufferSize=len; //发送的数据量大小 ==>> 单个字节 DMA_Init(DMA1_Channel7, &DMA_InitStructure_USART2_TX); DMA_Cmd(DMA1_Channel7, ENABLE); //再使能 DMA while(DMA_GetFlagStatus(DMA1_FLAG_TC7) == RESET) {;}; } //由于DMA开启的是正常模式,所以每次转运完成之后都需要重新使能,实际测试时不用先失能,可能DMA_DeInit已经失能过了。 下面给出正常串口发送的测试代码: void USARTX_SendByte(USART_TypeDef *USARTx, uint8_t send_dat) { while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET); USART_SendData(USARTx, send_dat); } //备注:这里先写while 再写发送效果会比先写发送再写while好,具体情况需要查看串口发送流程和状态位,如果是后者的话可能会丢数据; 下面给出串口接收测试代码: //本函数会将一帧数据收到的字节数发送,也可根据接收到的字节数将缓存区中收到的数据都发送 void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_IDLE) == SET) { USART_ClearITPendingBit(USART2 , USART_IT_IDLE); USART2_Revnum = USART2->STATR; USART2_Revnum = USART2->DATAR; DMA_Cmd(DMA1_Channel6, DISABLE); USART2_Revnum =USART2_BUFLEN_RX - DMA_GetCurrDataCounter(DMA1_Channel6); DMA1_Channel6->CNTR = USART2_BUFLEN_RX;//重新设置接收字数 while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); USART_SendData(USART2, USART2_Revnum); DMA_Cmd(DMA1_Channel6, ENABLE); } } 以上就是笔者测试用的配置代码 ,和沁恒官网给的测试例程略微有点出入,利用了串口空闲中断来转运接收数据,优势在于比如甲一次发给了我32位字节,我这边USART实际只进入了1次串口中断。另外需要提醒的是,进入串口中断一定要先读SR ,再读DR寄存器,用于清除状态位,方便下一帧数据接收。 至于使用DMA发送数据的优势我目前没发现有多大,可能是代码写的问题,实际测试时,115200的波特率使用USART2一次发送15个字节,使用DMA发送比普通库函数发送快了约50us,此波特率下发送一个字节约耗时1/115200 * 10 = 87us,也就是说快一个字节不到,看发送函数可知库函数发送和DMA转运都需要等待while 待状态位转变,笔者认为使用DMA节省的可能是是发送一个字节与另一个字节的装载数据间隙,可能有误,也没有去深究,毕竟只是使用库函数发送数据也能达到500000的比特率,单片机发送字节上位机接收无误。 另外笔者使用的测时函数是借鉴无名小哥大佬开源的,此类思想在多款单片机上都可用,在此献上: .c文件 #define TIMX TIM5 void GetTime_Start(ST_TimeTest_INFO* pst_Timetest) { pst_Timetest->time_start = (uint32_t)TIMX->CNT + testTime_Cnt*10000; } void GetTime_End(ST_TimeTest_INFO* pst_Timetest) { pst_Timetest->time_end = (uint32_t)TIMX->CNT + testTime_Cnt*10000; pst_Timetest->time_cost = pst_Timetest->time_end - pst_Timetest->time_start; } void GetTime_Period(ST_TimeTest_INFO* pst_Timetest) { pst_Timetest->time_now = (uint32_t)TIMX->CNT + testTime_Cnt*10000; pst_Timetest->time_period = pst_Timetest->time_now - pst_Timetest->time_last; pst_Timetest->time_last = pst_Timetest->time_now; } .h文件 typedef struct{ uint32_t time_start; uint32_t time_end; uint32_t time_now; uint32_t time_last; uint32_t time_period; uint32_t time_cost; }ST_TimeTest_INFO; 以上,欢迎大家在评论区交流,祝好! |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |