Em sistemas embarcados, a captura de sinais analógicos com alta precisão e controle rigoroso sobre a frequência de amostragem é um requisito frequente. Uma abordagem altamente eficiente para microcontroladores da família GD32 envolve utilizar um temporizador (Timer) para gerar o gatilho de conversão do conversor analógico-digital (ADC), combinado com o controlador de Acesso Direto à Memória (DMA) para trasnferir os resultados diretamente para a memória RAM.
Arquitetura do Sistema
- Timer de Disparo: Atua como a fonte de sincronização. O evento de atualização do temporizador inicia uma conversão no ADC. Ajustando o período deste timer, controla-se diretamente a taxa de amostragem.
- Conversor ADC: Configurado para operar no modo de varredura (Scan Mode), ignorando gatilhos de software e respondendo exclusivamente aos sinais de hardware.
- Controlador DMA: Assume a responsabilidade de mover os registradores de dados do ADC para um buffer alocado na memória, eliminando a sobrecarga do núcleo do processador.
Cálculo da Frequência de Amostragem
A taxa de amostragem é derivada matematicamente do clock do timer, do valor do prescaler (PSC) e do registrador de auto-reload (ARR):
Taxa = Clock_Timer / ((PSC + 1) * (ARR + 1))
Por exemplo, com um clock de timer de 60 MHz, um prescaler de 59 e um período de auto-reload de 99, a taxa resultante será de exatos 10 kHz.
Ambiente de Desenvolvimento e Hardware
- Microcontrolador: GD32F303VET6
- IDE: Keil MDK
- Biblioteca: GD32F10x Standadr Peripheral Library
- Mapeamento de Periféricos:
- Canais ADC: PA0, PA1, PA2 (ADC0_IN0 a IN2)
- Timer de Disparo: TIMER2 (Gera o sinal TRGO)
- Timer de Janela: TIMER6 (Controla a duração total da aquisição)
- Canal DMA: DMA0_Channel0 (Vinculado ao ADC0)
Implementação do Firmware
Configuração do Periférico ADC
O módulo ADC é preparado no modo independente, com varredura ativa e conversão contínua desativada, dependendo estritamante do gatilho externo provido pelo TIMER2.
void setup_adc_peripheral(void)
{
adc_deinit(ADC0);
/* Configuração de modo independente, scan habilitado e contínuo desabilitado */
adc_mode_config(ADC_MODE_FREE);
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE);
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 3);
/* Definição dos canais de leitura com tempo de amostragem de 7.5 ciclos */
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_0, ADC_SAMPLETIME_7POINT5);
adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_1, ADC_SAMPLETIME_7POINT5);
adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_2, ADC_SAMPLETIME_7POINT5);
/* Configuração do gatilho externo via TIMER2 TRGO */
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_EXTTRIG_REGULAR_T2_TRGO);
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
/* Habilita a requisição de transferência via DMA */
adc_dma_mode_enable(ADC0);
/* Inicialização e calibração do conversor */
adc_enable(ADC0);
delay_1ms(1);
adc_calibration_enable(ADC0);
}
Configuração do Controlador DMA
O DMA é parametrizado para transferir blocos de dados de 16 bits do registrador de dados do ADC para um buffer de memória fornecido dinamicamente, gerando uma interrupção ao final do bloco.
void configure_dma_transfer(uint16_t *target_buffer, uint16_t num_samples)
{
dma_parameter_struct dma_cfg;
dma_deinit(DMA0, DMA_CH0);
dma_cfg.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_cfg.memory_addr = (uint32_t)target_buffer;
dma_cfg.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_cfg.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_cfg.number = num_samples;
dma_cfg.periph_addr = (uint32_t)&ADC_RDATA(ADC0);
dma_cfg.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_cfg.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
dma_cfg.priority = DMA_PRIORITY_HIGH;
dma_init(DMA0, DMA_CH0, &dma_cfg);
/* Configuração para modo单次 (single-shot) com interrupção de conclusão */
dma_circulation_disable(DMA0, DMA_CH0);
dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF);
nvic_irq_enable(DMA0_Channel0_IRQn, 0, 0);
}
Timer de Disparo (TIMER2)
Este bloco calcula os parâmetros de tempo dinamicamente para atingir a frequência desejada e configura a saída mestra (TRGO) para notificar o ADC a cada estouro do contador.
void init_trigger_timer(uint32_t target_freq_hz)
{
timer_parameter_struct timer_params;
timer_deinit(TIMER2);
timer_struct_para_init(&timer_params);
/* Baseado em um clock APB1 de 60MHz */
uint32_t psc_val = 59;
uint32_t arr_val = (60000000UL / (psc_val + 1) / target_freq_hz) - 1;
timer_params.prescaler = psc_val;
timer_params.alignedmode = TIMER_COUNTER_EDGE;
timer_params.counterdirection = TIMER_COUNTER_UP;
timer_params.period = arr_val;
timer_params.clockdivision = TIMER_CKDIV_DIV1;
timer_params.repetitioncounter = 0;
timer_init(TIMER2, &timer_params);
/* Configura o evento de Update como fonte de gatilho TRGO */
timer_master_output_trigger_source_select(TIMER2, TIMER_TRI_OUT_SRC_UPDATE);
}
Timer de Janela de Aquisição (TIMER6)
Para limitar a duração da amostragem (por exemplo, coletar dados por exatamente 1 segundo antes de processar), um segundo temporizador é configurado para gerar uma interrupção de estouro no final do período.
void setup_acquisition_window_timer(void)
{
timer_parameter_struct timer_params;
rcu_periph_clock_enable(RCU_TIMER6);
timer_deinit(TIMER6);
/* Clock de 120MHz, prescaler de 12000 (10kHz). Período de 10000 resulta em 1 segundo */
timer_params.prescaler = 11999;
timer_params.alignedmode = TIMER_COUNTER_EDGE;
timer_params.counterdirection = TIMER_COUNTER_UP;
timer_params.period = 9999;
timer_params.clockdivision = TIMER_CKDIV_DIV1;
timer_params.repetitioncounter = 0;
timer_init(TIMER6, &timer_params);
/* Habilita a interrupção de atualização para sinalizar o fim da janela de captura */
timer_interrupt_enable(TIMER6, TIMER_INT_UP);
nvic_irq_enable(TIMER6_IRQn, 2, 0);
}