Programar
Arquitetura Modular
Uma arquitetura modular baseada em máquinas de estado permite:
Que terceiros compreendam facilmente a lógica do programa e das suas partes.
Testar cada módulo de forma independente.
Garantir que diferentes tarefas (ex.: leitura de sensores, comando do motor, interface com utilizador) não se bloqueiam mutuamente.
Evitar o uso de
delay()
, que pode ser fatal num sistema de controlo em tempo real.Usar de forma eficiente os recursos de hardware do Arduino, como o gerador PWM ou as interrupções, libertando o processador de tarefas críticas temporizadas.
Introdução a Máquinas de Estado
Uma máquina de estados finitos (FSM – Finite State Machine) é um modelo que organiza o comportamento de um sistema em estados, com transições entre eles, desencadeadas por eventos.
Elementos principais:
Estados: configurações em que o sistema pode estar (ex.: “verde”, “amarelo”, “vermelho”).
Estado inicial: ponto de partida.
Transições: regras de mudança de estado.
Eventos: condições que causam a transição.
Estado final: opcional, marca o fim da execução.
Exemplo genérico: um semáforo alterna entre verde, amarelo e vermelho, com tempos definidos.
Exemplo 1 — Dois LEDs a Piscar em Concorrência
Um primeiro exercício útil é piscar dois LEDs com frequências diferentes. Cada LED é controlado por uma máquina de estado independente, mas ambas executam em concorrência dentro do loop()
.
#define LED1 2
#define LED2 3
float f1 = 1.0; // Frequência LED1 [Hz]
float f2 = 2.0; // Frequência LED2 [Hz]
int duty = 50; // Duty cycle (%)
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
Serial.begin(115200);
}
void loop() {
autoBlink(LED1, f1, duty);
autoBlink(LED2, f2, duty);
}
void autoBlink(int pin, float f, int duty) {
const int APAGADO = 0, ACESO = 1;
static int state = APAGADO;
static unsigned long t0 = millis();
int T = 1000 / f;
int TH = T * duty / 100;
int TL = T - TH;
switch (state) {
case APAGADO:
if (millis() - t0 > TL) {
digitalWrite(pin, HIGH);
t0 = millis();
state = ACESO;
}
break;
case ACESO:
if (millis() - t0 > TH) {
digitalWrite(pin, LOW);
t0 = millis();
state = APAGADO;
}
break;
}
}
Exemplo 2 — Máquina de Estado Temporal (Protocolo)
Além de FSMs que descrevem sequências de estados físicos, também podemos ter máquinas de estado que implementam protocolos temporais.
Exemplo: criar uma rampa de valores PWM para gerar uma forma de onda triangular (útil em ensaios de calibração de motores).
#define MOTOR_PIN 5
int pwmValue = 0;
int step = 5;
void loop() {
static unsigned long t0 = millis();
unsigned long T = 50; // passo de 50 ms
if (millis() - t0 > T) {
pwmValue += step;
if (pwmValue >= 255 || pwmValue <= 0) step = -step;
analogWrite(MOTOR_PIN, pwmValue);
t0 = millis();
}
}
Este tipo de FSM é útil para ensaios automáticos (ex.: rampa ou senoide) sem intervenção do utilizador.
Exemplo 3 — FSM baseada em Interação do Utilizador
Outra FSM típica responde a eventos externos, como o pressionar de um botão.
#define BUTTON_PIN 7
#define LED_PIN 8
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
static int state = 0;
static int lastButton = HIGH;
int button = digitalRead(BUTTON_PIN);
if (lastButton == HIGH && button == LOW) { // deteta transição
state = !state;
digitalWrite(LED_PIN, state);
}
lastButton = button;
}
Esta FSM mostra a diferença entre um protocolo temporal (exemplo anterior) e um protocolo baseado em eventos externos.
Esqueleto para o Aeropêndulo
Segue uma estrutura modular que poderá ser usada como ponto de partida:
void setup() {
initSensor();
initMotor();
initControl();
}
void loop() {
readSensor();
computeControl();
commandMotor();
logData();
}
Cada função pode ser implementada como uma máquina de estado própria, permitindo testar e substituir módulos sem afetar os restantes.
Interrupções no Arduino
O Arduino permite associar funções a interrupções de hardware, que são executadas automaticamente quando ocorre um evento (ex.: transição num pino digital, overflow de um timer).
Exemplo: Contagem de Pulsos (Encoder Simples)
#define ENCODER_PIN 2
volatile long counter = 0;
void ISR_encoder() {
counter++;
}
void setup() {
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN), ISR_encoder, RISING);
Serial.begin(115200);
}
void loop() {
Serial.println(counter);
delay(500);
}
Para encoders em quadratura, usam-se duas interrupções (canais A e B), permitindo detetar também o sentido de rotação.