https://images.unsplash.com/photo-1553451166-232112bda6f6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=872&q=80

Photo by Gaelle Marcel on Unsplash

Projeto Final

Atvidade: Gerador de Onda Senoidal 40 Hz

Data: 10/11/2022, 14/11/2022, 15/11/2022, 16/11/2022.

Ideia: gerar uma onda senoidal de 40 Hz, usando saída PWM filtrada do Arduino.

Detalhes: placas Arduíno não possuem conversores D/A, então a ideia é filtrar alguma saída PWM do Arduíno.

Gerador PWM no Arduíno

A ideia aqui é gerar um nível DC para atuar como sinal atuador de controle para o kit da Feedback. Temos que disponibilizar uma tensão DC variando entre 0 até 5 Volts numa das entradas do módulo Servo Amplificador (SA150D). Eventualmente (para atuar como um seguidor de tensão), podemos usar o módulo pré-amplificador (PA150C) para a partir deste, injetar o sinal de controle no módulo Servo Amplificador. Algo semelhante ao ilustrado na próxima figura:

conexao_saida_PWM_filtrada_kit_feedback.png

Supondo que vamos gerar a onda senoidal na faixa dos 40 Hz, uma boa frequência de corte para o filtro passa-baixas aqui seria a partir dos 400 Hz.

É bom lembrar que o sinal PWM gerado por “default” pelo Arduíno, parece:

A tabela a seguir detalha melhor que temporizadores do Arduíno trabalham com que pino para gerar sinal PWM:

Pinos Arduíno (PWM) Timmer Registrador Frequência
3 Timer2 OC2B 490 Hz
5 Timer0 OC0B 980 Hz
6 Timer0 OC0A 980 Hz
9 Timer1 OC1A 490 Hz
10 Timer1 OC1B 490 Hz
11 Timer2 OC2A 490 Hz

Note que:

É melhor sempre optar pela frequência mais elevada possível para o gerador PWM. Neste caso, seria 980 HZ, encontrada apenas nos pino 5 e 6 do Arduíno. Neste caso, vamos optar por usar o pino 5.

Cuidados: o próprio site do Arduino alerta para o fato de que os pinos 5 e 6 usam o Timer0 (frequencia mais alta) mas como este timer também é compartilhado com as funções millis() e delay(), o usuário DEVE esperar valores mais elevados que o solicitado se insistir em usar estas últimas funções. Este problema é mais comum acontecer para baixos valores de duty-cycle. Esperamos que isto não ocorra no nosso caso.

Filtro RC na saída PWM

Considerando que vamos trabalhar com o pino 5 (fPWM=980), podemos filtrar este sinal via filtro passa-baixas (FPB) usando uma rede RC, estabelecendo a freq. de corte do filtro uma década abaixo, em fc=98 Hz. Isto significa que componentes na faixa de 980 Hz serão reduzidos de 20 db, um “ganho”, K=1020/20=0,1 em 980 Hz.

A frequência de corte do FPB é definida como:

fc=12πRC

Usando R = 10 KΩ, C deverá ser de:

>> R=10E3;
>> fc=98;
>> C=1/(2*pi*R*fc)
C =
    1.624e-07

Iso resulta num capacitor de 164×109 F =164 nF. Valores comerciais mais próximos estão em 150 nF e 220 nF. Recalculando as frequências de corte, teríamos:

>> fc=1/(2*pi*10E3*C)
fc =
        106.1
>> C=220E-9;
>> fc=1/(2*pi*10E3*C)
fc =
       72.343

Gerando a Onda Senoidal

Supondo que queremos gerar uma senoide de 40 Hz, seria adequado amostrá-la ou gerar um sinal à 400 Hz, o que rende 10 pontos por ciclo da senoide e resulta em algo como:

>> fsinal=40;
>> Tsinal=1/fsinal; 
>> fs=400;
>> Ts=1/fs; % freq de amostragem adotada
>> t=0:Ts:2*Tsinal; % gerando vetor tempo para 2 ciclos da senoide de 40 Hz
>> yd=sin(2*pi*fsinal*t);
>> t(length(t))  % valor final da var tempo findo os 2 períodos da senoide
ans =
         0.05 
>> figure;
>> ezplot('sin(2*pi*40*x)',[0 0.05])
>> hold on
>> plot(t,yd,'o-')
>> legend('senoide desejada', 'senoide sintetizada')
>> grid

