Depuração da porta RGB para GD32F450

A migração do driver RGB do projeto anterior baseado no GD32F407 para o GD32F450 seguiu um processo direto.

A inicialização do Temporizador 4 (Timer4) para controlar dois canais PWM via DMA envolve a configuração de GPIO, estruturas de temporização e parâmetros DMA.


/* Função de inicialização do Timer4 para dois canais PWM com DMA */
void init_timer4_rgb_driver(void)
{
    /* Ativar clocks para GPIO e periféricos necessários */
    rcu_periph_clock_enable(RCU_TIMER4);
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOC);
    rcu_periph_clock_enable(RCU_DMA0);

    dma_single_data_parameter_struct dma_cfg;
    timer_oc_parameter_struct oc_cfg;
    timer_parameter_struct timer_cfg;

    /* Configurar pino de controle de potência RGB */
    gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_14);
    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_14);

    /* Configurar pinos AF para PWM (PA0 e PA1) */
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0 | GPIO_PIN_1);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1);
    gpio_af_set(GPIOA, GPIO_AF_2, GPIO_PIN_0 | GPIO_PIN_1);

    /* Parâmetros gerais do Timer4 */
    timer_cfg.prescaler = 9;
    timer_cfg.alignedmode = TIMER_COUNTER_EDGE;
    timer_cfg.counterdirection = TIMER_COUNTER_UP;
    timer_cfg.period = 24;
    timer_cfg.clockdivision = TIMER_CKDIV_DIV1;
    timer_cfg.repetitioncounter = 0;
    timer_init(TIMER4, &timer_cfg);

    /* Configuração do canal 0 (PWM) */
    oc_cfg.outputstate = TIMER_CCX_ENABLE;
    oc_cfg.ocpolarity = TIMER_OC_POLARITY_HIGH;
    timer_channel_output_config(TIMER4, TIMER_CH_0, &oc_cfg);
    timer_channel_output_pulse_value_config(TIMER4, TIMER_CH_0, 0);
    timer_channel_output_mode_config(TIMER4, TIMER_CH_0, TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER4, TIMER_CH_0, TIMER_OC_SHADOW_ENABLE);

    /* Configuração do canal 1 (PWM) */
    timer_channel_output_config(TIMER4, TIMER_CH_1, &oc_cfg);
    timer_channel_output_pulse_value_config(TIMER4, TIMER_CH_1, 0);
    timer_channel_output_mode_config(TIMER4, TIMER_CH_1, TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER4, TIMER_CH_1, TIMER_OC_SHADOW_ENABLE);

    timer_auto_reload_shadow_disable(TIMER4);

    /* DMA para canal 0 (DMA0_CH2 -> Timer4_CH0) */
    dma_deinit(DMA0, DMA_CH2);
    dma_cfg.periph_addr = (uint32_t)&TIMER4->CH0CV;
    dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_cfg.memory0_addr = (uint32_t)pixel_buffer;
    dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_cfg.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;
    dma_cfg.direction = DMA_MEMORY_TO_PERIPH;
    dma_cfg.number = 42;
    dma_cfg.priority = DMA_PRIORITY_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH2, &dma_cfg);
    dma_channel_subperipheral_select(DMA0, DMA_CH2, DMA_SUBPERI6);
    dma_circulation_disable(DMA0, DMA_CH2);
    timer_dma_enable(TIMER4, TIMER_DMA_CH0D);
    dma_channel_disable(DMA0, DMA_CH2);

    /* DMA para canal 1 (DMA0_CH4 -> Timer4_CH1) */
    dma_deinit(DMA0, DMA_CH4);
    dma_cfg.periph_addr = (uint32_t)&TIMER4->CH1CV;
    dma_single_data_mode_init(DMA0, DMA_CH4, &dma_cfg);
    dma_channel_subperipheral_select(DMA0, DMA_CH4, DMA_SUBPERI6);
    dma_circulation_disable(DMA0, DMA_CH4);
    timer_dma_enable(TIMER4, TIMER_DMA_CH1D);
    dma_channel_disable(DMA0, DMA_CH4);

    timer_disable(TIMER4);
}

A inicialização do Timer2 para controlar um canal PWM adicional segue uma abordagem semelhante.


/* Função de inicialização do Timer2 para um canal PWM com DMA */
void init_timer2_rgb_driver(void)
{
    rcu_periph_clock_enable(RCU_GPIOB);
    rcu_periph_clock_enable(RCU_DMA0);
    rcu_periph_clock_enable(RCU_TIMER2);

    /* Configurar pino AF para PWM (PB1) */
    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
    gpio_af_set(GPIOB, GPIO_AF_2, GPIO_PIN_1);

    timer_oc_parameter_struct oc_cfg;
    timer_parameter_struct timer_cfg;

    /* Parâmetros do Timer2 */
    timer_cfg.prescaler = 9;
    timer_cfg.alignedmode = TIMER_COUNTER_EDGE;
    timer_cfg.counterdirection = TIMER_COUNTER_UP;
    timer_cfg.period = 24;
    timer_cfg.clockdivision = TIMER_CKDIV_DIV1;
    timer_cfg.repetitioncounter = 0;
    timer_init(TIMER2, &timer_cfg);

    /* Configuração do canal 3 (PWM) */
    oc_cfg.outputstate = TIMER_CCX_ENABLE;
    oc_cfg.ocpolarity = TIMER_OC_POLARITY_HIGH;
    timer_channel_output_config(TIMER2, TIMER_CH_3, &oc_cfg);
    timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_3, 0);
    timer_channel_output_mode_config(TIMER2, TIMER_CH_3, TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER2, TIMER_CH_3, TIMER_OC_SHADOW_ENABLE);

    timer_auto_reload_shadow_disable(TIMER2);

    /* DMA para canal 3 (DMA0_CH2 -> Timer2_CH3) */
    dma_single_data_parameter_struct dma_cfg;
    dma_deinit(DMA0, DMA_CH2);
    dma_cfg.periph_addr = (uint32_t)&TIMER2->CH3CV;
    dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_cfg.memory0_addr = (uint32_t)pixel_buffer;
    dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_cfg.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;
    dma_cfg.direction = DMA_MEMORY_TO_PERIPH;
    dma_cfg.number = 42;
    dma_cfg.priority = DMA_PRIORITY_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH2, &dma_cfg);
    dma_channel_subperipheral_select(DMA0, DMA_CH2, DMA_SUBPERI5);
    dma_circulation_disable(DMA0, DMA_CH2);
    timer_dma_enable(TIMER2, TIMER_DMA_UPD);
    dma_channel_disable(DMA0, DMA_CH2);

    timer_disable(TIMER2);
}

A lógica de envio de dados para os LEDs WS2812 envolve o preenchimento de um buffer com valores PWM correspondentes a bits 0 e 1, seguido pela ativação sequencial dos canais DMA.


/* Função principal para controle das tiras de LED RGB */
void processar_lampada_teclado(void)
{
    /* Preencher buffers com valor máximo (branco) */
    memset(rgb_buffer_1, 0xFF, sizeof(rgb_buffer_1));
    memset(rgb_buffer_2, 0xFF, sizeof(rgb_buffer_2));
    memset(rgb_buffer_3, 0xFF, sizeof(rgb_buffer_3));

    transmitir_ws2812(rgb_buffer_1, 17, CANAL_RGB_1);
    transmitir_ws2812(rgb_buffer_2, 10, CANAL_RGB_2);
    transmitir_ws2812(rgb_buffer_3, 17, CANAL_RGB_3);
}

