banner

Expansor de portas para ESP32 usando Arduino Mega com SPI



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)

Obs. A exibição das structs recebidas é feita por tasks.
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;
}




FAÇA O DOWNLOAD DOS ARQUIVOS




3 comentários:

  1. 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

    ResponderExcluir
  2. Parabéns! - pelo projeto, muito interessante.
    A 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.

    ResponderExcluir
  3. 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.

    ResponderExcluir

Tecnologia do Blogger.