Gráfico da senoide de 40 Hz amostrada à 400 Hz:

senoide_40_fs_400.png

Detalhe: no momento de codificar no Arduíno, esta onda senoidal deverá oscilar entre os valores 0 à 255 (tipo de dado int). Então, modificamos a rotina no Matlab para gerar a senóide nesta faixa de valores:

>> fsinal=40;
>> Tsinal=1/fsinal; 
>> fs=400;
>> Ts=1/fs % freq de amostragem adotada

>> t=0:Ts:2*Tsinal; % gerando vetor tempo para 2 ciclos da senoide de 40 Hz
>> 256/2
ans =
   128
>> yd=128+128*sin(2*pi*fsinal*t);
>> t(length(t))  % valor final da var tempo findo os 2 períodos da senoide
ans =
         0.05 
>> figure;
>> ezplot('128+128*sin(2*pi*40*x)',[0 0.05])
>> hold on
>> plot(t,yd,'o-')
>> legend('senoide desejada', 'senoide sintetizada')
>> grid

senoide_40_gain_offset_128.png

Então a equação final da senoide a ser implementada no Arduíno na rotina ISR é:

y(t)=128+128sin(2π40t)(1)

Note mais especificamente, que os primeiros 10 valores são repetidos de forma periódica e são:

>> [t(1:12)' yd(1:12)']
ans =
            0          128
       0.0025       203.24
        0.005       249.74
       0.0075       249.74
         0.01       203.24
       0.0125          128
        0.015       52.763
       0.0175       6.2648
         0.02       6.2648
       0.0225       52.763
        0.025          128
       0.0275       203.24

Outro detalhe: não podemos, dentro da rotina ISR para executar a eq. (1), usar alguma variável t porque fatalmente atingiríamos o limite (scopo) desta variável. A opção seria usar uma variável do tipo δt, para definir o “passo” ou incremento que teria que ser dado na variável t e da mesma forma, o limite da variável seria atingido. Uma opçao mais fácil e certeira nesta caso é, ao invés de passar como argumento de entrada, a variável t, passarmos o ângulo para a senóide neste ponto. Isto é, a senoide se repete a cada intervalo T que neste caso, corresponderia aos 360o (2π). Como esta senoide será gerada usando 10 pontos da mesma, basta dividir por igual o intervalo de 360o em 10 pontos, ou seja, un novo ponto da senoíde é gerado a cada Δt=11040=Ts=0,0025 segundos =2,5 ms, que equivale à Δθ=360o10=36o ou Δθ=2π10=0.62832 rad.

>> delta_theta=2*pi/10 % ciclo completo da senoide dividido em 10 pontos
delta_theta =
      0.62832
>> theta=0:delta_theta:2*pi;
>> theta'
ans =
            0
      0.62832
       1.2566
        1.885
       2.5133
       3.1416
       3.7699
       4.3982
       5.0265
       5.6549
       6.2832
>> 2*pi
ans =
       6.2832
>>

Note que o último valor de vetor acima, repete o primeira amostra (posição zero do vetor theta).

Podemos aproveitar e gerar um vetor no Matlab (que pode ser incorporado no código do Arduíno) com os 10 valores que devem ser gerados (o ciclo completo da senoide) a cada intervalo de tempo ou intervalo angular. Aproveitando os cálculos já gerados anteriormente (que estão no workspace do Matlab), seria algo como:

>> index=0:10;
>> escala=100/256
escala =
      0.39063
