Trabalhando com ADC no Franzininho C0: Tutorial Completo no STM32CubeIDE

Este post faz parte da série Franzininho C0 com STM32CubeIDE

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

ADC no Franzininho C0

ADC no Franzininho C0

Método Polling

Configurando CubeMX:

  1. Abra STM32Cube , crie um novo projeto e selecione o microcontrolador de destino “STM32C011F6P6”. 
  1. 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.
ADC no Franzininho C0
ADC no Franzininho C0
  1. 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”. 
ADC no Franzininho C0
ADC no Franzininho C0
  1. 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.
ADC no Franzininho C0
  1. 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.
ADC no Franzininho C0
  1. Gere o código em “Project” > “Generate Code”.

Código:

  1. 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"
  1. 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 */
}
  1. Na imagem abaixo é possível ver o código.
  1. 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:

  1. 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.
  1. Caso deseje, você pode desativar o clock. Não utilizaremos neste exemplo.
  1. Gere o código novamente.

Código:

  1. 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"
  1. 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
}
  1. 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
  1. Na imagem abaixo é possível ver o código.
  1. 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:

  1. 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”. 
  1. Após adicionar o novo canal DMA, configure o modo como “normal” e habilite “memory”.
  1. Volte em “Parameter Setting” e deixe habilitado (Enabled) a conversão em modo contínuo e também às requisições DMA. 
  1. Gere o código.

Código:

  1. 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"
  1. 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];
  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.

  1. Na função principal, int main, adicione a linha para calibrar o ADC.
 HAL_ADCEx_Calibration_Start(&hadc1); // Rotina de calibração do ADC
  1. 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);
  1. Na imagem abaixo é possível ver o código.
  1. 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. 

Franzininho C0 com STM32CubeIDE

Como usar display 7 segmentos com a Franzininho C0 Entendendo o Controle PWM e Aplicando no Franzininho C0
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Hardware » Microcontroladores » Trabalhando com ADC no Franzininho C0: Tutorial Completo no STM32CubeIDE

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: