Trabalhando com
Sinais Analógicos
e Digitais Utilizando
Arduino 8 bits
EPS
2017
Arduino Mega 2560
Fig. 01 - Arduino MEGA 2560.
Arduino Mega 2560
Microcontrolador ATmega2560
Tensão de Operação 5V
Tensão de Entrada 7-12V
(recomendada)
Tensão de Entrada (limite) 6-20V
Portas Digitais 54 (15 podem ser usadas
como PWM)
Fig. 02 - Arduino MEGA 2560. Portas Analógicas 16
Corrente por Pino de E/S 20 mA
Corrente para Pinos de 50 mA
3,3V
Memória Flash 256 KB (8KB utilizados pelo
bootloader)
SRAM 8 KB
EEPROM 4 KB
Clock 16 MHz
PINO do LED Interno 13
fonte: [Link]
Arduino UNO
Fig. 03 - Arduino UNO.
Arduino UNO ATmega328P
Microcontrolador ATmega328P
Tensão de Operação 5V
Tensão de Entrada 7-12V
(recomendada)
Tensão de Entrada (limite) 6-20V
Portas Digitais 14 ( 6 podem ser usadas
como PWM)
Portas Analógicas 6
Fig. 04 - Arduino UNO.
Corrente por Pino de E/S 20 mA
Corrente para Pinos de 50 mA
3,3V
Memória Flash 32 KB (0,5 KB utilizados
pelo bootloader)
SRAM 2 KB
EEPROM 1 KB
Clock 16 MHz
PINO do LED Interno 13
fonte: [Link]
Arduino Nano
Fig. 05 - Arduino Nano.
Arduino Nano
Microcontrolador ATmega328P
Tensão de Operação 5V
Tensão de Entrada 7-12V
(recomendada)
Portas Digitais 14 ( 6 podem ser usadas
como PWM)
Portas Analógicas 8
Corrente por Pino de E/S 40 mA
Fig. 06 - Arduino Nano.
Memória Flash 32 KB (2 KB utilizados pelo
bootloader)
SRAM 2 KB
EEPROM 1 KB
Clock 16 MHz
PINO do LED Interno 13
fonte: [Link]
digitalWrite()
Uma das principais vantagens do Arduino é a facilidade para
começar desenvolver códigos.
Os comandos básicos são bastante simples e não requerem
maior conhecimento sobre o microcontrolador.
Entretanto, para aplicações mais avançadas esta
simplicidade pode representar um problema na execução de
códigos que demandam mais velocidade.
Utilizando a função micros() para medir o tempo de execução
de uma rotina de repetição, pode se avaliar o custo do uso
da função digitalWrite() para escrever nas portas digitais.
//Se receber o caráter 's' inicia a rotina abaixo
if (inb=='s')
digitalWrite() {
long t0 = micros();
for (int i = 0; i < MAXLOOP; i++)
//------------------------------------------ {
//Número de repetições digitalWrite(13, HIGH); //Nível lógico alto na porta 13.
#define MAXLOOP 10000 digitalWrite(13, LOW); //Nível lógico baixo na porta
//------------------------------------------ 13.
void setup() }
{ long t1 = micros()-t0; //Tempo gasto em micro
pinMode(13, OUTPUT); segundos.
[Link](115200);
} [Link]("Tempo:");
//------------------------------------------ [Link]((float)t1/(float)MAXLOOP);
void loop() [Link](" us");
{
//Aguarda até receber um dado [Link]("Tempo total:");
//pela porta serial [Link](t1);
if ([Link]()) [Link](" us");
{ }
int inb = [Link](); }
} Programa: Digital_01.ino
Resultados (utilizando um Arduino Nano ATmega328P):
Tempo:6.92 us
Tempo total:69176 us
digitalWrite()
O tempo médio para executar a rotina de repetição do
código proposto foi de 6,92 microsegundos,
aparentemente não é muito tempo, mas pode ser
melhorado. Utilzou-se o tempo médio, pois a função
micros() possui uma resolução de quatro
microsegundos.
Uma forma mais eficiente é escrever diretamente nas
portas do Arduino sem utilizar a função digitalWrite().
digitalWrite()
Fig. 07 – Pinos ATmega328P (fonte: ATmega328-328P Datasheet)
digitalWrite()
Fig. 08 – Pinos Arduino/ATmega328P (fonte: [Link]
digitalWrite()
Registradores de Portas
As portas de E/S do Arduino (ATmega328P) possuem registradores que permitem
uma manipulação rápida dos estados dos pinos.
O ATmega328P possui três portas:
Porta B (pinos digitais de 8 a 13)
Porta C (pinos de entrada analógicos)
Porta D (pinos digitais de 0 a 7)
Cada uma das portas é controlada por três registradores:
O registrador DDR determina se o pino é entrada (INPUT) ou saída (OUTPUT).
O registrador PORT determina se o pino está em nível lógico HIGH ou LOW.
O registrador PIN lê o estado dos pinos configurados para entrada (INPUT) com a
função pinMode ().
Os registradores DDR e PORT podem ser escritos e lidos, já o registrador PIN é
apenas para leitura.
if (inb=='s')
{
digitalWrite() long t0 = micros();
for (int i = 0; i < MAXLOOP; i++)
{
//------------------------------------------------------------------------
//B5 igual ao pino 13.
-
PORTB = B00100000;
#define MAXLOOP 10000
PORTB = B00000000;
//------------------------------------------------------------------------
}
-
long t1 = micros()-t0;
void setup()
{
[Link]("Tempo:");
[Link](115200);
[Link]((float)t1/(float)MAXLOOP);
pinMode(13, OUTPUT);
[Link](" us");
}
[Link]("Tempo total:");
//------------------------------------------------------------------------
[Link](t1);
-
[Link](" us");
void loop()
}
{
}
if ([Link]())
}
{
int inb = [Link]();
Programa: Digital_02.ino
Resultados (utilizando um Arduino Nano ATmega328P):
Tempo:0.38 us
Tempo total:3772 us
digitalWrite()
O tempo médio foi reduizdo de 6,92 microsegundos para 0,38
microsegundos.
A macro _BV() facilita o acesso aos bits dos registradores, exemplo:
PORTB |= _BV(PB5); // Liga o Led
PORTB &= ~_BV(PB5); // Desliga o Led
Obs.: BV significa bit value.
Programa: Digital_03.ino
analogWrite()
Esta função é particularmente útil para
gerar sinais variaveis de tensão de
saída nos pinos do arduino, utilizando
a técnica de PWM ("Pulse Width
Modulation"), ou seja, Modulação por
Largura de Pulso.
Entretanto, os sinais de PWM do
Arduino (ATmega328P) por padrão
estão limitados a frequencia de 490 Hz
(pinos 9, 10, 3 e 11) e 980 Hz (pinos 5
e 6).
Frequência util para controle de
iluminação (LEDs), mas incoveniente
Fig. 09 – Sinais PWM (fonte: [Link]
para geração de sinais, por exemplo.
Programa: PWM_01.ino
analogWrite()
O arduino (ATmega328P) pode gerar frequências para pinos de PWM até
8Mhz. Para modificar esses valores é necessário alterar os valores dos
temporizadores que controlam os sinais de PWM. São três
temporizadores, um para cada dois pinos.
Pinos 5 e 6: controlados pelo Timer 0.
Pinos 9 e 10: controlados pelo Timer 1.
Pinos 11 e 3: controlados pelo Timer 2.
Além desses registros, arduino possui quatro modos de PWM:
PWM rápido.
Correção de fase.
Correção de fase e frequência.
CTC (Clear Timer and Compare).
Programa: PWM_01.ino
analogWrite()
Fig. 10 – Diagrama de tempo do modo Fast PWM (fonte: ATmega328-328P Datasheet)
Programa: PWM_01.ino
analogWrite()
Dentre os modos, apenas o modo PWM rápido será abordado.
Frequências de PWM para um clock de 16000000 Hz.
Pinos 5 e 6: controlados pelo Timer 0.
TCCR0B = (TCCR0B & 0b11111000) | valor;
valor Frequência Hz
1 62500
2 7812.5
3 976.5625
4 244.140625
5 61.03515625
Obs.: A alteração deste registrador altera também as funcoes delay() e millis().
Programa: PWM_01.ino
analogWrite()
Frequências de PWM para um clock de 16000000 Hz.
Pinos 9 e 10: controlados pelo timer 1.
TCCR1B = (TCCR1B & 0b11111000) | valor;
valor Frequência Hz
1 31372.55
2 3921.16
3 490.20
4 122.55
5 30.64
Programa: PWM_01.ino
analogWrite()
Frequências de PWM para um clock de 16000000 Hz.
Pinos 11 e 3: controlados pelo timer 2.
TCCR2B = (TCCR2B & 0b11111000) | valor;
valor Frequência Hz
1 31372.55
2 3921.16
3 980.39
4 490.20
5 245.10
6 122.55
7 30.64
Programa: PWM_01.ino
analogWrite()
Para alterar o duty cycle (ciclo de trabalho) dos sinais de PWM, pode se
utilizar a função analogWrite(), ou acessar diretamente os registradores.
Registrador Pino do
Arduino
OCR0A 6
OCR0B 5
OCR1A 9
OCR1B 10
OCR2A 11
OCR2B 3
Programa: PWM_01.ino
analogWrite()
Exemplo de como gerar um sinal senoidal de 60 Hz utilizando a
interrupção gerada pelo temporizador.
//-------------------------------------------------------------------------------------------------
//Número máximo de pontos no vetor da senoide.
#define MAXPOINTS 512
//-------------------------------------------------------------------------------------------------
// Variaveis globais
volatile unsigned int g_preload; //Variável para ajustar o contador do temporizador.
volatile unsigned char g_data[MAXPOINTS]; //Vetor que armazenará os valores da senoide.
volatile unsigned int g_nmostras = 0; //Contador auxliar.
volatile unsigned char g_output = 0; //Variavel de controle do sinal.
int g_SampFreq = 7680; // Frequência de amostragem (60*128) Hz
//-------------------------------------------------------------------------------------------------
// Rotina de interrupcao
ISR(TIMER1_OVF_vect)
{
TCNT1 = g_preload; // Carrega o preload
if (g_output)
{
OCR2B = g_data[g_nmostras]; //Altera o duty cycle.
if (++g_nmostras>=MAXPOINTS) g_nmostras = 0; //inclementa e zera o contador
//quando necessário.
}
}
Programa: PWM_02.ino
analogWrite()
//-------------------------------------------------------------------------------------------------
int timer1_int_setup(unsigned int freq)
{
// Configura o timer1
cli(); // desabilita todas as interrupcoes
TCCR1A = 0; //Limpa o registrador
TCCR1B = 0; //Limpa o registrador
//Encontra o prescale adequado.
unsigned int pre = 1;
unsigned int c1 = 16e6/(65536*freq);
while (pre<c1 && pre<1024) pre <<= 3;
if (pre<c1) return 0;
g_preload = 65536-(unsigned int)((double)16e6/(float)pre/(float)freq);
TCNT1 = g_preload;
switch(pre)
{
case 1: TCCR1B |= (1 << CS10); break;
case 8: TCCR1B |= (1 << CS11); break;
case 64: TCCR1B |= (1 << CS11) | (1 << CS10); break;
case 256: TCCR1B |= (1 << CS12); break;
case 1024: TCCR1B |= (1 << CS12) | (1 << CS10); break;
}
TIMSK1 |= (1 << TOIE1); // habilita a interrupcao de timer overflow
sei(); // habilita todas as interrupcoes
return 1;
} Programa: PWM_02.ino
analogWrite() //-----------------------------------------------------------------------
void loop()
{
//--------------------------------------------------------------------------/ if ([Link]())
/Preenche o vetor com os dados da senoide {
void tabela_senos(float freq) //Aguarda um comando para habilitar ou
{ //desabilitar a geração do sinal.
float amp = 127, ofs = 128; int inb = [Link]();
float w = (2*PI*freq)/g_SampFreq; if ((inb>='1')&&(inb<='2'))
for(int i=0; i<MAXPOINTS; i++) {
{ char op = inb-'0';
g_data[i] = amp*sin(w*i)+ofs; //Calcula o seno. switch(op)
} {
} case 1:
[Link]("Saida PWM habilitada.");
//-------------------------------------------------------------------------- g_nmostras = 0;
void setup() g_output = 1;
{ digitalWrite(13, HIGH);
[Link](115200); break;
pinMode(13, OUTPUT); case 2:
pinMode(3, OUTPUT); [Link]("Saida PWM desabilitada.");
tabela_senos(60.0); g_output = 0;
//Configura os pinos 11 e 3 para pwm de 31kHz. digitalWrite(13, LOW);
TCCR2B = (TCCR2B & 0b11111000) | 0x01; break;
analogWrite(3, 1); }
timer1_int_setup(g_SampFreq); }
} }
}
Programa: PWM_02.ino
analogWrite()
O sinal de PWM gerado precisa ser
filtrado Por um filtro do tipo passa
baixa.
Sugere-se um filtro do tipo RC,
conforme mostra a Fig. 10.
A frequência de corte pode ser
calculada por:
fc = 1/(2pRC)
Fig. 11 – Filtro Passa Baixa Protoboad.
Fig. 10 – Filtro Passa Baixa. Programa: PWM_02.ino
analogRead()
O Arduino contém um conversor analógico-digital de 10 bits com 6 canais (8
canais no Mini e no Nano) do tipo SAR ou conversor analógico/digital por
aproximações sucessivas.
Os sinais de entrada podem variar entre 0 e 5 volts que serão representados
por um número inteiro entre 0 e 1023.
A resolução das leituras é de 4.9 mV (5 volts / 1024).
A leitura de cada sinal leva em torno de 100 μs, limitando a taxa de
amostragem máxima a 10 kHz.
Fig. 12 – ADC aproximações sucessivas.
Programa: ADC_freq_01.ino
analogRead()
Fig. 13 – Esquemático do conversor A/DC (fonte: ATmega328-328P Datasheet)
.
Programa: ADC_freq_01.ino
analogRead()
Segundo o Application Note "AVR120: Characterization and Calibration of
the ADC on an AVR".
A precisão ADC depende do clock.
A máxima frequência de clock recomendada é limitada pelo DAC interno.
Para um desempenho ideal, o clock do ADC não deve exceder 200 kHz.
Entretanto, frequências de clock de até 1 MHz não reduzem
significativamente a resolução do ADC.
Como aumentar a taxa de amostragem da função analogRead() ?
O clock do Arduino é de 16 MHz, o prescale padrão utilizado é de 128,
gerando um clock do ADC de 125 KHz (16MHz / 128).
Uma conversão precisa de 13 ciclos de clock do ADC, assim, a taxa de
amostragem padrão é de cerca de 9600 Hz (125KHz / 13).
Observa-se que para elevar a taxa de amostragem é necessário alterar o
prescale. Esta modificação pode ser realizada acrescentando algumas linhas
de código na função setup ().
Programa: ADC_freq_01.ino
analogRead()
A tabela a seguir mostra os valores de prescale, os valores de registro e as
taxas de amostragem teóricas. Os valores de prescale inferiores a 16 não são
recomendados.
Prescale ADPS2 ADPS ADPS Clock Sampling
1 0 Freq. Rate (KHz)
(MHz)
2 0 0 1 8 615
4 0 1 0 4 307
8 0 1 1 2 153
16 1 0 0 1 76.8
32 1 0 1 0.5 38.4
64 1 1 0 0.25 19.2
128 1 1 1 0.125 9.6
Programa: ADC_freq_02.ino
analogRead()
O código abaixo configura a função analogRead() para 38.4 kHz.
Entretanto, a taxa real de aquisição depende das demais partes do código, por
exemplo, rotinas de loop e armazenamento dos valores lidos em variáveis ou
vetores.
void setup()
{
//ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); //9.6 kHz
//ADCSRA |= (1 << ADPS2) | (1 << ADPS1); //19.2 kHz
ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //38.4 kHz
//ADCSRA |= (1 << ADPS2); //76.8 kHz
.
.
.
}
Programa: ADC_freq_02.ino
analogRead()
A função analogReference() pode ser utilizada para configurar a tensão de
referência usada nas entradas analógicas, que por padrão é de 5 V, ou seja, a
tensão de alimentação do microcontrolador.
As opções de configuração disponíveis são:
DEFAULT: A tensão de referência analógica padrão, que é de 5 V, exceto em
Arduinos que operem em 3,3V, por exemplo, o Arduino Due.
INTERNAL: Referência interna de 1,1 V nos chips ATmega168 e
ATmega328, ou 2,56 V no Atmega8. Não está disponível no Arduino Mega.
INTERNAL1V1: Tensão de referência interna de 1,1V, somente para o
Arduino Mega.
INTERNAL2V56: Tensão de referência interna de 2,56V, somente para o
Arduino Mega.
EXTERNAL: Este é um valor de tensão de referência externa que pode ser
aplicada no pino AREF, dentro do intervalo de 0 a 5 V.
Programa: ADC_freq_02.ino