>> yd_per=yd*escala; % valor duty-cycle de saída em %
>> [index(1:11)' t(1:11)' theta(1:11)' yd(1:11)' yd_per(1:11)']
ans =
            0            0            0          128           50
            1       0.0025      0.62832       203.24       79.389
            2        0.005       1.2566       249.74       97.553
            3       0.0075        1.885       249.74       97.553
            4         0.01       2.5133       203.24       79.389
            5       0.0125       3.1416          128           50
            6        0.015       3.7699       52.763       20.611
            7       0.0175       4.3982       6.2648       2.4472
            8         0.02       5.0265       6.2648       2.4472
            9       0.0225       5.6549       52.763       20.611
           10        0.025       6.2832          128           50
>>

Algumas considerações:

  1. Estamos amostrando a senoide à 400 Hz, resultando em 10 pontos por ciclo;
  2. A “distância” (período de tempo) entre cada ponto amostrado é de: Ts=1/400=0,0025 ou 2,5 ms;
  3. No intervalo de tempo acima, a saída PWM do Arduíno em fPWM=980 Hz (TPWM=1/980=0,0010204 ou 1,0204 ms), consegue sintetizar: 2,45 ciclos do sinal PWM (Ts/TPWM=2.45).

Ou seja, nossa rotina ISR para geração dos pontos da senoide poderia usar um simples contador inteiro para contar pontos de 0 até 9, sendo que a cada vez que ultrapassasse o valor 9, seu valor voltasse à zero. Este contador poderia ser usado como um apontador para a posição angular (vetor theta), ou este contador poderia ser usado para cálculo do ângulo atual no período da senoide, dado certo período de tempo passado. Note que nossa rotina ISR não vai criar nenhuma variável t, nem necessita do vetor theta. Poderíamos simplesmente fazer algo como:

ISR(TIMER2_COMPA_vect){
  y = 0;  // por default, duty-cycle de saída = 0%
  if (generate) {
    theta = index * delta_th;  // calcula angulo atual da senoide
    y = 128.0 + 128.0*sin(theta);
    index++; // deixa pronto para próximo ponto
    if (index > 9) index = 0;
  }
  analogWrite(pino_PWM, y);
}

Obs.: mas atenção, a variável contadora neste caso, deveria ser global, ela não pode ser re-inicializada em zero (ou valor qualquer) à cada vez que o Arduíno executa a função acima. Por isto, algumas variáveis globais serão necessárias.

Configurando a ISR para amostragem da Senoide

Referências consultadas:

Verificando que timer do Arduíno se pode usar para trabalhar com:

Usando o pino 5 (fPWM=980Hz) do Arduíno para gerar o sinal PWM, temos que lembrar que o Timer0 ficou comprometido com o gerador PWM. Necessitamos gerar uma interrupção por software para ocorrer a cada 400 Hz.

Descobrindo o valor de prescaler necessário…

Compare Match Reg,CM=(Arduino Clock Freqprescaler×DesiredInterruptFreq)1

Lembrando que: O μC ATMEL 328/168 do Arduíno permite definir o prescaler apenas nos valores fixos: 1, 8, 64, 256 e 1024.

>> prescaler=[1 8 64 256 1024]; % valores padrões do Arduino
>> CM=(16E6./(prescaler.*400)-1);
>> [prescaler' CM']
ans =
            1        39999
            8         4999
           64          624
          256       155.25
         1024       38.063

Considerações sobre o prescaler e timer à ser usado:

Interface com Usuário

A questão é: — como vamos indicar ao Arduino quando o mesmo deve gerar a onda quadrada e quando ele deve “pausar” a onda quadrada?

Podemos acrescentar um botão (push-button) que simplesmente alterna o estado do do sistema entre: [true]=gerar senoide/[false]=parar de gerar senoide. Apertar este botão faz simplesmente o sistema alternar entre estes 2 estados.

Mas seria interessante indicar o “status” atual do sistema, para o usuário, de forma a que o mesmo se dê conta se a placa estã no modo “pause” (não gerar onda) ou no modo “gerar onda senoidal”. A ideia então é usar 3 leds que alternam entre 2 situações:

  1. Quando estiver no modo “pause”, os leds simplesmente piscam, indicando que o sistema está esperando “liberação” por parte do usuário.
  2. Quando o sistema está no modo gerar onda senoidal, um efeito visual de vai-e-vêm é gerado sobre os 3 leds da placa.

Segue código exemplo/teste da interface com usuário:

/****************************************************************
Teste interface saída

Fernando Passold, em 16/11/2022
*****************************************************************/
// Led 1, pin 2, active HIGH
// Led 2, pin 3, active HIGH
// Led 3, pin 4, active HIGH
#define pino_ESC  7  /* botão para ligar/desligar senoide, push-buttom active LOW */

// Seguem variáveis associadas com "interface de saída"
byte pinos_Leds[]={2,3,4};  // vetor que define pinos onde leds estão conectados
int  Leds;                  // descobre quantidade de leds, calculado em tempo de execução
int  led_ativo  = 0;        // indicador do led atual ativo na "interface de saída"
int  dir = 1;               // direção do movimento do led (+1 ou -1)
bool generate   = true;     // indicar gerar/não-gerar senoide
bool debouncing = false;    // realizando debouncing da chave ou não
bool blink      = false;    // indica quando leds para interface piscam ou não
                            // a ideia é que pisquem quando NÂO estamos gerando a senoide
unsigned long currentMillis = 0;          // "tempo"  atual do sistema
unsigned long previousMillis = 0;         // will store last time leds was updated
unsigned long time_pressed = 0;           // will store last time buttom was pressed
const unsigned long time_debounce = 150;  // periodo de tempo para debouncing de chave (milisegundos)
const unsigned long time_wait = 250;      // interval at which to change leds states (milliseconds)

void turn_off_leds() {
  // simplesmente apaga todos os leds da "interface de saida"
  for (byte i=0; i < Leds; i++){
    digitalWrite(pinos_Leds[i], HIGH);
  }
}

void turn_on_all_leds() {
  // simplesmemte ativa todos os leds da "interface de saída"
  for (byte i=0; i < Leds; i++){
    digitalWrite(pinos_Leds[i], LOW);
  }
}

void activate_next_led() {
  // ativa leds da "interface de saída" sequencialmente
  // usa variavel global led_ativo para saber qual led atualmente está ativo
  Serial.print("led_ativo="); Serial.print(led_ativo);
  Serial.print("\tdir="); Serial.println(dir);
  for (byte i=0; i < Leds; i++){
    if (i == led_ativo) digitalWrite(pinos_Leds[i], HIGH);
    else digitalWrite(pinos_Leds[i], LOW);
  }
  // deixa preparadao para próxima chamada
  led_ativo = led_ativo + dir;
  if (led_ativo >= Leds) {
    led_ativo = Leds-2;
    dir = -1;
  }
  if (led_ativo < 0) {
    led_ativo = 1;
    dir = 1;
  }
}

void init_vars(){
  // inicializa variáveis importantes
  previousMillis = millis();
  generate    = false;
  debouncing  = false;
  blink       = false;
  led_ativo   = 0;
  dir         = 1;  
}

void update_leds(){
  // atualiza interface de saida conforme "status" do sistema
  if (currentMillis - previousMillis >= time_wait) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    if (generate) {
      activate_next_led();
    } 
    else 
    {
      blink = !blink;
      if (blink) turn_on_all_leds();
      else turn_off_leds();
    }
  }  
}

