八层楼 发表于 2025-6-11 11:42

gd32f103c8t6开发板实现串口DMA空闲中断高速收发

采用GD32F103C8T6开发板实现了串口1和串口3的DMA空闲中断通信,在串口1上还添加了SP485,实现了485方向控制,

串口1的针脚为PA9和PA10

串口3的针脚为PB10和PB11,

由于在串口1添加了485模块,所以必须控制数据收发的方向控制,只有485模块的DE和RE为低电平时才能接收,高电平时才能发送,方向控制的针脚为PA8,实现代码如下:

/*
USART0
PA9:TX发送
PA10:RX 接收
DMA_CH4为接收
DMA_CH3为发送
*/

#include <string.h>
#include "gd32f10x.h"
#include "systick.h"
#include "usart0_rs485.h"

uint8_t usart0_rx_buf;
uint8_t usart0_tx_buf;
volatile static uint8_t usart0_tx_busy = 0; //发送状态
volatile uint8_t usart0_rx_flag = 0; //接收状态
volatile uint16_t usart0_rx_len = 0; //接收长度

//若串口0添加了485,则需要配置485方向控制使能
#define rs485_rx_en GPIO_BC(GPIOA)=GPIO_PIN_8;
#define rs485_tx_en GPIO_BOP(GPIOA)=GPIO_PIN_8;

void USART0_nvic_config(void)
{
    /* 设置优先级组 */
    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);

    //USART0 DMA0发送中断
    nvic_irq_enable(DMA0_Channel3_IRQn, 1, 0);

    //USART0 DMA0接收中断
    nvic_irq_enable(DMA0_Channel4_IRQn, 1, 0);

    //USART0 串口中断
    nvic_irq_enable(USART0_IRQn, 0, 0);
}

void USART0_rcu_config()
{
    rcu_periph_clock_enable(RCU_DMA0);

    /* enable GPIO clock */
    rcu_periph_clock_enable(RCU_GPIOA);

    /* enable USART clock */
    rcu_periph_clock_enable(RCU_USART0);

    rcu_periph_clock_enable(RCU_AF);   
}

