Neste artigo serão apresentados conceitos importantes acerca da aquisição de sinais. Serão abordados os problemas e as possíveis implementações de processos de amostragem de sinais utilizando microcontroladores. Para exemplificar, será realizada a aquisição dos valores do acelerômetro presente na placa de desenvolvimento FRDM KL25z, bem como a interface deste com outros sistemas.
A aquisição de sinais pode ser resumida pela medição de uma grandeza e, em geral, sua conversão para o meio digital, para que, assim, possa ser tratada e interpretada. São diversas as ocasiões em que sinais das mais diversas naturezas necessitam ser amostrados, tais como valores de corrente elétrica, tensão, temperatura, distância, velocidade, aceleração, posição, etc. Em projetos embarcados, os responsáveis pela aquisição e processamento de sinais são os microprocessadores e microcontroladores.
Há situações em que os dados são amostrados a uma frequência muito alta e em grande volume, tornando, assim, inviável ou indesejado o seu processamento em tempo real. Nesses casos os microcontroladores podem fazer a interface entre a grandeza medida e um dispositivo com maior poder de processamento, como um computador. Para isso, é necessário atentar-se a alguns aspectos, tais como o período de amostragem e os protocolos de comunicação.
Amostragem
A amostragem consiste em medir variáveis da natureza, convertê-las e armazenar seus valores de forma digital. Um sinal de tensão, por exemplo, pode ser amostrado através de um conversor analógico digital. O diagrama a seguir ilustra esta estrutura padrão para aquisição e processamento de sinais:
É necessário, além dos valores das medidas, ter informações acerca do tempo em que elas foram realizadas. Em grande parte das situações, deseja-se que o tempo entre amostras seja constante, já que, assim, faz-se possível a utilização de modelos matemáticos para análise, execução de sistemas de controle e processamento de sinais.
Teorema da Amostragem
Uma vez que a amostragem é um processo discreto e não contínuo, existe sempre um período entre amostras. A aquisição de sinais utilizando uma taxa de amostragem inadequada pode resultar na interpretação errônea dos dados.
O teorema da amostragem de Nyquist-Shannon diz que a frequência de amostragem deve ser pelo menos duas vezes maior do que a maior frequência presente no sinal. Isso vem do fato de que um período de uma onda pode ser tipicamente dividido em dois: semiciclo positivo e negativo. Amostrando um sinal duas vezes por período, garante-se que uma das amostras esteja no semiciclo positivo e outra no semiciclo negativo.
A figura a seguir ilustra um caso em que um sinal de frequência 1hz é amostrado a uma frequência de 2hz:
Foi aplicada uma aproximação de primeiro grau para aproximar o sinal amostrado de um sinal real. É importante ressaltar que esta aproximação não é passível de ser realizada em tempo real, já que, durante o período entre amostras, não é possível prever o valor da amostra seguinte.
Sendo assim, para sistemas de tempo real, é utilizado o retentor de ordem zero. Este tem o papel de manter constante os valores entre amostras. Aplicando-o ao sinal apresentado anteriormente, temos o seguinte resultado:
É possível perceber que a frequência do sinal amostrado se manteve, mas ocorreu um atraso de 90º entre os sinais.
Os sinais apresentados foram, propositalmente, amostrados no pico da forma de onda. Isto não é passível de ser controlado pelo sistema de amostragem real. Sendo assim, podem ocorrer erros que distorcem tanto a amplitude quanto a fase do sinal, quando trabalhando muito próximo ao limite da frequência de Nyquist. A figura a seguir ilustra uma dessas ocasiões:
Trabalhando com uma frequência de amostragem que não respeita o teorema de Nyquist, o sinal amostrado pode ser totalmente diferente do sinal real e levar a conclusões errôneas. A figura a seguir ilustra um sinal com frequência 1hz amostrado a 1.05hz:
Essa distorção é definida como aliasing. Para evitar que ela ocorra, normalmente antes de ser amostrado, o sinal é filtrado. O filtro aplicado é conhecido como filtro anti-aliasing, cuja frequência de corte é igual a metade da frequência de amostragem.
Para uma amostragem ideal, um sinal deve ser aquisitado a uma frequência de ao menos 5 a 10 vezes maior do que a maior frequência presente no sinal.
Aquisição de sinais com Microcontroladores
Para manter a aquisição das amostras com intervalos de tempo constante, a utilização de temporizadores é imprescindível. O fluxo do código deve ocorrer de maneira que a periodicidade em que o processo de amostragem se inicia seja constante. Este aspecto é um problema que surge pela utilização de rotinas de delay como a presente no framework do Arduino, por exemplo.
Amostragem Imprecisa
O código a seguir ilustra um código que apresenta uma imprecisão no processo de amostragem:
#define FREQ_AMOSTRAGEM 5 // 5 milissegundos
int amostra = 0;
while(1) {
amostra = analogRead(A0); // x milissegundos
processaSinal(amostra); // y milissegundos
delay(FREQ_AMOSTRAGEM); // 5 milissegundos
}
Apesar da utilização da rotina de delay, a periodicidade do loop principal não é igual a 5 milissegundos, e sim (x + y + 5) milissegundos. Desta maneira, o processo de amostragem sempre ocorre com um período maior do que o desejado.
Isto ocorre porque durante todo o código apenas a rotina de delay tem seu tempo de execução bem definido. A leitura dos valores, bem como o processamento da amostra podem não ser rápidos o suficiente e, além disso, terem seu tempo de execução inconstante. Uma simples condição durante o processamento altera o fluxo do código. Desta maneira, nem sempre são executadas as mesmas instruções, fazendo com que a periodicidade do processo seja atrasada, inconstante e com o acúmulo sucessivo de erros.
FreeRTOS
O artigo Usando FreeRTOS para aplicações com a FRDM KL25Z apresenta alguns conceitos e funcionalidades do deste Sistema Operacional de Tempo Real.
A fim de manter constante o processo de amostragem, o tempo gasto por todas as tarefas que o compõem deve ser levado em consideração. Sendo assim, o período do loop apresentado como um todo, deveria ser igual a 5 milissegundos.
O FreeRTOS possui uma função chamada vTaskDelayUntil que pode ser utilizada nesta ocasião.
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement);
Esta função realiza o bloqueio de uma tarefa por um tempo (segundo parâmetro) relativo ao período informado (primeiro parâmetro) baseado na frequência de atualização do sistema operacional (tick rate).
*pxPreviousWakeTime – Ponteiro para uma variável que guarda o último período em que a tarefa foi bloqueada.
xTimeIncrement – Número de ticks do sistema operacional que devem ser esperados.
O código a seguir apresenta o processo de amostragem utilizando esta função:
#define FREQ_AMOSTRAGEM (5 * configTICK_RATE_HZ / 1000) // 5 milissegundos
void taskAmostragem(void* pvParameters) {
int amostra = 0;
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount(); // Guarda o valor de tempo atual
while(1) {
// Espera o relógio do RTOS chegar em xTaskGetTickCount + 5ms e atualiza xTaskGetTickCount
vTaskDelayUntil(&xTaskGetTickCount, FREQ_AMOSTRAGEM);
amostra = adcRead();
processaSinal(amostra);
}
}
A função void taskAmostragem(void* pvParameters) é uma tarefa que será executada pelo sistema operacional.
O tempo de delay é calculado com base na frequência de atualização do tick do FreeRTOS. Se, por exemplo, o tick rate estiver configurado para 10khz, esperar por 10000 ticks equivale a 1 segundo. De forma análoga, esperar por (5 * configTICK_RATE_HZ) equivale a 5 segundos e (5 * configTICK_RATE_HZ / 1000), 5 milissegundos.
É criada, então, uma variável com nome de xTaskGetTickCount que tem como papel registrar os valores de tempo. Ela é inicializada com o valor atual do relógio de maneira que, na primeira execução da rotina de delay, seja esperado um tempo de 5 milissegundos relativo ao valor atual registrado.
Desta forma, a função vTaskDelayUntil sempre atualiza xTaskGetTickCount com o valor do relógio ao fim de sua execução, fazendo com que o próximo período de espera seja relativo à espera anterior, tornando-o independente das operações executadas durante o processo de amostragem.
Timer
De maneira análoga, os próprios temporizadores dos microcontroladores podem ser utilizados, dispensando a necessidade de um sistema operacional. Para aplicações mais complexas, pode ser configurada uma interrupção periódica disparada pelo temporizador. O processo de amostragem sendo realizado dentro da rotina de tratamento da interrupção garante sua periodicidade e independência do restante do código.
Para aplicações mais simples, o recenseamento é suficiente. Nele, após a configuração do temporizador, o código espera em um loop pela condição que indica o fim da temporização. O código a seguir ilustra de maneira genérica este funcionamento:
#include "timer.h"
#include "adc.h"
#define TEMPO_AMOSTRAGEM 5
int main() {
int amostra = 0;
// Inicialização do hardware
initTimer();
initAdc();
while(1) {
// Configura o período do timer e reseta suas flags
configTimer(TEMPO_AMOSTRAGEM);
// Processo de amostragem
amostra = adcRead();
processaSinal(amostra);
// Espera o tempo configurado
timerWait();
}
return 0;
}
A utilização de temporizadores por hardware não é influenciada pelo fluxo do código, evitando os problemas apresentados anteriormente. Isto se dá porque a contagem do tempo é independente do software o qual esta sendo executado.
Aplicação
Para exemplificar o processo de amostragem, será apresentado aqui a aquisição de valores do acelerômetro presente na placa de desenvolvimento FRDM KL25z e a interface deste com outros sistemas.
O acelerômetro presente nesta placa é o MMA8451Q, fabricado pela NXP. Este é um sensor I2C que realiza a leitura de valores de aceleração nos 3 eixos x, y e z. Tem 14 bits de resolução e uma taxa de saída de até 800 amostras por segundo.
Todos os drivers utilizados foram desenvolvidos e estão disponíveis em minha página do GitHub. Existe, também, uma série de artigos, por Evandro Teixeira, acerca da implementação de Bibliotecas de Software para a FRDM-KL25z.
O propósito deste exemplo é realizar a leitura dos valores de aceleração no eixo x através da comunicação I2C com o sensor. O responsável por garantir os requisitos temporais será o TPM (Timer/PWM Module), através do recenseamento. Este módulo foi, também, explicado na série de artigos mencionada.
Para aquisição dos dados, será utilizada a comunicação serial por meio do Debug Console, cujas funções de escrita funcionam de maneira semelhante àquelas padrões da linguagem C. Para garantir a formatação e a correta interpretação dos dados, é necessário definir um pequeno protocolo de comunicação.
Os valores lidos têm 14 bits de resolução e são sinalizados, portanto variam entre -8192 e 8191. O sensor foi configurado para que estes limites representem valores de aceleração entre -2 e 2 vezes a aceleração da gravidade.
Os valores serão enviados à porta serial da seguinte maneira: (\r valor \n). \r é o caractere de carriage return, que faz com que o próximo caractere seja impresso no início da linha. \n é o caractere de line feed, que faz com que o próximo caractere esteja na próxima linha.
Uma aceleração de -1G é expressa por: “\r-4095\n”, assim como 1G é expressa por: “\r4095\n”.
O código da aplicação é o apresentado a seguir:
#include <stdio.h>
#include "fsl_debug_console.h"
#include "tpm.h"
#include "tpm_types.h"
#include "gpio.h"
#include "gpio_types.h"
#include "i2c.h"
#include "i2c_types.h"
#include "mkl25_clock.h"
#include "mma8451q.h"
int main(void) {
int16_t amostra = 0;
// Init board hardware.
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
BOARD_InitDebugConsole();
gpio_init(GPIO_PORTE);
PORTE->PCR[24] = (PORTE->PCR[24] & ~(7 << 8)) | (5 << 8); // MUX PORTE24 I2C0 SCL
PORTE->PCR[25] = (PORTE->PCR[25] & ~(7 << 8)) | (5 << 8); // MUX PORTE25 I2C0 SDA
i2c_setup(I2C_MOD_I2C0, 0x1F);
tpm_init(TPM_TPM0, TPM_PRS_4, 14999); // 800hz Timer
mma8451q_setup(0x1D, MMA8451Q_RANGE_2G, I2C0);
PUTCHAR('\r');
for(;;) {
tpm_clear_flag(TPM0);
amostra = mma8451q_read(MMA8451Q_AXIS_X);
if (amostra < 0) {
PUTCHAR('-');
}
PRINTF("%d\n\r", amostra);
tpm_wait(TPM0);
}
return 0;
}
O hardware do Console Debug é inicializado. Os pinos da PORTE nos quais o acelerômetro está conectado são configurados como pinos da I2C0. A I2C0 é inicializada a uma velocidade de 200khz. Em seguida, o temporizador configurado com uma frequência de 800hz e o acelerômetro MMA8451Q (que tem como endereço 0x1D) é configurado na escala de 2G.
No loop principal, a flag que indica o fim da temporização é limpa e o eixo x do sensor é lido. A função para imprimir na porta serial não é capaz de imprimir o sinal, portanto, se a amostra é negativa, após o caractere \r, o sinal é enviado. Após o valor da amostra, a função tpm_wait() é responsável por promover a espera até o timer alcançar o valor de tempo referente ao próximo período de amostragem.
Utilizando um terminal serial como o Tera Term, é possível salvar o log de transmissão no formato de um arquivo texto para que o sinal possa ser analisado e processado por softwares como o MatLAB e Octave.
O seguinte sinal foi medido movimentando a placa e plotado utilizando o log gerado, carregando-o no MatLAB:
Uma vez definido o protocolo de comunicação, diminuindo a frequência de envio dos valores, é possível, de maneira fluida, apresentar os sinais em tempo real em uma interface gráfica.
Como o método de comunicação utilizado é uma porta serial USB, é possível implementar uma interface em C# assim como apresentado na série de artigos, por Fábio Souza, Comunicação Serial com C# e Arduino.
A interface implementada também está disponível em meu GitHub.

