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:
- Um bug conhecido no GD32F450: quando o DMA transfere valores zero, ele pode descartar os próximos um ou dois dados.
- 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:
- Ajustar a largura de transferência do DMA para 32 bits quando usando temporizadores com registradores de 32 bits.
- 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.