GD32F427 serial port idle interrupt DMA transceiver

Recently, I used GD32F427 to switch from the original ST HAL library to the GD library. It took a while to adjust the serial port DMA transceiver of GD32F427. Let’s go directly to the code. The same set of codes has adjusted USART0, USART1, USART2, and USART5. This article takes USART0 as an example.

First initialize NVIC.

void nvic_config(void)
{
   nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
   nvic_irq_enable(USART0_IRQn, 5, 0);
   nvic_irq_enable(USART1_IRQn, 5, 0);
   nvic_irq_enable(USART2_IRQn, 5, 0);
   nvic_irq_enable(USART5_IRQn, 5, 0);
}

Then the RCU clock is enabled

void rcu_config(void)
{
    rcu_periph_clock_enable( RCU_GPIOA );
    rcu_periph_clock_enable( RCU_GPIOB );
    rcu_periph_clock_enable( RCU_GPIOC );
    rcu_periph_clock_enable( RCU_GPIOD );
    rcu_periph_clock_enable( RCU_GPIOE );
    rcu_periph_clock_enable( RCU_GPIOH );
    /* enable DMA clock */
    rcu_periph_clock_enable(RCU_DMA0);
    rcu_periph_clock_enable(RCU_DMA1);
}

Then there is the serial port related initialization:

static void Drv_Usart0Config(void)
{
    usart_deinit(USART0);
    usart_disable(USART0);
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_USART0);
    gpio_af_set(GPIOA,GPIO_AF_7,GPIO_PIN_9|GPIO_PIN_10);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9|GPIO_PIN_10);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_9|GPIO_PIN_10);
    nvic_irq_enable(USART0_IRQn,5,0);
        
    usart_baudrate_set(USART0,115200);
    usart_parity_config(USART0,USART_PM_NONE);
    usart_word_length_set(USART0,USART_WL_8BIT);
    usart_stop_bit_set(USART0,USART_STB_1BIT);
    usart_hardware_flow_coherence_config(USART0,USART_HCM_NONE);
    usart_data_first_config(USART0,USART_MSBF_LSB);
    
    usart_enable(USART0);
    
    usart_transmit_config(USART0,USART_TRANSMIT_ENABLE);
    usart_receive_config(USART0,USART_RECEIVE_ENABLE);
    
    usart_dma_transmit_config(USART0, USART_TRANSMIT_DMA_ENABLE);//Open serial port DMA transmission
    usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);//Open serial port DMA reception
    
    usart_flag_clear(USART0, USART_FLAG_TC);
    usart_interrupt_enable(USART0,USART_INT_IDLE);//Use serial port idle interrupt
}

Then initialize DMA: TX and RX are initialized separately, or they can be initialized together, no matter what.

Pay attention to the chip manual and find the DMA channel corresponding to the serial port:

#define UART0_TX_DMA_CHANEL DMA_CH7
#define UART0_RX_DMA_CHANEL DMA_CH5

#define UART0_TX_DMA_CHANEL DMA_CH7
#define UART0_RX_DMA_CHANEL DMA_CH5
#define SCI_RX_LENGTH 128 /*Serial port receiving buff length*/
uint8_t UART0_RxDMABuffer[SCI_RX_LENGTH];
uint8_t UART0_TxDMABuffer[SCI_RX_LENGTH];

static void Drv_Usart0TX_DMAConfig(void)
{
    dma_single_data_parameter_struct dma_init_struct;
    /* enable DMA0 */

    rcu_periph_clock_enable(RCU_DMA1);

    /* deinitialize DMA channel6(USART0 tx) */
    dma_deinit(DMA1, UART0_TX_DMA_CHANEL);//Tx corresponds to DMA0 channel 6
    dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
    dma_init_struct.memory0_addr = (uint32_t)UART0_TxDMABuffer;
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.number = SCI_RX_LENGTH;
    dma_init_struct.periph_addr = (uint32_t)( & amp;USART_DATA(USART0));
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(DMA1, UART0_TX_DMA_CHANEL, & amp;dma_init_struct);
    dma_channel_subperipheral_select(DMA1, UART0_TX_DMA_CHANEL, DMA_SUBPERI4);
    //configure DMA mode
    dma_circulation_disable(DMA1, UART0_TX_DMA_CHANEL);
}