Conclusão
Neste artigo foram apresentados alguns conceitos pertinentes a amostragem e processamento de sinais. Foram discutidos os problemas e alternativas para a implementação da aquisição de sinais utilizando microcontroladores. Foi apresentado, também, a leitura dos valores do acelerômetro com a placa FRDM KL25z, assim como sua visualização por meio da comunicação serial.
Saiba Mais
Controladores e Filtros digitais Utilizando o FreeRTOS e a KL25z
Bibliotecas de Software para a FRDM KL25z
Introdução a Aquisição e Processamento de Sinais – USP
The Scientist and Engineer’s Guide to Digital Signal Processing – Smith









Ola Thiago, isso pode ser transformado para plataforma MQL5 ?
Se sim, poderia me enviar este codigo em meu email por favor?
Parabens pelo trabalho!
/ JB
Olá Thiago, tudo bem?
Apenas dando um feedback, a nomenclatura TPM geralmente é utilizada para Trusted Platform Module, timer geralmente utiliza-se TIM mesmo. Pode ocorrer de estarmos buscando um termo aqui no blog e nos depararmos com o outro, como foi o meu caso aqui hehe.
Parabéns Thiago, excelente artigo!
Obrigado por compartilhar esse conhecimento conosco!
Muito obrigado, Jonnes!
Parabéns Thiago, o artigo está excelente!
Muito obrigado, Rubens! Fico feliz que tenha gostado.
Ótima abordagem.
Muito obrigado!
Muito legal seu artigo. Exemplificou bem na prática de maneira lúdica o processo da amostragem no micro e a questão fundamental do tempo. Seja com RTOS oi nos temporizadores. Eu prefiro usar as int de timer. Parabéns.
Muito obrigado! Feliz que gostou.
Certamente tratar a aquisição de sinais em uma interrupção é muitas vezes a maneira mais otimizada.