Introdução
Quando pensamos em eletrônica digital, é comum imaginar que tudo funcione apenas com valores binários (0 e 1). No entanto, sistemas embarcados são dispositivos que interagem com o mundo real, e no mundo real, as informações são “analógicas” e não “digitais”. Nesse artigo vamos explorar o ADC no Franzininho C0 com exemplos usando polling, interrupção e DMA.
O Que é um Conversor Analógico-Digital (ADC)?
Para um microcontrolador poder compreender valores analógicos, como a temperatura captada por um sensor, é necessário um conversor analógico-digital. O ADC é o dispositivo que traduz um sinal analógico em um sinal digital que o microcontrolador pode processar.
Como funciona um ADC?
Vamos pegar o exemplo de um microfone. O áudio captado por ele é um sinal analógico. Com a ajuda de um ADC, podemos transformar esses sinais analógicos em sinais digitais, permitindo que um software processe o som. No caso, o ADC do STM32 usa um método chamado “aproximação sucessiva”, que gera um conjunto de tensões discretas e as compara com a amostra de tensão de entrada.
Características dos ADCs
Os ADCs têm duas características principais:
- Resolução [bits]: O número de bits usados para representar o sinal digital.
- Taxa de amostragem [Hz]: A velocidade com que o ADC funciona.
Por exemplo, um ADC de 8 bits com uma taxa de amostragem de 1 kHz possui 256 (2⁸) níveis para representar o sinal digital e leva 1 milissegundo para converter um sinal analógico em digital.
Um sinal analógico é expresso em tensão [V], e alguns aspectos importantes são:
- Tensão em escala real: O valor máximo da tensão de entrada que pode ser convertido em digital.
- Resolução de tensão: A tensão total dividida pelo número de níveis.
Por exemplo, um ADC de 8 bits com uma tensão total de 3,3 V tem uma resolução de 3,3/256 níveis = 12,9 mV. A resolução é o menor valor de tensão que o ADC pode diferenciar.
Polling, Interrupção e DMA: Qual é a Melhor Opção para Gerenciar Leitura ADC?
Ao trabalhar com leituras ADC, você pode escolher entre três métodos principais: Polling, Interrupção e DMA. Cada um tem suas vantagens e desvantagens, dependendo das necessidades da sua aplicação.
Polling
O microcontrolador inicia a leitura do ADC e aguarda até que a conversão seja concluída antes de continuar com outras tarefas.
- Prós: Simples de implementar e não requer periféricos adicionais.
- Contras: Desperdiça o tempo da CPU, pois fica esperando o ADC terminar.
Interrupção
O microcontrolador começa a leitura do ADC e continua executando outras tarefas. Quando a conversão termina, o ADC envia uma “interrupção” para o microcontrolador, que então pausa o que está fazendo para processar a leitura.
- Prós: Economiza tempo de CPU e permite que o microcontrolador execute outras tarefas enquanto espera.
- Contras: Pode ser ineficiente se houver muitas interrupções frequentes, pois o processador ficará ocupado gerenciando-as.
DMA (Direct Memory Access)
O microcontrolador instrui o DMA, uma unidade de controle independente, para iniciar e gerenciar a conversão do ADC. Durante essa operação, o microcontrolador continua suas tarefas. Quando a conversão termina, o DMA envia uma interrupção ao microcontrolador.
- Prós: Libera a CPU das operações do ADC, permitindo que ela se concentre em outras tarefas.
- Contras: O DMA pode ser mais lento que a CPU para operações simples e é mais complexo de configurar em comparação com Polling e Interrupção.
Qual Método Escolher?
Não há um método universalmente melhor; a escolha depende da sua aplicação específica:
- Polling é ideal para tarefas simples onde a espera pela conversão não é um problema.
- Interrupção é útil quando você precisa que a CPU esteja disponível para outras tarefas, mas as interrupções não são tão frequentes.
- DMA é indicado para aplicações que exigem alta eficiência e minimização da carga da CPU, especialmente com grandes volumes de dados.
ADC no Franzininho C0: Projeto de Leitura de Potenciômetro Usando Polling, Interrupção e DMA
Neste tutorial, vamos demonstrar como implementar a leitura de um potenciômetro utilizando três métodos diferentes: Polling, Interrupção e DMA.
Materiais Necessários:
- 1 Franzininho C0
- 1 Potenciômetro de 10k ohms
- Cabos de conexão
Montagem do Circuito para leitura do ADC no Franzininho C0
Método Polling
Configurando CubeMX:
- Abra STM32Cube , crie um novo projeto e selecione o microcontrolador de destino “STM32C011F6P6”.
- Vá para a página de configurações de relógio e em HCLK digite 48 MHz para a frequência de saída desejada do sistema. Pressione a tecla “Enter”, depois volte para a página de configuração dos pinos, selecione “Trace and Debug” e habilite “Serial Wire.
- Em seguida, vamos configurar a UART. Em SYS, habilite o uso dos pinos PA9 e PA10. Depois, selecione PA10 como “USART1_RX” e PA9 como “USART1_TX” e em “Connectivity” selecione a opção “USART1” e escolha o Mode “Asynchronous”.
- Para configuração do ADC, clique em Analog > ADC e selecione “IN7”, pois estamos utilizando a porta PA7, que corresponde ao canal 7 do ADC de acordo com o pinout da Franzininho C0. Para o modo polling, mantenha as demais configurações do ADC nos parâmetros padrão.
- Também vamos habilitar o TIM3 e configurá-lo para gerar uma interrupção a cada 1 segundo. Para isso, use o valor do Prescaler como 48000-1 e do contador como 1000-1.
- Gere o código em “Project” > “Generate Code”.
Código:
- Em Core > Src > main.c ,adicione as bibliotecas de entrada/saída padrão e a biblioteca para manipulação de strings no início do arquivo:
#include "stdio.h"
#include "string.h"- Modifique a função principal, int main, com o código abaixo.
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_ADC1_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim3); // Inicia contagem
HAL_ADCEx_Calibration_Start(&hadc1); // Rotina de calibração do ADC
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if(__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE)){
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
// ADC Polling
HAL_ADC_Start (&hadc1); // inicia leitura ADC
HAL_ADC_PollForConversion (&hadc1, 1000); //aguarda conversao seja concluida
uint32_t leituraPotenciometro = HAL_ADC_GetValue (&hadc1); // ler valor analogico
HAL_ADC_Stop(&hadc1);
char msg[50];
sprintf(msg, "Leitura Potenciometro: %lu\r\n", leituraPotenciometro); // Formata mensagem
HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), 1000); // Imprime Serial
}
}
/* USER CODE END 3 */
}
- Na imagem abaixo é possível ver o código.
- Ao finalizar o código, partiremos para gravação. Nessa etapa você pode utilizar o ST-Link ou utilizar um cabo usb como apresentado em um dos tutoriais desta série.
Explicação do código
Neste código, configuramos o ADC (Conversor Analógico-Digital) e a UART para ler um valor analógico de um potenciômetro e transmiti-lo pela UART a cada atualização do temporizador.
A função main começa inicializando várias bibliotecas e periféricos necessários. Depois dessas inicializações, HAL_TIM_Base_Start(&htim3) inicia o Timer 3, que é usado para controlar a frequência das leituras do ADC. Para garantir que o ADC forneça leituras precisas, HAL_ADCEx_Calibration_Start(&hadc1) executa uma calibração do ADC.
Dentro do loop while(1), quando o Timer 3 atingi o tempo configurado para realizar uma nova leitura, o ADC é iniciado (HAL_ADC_Start(&hadc1)) e o código espera até que a conversão seja concluída (HAL_ADC_PollForConversion(&hadc1, 1000)) com um timeout de 1000 milissegundos. Uma vez concluída, o valor convertido é obtido (uint32_t leituraPotenciometro = HAL_ADC_GetValue(&hadc1)) e a conversão é parada (HAL_ADC_Stop(&hadc1)). Após obter o valor lido do ADC, o código formata este valor em uma string (sprintf(msg, "Leitura Potenciometro: %lu\r\n", leituraPotenciometro)) e o transmite via UART (HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), 1000) ).
Funcionamento

