Apostila de Programação com Arduino

2. Aplicação Alarme no Arduino

A fim de ilustrar como adaptar a implementação de uma máquina de estados finitos para o Arduino, vamos utilizar o exemplo do nosso alarme implementado em C (https://gitlab.uspdigital.usp.br/andre.kubagawa/alarme). Na sua pasta de sketches (projetos) do Arduino, clone o repositório do alarme para iniciar o desenvolvimento da nossa aplicação.

2.1 Adaptações para projeto Arduino

Antes de começar a editar o código, temos que modificar a estrutura do projeto para adequar ao padrão do Arduino. Essencialmente, necessitamos que o arquivo principal (main.c) tenha o mesmo nome do diretório no qual está contido (alarme) e deve ter a extensão .ino. Para tal, siga os seguintes passos:

  1. mova todos os arquivos da pasta src para a pasta raiz (alarme);
  2. renomeie o arquivo main.c para alarme.ino; e
  3. altere as extensões de todos os arquivos .c para .cpp.

Esta última etapa é necessária pois um sketch Arduino adota a linguagem C++, cuja extensão dos arquivos de código fonte é .cpp.

Por fim, vamos remover os arquivos Makefile, sensores.h, sensores.c, zonas.h e zonas.c, que não serão utilizados. Não deixe de remover as diretivas #include "sensores.h" e #include "zonas.h" do arquivo alarme.ino. Ao final, a estrutura do seu projeto deve ser:

📦alarme
 ┣ 📜.gitignore
 ┣ 📜alarme.ino
 ┣ 📜comunicacao.cpp
 ┣ 📜comunicacao.h
 ┣ 📜definicoes_sistema.h
 ┣ 📜ihm.cpp
 ┣ 📜ihm.h
 ┣ 📜README.md
 ┣ 📜senhas.cpp
 ┣ 📜senhas.h
 ┣ 📜sirene.cpp
 ┣ 📜sirene.h
 ┣ 📜state_machine.svg
 ┣ 📜timer.cpp
 ┗ 📜timer.h

2.2 Substituindo as chamadas de funções da biblioteca padrão de C

No Arduino, não temos as funcionalidades necessárias para realizar as funções printf e scanf, que escreve na tela e lê entradas do teclado, respectivamente. Vamos substituir estas funções pelas de comunicação serial do Arduino.

No caso do printf, basta utilizar o método Serial.print ou Serial.println. No entanto, estes não suportam especificadores de formato. Uma solução é fazer múltiplas chamadas; sendo assim, o código

printf("Estado: %d Evento: %d Acao:%d\n", estado, codigoEvento, codigoAcao);

pode ser substituído por

Serial.print("Estado: ");
Serial.print(estado);
Serial.print(" Evento: ");
Serial.print(codigoEvento);
Serial.print(" Acao: ");
Serial.println(codigoAcao);

Também é necessário substituir os headers stdio.h e stdlib.h por (Arduino.h).

Por exemplo, o arquivo comunicacao.cpp deve ficar:

#include <Arduino.h>

#include "definicoes_sistema.h"
#include "comunicacao.h"

/************************
 com_notificar
 Envia mensagem para a Central
 entradas
   texto : texto para envio para Central
 saidas
   nenhuma
*************************/
void com_notificar(char* texto)
{
    Serial.print("Comunicacao com a Central: ");
    Serial.println(texto);
}

Por fim, é necessário inicializar a comunicação serial na função setup() do alarme.ino, como no exemplo do capítulo 1.

2.3 Adaptação dos componentes ihm e Timer

Os eventos serão gerados a partir do recebimento de mensagens via serial. Assim, a função ihm_obterTeclas do componente ihm pode ser modificado para

...

/************************
 ihm_obterTecla
 Obtem tecla do teclado
 entradas
   nenhuma
 saidas
   teclas lidas do teclado
*************************/
char* ihm_obterTeclas()
{
  // Serial.print("obter teclas:");
  int read_count = 0;
  
  // check for input
  if (Serial.available() > 0) {
    // read the incoming bytes:
    read_count = Serial.readBytesUntil('\n',buf, sizeof(buf)/sizeof(buf[0]) - 1);
  }
  
  buf[read_count] = '\0';
  if(read_count > 0) {
  	Serial.println(buf);
  }
  
  return buf;
}
}

Vamos implementar o componente timer utilizando o relógio do próprio Arduino. Assim, o arquivo timer.cpp deve ser modificado para

#include <Arduino.h>

#include "definicoes_sistema.h"
#include "timer.h"

#define TEMPO 10

int tmr_situacao = false;
unsigned long horaInicio;

/*******************************
 tmr_iniciar
 Aciona ou desaciona o timer
 entradas
   controle: TRUE:liga FALSE:desliga
 saidas
   nenhuma
********************************/
void tmr_iniciar(int controle)
{
   tmr_situacao = controle;
   if (controle)
   {
      horaInicio = millis();
   }
}

