Manual

do

Maker

.

com

Controle de até 8 relés utilizando GPIO e I2C

Controle de até 8 relés utilizando GPIO e I2C

Controle de botoeira

Essa solução não é o que se pode chamar de "brilhante", mas é uma maneira de economizar uma grana quando você precisa controlar a condição de vários relés baseados no estado de uma botoeira.

Essas chaves na base dos quadros de comando não são botões de pulso, eles preservam um estado. Nesse caso, suponhamos que você precise monitorar 8 estados de botões para, conforme o estado, acionar até 8 relés. Nesse caso, você já teria que considerar de imediato um Arduino Mega, porque seriam necessários 16 GPIOs para fazer esse controle. Isso, se não tiver mais absolutamente nada a controlar, mas se você está optando por um controle através de uma MCU, certamente tem mais coisa a controlar (como por exemplo, LEDs de status, como os LEDs dispostos na caixa). Bem, tem uma solução simples para aproveitar os recursos do seu Arduino somado a um expansor de IO PCF8574.

Exemplo de uso com 1 PCF8574

Supondo que esteja utilizando um Arduino UNO, Leonardo ou Nano, serão necessários apenas 8 pinos de GPIO para acionar o relé e 2 pinos do I²C para controlar o PCF8574. Conforme o estado de cada pino do PCF8574, os relés correspondentes serão acionados ou desligados. Para isso, utilizaremos deslocamento de bits no PCF8574 e para relacionar os valores do PCF8574 aos respectivos pinos dos relés, podemos fazer um array contendo os pinos desses relés, depois varremos em um loop o estado de cada pino. Se você precisa de informações sobre a manipulação do PCF8574, recomendo esse artigo. Se estiver querendo descobrir o endereçamento do seu PCF8574, a forma mais simples é utilizando o scanner, descrito mais abaixo. Agora, vamos ao código, devidamente comentado.

#include <Wire.h>

#define ADDRESS      0x22 //endereço do "meu" PCF8574

#define TURN_OFF     1 //meu modulo desliga em HIGH
#define TURN_ON      0 //e liga em LOW

byte buf = 0; //buffer para armazenar o valor do PCF8574

byte relays[8] = {4,5,8,9,10,11,12,13}; //pinos para cada um dos reles

//função para ligar ou desligar os relés conforme os valores do PCF8574
void changeRelayState(){
  byte state = buf; //equipara o buffer com o comparador e faz um loop nos pinos
  for (int i=0;i<8;i++){
    state = buf|(1<<i); //incrementa o valor do buffer com a respectiva posição
    //se for possível incrementar o valor do buffer, significa que o 
    //pino do PCF8574 está em LOW
    if (state != buf){
      digitalWrite(relays[i],TURN_OFF); //nesse caso, desliga o relay da posição 'i'
      Serial.print("OFF: ");
      Serial.println(relays[i]);
      state = buf;
    }
    /*Senão, liga o relay da posição 'i'*/
    else{
      digitalWrite(relays[i],TURN_ON);
      Serial.print("ON: ");
      Serial.println(relays[i]);
    }
  }
}

void setup()
{
  Wire.begin();
  Serial.begin(115200);

  /*
   * Como estou utilizando apenas um módulo duplo, fiz o pinMode diretamente, mas
   * se tiver vários relays, basta rodar esse loop no mesmo tamanho do array relays[],
   * com limite de 8, já que o máximo de pinos no PCF8574 é 8.
  for (int i=0;i<8;i++){
    pinMode(relays[i],OUTPUT);
  }
  */
  pinMode(RELAY_PORT_1,OUTPUT);
  pinMode(RELAY_PORT_2,OUTPUT);

  digitalWrite(relays[0],TURN_ON); //estado inicial: ligado
  digitalWrite(relays[1],TURN_ON);
   
  delay(2000);
  Serial.println("Starting...");
}