Método Interrupção
Configurando CubeMX:
- Considerando as configurações já feitas para o método polling, o único detalhe que devemos realizar é habilitar a interrupção. Dessa forma, clique sobre o arquivo “.ioc”, vá em Analog > ADC > NVIC Settings e habilite a interrupção.
- Caso deseje, você pode desativar o clock. Não utilizaremos neste exemplo.
- Gere o código novamente.
Código:
- Em Core > Src > main.c ,adicione novamente as bibliotecas de entrada/saída padrão e a biblioteca para manipulação de strings no início do arquivo:
#include "stdio.h"
#include "string.h"- Crie a função de Callback ADC abaixo:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
uint32_t leituraPotenciometro = HAL_ADC_GetValue(&hadc1);
char msg[50];
sprintf(msg, "Leitura Potenciometro com Interrupcao: %lu\r\n", leituraPotenciometro); // Formata mensagem
HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), 1000); // Imprime Serial
}
- No código principal, int main, adicione a função de calibração do ADC e inicie o ADC para trabalhar no modo interrupção.
HAL_ADCEx_Calibration_Start(&hadc1); // Rotina de calibração do ADC
HAL_ADC_Start_IT(&hadc1); // Inicia ADC interrupcao- Na imagem abaixo é possível ver o código.
- Ao finalizar o código, partiremos para gravação. Nessa etapa você pode utilizar o ST-Link ou utilizar um cabo usb como apresentado em um dos tutoriais desta série.
Explicação do código:
Neste código, você está configurando o ADC para ler o valor do potenciômetro usando interrupções. A cada leitura concluída, uma interrupção será gerada, e a função de callback HAL_ADC_ConvCpltCallback será chamada automaticamente. Esta função coleta o valor lido, o formata e transmite via UART.
Funcionamento