/* Função de transmissão de dados para LEDs WS2812 */
void transmitir_ws2812(uint8_t (*cor)[3], uint16_t quantidade, uint8_t canal_pwm)
{
    uint16_t posicao_memoria = 0;
    uint16_t tamanho_buffer = (quantidade * 24) + 42;

    while (quantidade--)
    {
        /* Transmissão dos 8 bits do componente verde */
        for (uint8_t bit = 0; bit < 8; bit++)
        {
            uint8_t valor_pwm = ((cor[0][1] << bit) & 0x80) ? 17 : 9;
            pwm_buffer[posicao_memoria++] = valor_pwm;
        }

        /* Transmissão dos 8 bits do componente vermelho */
        for (uint8_t bit = 0; bit < 8; bit++)
        {
            uint8_t valor_pwm = ((cor[0][0] << bit) & 0x80) ? 17 : 9;
            pwm_buffer[posicao_memoria++] = valor_pwm;
        }

        /* Transmissão dos 8 bits do componente azul */
        for (uint8_t bit = 0; bit < 8; bit++)
        {
            uint8_t valor_pwm = ((cor[0][2] << bit) & 0x80) ? 17 : 9;
            pwm_buffer[posicao_memoria++] = valor_pwm;
        }
    }

    /* Preencher o restante do buffer com zeros (reset) */
    while (posicao_memoria < tamanho_buffer)
    {
        pwm_buffer[posicao_memoria++] = 0;
    }

    /* Ativação sequencial baseada no canal PWM */
    switch (canal_pwm)
    {
        case 1: /* Timer4_CH0 */
            dma_transfer_number_config(DMA0, DMA_CH2, tamanho_buffer);
            dma_channel_enable(DMA0, DMA_CH2);
            timer_enable(TIMER4);
            while (!dma_flag_get(DMA0, DMA_CH2, DMA_FLAG_FTF));
            dma_channel_disable(DMA0, DMA_CH2);
            dma_flag_clear(DMA0, DMA_CH2, DMA_FLAG_FTF);
            timer_disable(TIMER4);
            break;

        case 2: /* Timer4_CH1 */
            dma_transfer_number_config(DMA0, DMA_CH4, tamanho_buffer);
            dma_channel_enable(DMA0, DMA_CH4);
            timer_enable(TIMER4);
            while (!dma_flag_get(DMA0, DMA_CH4, DMA_FLAG_FTF));
            dma_channel_disable(DMA0, DMA_CH4);
            dma_flag_clear(DMA0, DMA_CH4, DMA_FLAG_FTF);
            timer_disable(TIMER4);
            break;

        case 3: /* Timer2_CH3 */
            dma_transfer_number_config(DMA0, DMA_CH2, tamanho_buffer);
            dma_channel_enable(DMA0, DMA_CH2);
            timer_enable(TIMER2);
            while (!dma_flag_get(DMA0, DMA_CH2, DMA_FLAG_FTF));
            dma_channel_disable(DMA0, DMA_CH2);
            dma_flag_clear(DMA0, DMA_CH2, DMA_FLAG_FTF);
            timer_disable(TIMER2);
            break;
    }
}

Durante os testes, os canais RGB2 e RGB3 funcionaram corretamente, mas o RGB1 travou em um loop while. A análise revelou um conflito de canal DMA: tanto o Timer4_CH0 quanto o Timer2_CH3 utilizavam o DMA0_CH2. Como a inicialização do Timer2 ocorria após a do Timer4, ela sobrescrevia a configuração anterior.

A solução envolveu reconfigurar o DMA antes de cada uso, adicionando funções auxiliares:


/* Reconfigurar DMA para o canal 3 do Timer2 */
void reconfigurar_dma_timer2_ch3(void)
{
    dma_single_data_parameter_struct dma_cfg;
    dma_deinit(DMA0, DMA_CH2);
    dma_cfg.periph_addr = (uint32_t)&TIMER2->CH3CV;
    dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_cfg.memory0_addr = (uint32_t)pixel_buffer;
    dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_cfg.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;
    dma_cfg.direction = DMA_MEMORY_TO_PERIPH;
    dma_cfg.number = 42;
    dma_cfg.priority = DMA_PRIORITY_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH2, &dma_cfg);
    dma_channel_subperipheral_select(DMA0, DMA_CH2, DMA_SUBPERI5);
    dma_circulation_disable(DMA0, DMA_CH2);
}