void setup() {
  Leds = sizeof(pinos_Leds) / sizeof(byte); // descobre quantidade de leds usados
  for (byte i=0; i <= Leds; i++){
    pinMode(pinos_Leds[i], OUTPUT);
    digitalWrite(pinos_Leds[i], HIGH);
  }
  pinMode(pino_ESC, INPUT);   // botão ESC

  turn_off_leds();

  Serial.begin(9600); // depois tentar aumentar para 115 Kbps
  // Serial.begin(115200);
  delay(100);
  Serial.print("\nLeds="); Serial.println(Leds);
  turn_off_leds();
  init_vars();
  Serial.println("Setup-Start done...\n");
} // end setup()

void loop() {
  currentMillis = millis();   // atualizando esta variável
  update_leds(); // atualiza interface de saída (leds)
  // lê botão ESC para alternar gerar/não-generar senoide mas apenas fora do periodo do debouncing
  if (!debouncing) {
    if (digitalRead(pino_ESC) == LOW) {
      // deveria deixar passar uns 50 ~ 100 ms a titulo de debouncing do botão
      // não podemos usar delay(100) porque influencia função analogWrite() - gerador PWM "interno" do Arduino
      debouncing = true;
      time_pressed = millis();
      generate = !generate;  // simplesmente alterna estado
      // atualiza info e variaveis para nova condição
      if (generate) {
        Serial.println("Generating sine wave...");
        led_ativo = 0;  // reinicia interface de saida
      } else {
        Serial.println("NOT generating sine wave (pause)...");
      }
    }
  }
  if (debouncing) {
    // Significa que estamos dentro de periodo de debouncing em que a chave é ignorada
    // falta verificar se passou tempo para "desligar" o debouncing
    if (currentMillis - time_pressed >= time_debounce) {
      debouncing = false;  // habilita novas leituras do botão
    }
  }
}  // end loop()

Referência consultada:

Transmissão serial de dados