static void Drv_Usart0RX_DMAConfig(void)
{
    dma_single_data_parameter_struct dma_parameter;
    /* enable DMA0 */
    rcu_periph_clock_enable(RCU_DMA1);
    /*Receive dm0 channel5(USART0 rx) */
    dma_deinit(DMA1, UART0_RX_DMA_CHANEL);//rx corresponds to DMA0 channel 5
    dma_parameter.direction = DMA_PERIPH_TO_MEMORY;
    dma_parameter.periph_addr = (uint32_t)( & amp;USART_DATA(USART0));
    dma_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_parameter.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_parameter.memory0_addr = (uint32_t)UART0_RxDMABuffer;
    dma_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_parameter.number = SCI_RX_LENGTH;
    dma_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
    dma_single_data_mode_init(DMA1, UART0_RX_DMA_CHANEL, & amp;dma_parameter);

    /* configure DMA mode */
    dma_channel_subperipheral_select(DMA1, UART0_RX_DMA_CHANEL, DMA_SUBPERI4);
    dma_channel_enable(DMA1, UART0_RX_DMA_CHANEL);
}

After the serial port and DMA are initialized, the serial port can be used, but since we use interrupts, we need to add an interrupt function:

void USART0_IRQHandler(void)
{
    if(usart_interrupt_flag_get(USART0,USART_INT_FLAG_IDLE) != RESET)
    {
        usart_interrupt_flag_clear(USART0,USART_INT_FLAG_IDLE);
        USART_STAT0(USART0);
        USART_DATA(USART0);
        dma_channel_disable(DMA1, UART0_RX_DMA_CHANEL);
        UART0_RecCount = SCI_RX_LENGTH - dma_transfer_number_get(DMA1, UART0_RX_DMA_CHANEL);
        if(UART0_RecCount != 0 & amp; & amp; UART0_RecCount < SCI_RX_LENGTH)
        {
            g_UART0_Recv.RxLen = UART0_RecCount;
            memcpy(g_UART0_Recv.RxBuffer,UART0_RxDMABuffer,UART0_RecCount);
            memset(UART0_RxDMABuffer,0,sizeof(UART0_RxDMABuffer));
            //Reset DMA transfer
            dma_memory_address_config(DMA1, UART0_RX_DMA_CHANEL,DMA_MEMORY_0,(uint32_t)UART0_RxDMABuffer);
            dma_transfer_number_config(DMA1, UART0_RX_DMA_CHANEL,SCI_RX_LENGTH);
            dma_flag_clear(DMA1, UART0_RX_DMA_CHANEL, DMA_FLAG_FTF);
            dma_channel_enable(DMA1, UART0_RX_DMA_CHANEL); ///* Enable DMA transmission
        }
        else
        {
            memset(g_UART0_Recv.RxBuffer,0,SCI_RX_LENGTH);
        }
    }
    return ;
}

The interrupt function performs operations such as idle interrupt judgment, clearing the idle interrupt flag, data read length, copying, and re-enabling DMA reception.

Serial port DMA transceiver should also be done to reduce the workload of MCU.

void Drv_UART0_Send(uint8_t *pData,uint16_t Size){
    usart_flag_clear(USART0, USART_FLAG_TC);

    dma_channel_disable(DMA1, UART0_TX_DMA_CHANEL);
    dma_flag_clear(DMA1, UART0_TX_DMA_CHANEL, DMA_FLAG_FTF);
    dma_memory_address_config(DMA1, UART0_TX_DMA_CHANEL, DMA_MEMORY_0, (uint32_t)data);
    dma_transfer_number_config(DMA1, UART0_TX_DMA_CHANEL, len);
    dma_channel_enable(DMA1, UART0_TX_DMA_CHANEL);
// while (usart_flag_get(USART0, USART_FLAG_TC)!=RESET);
    while ( RESET == dma_flag_get( DMA1, UART0_TX_DMA_CHANEL, DMA_INTF_FTFIF ) ) //Get the transmission completed
    {
    }
}

Let’s look at the main program initialization:

int main(void)
{
    rcu_config();
    nvic_config();
    /* configure systick */
    systick_config();
// SEGGER_SYSVIEW_Conf();
    SysClk = rcu_clock_freq_get(CK_SYS); //120M
    HClk = rcu_clock_freq_get(CK_AHB); //120M
    PClk1 = rcu_clock_freq_get(CK_APB1); //30M
    PClk2 = rcu_clock_freq_get(CK_APB2); //60M
    /* enable the LEDs GPIO clock */
    
    MX_GPIO_Init();
    Drv_UsartConfig();
    Drv_UsartDMAInit();
    /************************/
    MX_FREERTOS_Init();
    /************************/

    vTaskStartScheduler();
    
      while (1)
      {

}

Run it every 20ms in the task. Yes, simply judge that the received length is greater than 0, and then send the received data to verify whether the serial port is working properly.

void USART_Commtask(void const * argument)
{

  for(;;)
  {

    if(g_UART0_Recv.RxLen > 0)
    {
        Drv_UART0_Send(g_UART0_Recv.RxBuffer,g_UART0_Recv.RxLen);
        g_UART0_Recv.RxLen = 0;
    }
    osDelay(20);
  }
}

The following is an upward adjustment diagram:

Sending and receiving perfect. No packet loss. The same is true for the other three serial ports.