Você quer ganhar dinheiro com Arduino?
Quer ganhar dinheiro com Internet das Coisas? Então, você precisa investir no
seu conhecimento e também se equipar com coisas boas para aprender na prática.
Hoje vou te mostrar como expandir as portas do ESP32 com o Arduino Mega. Vamos
utilizar comunicação SPI entre ESP32 e Arduino Mega, exibindo uma explicação do
envio do estado dos pinos Digitais e Analógicos (AD) do Arduino Mega para o
ESP32.
A principal vantagem do
Arduino Mega, em minha opinião, é que ele tem muitos IOs e, portanto, a melhor
opção para projetos que necessitam de diversas portas.
RECURSOS USADOS
- ESP-WROOM32 (38 pinos)
- Arduino Mega 2560
- Protoboard
- Conversor de nível lógico 3V3-5V
- Jumpers
- Push button
- Resistor 10K ohm
MONTAGEM
*Conexão utilizando conversor
de nível:
D50 > H1 L1 < GPIO19
D51 > H2
L2 < GPIO23
D52 > H3
L3 < GPIO18
D53 > H4
L4 < GPIO5
VISUALIZAÇÃO NO MONITOR SERIAL
Monitor serial – Arduino Mega
(Após apertar o botão)
Monitor serial – ESP32
(Ao receber os dados)
Caso uma task esteja exibindo
os valores, outra task irá aguardar até que essa primeira termine (verificando o
estado da flag serialIsBusy).
A ordem de exibição das tasks
não importa. A task que conseguir setar a flag serialIsBusy para true primeiro é
a próxima que será executada.
CÓDIGO ARDUINO
Fluxograma
CÓDIGO ESP32
Fluxograma
Configurações
ESP32 - Bibliotecas
necessárias:
·
Biblioteca Slave SPI ESP32
·
Biblioteca Simple Array
CÓDIGO ARDUINO
Declarações e variáveis
// Biblioteca SPI #include <SPI.h> // Pinos SPI default utilizados #define GPIO_MISO 50 #define GPIO_MOSI 51 #define GPIO_SCK 52 #define GPIO_SS 53 // Setamos as configuraçõs SPI para 16 MHz, bit mais significativo primeiro e SPI_MODE0 SPISettings settings(16000000, MSBFIRST, SPI_MODE0); // Botão que servirá como disparo de envio SPI int eventPin = 13; // Estruturas que guardarão os valores dos pinos Digitais e AD // As estruturas devem possuir no máximo 32 bytes (tamanho máximo de transações SPI). Um valor digital ocupa 1 byte de espaço, enquanto um valor AD ocupa 2 bytes. // A variável type indica qual o tipo de estrutura for enviado, assim, do lado do slave, podemos identificar qual é a estrutura que foi recebida // A struct "digitalPinsUntil33" guarda os valores dos pinos D2 a D33, exceto o D13 (pino usado para o botão). Possui um tamanho de 32 bytes struct digitalPinsUntil33 { uint8_t type = 1; uint8_t digitalPins[31]; }; // A struct "digitalAndADPins" guarda os valores dos pinos D32 a D49 e também os A0 a A6. Possui um tamanho de 31 bytes struct digitalAndADPins { uint8_t type = 2; uint8_t digitalPins[16]; uint16_t ADPins[7]; }; // A struct "ADPinsLeft" guarda os valores restantes de AD, sendo de A7 a A15. Possui um tamanho de 19 bytes struct ADPinsLeft { uint8_t type = 3; uint16_t ADPins[9]; };
Setup
void setup() { // Iniciamos a velocidade da serial em 115200 para debug Serial.begin(115200); // Caso seja necessário saber qual o tamanho da estrutura basta descomentar o código abaixo e visualizar no monitor serial /* Serial.println((int)sizeof(digitalPinsUntil33)); // 32 Serial.println((int)sizeof(digitalAndADPins)); //31 Serial.println((int)sizeof(ADPinsLeft)); //19 */ // Iniciamos a SPI SPI.begin(); // Setamos o pino do botão como entrada pinMode(eventPin, INPUT); // Setamos o pino slave select como saída pinMode(GPIO_SS, OUTPUT);
// Setamos o pino Clock como saída pinMode(GPIO_SCK, OUTPUT); // Setamos o pino Clock para baixo (SPI_MODE0) digitalWrite(GPIO_SCK, LOW); // Deixamos o slave desabilitado (quando low significa que uma nova transação será feita) digitalWrite(GPIO_SS, HIGH); delay(10); }
Loop
void loop() { // Se o botão foi pressionado if(digitalRead(eventPin) == HIGH) { Serial.println("Lendo pinos"); // Efetuamos as leituras de todos os pinos readPins(); Serial.println("Enviando parte 1"); // Enviamos a primeira estrutura em uma transação sendToESP(DPins1); Serial.println("Enviando parte 2"); // Enviamos a segunda estrutura em outra transação sendToESP(Dpins2_ADPins1);
Serial.println("Enviando parte 3"); // Enviamos a terceira estrutura em outra transação sendToESP(ADPins2); Serial.println("OK"); // Enquanto o botão continuar pressionado, aguardamos // Impedindo múltiplos envios se o botão ficar pressionado por muito tempo while(digitalRead(eventPin) == HIGH) delay(1); } else delay(10); }
ReadPins
// Função que lê os pinos e guarda seus valores nas estruturas void readPins() { // Estrutura do tipo 1: digitalPinsUntil33 DPins1.digitalPins[0] = digitalRead(2); DPins1.digitalPins[1] = digitalRead(3); DPins1.digitalPins[2] = digitalRead(4); DPins1.digitalPins[3] = digitalRead(5); DPins1.digitalPins[4] = digitalRead(6); DPins1.digitalPins[5] = digitalRead(7); DPins1.digitalPins[6] = digitalRead(8); DPins1.digitalPins[7] = digitalRead(9); DPins1.digitalPins[8] = digitalRead(10); DPins1.digitalPins[9] = digitalRead(11); DPins1.digitalPins[10] = digitalRead(12); // O Botao está ligado na gpio13, então não faz parte dos pinos válidos DPins1.digitalPins[11] = digitalRead(14); DPins1.digitalPins[12] = digitalRead(15); DPins1.digitalPins[13] = digitalRead(16); DPins1.digitalPins[14] = digitalRead(17); DPins1.digitalPins[15] = digitalRead(18);
DPins1.digitalPins[16] = digitalRead(19); DPins1.digitalPins[17] = digitalRead(20); DPins1.digitalPins[18] = digitalRead(21); DPins1.digitalPins[19] = digitalRead(22); DPins1.digitalPins[20] = digitalRead(23); DPins1.digitalPins[21] = digitalRead(24); DPins1.digitalPins[22] = digitalRead(25); DPins1.digitalPins[23] = digitalRead(26); DPins1.digitalPins[24] = digitalRead(27); DPins1.digitalPins[25] = digitalRead(28); DPins1.digitalPins[26] = digitalRead(29); DPins1.digitalPins[27] = digitalRead(30); DPins1.digitalPins[28] = digitalRead(31); DPins1.digitalPins[29] = digitalRead(32); DPins1.digitalPins[30] = digitalRead(33); // Estrutura do tipo 2: digitalAndADPins Dpins2_ADPins1.digitalPins[0] = digitalRead(34); Dpins2_ADPins1.digitalPins[1] = digitalRead(35); Dpins2_ADPins1.digitalPins[2] = digitalRead(36); Dpins2_ADPins1.digitalPins[3] = digitalRead(37);
Dpins2_ADPins1.digitalPins[4] = digitalRead(38); Dpins2_ADPins1.digitalPins[5] = digitalRead(39); Dpins2_ADPins1.digitalPins[6] = digitalRead(40); Dpins2_ADPins1.digitalPins[7] = digitalRead(41); Dpins2_ADPins1.digitalPins[8] = digitalRead(42); Dpins2_ADPins1.digitalPins[9] = digitalRead(43); Dpins2_ADPins1.digitalPins[10] = digitalRead(44); Dpins2_ADPins1.digitalPins[11] = digitalRead(45); Dpins2_ADPins1.digitalPins[12] = digitalRead(46); Dpins2_ADPins1.digitalPins[13] = digitalRead(47); Dpins2_ADPins1.digitalPins[14] = digitalRead(48); Dpins2_ADPins1.digitalPins[15] = digitalRead(49); Dpins2_ADPins1.ADPins[0] = analogRead(A0); Dpins2_ADPins1.ADPins[1] = analogRead(A1); Dpins2_ADPins1.ADPins[2] = analogRead(A2); Dpins2_ADPins1.ADPins[3] = analogRead(A3); Dpins2_ADPins1.ADPins[4] = analogRead(A4); Dpins2_ADPins1.ADPins[5] = analogRead(A5); Dpins2_ADPins1.ADPins[6] = analogRead(A6);
// Estruturas do tipo 3: ADPinsLeft ADPins2.ADPins[0] = analogRead(A7); ADPins2.ADPins[1] = analogRead(A8); ADPins2.ADPins[2] = analogRead(A9); ADPins2.ADPins[3] = analogRead(A10); ADPins2.ADPins[4] = analogRead(A11); ADPins2.ADPins[5] = analogRead(A12); ADPins2.ADPins[6] = analogRead(A13); ADPins2.ADPins[8] = analogRead(A14); ADPins2.ADPins[7] = analogRead(A15); }
sendToESP – Struct 1
// Função que envia os valores da estrutura do tipo 1 via SPI void sendToESP(struct digitalPinsUntil33 dados) { // Selecionamos o slave (esp) digitalWrite(GPIO_SS,LOW); // Iniciamos uma transação SPI.beginTransaction(settings); // Criamos um ponteiro que aponta para o primeiro byte da estrutura 'dados' uint8_t *p = (uint8_t*)&dados; // Percorremos byte a byte da estrutura, enviando-os um por vez, até que todos os 32 bytes sejam enviados for(int i=0; i<=sizeof(digitalPinsUntil33); i++) SPI.transfer(*p++); // Finalizamos a transação SPI.endTransaction(); // Desativamos o slave (esp) digitalWrite(GPIO_SS,HIGH); }
sendToESP – Struct 2
// Função que envia os valores da estrutura do tipo 2 via SPI void sendToESP(struct digitalAndADPins dados) { // Selecionamos o slave (esp) digitalWrite(GPIO_SS,LOW); // Iniciamos uma transação SPI.beginTransaction(settings); // Criamos um ponteiro que aponta para o primeiro byte da estrutura 'dados' uint8_t *p = (uint8_t*)&dados; // Percorremos byte a byte da estrutura, enviando-os um por vez, até que todos os 31 bytes sejam enviados for(int i=0; i<=sizeof(digitalAndADPins); i++) SPI.transfer(*p++); // Finalizamos a transação SPI.endTransaction(); // Desativamos o slave (esp) digitalWrite(GPIO_SS,HIGH); }
sendToESP – Struct 3
// Função que envia os valores da estrutura do tipo 3 via SPI void sendToESP(struct ADPinsLeft dados) { // Selecionamos o slave (esp) digitalWrite(GPIO_SS,LOW); // Iniciamos uma transação SPI.beginTransaction(settings); // Criamos um ponteiro que aponta para o primeiro byte da estrutura 'dados' uint8_t *p = (uint8_t*)&dados; // Percorremos byte a byte da estrutura, enviando-os um por vez, até que todos os 19 bytes sejam enviados for(int i=0; i<=sizeof(ADPinsLeft); i++) SPI.transfer(*p++); // Finalizamos a transação SPI.endTransaction(); // Desativamos o slave (esp) digitalWrite(GPIO_SS,HIGH); }
CÓDIGO ESP
Declarações e variáveis
// Biblioteca SlaveSPI para ESP32 #include <SlaveSPI.h> // Biblioteca SPI #include <SPI.h> #include "esp_task_wdt.h" // Pinos SPI utilizados #define _MISO (gpio_num_t)19 #define _MOSI (gpio_num_t)23 #define _SCK (gpio_num_t)18 #define _SS (gpio_num_t)5 // Objeto SlaveSPI, setamos para virtual SPI (VSPI) SlaveSPI slave(VSPI_HOST); // Variável usada para que duas tasks não exibam na serial ao mesmo tempo, evitando que os valores se embaralhem durante suas exibições bool serialIsBusy = false; // Variáveis que correspondem ao código do Arduino Mega (explicação no código do Arduino Mega) struct digitalPinsUntil33 { uint8_t type = 1; uint8_t digitalPins[31]; }; struct digitalAndADPins { uint8_t type = 2; uint8_t digitalPins[16]; uint16_t ADPins[7]; }; struct ADPinsLeft { uint8_t type = 3; uint16_t ADPins[9]; }; struct digitalPinsUntil33 DPins1; struct digitalAndADPins Dpins2_ADPins1; struct ADPinsLeft ADPins2;
Setup
void setup() { // Desabilitamos o watchdog de hardware do core 0 //disableCore0WDT(); // Desnecessário // Setamos a velocidade da serial para 115200 Serial.begin(115200); // Setamos o pino Slave Select como entrada pinMode(_SS, INPUT); // Setamos o pino Clock como saída pinMode(_SCK, OUTPUT); // Setamos o pino Clock para baixo (SPI_MODE0) digitalWrite(_SCK, LOW); // Iniciamos a SPI setando os pinos, tamanho máximo de transmissões (32 bytes) e setando a função callback (opcional) slave.begin(_MISO, _MOSI, _SCK, _SS, 32, callback_after_slave_tx_finish); }
Loop
void loop() { // Verificamos se existem dados a serem lidos na SPI if(slave.getInputStream()->length() && digitalRead(_SS) == HIGH) { uint8_t type; // Obtemos o primeiro byte, que indica o tipo de struct recebida slave.getInputStream()->getBytes(&type, sizeof(uint8_t)); // Efetuamos a leitura de acordo com o seu tipo readData(type); // Limpamos o buffer slave.flushInputStream(); } }
ReadData
// Função que lê os dados, atribuindo aos endereços das structs void readData(uint8_t type) { // Executamos um switch/case para o tipo (type) switch(type) { case 1: // Atribuimos ao endereço da struct global DPins1, os valores em bytes recebidos por SPI slave.getInputStream()->getBytes(&DPins1, sizeof(digitalPinsUntil33)); break; case 2: // Atribuimos ao endereço da struct global Dpins2_ADPins1, os valores em bytes recebidos por SPI slave.getInputStream()->getBytes(&Dpins2_ADPins1, sizeof(digitalAndADPins)); break; case 3: // Atribuimos ao endereço da struct global ADPins2, os valores em bytes recebidos por SPI slave.getInputStream()->getBytes(&ADPins2, sizeof(ADPinsLeft)); break; default: // Se o tipo não for 1, 2 ou 3, exibimos erro e abortamos a função Serial.println("Erro ao obter primeiro byte!"); return; break; } // Executamos uma task no core 0 para exibirmos na serial os valores das structs // Assim podemos voltar para a rotina de leitura SPI mais rapidamente xTaskCreatePinnedToCore(taskPrintValues, "taskPrintValues", 10000, (void*)type, 2, NULL, 0); }
TaskPrintValues
// Task que exibe os valores dos pinos de acordo com o tipo de estrutura void taskPrintValues(void *p) { // Recebemos o tipo por parâmetro // 1 - primeira struct // 2 - segunda struct // 3 - terceira struct // Obs. Não é obrigatório enviar as estruturas na ordem, desde que seus tipos sejam enviados corretamente uint8_t type = *((uint8_t*)(&p)); // Flag que evita imprimir os dados embaralhados (por mais de uma task) while(serialIsBusy) delay(1); // Setamos a flag para true serialIsBusy = true; // Exibimos na serial Serial.println("\nTipo:"+String(type));
PrintStatePins
// Para cada tipo, chamamos a função printStatePins enviando uma determinada struct switch(type) { case 1: printStatePins(DPins1); break; case 2: printStatePins(Dpins2_ADPins1); break; case 3: printStatePins(ADPins2); break; default: // Se o tipo não for 1, 2 ou 3, exibimos erro Serial.println("taskPrintValues: Tipo invalido"); break; } // Setamos a flag para false serialIsBusy = false; // Encerramos a task vTaskDelete(NULL); }
printStatePins – Struct 1
// Função que exibe na serial os valores da primeira estrutura (D2 até D33) void printStatePins(struct digitalPinsUntil33 DPins1) { // Percorremos o vetor de pinos digitais até o GPIO12 exibindo na serial // Exemplo de exibição: "D3 = 0" for(int i=0; i<11; i++) Serial.println("D"+String(i+2)+" = "+String(DPins1.digitalPins[i])); // Sabemos que o GPIO13 não faz parte dos pinos válidos, portanto na exibição pulamos do D12 para o D14 for(int i=11; i<31; i++) Serial.println("D"+String(i+3)+ " = "+String(DPins1.digitalPins[i])); }
printStatePins – Struct 2
// Sobrecarga da função acima // Função que exibe na serial os valores da segunda estrutura (D34 até D49 e A0 até A6) void printStatePins(struct digitalAndADPins Dpins2_ADPins1) { // Percorremos o vetor com o restante dos pinos digitais exibindo na serial do D34 ao D49 // Exemplo de exibição: "D34 = 0" for(int i=0; i<16; i++) Serial.println("D"+String(i+34)+" = "+String(Dpins2_ADPins1.digitalPins[i])); // Percorremos o vetor com os valores AD do A0 ao A6 e exibimos na serial for(int i=0; i<7; i++) Serial.println("AD"+String(i)+ " = "+String(Dpins2_ADPins1.ADPins[i])); }
printStatePins – Struct 3
// Sobrecarga da função acima // Função que exibe na serial restante dos valores AD (do A7 ao A15) void printStatePins(struct ADPinsLeft ADPins2) { // Percorremos o vetor com os valores AD do A7 ao A15 e exibimos na serial for(int i=0; i<9;i++) Serial.println("AD"+String(i+7)+" = "+String(ADPins2.ADPins[i])); }
Função call-back setada no setup (opcional)
// Função callback que é chamada logo após uma transação SPI int callback_after_slave_tx_finish() { //Serial.println("[slave_tx_finish] slave transmission has been finished!"); //Serial.println(slave[0]); return 0; }
7 Comentários
Boa tarde Fernando.... tudo bem com você, parabéns pelo projeto e pelo grande profissional que você é,eu assisto seus videos no youtube, e acho suas explicações excelentes e de ótimo conteúdo
ResponderExcluirParabéns! - pelo projeto, muito interessante.
ResponderExcluirA titulo de experiência, segue algumas questões:
- Seria muito complicado eu adaptar este projeto para o Arduino Uno e ESP32 30 pinos?
- As bibliotecas utilizadas teriam que ser modificadas?
Saudações, aguardo retorno.
Bom dia. parabéns pela explicação e conteudo. gostaria de tirar umas duvidas, eu estava montando um sistema baseado em i2c, e estou avaliando a possibilidade de uso via spi. obrigado.
ResponderExcluirBoa noite Fernando, eu possuo um projeto com ESP32 que está programado para ser controlado pela Alexa (Amazon Echo Dot), utilizando a biblioteca do fauxmo. Eu gostaria de enviar o status da porta do ESP32 para o Arduino Mega. Isso seria possível de se adaptar com esse seu projeto?
ResponderExcluirObrigado e parabéns pelo canal!
Boa Noite Mestre! Que ótima idéia de projeto! Parabéns pela aula! Gostei muito e estou tentando fazer, mas estou com problemas, onde aparece o mesmo erro na compilaçao do ESP32: 'class SlaveSPI' has no member named 'getInputStream'. Acho que o problema é library, mas não estou conseguindo resolver, tem alguma idéia do que está acontecendo? Obrigado!
ResponderExcluirEste comentário foi removido pelo autor.
ExcluirHugo, estou com o mesmo problema. Como tu resolveu?!
ExcluirForte Abraço.