void USART0_dma_recv_config()
{
    dma_parameter_struct dma_init_struct;

    /* deinitialize DMA channel4 (USART0 rx) */
    dma_deinit(DMA0, DMA_CH4);
    dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;
    dma_init_struct.memory_addr = (uint32_t)usart0_rx_buf;
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
    dma_init_struct.number = sizeof(usart0_rx_buf);
    dma_init_struct.periph_addr = (uint32_t)&USART_DATA(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_init(DMA0, DMA_CH4, &dma_init_struct);

    //dma_interrupt_enable(d->dma_periph, d->dma_rx_ch, DMA_INT_FTF);

    /* configure DMA mode */
    dma_circulation_disable(DMA0, DMA_CH4);
    /* enable DMA channel4 */
    dma_channel_enable(DMA0, DMA_CH4);   

}

void USART0_dma_send_config()
{
    uint32_t dma = DMA0;
    dma_channel_enum ch = DMA_CH3;
    dma_parameter_struct ds;
    uint32_t cpLen = sizeof(usart0_tx_buf);


    /* 复位DMA参数结构体变量 */   
    dma_struct_para_init(&ds);
    dma_deinit(dma, ch);
    ds.direction = DMA_MEMORY_TO_PERIPHERAL;
    ds.memory_addr = (uint32_t)usart0_tx_buf;
    ds.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    ds.memory_width = DMA_MEMORY_WIDTH_8BIT;
    ds.number = cpLen;
    ds.periph_addr = (uint32_t)&USART_DATA(USART0);
    ds.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    ds.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
    ds.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_init(dma, ch, &ds);

    dma_circulation_disable(dma, ch);
    dma_memory_to_memory_disable(dma, ch);
    dma_interrupt_enable(dma, ch, DMA_INT_FTF);
    //dma_channel_enable(dma, ch);
    usart_dma_transmit_config(dma, USART_TRANSMIT_DMA_ENABLE);
}

void USART0_dma_send(uint8_t *p, uint32_t number)
{
    uint32_t dma = DMA0;
    dma_channel_enum ch = DMA_CH3;   
    uint32_t cpLen = number;
    if(cpLen > sizeof(usart0_tx_buf)){
      cpLen = sizeof(usart0_tx_buf);
    }
    memcpy(usart0_tx_buf, p, cpLen);

    //准备发送
    rs485_tx_en;

    //关闭DMA发送
    dma_channel_disable(dma, ch);

    //在发送地址改变的情况下需要重新配置发送地址
    //dma_memory_address_config(dma, ch, (uint32_t)usart0_tx_buf);

    //重新配置发送数据的长度
    dma_transfer_number_config(dma, ch, number);

    //启动DMA发送
    dma_channel_enable(dma, ch);
}

void USART0_dma_recv_reconfig(void)
{
    //uint32_t dma = DMA0;
    //dma_channel_enum ch = DMA_CH4;
    usart0_rx_flag = 0;
    usart0_rx_len = 0;
    memset(usart0_rx_buf,0,sizeof(usart0_rx_buf));
}

void USART0_Config(void)
{
    uint32_t usart_periph = USART0;

    //配置中断
    USART0_nvic_config();

    //配置时钟
    USART0_rcu_config();

    //使用PA8控制485的方向
    gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP,ENABLE);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
    rs485_rx_en;

    /* connect port to USARTx_Tx PA9 */
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

    /* connect port to USARTx_Rx PA10*/
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);

    /* USART configure */
    usart_deinit(usart_periph);
    usart_baudrate_set(usart_periph, 115200U);
    usart_word_length_set(usart_periph, USART_WL_8BIT);
    usart_stop_bit_set(usart_periph, USART_STB_1BIT);
    usart_parity_config(usart_periph, USART_PM_NONE);
    usart_receive_config(usart_periph, USART_RECEIVE_ENABLE);
    usart_transmit_config(usart_periph, USART_TRANSMIT_ENABLE);
    usart_hardware_flow_rts_config(usart_periph, USART_RTS_DISABLE);        /* 无硬件流控制 */
    usart_hardware_flow_cts_config(usart_periph, USART_CTS_DISABLE);        /* 无硬件流控制 */
    usart_dma_receive_config(usart_periph, USART_RECEIVE_DMA_ENABLE);
    usart_dma_transmit_config(usart_periph, USART_TRANSMIT_DMA_ENABLE);
    usart_enable(usart_periph);

    //配置DMA
    USART0_dma_recv_config();

    //配置串口空闲中断和错误中断   
    usart_interrupt_enable(usart_periph, USART_INT_IDLE);
    usart_interrupt_enable(usart_periph, USART_INT_ERR);
    usart_interrupt_enable(usart_periph, USART_INT_PERR);   
    //配置传输完成中断
    usart_interrupt_enable(usart_periph, USART_INT_TC);   

    USART0_dma_send_config();
}

void USART0_IRQHandler(void)
{
    uint32_t usart_periph = USART0;
    uint32_t dma = DMA0;
    uint32_t err_flag = 0;
    uint32_t flag = 0;
    dma_channel_enum ch = DMA_CH4;

    uint32_t stat = USART_STAT(USART0);
    /* 统一错误处理(寄存器级) 性能影响‌:寄存器级操作比库函数更高效,但需确保位映射正确*/
    if(stat & (USART_FLAG_ORERR | USART_FLAG_FERR | USART_FLAG_NERR | USART_FLAG_PERR)) {
      uint16_t data = USART_DATA(USART0);// 关键清除操作
      err_flag |= (stat & USART_FLAG_ORERR) ? 0x01 : 0;
      err_flag |= (stat & USART_FLAG_NERR)? 0x02 : 0;
      err_flag |= (stat & USART_FLAG_FERR)? 0x04 : 0;
      err_flag |= (stat & USART_FLAG_PERR)? 0x10 : 0;
    }

    if(RESET != usart_interrupt_flag_get(usart_periph, USART_INT_FLAG_IDLE)) {
      /* clear IDLE flag */
      usart_interrupt_flag_clear(usart_periph, USART_INT_FLAG_IDLE);
      usart_data_receive(usart_periph);

      /* number of data received */
      usart0_rx_len = sizeof(usart0_rx_buf) - (dma_transfer_number_get(dma, ch));
      usart0_rx_flag = 1;

      //关闭DMA接收
      dma_channel_disable(dma, ch);
      //重新配置接收数据的长度
      dma_transfer_number_config(dma, ch, sizeof(usart0_rx_buf));
      //重启DMA接收
      dma_channel_enable(dma, ch);
    }

    /*传输完成中断
    触发时机:满足以下条件同时成立时触发:
    发送数据寄存器(TDR)为空
    移位寄存器完成最后一bit数据的物理发送
    */
    flag = USART_INT_FLAG_TC;
    if(usart_interrupt_flag_get(usart_periph, flag)){
      usart_interrupt_flag_clear(usart_periph, flag);
      usart0_tx_busy = 0;
      //传输完成后485启动接收使能,低电压
      rs485_rx_en;
    }

    /* 错误恢复处理 */
    if(err_flag){
      usart_disable(usart_periph);
      usart_enable(usart_periph); // 复位USART
    }
}