Método DMA
Configurando CubeMX:
- Considerando as configurações já feitas para o método polling, a única coisa que devemos fazer é habilitar a interrupção. Dessa forma, clique sobre o arquivo “.ioc”. Depois clique em Analog > ADC > DMA Setting, selecione o “ADC1” e clique em “ADD”.
- Após adicionar o novo canal DMA, configure o modo como “normal” e habilite “memory”.
- Volte em “Parameter Setting” e deixe habilitado (Enabled) a conversão em modo contínuo e também às requisições DMA.
- Gere o código.
Código:
- Em Core > Src > main.c ,adicione novamente as bibliotecas de entrada/saída padrão e a biblioteca para manipulação de strings no início do arquivo:
#include "stdio.h"
#include "string.h"- Declare um array para armazenar a leitura do ADC. Nesse exemplo estamos lendo apenas um canal, então o tamanho do array é 1.
uint16_t adc_data[1];- Adicione a função callback que será chamada automaticamente quando o DMA completar a transferência dos dados do ADC para o
array adc_data.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
HAL_ADC_Stop_DMA(&hadc1);
if (hadc->Instance == ADC1) { // Verifica se o callback é do ADC1
char msg[50];
// Formata a mensagem com os valores lidos do ADC
sprintf(msg, "Leitura Potenciometro com DMA: %u\r\n", adc_data[0]);
// Envia a mensagem pela UART
HAL_UART_Transmit(&huart1, (uint8_t *)msg, strlen(msg), 1000);
}
}
É válido destacar que HAL_ADC_Stop_DMA(&hadc1) é opcional e depende da configuração do seu projeto. Ela garante que o DMA não continue funcionando até que seja explicitamente reiniciado. Além disso, a linha if (hadc->Instance == ADC1) verifica se a interrupção foi gerada pelo ADC1, considerando que em outros projetos é possível utilizar mais de um ADC.
- Na função principal, int main, adicione a linha para calibrar o ADC.
HAL_ADCEx_Calibration_Start(&hadc1); // Rotina de calibração do ADC- Na função principal, dentro do loop de repetição while, inicie o ADC com DMA.
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_data, 1);- Na imagem abaixo é possível ver o código.
- Ao finalizar o código, partiremos para gravação. Nessa etapa você pode utilizar o ST-Link ou utilizar um cabo usb como apresentado em um dos tutoriais desta série.
Explicação do código:
Neste trecho de código, utilizamos o DMA para realizar a leitura do ADC. A função HAL_ADC_ConvCpltCallback é chamada automaticamente quando o DMA completa a transferência, formatando o valor lido e enviando-o via UART. Este método é muito eficiente para leituras contínuas e rápidas de sensores analógicos, principalmente quando estamos utilizando mais de um canal ADC.
Funcionamento
Conclusão
Neste artigo, explicamos como trabalhar com ADC no Franzininho C0 usando os métodos Polling, Interrupção e DMA. Agora você compreende as diferenças entre esses métodos e sabe como aplicá-los para ler valores analógicos, como os de um potenciômetro, e transmiti-los via UART.