Também queremos transmitir dados pela porta serial do Arduino (TX = pin 1, RX = pin 0).

A porta serial seria trabalha na forma:

void setup() {
  // open a serial connection
  Serial.begin(9600); // mudar para 115200 baud
  while (!SerialUSB); // sugerido para esperar abertura da porta serial com PC
}

void loop() {
  Serial.write(45); // send a byte with the value 45

  int bytesSent = Serial.write("hello");  //send the string "hello" and return the length of the string.
}

Observações:

A função analogWrite()

A função analogWrite() não gera realmente um sinal analógico e sim um sinal PWM nos pinos 3, 5, 6, 9, 10 e 11.

No nosso caso, usaremos o pino 5 (ou 6), o código para esta função deveria ficar algo semelhante à:

int ledPWM = 5;      // ou pino 6

void setup() {
  // Note: não é necessário definir como OUTPUT, pinos usados com algum PWM
  // a menos que se queria colocar um led neste pino para testar
  // visualmente o resultado da modulação PWM
  pinMode(ledPWM, OUTPUT);  // sets the pin as output
}

void loop() {
  analogWrite(ledPWM, valor); // valor = 0 (0%) até 255 (100%)
}

Versão final

Segue diagrama elétrico da montagem:

gerador_senoidal_arduino.drawio.jpg

Segue pinagem do led e do push-button:

Pinagem do led Pinos push-button
led_3.jpg uso_push_button_na.png

Segue versão final do código senoide.ino:

/****************************************************************
Teste interface saída

Fernando Passold, em 16/11/2022
*****************************************************************/
// Led 1, active HIGH
// Led 2, active HIGH
// Led 3, active HIGH
// Pin 5 = time-consuming ISR verifying with osciloscope
// Pin 6 = PWM output
#define pino_ESC  7                           // botão para ligar/desligar senoide, push-buttom active LOW
#define pino_PWM 6                            // saida PWM à 980 Hz (apenas pinos 5 e 6), uso com analogWrite()
#define pino_monitor 5                        // apenas para monitorar dutty-cycle da rotina de ISR (usar osciloscópio)

// Seguem variaveis associadas com "interface de saída"
byte pinos_Leds[]={2,3,4};
int  Leds; // define quantidade de leds, calculado em tempo de execução
int  led_ativo  = 0;      // indicador do led atual ativo na "interface de saída"
int  dir = 1;             // direção do movimento do led (+1 ou -1)
bool generate   = true;   // indica gerar/não-gerar senoide
bool debouncing = false;
bool blink      = false;  // indica quando leds para interface piscam ou não
                          // a ideia é que pisquem quando NÂO estamos gerando a senoide
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;         // will store last time leds was updated
unsigned long time_pressed = 0;           // will store last time buttom was pressed
const unsigned long time_debounce = 150;  // periodo de tempo para debouncing de chave (milisegundos)
const unsigned long time_wait = 250;      // interval at which to change leds states (milliseconds)

// Seguem variáveis relacionadas com geração da senoide 40 Hz
int index = 0; // ponto da senoide sendo sintetizado
const float delta_th = 2.0 * PI / 10.0;  // incremento angular entre os 10 pontos da senoide
// resulta em: delta_th = 0.63 (0,6283185307179586476925286766559)
float theta; // angulo atual da senoide
float y; // amplitude da senoide

void turn_off_leds() {
  // simplesmente apaga todos os leds da "interface de saida"
  for (byte i=0; i < Leds; i++){
    digitalWrite(pinos_Leds[i], HIGH);
  }
}

void turn_on_all_leds() {
  // simplesmemte ativa todos os leds da "interface de saída"
  for (byte i=0; i < Leds; i++){
    digitalWrite(pinos_Leds[i], LOW);
  }
}

void activate_next_led() {
  // ativa leds da "interface de saída" sequencialmente
  // usa variavel global led_ativo para saber qual led atualmente está ativo
  Serial.print("y="); Serial.print(y);
  Serial.print("\tled_ativo=");
  Serial.print(led_ativo);
  Serial.print("\tdir=");
  Serial.println(dir);
  for (byte i=0; i < Leds; i++){
    if (i == led_ativo) digitalWrite(pinos_Leds[i], HIGH);
    else digitalWrite(pinos_Leds[i], LOW);
  }
  // deixa preparadao para próxima chamada
  led_ativo = led_ativo + dir;
  if (led_ativo >= Leds) {
    led_ativo = Leds-2;
    dir = -1;
  }
  if (led_ativo < 0) {
    led_ativo = 1;
    dir = 1;
  }
}

