Monte um programa do tipo MESTRE ENVIADOR / ESCRAVO RECEBEDOR em Arduino para atender um sistema de controle de avanço de um cilindro pneumático (saída 7 do ESCRAVO) em função de um botão acionado no MESTRE (entrada 2). O cilindro deverá ficar acionado por 3s e então retornar.
Passo 1
O que o enunciado pede e as informações que ele dá:
- Pede um par de programas Arduino no esquema “MESTRE ENVIADOR / ESCRAVO RECEBEDOR”.
- No MESTRE há um botão ligado na entrada digital
que, quando acionado, deve comandar o ESCRAVO. - No ESCRAVO a saída digital
comanda o avanço de um cilindro pneumático (ou seja, aciona a válvula/driver do cilindro). - Quando o MESTRE detecta o botão pressionado, o ESCRAVO deve acionar a saída por
e então retornar (desligar). - Temos exatamente uma operação simples: enviar um comando do mestre para o escravo e o escravo acionar a saída por
.
Passo 2
Conexões e segurança elétrica (tudo que precisa estar fisicamente certo antes de programar)
- Botão no MESTRE: recomendo usar o pino
configurado com pull-up interno e botão para GND. Assim, o estado normal é HIGH e, ao pressionar, vai para LOW. Isso evita necessidade de resistor externo. - Saída no ESCRAVO: não ligue o cilindro ou a válvula diretamente ao pino
do Arduino. A saída do Arduino só fornece até cerca de e não suporta cargas indutivas. Use um driver adequado: um transistor/MOSFET com diodo de roda-livre se for solenóide, ou um módulo de driver/relé, e uma fonte de alimentação adequada para a válvula. - Comunicação entre Arduino MESTRE e ESCRAVO: usaremos I2C (Wire). Precisa de SDA e SCL entre os dois Arduinos e um GND comum. Observe que I2C usa resistor pull-up nas linhas SDA/SCL; muitas placas Arduino já têm esses resistores, mas em caso de montagem com fios longos talvez seja necessário adicionar resistores externos de cerca de
.
Passo 3
Escolha do método de comunicação e por que (explicação didática)
- O enunciado pede um par Mestre/Escravo; I2C (protocolo TWI do Arduino, com a biblioteca Wire) é natural para isso: permite que um Arduino (mestre) envie bytes para outro (endereço do escravo) com poucas linhas de código e sem precisar de bibliotecas extras.
- Alternativas seriam Serial (UART) ou SPI. Usei I2C porque é simples para comandos curtos entre dois Arduinos no mesmo barramento e porque o enunciado fala explicitamente em MESTRE/ESCRAVO.
- Resumo: MESTRE faz Wire.beginTransmission(endereco); Wire.write(comando); Wire.endTransmission(); ESCRAVO registra uma função de recebimento que indica que chegou comando.
Passo 4
Lógica do MESTRE — comportamento desejado e detalhes de implementação
- Precisamos ler o botão em
e enviar exatamente um comando quando o botão for pressionado (evitar enviar continuamente enquanto seguram o botão). - Para isso usamos:
- pinMode(
, INPUT_PULLUP) — deixa o pino em HIGH quando não pressionado. - detecção de borda (quando o estado estável muda de HIGH para LOW) para enviar apenas uma vez por pressão.
- debounce simples por software: quando o estado muda, esperar
para garantir que o sinal não é ruído mecânico do botão.
- pinMode(
- Funções Wire usadas no MESTRE:
- Wire.begin() — inicializa I2C como mestre.
- Wire.beginTransmission(endereco) — começa uma transmissão para o escravo de endereço especificado.
- Wire.write(valor) — escreve um byte (o comando).
- Wire.endTransmission() — envia os bytes no barramento.
- Escolhi enviar um único byte com valor
como sinal "acionar por ".
Passo 5
Código do MESTRE com explicações linha a linha
- Antes de mostrar o código, explico as variáveis:
-
SLAVE_ADDR é o endereço do escravo na rede I2C (vou usar ). -
buttonPin é . - debounceDelay =
ms.
-
- Aqui está o código do MESTRE (copie e grave no Arduino que ficará com o botão):
#include <Wire.h>
const byte SLAVE_ADDR = 8; // endereço I2C do escravo
const int buttonPin = 2; // botão no pino digital 2 (usar INPUT_PULLUP)
const unsigned long debounceDelay = 50; // debounce em ms
int lastButtonReading = HIGH; // última leitura bruta
int stableState = HIGH; // estado estável após debounce
unsigned long lastDebounceTime = 0;
void setup() {
Wire.begin(); // inicializa I2C como mestre
pinMode(buttonPin, INPUT_PULLUP);
}
void loop() {
int reading = digitalRead(buttonPin);
// se a leitura mudou, reinicia o temporizador de debounce
if (reading != lastButtonReading) {
lastDebounceTime = millis();
}
// se já passou o tempo de debounce, atualiza o estado estável
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != stableState) {
stableState = reading;
// detecta borda de HIGH -> LOW (botão pressionado)
if (stableState == LOW) {
// envia comando simples ao escravo
Wire.beginTransmission(SLAVE_ADDR);
Wire.write(1); // comando: 1 = acionar por 3s
Wire.endTransmission();
}
}
}
lastButtonReading = reading;
}
Passo 6
Lógica do ESCRAVO — comportamento, atenção a interrupções e temporização
- O ESCRAVO recebe bytes via I2C. A biblioteca Wire permite registrar uma função de callback que é chamada quando o escravo recebe dados: Wire.onReceive(receiveEvent).
- Importante: essa função de callback é executada em contexto interno da biblioteca (próximo de uma rotina de interrupção) — portanto NÃO se deve fazer operações longas ou usar delay() lá dentro. Em vez disso, a função deve apenas ler/consumir os dados e sinalizar para o loop principal do Arduino que um comando chegou (usando uma variável marcada como volatile).
- Para manter o pino
acionado por sem travar a detecção de novos comandos, podemos: - Usar uma variável que marca “ativo”, guardar o tempo de início com millis() e, no loop(), verificar se já passaram
para desativar.
- Usar uma variável que marca “ativo”, guardar o tempo de início com millis() e, no loop(), verificar se já passaram
- Variáveis importantes:
-
volatile bool cmdReceived — sinaliza que a ISR gravou que chegou um comando. -
unsigned long startTime — marca o instante em que a saída foi ligada. -
const unsigned long activeTime = 3000 — tempo em ms que a saída deve ficar ativa.
-
Passo 7
Código do ESCRAVO com explicações
- Antes de colar o código: lembre-se de que o pino
deve controlar um driver/relé/transistor que alimenta a válvula, nunca a carga diretamente. - Abaixo o código do ESCRAVO (carregue no Arduino que controla o cilindro):
#include <Wire.h>
const byte SLAVE_ADDR = 8; // mesmo endereço do mestre
const int outputPin = 7; // pino que comanda o driver/relé da válvula
volatile bool cmdReceived = false; // sinalizado pela função de recebimento
unsigned long startTime = 0;
bool active = false;
const unsigned long activeTime = 3000; // 3 segundos em ms
// Callback chamado quando dados chegam via I2C
void receiveEvent(int howMany) {
// lê e consome os bytes recebidos (não fazer delay aqui)
while (Wire.available()) {
Wire.read(); // apenas consome o byte; poderíamos checar valor se precisasse
cmdReceived = true; // flag para o loop principal
}
}
void setup() {
Wire.begin(SLAVE_ADDR); // inicializa I2C como escravo
Wire.onReceive(receiveEvent); // registra callback
pinMode(outputPin, OUTPUT);
digitalWrite(outputPin, LOW); // garante estado desligado inicialmente
}
void loop() {
// se chegou comando e não estamos ativos, inicia acionamento
if (cmdReceived && !active) {
cmdReceived = false; // consome o comando
active = true;
startTime = millis();
digitalWrite(outputPin, HIGH); // aciona o driver/relé
}
// se está ativo, verifica se o tempo passou
if (active) {
if ((millis() - startTime) >= activeTime) {
digitalWrite(outputPin, LOW); // desliga após 3s
active = false;
}
}
// aqui o loop fica livre para outras tarefas se necessário
}
Passo 8
Verificação, testes e pontos práticos (dicas de prova e de bancada)
- Teste sem carga primeiro: conecte um LED com resistor no pino
do ESCRAVO para ver se o comando chega e se ele fica ON por . - Depois teste com o driver: verifique a corrente e tensão da válvula, use transistor/MOSFET ou módulo de relé adequados e proteja contra picos (diodo flyback se for solenóide).
- Se o botão não responder, verifique o pull-up (pino configurado em INPUT_PULLUP) e que o botão está ligando o pino a GND quando pressionado.
- Se o comando é enviado várias vezes por pressão, verifique o debounce no mestre (aqui usamos
) e a lógica de borda que envia só quando o estado estável muda para LOW. - Em aplicações industriais use isolamento (optocoupler, relé com separação) e não deixe o Arduino exposto às tensões da válvula.
Passo 9
Resumo prático rápido (pra levar pra prova)
- Mestre: ler botão em
, debounce, detectar borda, enviar byte via I2C para escravo endereço . - Escravo: Wire.onReceive para marcar flag, no loop checar flag, acionar saída
por usando millis() para temporizar. - Sempre usar driver entre Arduino e válvula.
Resposta
MESTRE (sketch para o Arduino que tem o botão):
#include <Wire.h>
const byte SLAVE_ADDR = 8; // endereço I2C do escravo
const int buttonPin = 2; // botão no pino digital 2 (usar INPUT_PULLUP)
const unsigned long debounceDelay = 50; // debounce em ms
int lastButtonReading = HIGH; // última leitura bruta
int stableState = HIGH; // estado estável após debounce
unsigned long lastDebounceTime = 0;
void setup() {
Wire.begin(); // inicializa I2C como mestre
pinMode(buttonPin, INPUT_PULLUP);
}
void loop() {
int reading = digitalRead(buttonPin);
// se a leitura mudou, reinicia o temporizador de debounce
if (reading != lastButtonReading) {
lastDebounceTime = millis();
}
// se já passou o tempo de debounce, atualiza o estado estável
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != stableState) {
stableState = reading;
// detecta borda de HIGH -> LOW (botão pressionado)
if (stableState == LOW) {
// envia comando simples ao escravo
Wire.beginTransmission(SLAVE_ADDR);
Wire.write(1); // comando: 1 = acionar por 3s
Wire.endTransmission();
}
}
}
lastButtonReading = reading;
}
ESCRAVO (sketch para o Arduino que comanda o cilindro na saída \7):
#include <Wire.h>
const byte SLAVE_ADDR = 8; // mesmo endereço do mestre
const int outputPin = 7; // pino que comanda o driver/relé da válvula
volatile bool cmdReceived = false; // sinalizado pela função de recebimento
unsigned long startTime = 0;
bool active = false;
const unsigned long activeTime = 3000; // 3 segundos em ms
// Callback chamado quando dados chegam via I2C
void receiveEvent(int howMany) {
// lê e consome os bytes recebidos (não fazer delay aqui)
while (Wire.available()) {
Wire.read(); // apenas consome o byte; poderíamos checar valor se precisasse
cmdReceived = true; // flag para o loop principal
}
}
void setup() {
Wire.begin(SLAVE_ADDR); // inicializa I2C como escravo
Wire.onReceive(receiveEvent); // registra callback
pinMode(outputPin, OUTPUT);
digitalWrite(outputPin, LOW); // garante estado desligado inicialmente
}
void loop() {
// se chegou comando e não estamos ativos, inicia acionamento
if (cmdReceived && !active) {
cmdReceived = false; // consome o comando
active = true;
startTime = millis();
digitalWrite(outputPin, HIGH); // aciona o driver/relé
}
// se está ativo, verifica se o tempo passou
if (active) {
if ((millis() - startTime) >= activeTime) {
digitalWrite(outputPin, LOW); // desliga após 3s
active = false;
}
}
// aqui o loop fica livre para outras tarefas se necessário
}