关于CH32V307使用串口+DMA发送与接收的配置和一点见解

您所在的位置:网站首页 串口数据的接收与处理方法是 关于CH32V307使用串口+DMA发送与接收的配置和一点见解

关于CH32V307使用串口+DMA发送与接收的配置和一点见解

2024-07-12 09:09| 来源: 网络整理| 查看: 265

        笔者之前一直好奇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