/* Reconfigurar DMA para o canal 0 do Timer4 */
void reconfigurar_dma_timer4_ch0(void)
{
    dma_single_data_parameter_struct dma_cfg;
    dma_deinit(DMA0, DMA_CH2);
    dma_cfg.periph_addr = (uint32_t)&TIMER4->CH0CV;
    dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_cfg.memory0_addr = (uint32_t)pixel_buffer;
    dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_cfg.periph_memory_width = DMA_PERIPH_WIDTH_16BIT;
    dma_cfg.direction = DMA_MEMORY_TO_PERIPH;
    dma_cfg.number = 42;
    dma_cfg.priority = DMA_PRIORITY_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH2, &dma_cfg);
    dma_channel_subperipheral_select(DMA0, DMA_CH2, DMA_SUBPERI6);
    dma_circulation_disable(DMA0, DMA_CH2);
}

Após essa correção, o fluxo de execução tornou-se estável, mas os LEDs permaneceram apagados. Testes adicionais confirmaram que o temporizador produzia sinais PWM corretos por software, sugerindo um problema na transferência DMA.

Consultas com o fabricante revelaram dois problemas críticos:

  1. Um bug conhecido no GD32F450: quando o DMA transfere valores zero, ele pode descartar os próximos um ou dois dados.
  2. Diferenças nos registradores de comparação dos temporizadores: os canais CH1CV do Timer1 e Timer4 são de 32 bits, enquanto os CHCV do Timer2 e Timer3 são de 16 bits.

A configuração inicial da largura de transferência do DMA como 16 bits causava a escrita de dados de 16 bits em um registrdaor de 32 bits, resultando em valores de comparação absurdos (17 << 16 = 1114112), muito acima do período de 35 do temporizador.

A solução final envolveu duas alterações:

  1. Ajustar a largura de transferência do DMA para 32 bits quando usando temporizadores com registradores de 32 bits.
  2. Utilizar o modo de atualização (TIMER_DMA_UPD) em vez de disparo por canal para evitar o bug de perda de dados.

Exemplo de configuração corrigida para o Timer4_CH0 usando DMA0_CH0 com atualização:


/* Configuração alternativa usando atualização do temporizador */
void configurar_timer4_com_dma_update(void)
{
    /* ... configurações anteriores ... */

    /* DMA usando canal 0 e atualização do temporizador */
    dma_deinit(DMA0, DMA_CH0);
    dma_cfg.periph_addr = (uint32_t)&TIMER4->CH0CV;
    dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_cfg.memory0_addr = (uint32_t)pixel_buffer;
    dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_cfg.periph_memory_width = DMA_PERIPH_WIDTH_32BIT;
    dma_cfg.direction = DMA_MEMORY_TO_PERIPH;
    dma_cfg.number = 42;
    dma_cfg.priority = DMA_PRIORITY_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH0, &dma_cfg);
    dma_channel_subperipheral_select(DMA0, DMA_CH0, DMA_SUBPERI6);
    dma_circulation_disable(DMA0, DMA_CH0);
    
    /* Usar atualização em vez de evento do canal */
    timer_dma_enable(TIMER4, TIMER_DMA_UPD);
    dma_channel_disable(DMA0, DMA_CH0);

    /* ... restante da configuração ... */
}

Após essas correções, os LEDs WS2812 passaram a ser controlados de forma estável, sem perda de dados ou travamentos.

Tags: GD32F450 WS2812 DMA PWM TIMER4

Publicado em 5-31 08:47 por Thomas