//DMA0 ch4接收中断
void DMA0_Channel4_IRQHandler()
{
    uint32_t usart_periph = USART0;
    uint32_t dma = DMA0;
    dma_channel_enum ch = DMA_CH4;
    if(dma_interrupt_flag_get(dma, ch, DMA_INT_FLAG_FTF)) {
      /* clear IDLE flag */
      usart_data_receive(usart_periph);

      usart0_rx_len = sizeof(usart0_rx_buf) - (dma_transfer_number_get(usart_periph, ch));
      usart0_rx_flag = 1;      
      //清除接收完成中断
      dma_interrupt_flag_clear(dma, ch, DMA_INT_FLAG_FTF);
    }
    if(dma_interrupt_flag_get(dma, ch, DMA_INT_FLAG_ERR)) {
      dma_channel_disable(dma, ch);
      dma_interrupt_flag_clear(dma, ch, DMA_INT_FLAG_ERR);
      dma_channel_enable(dma, ch);
    }
}

//USART0 DMA0 ch3发送中断
void DMA0_Channel3_IRQHandler(void)
{
    uint32_t dma = DMA0;
    dma_channel_enum ch = DMA_CH3;
    //触发时机:当DMA传输计数器递减至0时自动触发,代表可以添加数据到缓冲区了
    if(dma_interrupt_flag_get(dma, ch, DMA_INT_FLAG_FTF)){
      dma_interrupt_flag_clear(dma, ch, DMA_INT_FLAG_FTF);
    }
    if(dma_interrupt_flag_get(dma, ch, DMA_INT_FLAG_ERR)) {
      dma_channel_disable(dma, ch);
      dma_interrupt_flag_clear(dma, ch, DMA_INT_FLAG_ERR);
      dma_channel_enable(dma, ch);
    }
}
void wait_usart0_send_finish(void)
{
    while(usart0_tx_busy){};
}

void usart0_set_tx_busy(uint8_t busy)
{
    usart0_tx_busy = busy;
}
uint8_t usart0_get_tx_busy(void)
{
    return usart0_tx_busy;
}



源代码地址为:gd32f103c8t6-practice: 练习gd32f103c8t6开发板

购买的gd32f103c8t6开发板的链接为:

全新GD32F103C8T6开发板 含例程 含教学视频 GD32学习板核心板-淘宝网



————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/chencichang/article/details/148028369

beacherblack 发表于 2025-7-4 12:25

可以显著提高数据处理的速度和可靠性,特别是在数据量大或实时性要求高的场景下

timfordlare 发表于 2025-7-6 16:07

传输数量应小于数据缓冲区的大小,以避免溢出。

sesefadou 发表于 2025-7-11 11:01

环形队列是一种数据结构,用于在有限的空间内循环存储数据

elsaflower 发表于 2025-7-11 13:15

在串口通信中,环形队列可以用来暂存接收到的数据,直到应用程序准备好处理它们

ingramward 发表于 2025-7-11 16:10

配置DMA通道,使其在检测到USART的空闲中断时启动。空闲中断表明一帧数据已经接收完成

ulystronglll 发表于 2025-7-12 10:21

可以在数据接收后进行校验,确保数据的完整性和准确性。

hearstnorman323 发表于 2025-7-12 12:48

在中断处理函数中添加错误处理机制

rosemoore 发表于 2025-7-12 16:33

结合空闲中断实现高效数据传输。            

everyrobin 发表于 2025-7-12 18:19

在USART的空闲中断服务函数中,将接收到的数据从DMA缓冲区移动到环形队列中
页: [1]
查看完整版本: gd32f103c8t6开发板实现串口DMA空闲中断高速收发