void loop()
{  
  Wire.beginTransmission(ADDRESS); //inicia transmissao no barramento i2c
  Wire.write(0xFF); //coloca tudo em HIGH. O que estiver em GND vai para LOW sozinho.
  Wire.endTransmission(); //fim do envio do comando.
  delay(1000);

  Wire.requestFrom(ADDRESS,1); //solicita 1 byte (8 bits)
  if (Wire.available()){
    buf = Wire.read(); //armazena no buffer a leitura desse byte
  }
  Serial.println(buf);
  changeRelayState();//manipula os relays conforme os bits do PCF8574
}

Você pode questionar: "Mas, se o relay já está ligado, vai fazer digitalWrite novamente". Sim, vai fazer mesmo, mas não muda nada. Assim haverá também uma ação imediata sem a necessidade de mais uma instrução do comparador condicional 'if'.

Outra coisa que talvez lhe gere dúvida é sobre o deslocamento de bits. No video estou explicando como fiz a relação entre os bits do PCF8574 e os pinos dos relés, inclusive mostrando in flow no bPython para que fique fácil compreender.

Scanner I2C

Eu não decoro os endereçamentos dos PCF8574 e tenho uma baita preguiça de procurar até mesmo nos meus artigos, então, gosto de usar esse scanner I²C que encontrei há algum tempo no http://forum.arduino.cc/index.php?topic=197360 e que tem evoluido com o passar do tempo. Tudo o que você precisa fazer é colar esse código em um sketch, subir para o Arduino com o dispositivo I²C devidamente conectado e se guiar pelo menu que aparece no monitor serial. Dá pra fazer um single scan com 's' ou um continuous scan com 'c', entre outros parâmetros configuráveis, mas não necessários para a maioria dos dispositivos que normalmente utilizamos.

//
//    FILE: MultiSpeedI2CScanner.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.7
// PURPOSE: I2C scanner at different speeds
//    DATE: 2013-11-05
//     URL: http://forum.arduino.cc/index.php?topic=197360
//
// Released to the public domain
//

#include <Wire.h>
#include <Arduino.h>

TwoWire *wi;

const char version[] = "0.1.7";


// INTERFACE COUNT (TESTED TEENSY 3.5 AND ARDUINO DUE ONLY)
int wirePortCount = 1;
int selectedWirePort = 0;


// scans devices from 50 to 800KHz I2C speeds.
// lower than 50 is not possible
// DS3231 RTC works on 800 KHz. TWBR = 2; (?)
const long allSpeed[] = {
  50, 100, 200, 300, 400, 500, 600, 700, 800
};
long speed[sizeof(allSpeed) / sizeof(allSpeed[0])];
int speeds;

int addressStart = 0;
int addressEnd = 127;


// DELAY BETWEEN TESTS
#define RESTORE_LATENCY  5    // for delay between tests of found devices.
bool delayFlag = false;


// MINIMIZE OUTPUT
bool printAll = true;
bool header = true;


// STATE MACHINE
enum states {
  STOP, ONCE, CONT, HELP
};
states state = STOP;


// TIMING
uint32_t startScan;
uint32_t stopScan;


void setup()
{
  Serial.begin(115200);
  delay(2000);
  Serial.println("Starting...");
  Wire.begin();

#if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1
  Wire1.begin();
  wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2
  Wire2.begin();
  wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3
  Wire3.begin();
  wirePortCount++;
#endif

  wi = &Wire;

  setSpeed('0');
  displayHelp();
}


