Tópicos
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.
Pra quem não sabe, uma “botoeira” é um painel de botões para definir quando algo deve estar ligado ou desligado. Esse quadro de comando é um exemplo:

Essas chaves na base do quadro 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!
Inscreva-se no nosso newsletter, alí em cima à direita e receba novos posts por email.
Siga-nos no Do bit Ao Byte no Facebook.
Prefere twitter? @DobitAoByte.
Inscreva-se no nosso canal Do bit Ao Byte Brasil no YouTube.
Nossos grupos:
Arduino BR – https://www.facebook.com/groups/microcontroladorarduinobr/
Raspberry Pi BR – https://www.facebook.com/groups/raspberrybr/
Orange Pi BR – https://www.facebook.com/groups/OrangePiBR/
Odroid BR – https://www.facebook.com/groups/odroidBR/
Sistemas Embarcados BR – https://www.facebook.com/groups/SistemasEmbarcadosBR/
MIPS BR – https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte – https://www.facebook.com/groups/dobitaobyte/
Próximo post a caminho!
1 comentário