void init_vars(){
  previousMillis = millis();
  generate    = false;
  debouncing  = false;
  blink       = false;
  led_ativo   = 0;
  dir = 1;    // direção "movimento" dos leds (+1 ou -1)
  index = 0;  // ponto inicial da senoide  
}

void update_leds(){
  // atualiza interface de saida conforme "status" do sistema
  if (currentMillis - previousMillis >= time_wait) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    if (generate) {
      activate_next_led();
    } 
    else 
    {
      blink = !blink;
      if (blink) turn_on_all_leds();
      else turn_off_leds();
    }
  }  
}

//  Segue codigo da ISR que gera os pontos da senoide
ISR(TIMER2_COMPA_vect){
  digitalWrite(pino_monitor, HIGH); // enters on ISR
  y = 0;  // por default, duty-cycle de saída = 0%
  if (generate) {
    theta = index * delta_th;  // calcula angulo atual da senoide
    y = 128.0 + 128.0*sin(theta);
    index++; // deixa pronto para proximo ponto
    if (index > 9) index = 0;
  }
  analogWrite(pino_PWM, y);
  digitalWrite(pino_monitor, LOW);  // get out from ISR
}

void setup() {
  // put your setup code here, to run once:
  Leds = sizeof(pinos_Leds) / sizeof(byte);
  for (byte i=0; i<Leds; i++){
    pinMode(pinos_Leds[i], OUTPUT);
    digitalWrite(pinos_Leds[i], HIGH);
  }
  pinMode(pino_ESC, INPUT);   // botão ESC
  pinMode(pino_PWM, OUTPUT);
  pinMode(pino_monitor, OUTPUT);

  Serial.begin(9600); // depois tentar aumentar para 115 Kbps
  // Serial.begin(115200);
  delay(100);
  Serial.println("Setup");
  Serial.print("\tLeds = "); Serial.println(Leds);
  Serial.print("\tPI = "); Serial.println(PI);
  Serial.print("\tdelta_th = "); Serial.println(delta_th);
  Serial.println("\tConfiguring ISR using timer2...");

  // configurando inicialização do timer2 usado para a ISR
  cli();        //stop interrupts
  TCCR2A = 0;   // set entire TCCR2A register to 0
  TCCR2B = 0;   // same for TCCR2B
  TCNT2 = 0;    // initialize counter value to 0
  // set timer2 interrupt at 400 Hz
  // set compare match register for 400 Hz increments:
  OCR2A = 155;  // = (16*10^6) / (400*256) - 1 (must be <256)
  // set CS prescaler for timer2 to 256 (CS2:0)=6(10)=110(2):
  TCCR2B |= (1 << CS22) | (1 << CS21) | (0 << CS20);
  TCCR2A |= (1 << WGM21);   // turn on CTC mode for timer2
  TIMSK2 |= (1 << OCIE2A);  // enable timer compare interrupt
  sei();                    //allow interrupts 
  // fim configuração ISR

  turn_off_leds();
  init_vars();
  Serial.println("Setup done...\n");
} // end setup()

void loop() {
  currentMillis = millis();   // atualizando esta variável
  update_leds(); // atualiza interface de saída (leds)
  // lê botão ESC para alternar gerar/não-generar senoide mas apenas fora do periodo do debouncing
  if (!debouncing) {
    if (digitalRead(pino_ESC) == LOW) {
      // deveria deixar passar uns 50 ~ 100 ms a titulo de debouncing do botão
      // não podemos usar delay(100) porque influencia função analogWrite() - gerador PWM "interno" do Arduino
      debouncing = true;
      time_pressed = millis();
      generate = !generate;  // simplesmente alterna estado
      // atualiza info e variaveis para nova condição
      if (generate) {
        index = 0;      // reinicia senoide
        Serial.println("Generating sine wave...");
        led_ativo = 0;  // reinicia interface de saida
      } else {
        Serial.println("NOT generating sine wave (pause)...");
      }
    }
  }
  if (debouncing) {
    // Significa que estamos dentro de periodo de debouncing em que a chave é ignorada
    // falta verificar se passou tempo para "desligar" o debouncing
    if (currentMillis - time_pressed >= time_debounce) {
      debouncing = false;  // habilita novas leituras do botão
    }
  }
}  // end loop()