void loop()
{
  char command = getCommand();
  switch (command)
  {
    case '@':
      selectedWirePort = (selectedWirePort + 1) % wirePortCount;
      Serial.print(F("I2C PORT=Wire"));
      Serial.println(selectedWirePort);
      switch (selectedWirePort)
      {
        case 0:
          wi = &Wire;
          break;
        case 1:
#if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1
          wi = &Wire1;
#endif
          break;
        case 2:
#if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2
          wi = &Wire2;
#endif
          break;
        case 3:
#if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3
          wi = &Wire3;
#endif
          break;
      }
      break;

    case 's':
      state = ONCE;
      break;
    case 'c':
      state = CONT;
      break;
    case 'd':
      delayFlag = !delayFlag;
      Serial.print(F("<delay="));
      Serial.println(delayFlag ? F("5>") : F("0>"));
      break;

    case 'e':
      // eeprom test TODO
      break;

    case 'h':
      header = !header;
      Serial.print(F("<header="));
      Serial.println(header ? F("yes>") : F("no>"));
      break;
    case 'p':
      printAll = !printAll;
      Serial.print(F("<print="));
      Serial.println(printAll ? F("all>") : F("found>"));
      break;

    case '0':
    case '1':
    case '2':
    case '4':
    case '8':
      setSpeed(command);
      break;

    case 'a':
      setAddress();
      break;

    case 'q':
    case '?':
      state = HELP;
      break;
    default:
      break;
  }

  switch (state)
  {
    case ONCE:
      I2Cscan();
      state = HELP;
      break;
    case CONT:
      I2Cscan();
      delay(1000);
      break;
    case HELP:
      displayHelp();
      state = STOP;
      break;
    case STOP:
      break;
    default: // ignore all non commands
      break;
  }
}


void setAddress()
{
  if (addressStart == 0)
  {
    addressStart = 8;
    addressEnd = 120;
  }
  else
  {
    addressStart = 0;
    addressEnd = 127;
  }
  Serial.print(F("<address Range = "));
  Serial.print(addressStart);
  Serial.print(F(".."));
  Serial.print(addressEnd);
  Serial.println(F(">"));

}

void setSpeed(char sp)
{
  switch (sp)
  {
    case '1':
      speed[0] = 100;
      speeds = 1;
      break;
    case '2':
      speed[0] = 200;
      speeds = 1;
      break;
    case '4':
      speed[0] = 400;
      speeds = 1;
      break;
    case '8':
      speed[0] = 800;
      speeds = 1;
      break;
    case '0':  // reset
      speeds = sizeof(allSpeed) / sizeof(allSpeed[0]);
      for (int i = 0; i < speeds; i++)
      {
        speed[i] = allSpeed[i];
      }
      break;
  }
}

char getCommand()
{
  char c = '\0';
  if (Serial.available())
  {
    c = Serial.read();
  }
  return c;
}

void displayHelp()
{
  Serial.print(F("\nArduino MultiSpeed I2C Scanner - "));
  Serial.println(version);
  Serial.println();
  Serial.print(F("I2C ports: "));
  Serial.println(wirePortCount);
  Serial.println(F("\t@ = toggle Wire - Wire1 - Wire2 [TEENSY 3.5 or Arduino Due]"));
  Serial.println(F("Scanmode:"));
  Serial.println(F("\ts = single scan"));
  Serial.println(F("\tc = continuous scan - 1 second delay"));
  Serial.println(F("\tq = quit continuous scan"));
  Serial.println(F("\td = toggle latency delay between successful tests. 0 - 5 ms"));
  Serial.println(F("Output:"));
  Serial.println(F("\tp = toggle printAll - printFound."));
  Serial.println(F("\th = toggle header - noHeader."));
  Serial.println(F("\ta = toggle address range, 0..127 - 8..120"));
  Serial.println(F("Speeds:"));
  Serial.println(F("\t0 = 50 - 800 Khz"));
  Serial.println(F("\t1 = 100 KHz only"));
  Serial.println(F("\t2 = 200 KHz only"));
  Serial.println(F("\t4 = 400 KHz only"));
  Serial.println(F("\t8 = 800 KHz only"));
  Serial.println(F("\n\t? = help - this page"));
  Serial.println();
}


