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 ...2... que, quando acionado, deve comandar o ESCRAVO.
  • No ESCRAVO a saída digital ...7... 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 ...3\,\text{s}... 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 ...3\,\text{s}....

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 ...2... 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 ...7... do Arduino. A saída do Arduino só fornece até cerca de ...20\,\text{mA}... 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 ...4,7\,\text{k}\Omega....

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 ...2... e enviar exatamente um comando quando o botão for pressionado (evitar enviar continuamente enquanto seguram o botão).
  • Para isso usamos:
    • pinMode( ...2..., 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 ...50\,\text{ms}... para garantir que o sinal não é ruído mecânico do botão.
  • 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 ...1... como sinal "acionar por ...3\,\text{s}...".

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 ...8...).
    • ...buttonPin ... é ...2....
    • debounceDelay = ...50... 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 ...7... acionado por ...3\,\text{s}... 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 ...3000\,\text{ms}... para desativar.
  • 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 ...7... 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 ...7... do ESCRAVO para ver se o comando chega e se ele fica ON por ...3\,\text{s}....
  • 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 ...50\,\text{ms}...) 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 ...2..., debounce, detectar borda, enviar byte via I2C para escravo endereço ...8....
  • Escravo: Wire.onReceive para marcar flag, no loop checar flag, acionar saída ...7... por ...3\,\text{s}... 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
}

Exercícios relacionados