关于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;

 以上,欢迎大家在评论区交流,祝好!

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
对于 ch32v307vct6 芯片来说,配置串口需要以下步骤: 1. 确定串口配置参数,包括波特率、数据位、校验位和停止位等。 2. 配置 GPIO,将对应的引脚设置为串口功能。 3. 配置串口控制器,包括设置波特率、数据位、校验位和停止位等参数,以及开启串口发送接收中断。 4. 编写相应的串口发送接收函数,用于向串口发送数据和接收数据。 下面是一个简单的示例代码,演示如何配置 ch32v307vct6 的串口: ```c #include "ch32v307.h" #define UART_TX_PIN GPIO_Pin_10 #define UART_RX_PIN GPIO_Pin_11 void uart_init(uint32_t baud_rate, uint8_t data_bits, uint8_t parity, uint8_t stop_bits) { // 使能 GPIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置 GPIO 引脚为复用功能 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = UART_TX_PIN | UART_RX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 使能 USART 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 配置 USART 参数 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = baud_rate; USART_InitStructure.USART_WordLength = data_bits; USART_InitStructure.USART_Parity = parity; USART_InitStructure.USART_StopBits = stop_bits; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); // 开启 USART 中断 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 使能 USART USART_Cmd(USART2, ENABLE); } void uart_send_byte(uint8_t byte) { while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); USART_SendData(USART2, byte); } uint8_t uart_receive_byte(void) { while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET); return USART_ReceiveData(USART2); } ``` 在上面的代码中,我们首先定义了 UART_TX_PIN 和 UART_RX_PIN 分别表示串口发送接收引脚所在的 GPIO 引脚号。 然后,在 uart_init 函数中,我们依次进行了 GPIO 配置、USART 配置和中断配置等操作。其中,baud_rate、data_bits、parity 和 stop_bits 分别表示波特率、数据位、校验位和停止位等参数。 最后,我们分别定义了 uart_send_byte 和 uart_receive_byte 两个函数,用于向串口发送数据和接收数据。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值