void I2Cscan()
{
  startScan = millis();
  uint8_t count = 0;

  if (header)
  {
    Serial.print(F("TIME\tDEC\tHEX\t"));
    for (uint8_t s = 0; s < speeds; s++)
    {
      Serial.print(F("\t"));
      Serial.print(speed[s]);
    }
    Serial.println(F("\t[KHz]"));
    for (uint8_t s = 0; s < speeds + 5; s++)
    {
      Serial.print(F("--------"));
    }
    Serial.println();
  }

  // TEST
  // 0.1.04: tests only address range 8..120
  // --------------------------------------------
  // Address  R/W Bit Description
  // 0000 000   0 General call address
  // 0000 000   1 START byte
  // 0000 001   X CBUS address
  // 0000 010   X reserved - different bus format
  // 0000 011   X reserved - future purposes
  // 0000 1XX   X High Speed master code
  // 1111 1XX   X reserved - future purposes
  // 1111 0XX   X 10-bit slave addressing
  for (uint8_t address = addressStart; address <= addressEnd; address++)
  {
    bool printLine = printAll;
    bool found[speeds];
    bool fnd = false;

    for (uint8_t s = 0; s < speeds ; s++)
    {
#if ARDUINO >= 158
      wi->setClock(speed[s] * 1000);
#else
      TWBR = (F_CPU / (speed[s] * 1000) - 16) / 2;
#endif
      wi->beginTransmission (address);
      found[s] = (wi->endTransmission () == 0);
      fnd |= found[s];
      // give device 5 millis
      if (fnd && delayFlag) delay(RESTORE_LATENCY);
    }

    if (fnd) count++;
    printLine |= fnd;

    if (printLine)
    {
      Serial.print(millis());
      Serial.print(F("\t"));
      Serial.print(address, DEC);
      Serial.print(F("\t0x"));
      if (address < 0x10) Serial.print(0, HEX);
      Serial.print(address, HEX);
      Serial.print(F("\t"));

      for (uint8_t s = 0; s < speeds ; s++)
      {
        Serial.print(F("\t"));
        Serial.print(found[s] ? F("V") : F("."));
      }
      Serial.println();
    }
  }

  stopScan = millis();
  if (header)
  {
    Serial.println();
    Serial.print(count);
    Serial.print(F(" devices found in "));
    Serial.print(stopScan - startScan);
    Serial.println(F(" milliseconds."));
  }
}

E se eu quiser controlar por pulso?

Se for controlar por pulso, pra fazer com PCF8574 fica um pouco complicado. Eu fiz um pequeno código de exemplo para controle por pulso sem a utilização do módulo I²C. Basicamente, coloco um botão com "destino" ao GND e coloco um pino em pullup. Quando ele for para GND, ele muda o estado em uma variável de memória, chamada ports**[]**, que é um array de estados.

bool ports[2] = {1};
void setup() {
  Serial.begin(115200);
  //coloca em pullup e quando for pra gnd, gera o evento
  pinMode(2,INPUT_PULLUP);
  pinMode(4,OUTPUT);
  pinMode(5,OUTPUT);
  digitalWrite(4,HIGH);
  digitalWrite(5,HIGH);
}

void loop() {
  if (digitalRead(2) == LOW){
    ports[0] = !ports[0];
    digitalWrite(4,ports[0]);
    Serial.println(ports[0]);
    while(digitalRead(2) == LOW);
  }
  delay(200); //so pra nao retornar imediatamente ao evento
  // put your main code here, to run repeatedly:

}

Onde comprar o PCF8574

Sempre recomendo um dos parceiros do site, mas meus caros, "nenhum" deles tem o módulo, ou seja, essa vou ficar devendo. O dispositivo é realmente barato e vale a pena a aquisição. Inclusive, no próximo artigo vou mostrar como espelhar a configuração de um PCF8574 em outro PCF8574, o que simplifica mais ainda o controle dos relés. Também vou mostrar como fazer espelhamento remoto dos relés utilizando o rádio NRF24L01, é só acompanhar!

https://youtu.be/h3D7ln2FKbQ

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.