Exemplo de saída gerada no Monitor Serial (9600 baud):

17:38:24.949 -> Setup
17:38:24.949 ->  Leds = 3
17:38:24.980 ->  PI = 3.14
17:38:24.980 ->  delta_th = 0.63
17:38:25.012 ->  Configuring ISR using timer2...
17:38:25.054 -> Setup done...
17:38:25.054 -> 
17:38:35.891 -> Generating sine wave...
17:38:35.969 -> y=249.74 led_ativo=0 dir=1
17:38:36.002 -> NOT generating sine wave (pause)...
17:38:37.333 -> Generating sine wave...
17:38:37.488 -> y=203.24 led_ativo=0 dir=1
17:38:37.710 -> y=203.24 led_ativo=1 dir=1
17:38:37.976 -> y=203.24 led_ativo=2 dir=1
17:38:38.240 -> y=203.24 led_ativo=1 dir=-1
17:38:38.460 -> y=203.24 led_ativo=0 dir=-1
17:38:38.721 -> y=203.24 led_ativo=1 dir=1
17:38:38.959 -> y=249.74 led_ativo=2 dir=1
17:38:39.208 -> NOT generating sine wave (pause)...

A próxima figura capturada de osciloscópio demostra a onda senoidal gerada na frequência de 40,6 Hz:

TEK0029_red.JPG

Na figura anterior, a curva em amarelo ressalta o sinal PWM filtrado, que gera uma onda semelhante à uma senoide. Neste caso, ainda usando C=150 nF no filtro Passa Baixas. E a onda inferior (curva em ciano), mostra a saída do pino 5, usada para monitorar o período de tempo gasto para processar a rotina de ISR.

De forma a melhorar a filtragem, foi colocado outro capacitor de C=220 nF em paralelo com o anterior, resultando numa capacitância final de C=C1+C2=370 nF, o que, juntamente com o resistor de 10 KΩ, baixou a frequência de corte do filtro de 106,1 Hz para:

fc=12πRC=12π(10×103)(370×109)

fc=43,015 Hz

Resultando num sinal um pouco mais “suave”:

TEK0043_red.JPG

A próxima figura mostra a relação entre o sinal PWM filtrado (curva superior, em amarelo) e o sinal PWM bruto (curva inferior, em ciano):

TEK0037_red.JPG

Note como seu duty-cycle varia conforme o ponto da senoide sendo sintetizado.

Um “zoom” sobre a figura anteior permite perceber um pouco melhor como a onda senoidal é gerada:

TEK0038_red.JPG

A próxima figura, destaca o tempo de processamento gasto pela rotina de ISR

TEK0046_red.JPG

Algumas medições foram realizadas e um dos maiores valores registrados para o período de tempo em que o pino 5 (monitor) permanece em nível lógico ALTO (Arduíno processando a ISR) foi de 166,9 μs (na média era gasto 159,8 μs). Note que as medições comprovam que a frequência da ISR ficou entre 400 e 403 Hz (T=1/400=2,5 ms). Isto permite concluir que a rotina ISR ocupou 167×1062500×106×100%=6,68% da frequência selecionada para esta ISR.

Isto significa que o Arduíno “gastou”: 167 μs para realizar 2 adições float + 2 multiplicações float + 1 cálculo de seno (em float) quando operado à 16 MHz (T=1/(16×106)=62,5×109 = 62,5 ns), ou aproximadamente (167000×109)/(62,5×109)=2672 ciclos de clock para realizar estes cálculos.

Observação final: uma senóide bem menos ruidosa poderia ser obtida se o PWM do Arduíno oscilasse numa frequência muito superior à naturalmente adotada para a função analogWrite() usada para gerar os próprios pontos da senóide. Em números um bom valor seria uma frequência 10× maior que a frequência usada para amostrar a senóide, ou fPWM10×4004 KHz, mas isto, implicaria recriar esta função, e a ideia aqui era testar que resultado poderia ser obtido com o PWM que “vêm de fábrica” com a IDE do Arduíno.

Fim


Fernando Passold, em 10/11/2022, 14/11/2022, 15/11/2022, 16/11/2022.