/*******************************
 tmr_timeout
 Retorna se o timer esta em timeout.
 entradas
    nenhuma
 saidas
    FALSE: nao houve estouro do temporizador
    TRUE: houve estouro do temporizador
********************************/
int tmr_timeout()
{
    if (tmr_situacao == false)
    {
        return false;
    }
    if(millis() - horaInicio > TEMPO*1000)
    {
        return true;
    }
    return false;
}

2.4 Reestruturação do código para setup e loop

Agora vamos trabalhar no arquivo principal, o alarme.ino. Analisando a função main, podemos separá-la em inicialização e operação, que ocorre dentro de um loop while infinito.

Inicialmente, vamos mover a inicialização das variáveis simples para o escopo global, ou seja, fora da função main, escreva

...
/***********************************************************************
 Estaticos
 ***********************************************************************/
  int codigoEvento = NENHUM_EVENTO;
  int eventoInterno = NENHUM_EVENTO;
  int estado = ESPERA;
...

Assim, a nossa função setup deve ficar:

void setup() {
  Serial.begin(9600);

  iniciaSistema();
  Serial.println("Alarme iniciado");
} // setup

Depois, devemos mover o loop infinito para a função loop:

void loop() {
  if (eventoInterno == NENHUM_EVENTO) {
      codigoEvento = obterEvento();
  } else {
      codigoEvento = eventoInterno;
  }
  if (codigoEvento != NENHUM_EVENTO)
  {
      codigoAcao = obterAcao(estado, codigoEvento);
      estado = obterProximoEstado(estado, codigoEvento);
      eventoInterno = executarAcao(codigoAcao);
      Serial.print("Estado: ");
      Serial.print(estado);
      Serial.print(" Evento: ");
      Serial.print(codigoEvento);
      Serial.print(" Acao: ");
      Serial.println(codigoAcao);
  }
} // loop

Com isso, finalizamos a nossa aplicação de alarme. Para testar, compile e faça o upload para o Arduino; sua operação pode ser realizada totalmente pelo monitor serial do Arduino IDE.

No caso de dúvidas, o código final da aplicação pode ser consultado em

https://gitlab.uspdigital.usp.br/andre.kubagawa/alarme_arduino/-/tree/master/alarme_basico

2.5 Código final completo no Tinkercad

O Tinkercad possui a limitação de apenas um arquivo de código fonte. Isto significa que temos que juntar todos os arquivos do projeto do alarme.

No repositório foi incluído um script Python que faz a conversão automática dos projetos de alarme:

https://gitlab.uspdigital.usp.br/andre.kubagawa/alarme_arduino/-/blob/master/ino2Tinkercad.py

para usá-lo, é necessário executar o seguinte comando a partir da pasta do projeto:

python ..\ino2Tinkercad.py alarme.ino -o ../alarme_tinkercad.ino

Depois copie e cole o conteúdo de alarme_tinkercad.ino para o seu circuito no Tinkercad.

O código final pode ser obtido em:

https://gitlab.uspdigital.usp.br/-/snippets/17

2.6 Adicionando um buzzer piezoelétrico

Para deixar nossa aplicação um pouco mais interessante, vamos adicionar um buzzer para simular a sirene do nosso alarme. Primeiro, crie o seguinte circuito:

Depois, declare uma nova função do componente sirene em sirene.h:

...

/************************
 sne_setup
 Configura a sirene
 entradas
   pin: pino do buzzer
 saidas
   nenhuma
*************************/
extern void sne_setup(int pin);

..

e modifique as suas definições em sirene.cpp:

#include <Arduino.h>

#include "definicoes_sistema.h"
#include "sirene.h"

#define TONE_FREQ 1000
#define BEEP_TIME 100

int sne_pin;

/************************
 sne_setup
 Configura a sirene
 entradas
   pin: pino do buzzer
 saidas
   nenhuma
*************************/
void sne_setup(int pin)
{
  sne_pin = pin;
  pinMode(sne_pin, OUTPUT);
}

/************************
 sne_bip
 Aciona momentaneamente a sirene
 entradas
   nenhuma
 saidas
   nenhuma
*************************/
void sne_bip()
{
  tone(sne_pin, TONE_FREQ, BEEP_TIME);
}


/************************
 sne_acionar
 Aciona ou desaciona a sirene
 entradas
   controle: TRUE:ligar FALSE:desligar
 saidas
   nenhuma
*************************/
void sne_acionar(int controle)
{
  if(controle)
    tone(sne_pin, TONE_FREQ);
  else
    noTone(sne_pin);
}

Por fim, no arquivo .ino principal, insira a chamada para a função sne_setup() dentro de setup:

void setup()
{
  sne_setup();
  Serial.begin(9600);
  Serial.println("Maquina de estados iniciada");
}

Agora, ao acionar o alarme, o buzzer deve gerar um beep curto; e quando o alarme for disparado, o buzzer deve emitir um som contínuo.

Bibliografia para este capítulo