Estou mais ou menos na metade do projeto do relógio cuco, já fiz o controle da parte mecânica, conforme os recursos utilizados nesse outro artigo, também já fiz o teste inicial de pegar hora da internet usando NTP como descrito nesse artigo. Agora é hora de controlar os LEDs RGB que farão a simulação da iluminação do dia, desde a alvorada até o crepúsculo, utilizando PCA9685 e ESP32.
Table of Contents
PCA9685 e ESP32
A biblioteca é a mesma utilizada no Arduino (existem diversas, vou mostrar a que escolhi). Esse módulo serve tanto para fazer PWM para LEDs (podendo ter até 5 LEDs RGB ou 16 comuns) como para controlar até 16 servos motor. Provavelmente é a melhor opção para o controle de LEDs RGB tradicionais, mas uma opção que poderia ser melhor que essa seria o uso de LEDs endereçáveis como o WS2812, que é bem prático e fácil de controlar. Não lembro se já escrevi sobre ele, mas farei um artigo tão logo seja possível.
Antes que eu me esqueça, o LED RGB deve ser anodo comum. Se você utilizar catodo comum, os LEDs não acenderão porque os pinos do PCA9685 são apenas sinal.
O PCA9685 é um módulo i2c com 12 bits de resolução, expansível até 4 módulos. Não sei se todos vem com o mesmo endereço, mas como eu não sabia qual era o endereço padrão, utilizei um scanner i2c para detectar. O código do scanner é esse:
// // 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(18,19); #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.")); } }
Basta subir esse sketch e abrir o terminal, então quando aparecer o menu você pode optar por um single scan (s) ou continuous (c). Deve aparecer uma linha completa com V maiúsculo no endereço encontrado. Caso não apareça, procure acertar o SDA e SCL, que é a causa mais comum para um eventual erro.
No meu caso, o scanner mostrou o dispositivo no endereço 0x70, então reatribuí o valor da macro que definia o endereço do dispositivo na biblioteca. Estou utilizando o ESP32 Wemos da CurtoCircuito e selecionei os pinos 18 para SDA e 19 para SCL. Vou fragmentar o código que escrevi para explicá-lo, mas se você não gosta de absorver conceitos e quer apenas o código para funcionar, é só ir à seção Codigo Completo.
Utilizando Atom com Platformio
Dessa vez resolvi usar o Atom com alguns plugins para ficar bem legal. Baixe o Atom no site oficial ou use o repositório de sua distribuição Linux, se for seu sistema:
sudo su apt-get update apt-get install atom
Após a instalação do Atom, você poderá instalar recursos guiado pela própria IDE. Procure por PlatformIO e Clang. Mais uma vez, se estiver no Linux, basta utilizar o mesmo esquema acima:
sudo su apt-get update apt-get install clang exit
O comando exit é para sair do usuário root. Depois de instalados e reiniciado o Atom, abrir-se-á uma aba intitulada PlatformIO Home.
A partir dela você escolhe a plataforma que deseja programar e as dependências serão baixadas automaticamente. Nesse mesmo menu tem uma seção Libraries, onde você precisará digitar PCA9685.
É essa primeira a que instalei. Depois é só criar um projeto novo, escolher a placa e a plataforma de programação (ESP-IDF, Arduino etc).
Código completo
Os includes são o Wire, a PCA9685 e Arduino, para importar os recursos da API do Arduino. Fiz os defines para o I2C e para o endereço do módulo e o resto explico no próprio código.
#include <Arduino.h> #include <PCA9685.h> #include <Wire.h> //pinos escolhidos para o i2c #define SDA 18 #define SCL 19 //endereço da placa #define PCA9685_BASEADR 0x70 //instância da PCA9685. Os parâmetros são endereço, tipo de controle e frequência PCA9685 driver = PCA9685(PCA9685_BASEADR, PCA9685_MODE_LED_DIRECT, 800.0); //cockcrow canto do galo // função para controlar os LEDs RGB void ledControl(byte led_number); //variável alocada para copia do array correspondente à iluminação, conforme a hora do dia int *weather = (int*) malloc(sizeof(int)*3); byte myTime = 20; //TODO: criar um timer para acumular horas //estrutura para armazenar as cores que correspondam à iluminação conforme a hora do dia struct schemes{ int dawn[3] = {600,600,600}; int morning[3] = {1200,1200,1200}; int midday[3] = {4095,4095,4095}; int afternoon[3] = {1500,1500,1500}; int almostNight[3] = {400,400,400}; int twilight[3] = {100,100,100}; }; struct schemes schemesValues; void setup() { // Serial para debug Serial.begin(115200); // inicializa o i2c Wire.begin(SDA,SCL); // inicializa a PCA9685 driver.setup(); //desliga todos os pinos da PCA9685 for (byte i=0;i<16;i++){ driver.getPin(i).setValueAndWrite(0); } //delay para dar tempo de abrir a serial e assistir o debug delay(2000); ledControl(2); } /* Como estou fazendo o teste apenas no primeiro LED, a função se limita a um array de 3 bits, para R, G e B. Depois os valores serão simplesmente estendidos aos demais pinos, mas eu queria testar o controle primeiro. */ void ledControl(byte led_number){ //como free() está com bug, não posso criar a cada chamada, então //deixo global e só reatribuo o valor //int *weather = (int*) malloc(sizeof(int)*3); byte max = led_number*3; // desse modo configuro o array nos pinos corretos /*as condicionais abaixo podem ser modificadas como quiser. Eu selecionei aproximadamente os horários em que a luz pode variar mais. A variavel myTime guardará as horas por intermédio de uma task (que será criada) a posteriori. Conforme o horário, os valores virão com as cores (ainda a ajustar) que representem aproximadamente a luz do respectivo horário.*/ if (myTime > 0 && myTime < 6){ weather = schemesValues.dawn; } else if (myTime >5 && myTime < 11){ weather = schemesValues.morning; } else if (myTime >10 && myTime < 16){ weather = schemesValues.midday; } else if (myTime >15 && myTime < 17){ weather = schemesValues.afternoon; } else if (myTime >16 && myTime < 19){ weather = schemesValues.almostNight; } else{ weather = schemesValues.twilight; } //Tendo determinado o array de cores a utilizar, agora atribui-se aos pinos. for (byte i=0;i<3;i++){ driver.getPin(max).setValueAndWrite(weather[i]); Serial.println(weather[i]); max++; } /* Como free() deu um bug, tive que criar a variável weather global e apenas zerar os valores após o uso.*/ memset(weather, 0, 3); //free está dando erro no esp32 //free(weather); } void loop() { /* Essa rotina faz um fade no LED, foi usada só para teste no pino 0. Serial.println("loop ok"); for (int i = 0; i < PCA9685_MAX_VALUE; i = i + 8){ // set the pwm value of the first led driver.getPin(PCA9685_LED0).setValueAndWrite(weather[i]); delay(5); } */ }
É só fazer o wiring, upload do programa e usar. Farei um video em algumas horas fazendo um tour pela IDE e mostrando a placa e o funcionamento, não deixe de se inscrever em nosso canal DobitAoByteBrasil no Youtube!
Siga-nos no Do bit Ao Byte no Facebook.
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/
Sistemas Eletrônicos
https://www.facebook.com/groups/544679592355388/
Projetos Arduino | Eletrônica
https://www.facebook.com/groups/projetosarduinoeletronica/
ESP8266 e ESP32
https://www.facebook.com/groups/ESP8266BR/
ARM Brasil
https://www.facebook.com/groups/508153815944410/
MIPS BR
https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte
https://www.facebook.com/groups/dobitaobyte/
Próximo post a caminho!
2 thoughts on “Controle de LED RGB com PCA9685 e ESP32”
Comments are closed.