Conhecendo o Protocolo SPI com Arduino

Protocolo-SPI
Font Size

Olá, tudo bem? No post de hoje iremos finalizar o nosso estudo sobre os protocolos de comunicação utilizando o Arduino. Dessa forma, hoje estaremos conhecendo o protocolo SPI com arduino. Ele, assim como o protocolo I2C já estudado, é amplamente utilizado devido o seu modo de operação Full-Duplex. Então quer saber como tudo isso funciona? Vamos nessa então!

Funcionamento do Protocolo SPI

O protocolo SPI foi desenvolvido pela Motorola e rapidamente ganhou espaço no mercado devido a sua simplicidade de utilização, precisando apenas de 3 fios + 1 para cada dispositivo interligado à ele, para que possa realizar a comunicação entre todos.

Portanto, toda essa comunicação é feita de forma serial. Analisando então, o termo SPI, do inglês Serial Peripheral Interface, conseguimos identificar isso. Dessa forma, como seu nome sugere, seu propósito é interligar periféricos a um dispositivo mestre utilizando comunicação serial. Por exemplo, podemos encontrar esse protocolo no PlayStation 2, o qual utiliza este protocolo em seus controles.

Mas como o protocolo SPI trabalha?

Acima de tudo, devemos saber que seu propósito é interligar periféricos a um dispositivo mestre. Por exemplo, essa ideia é a mesma do protocolo I2C, caso não tenha lido ainda, aconselho dar uma olhada para melhor compreensão, abaixo tem o link do post, beleza?

Então, já sabemos que ele tem seu mestre e seus escravos como no protocolo I2C. Mas sabe aqueles 3 fios + 1 para cada escravo? Como eles trabalham? Para isso, nós temos 3 principais conexões:

Ou seja, no MOSI temos o canal de comandos do mestre para seus escravos e só. Contudo no MISO, temos o contrário, é onde o escravo envia os dados solicitados pelo mestre. Por fim, o canal SCLK é o nosso clock, a velocidade das trocas de informações. Observe tudo isso configurado na imagem abaixo:

Ligação entre os dispositivos do barramento

Em resumo, na imagem acima temos toda a ligação dos barramentos. Observe, portanto, que o MOSI do mestre é conectado no MOSI do escravo, o mesmo vale para o barramento MISO e SCLK. Contudo temos mais alguns pinos, como no exemplo acima temos 3 escravos, é necessário ter mais 3 pinos além dos 3 principais.

Analisando os Pinos SSX

Assim, temos os pinos SS1, SS2, SS3. O que eles são? Em resumo, eles são os pinos de seleção (Slave Select), o qual vai dizer qual escravo o mestre quer se comunicar naquele momento. Dessa forma, quando o pino Slave Select de um dispositivo está no nível baixo, ele se comunica com o mestre. Assim, quando ele está alto, ignora o mestre. Como resultado, isso irá permitir que você tenha vários dispositivos SPI compartilhando os mesmo barramentos de MISO, MOSI e SCLK como na imagem.

Além disso, a sua arquitetura é do tipo Full-Duplex. Ou seja, como já explicado no post do protocolo I2C, a arquitetura Full-Duplex permite que o envio/recebimento de dados sejam feitos simultaneamente. Perceba que isso é possível uma vez que os barramentos (o canal de comunicação) são independentes, ou seja, ou você envia ou recebe. Beleza? 

Alguns pontos importantes

Acima de tudo, é importante saber que quando trabalhamos com o protocolo SPI, temos que analisar o dispositivo utilizado para saber alguns dados importantes dele, tais como:

Os dois primeiros dados é fácil entender, mas vamos analisar o clock de dados e esses estados de subida e descida. Só para exemplificar, temos 4 modos de comunicação que o dispositivo pode trabalhar. O que eles fazem?

Esses modos controlam o chamado “Fase do clock” (CPHA) o qual vai definir se os dados são inseridos e/ou desativados na borda de subida ou descida do sinal do clock.

Além disso, temos também a “Polaridade do clock” (CPOL). Que raios é isso? Essa polaridade vai informar o estado que o clock vai assumir quando ele ficar ocioso. Ou seja, quando o clock estiver “a toa” ele pode assumir o estado alto ou baixo.

Os quatro modos trabalham segundo a imagem abaixo:

Modos de operação da Polaridade do Clock
Modo Polaridade do Relógio (CPOL) Fase do Relógio (CPHA) Borda de saída Captura de dados
SPI_MODE0 0 0 Descida Subida
SPI_MODE1 0 1 Subida Descida
SPI_MODE2 1 0 Subida Descida
SPI_MODE3 1 1 Descida Subida

Benefícios e Limitação do Protocolo SPI

Assim como o I2C, o protocolo SPI tem suas vantagens e desvantagens. Portanto, cabe ao usuário analisar qual está mais adequado para a sua aplicação e estar conhecendo o protocolo SPI. Dessa forma, as vantagens que temos no SPI são:

Assim, entrando agora nas suas desvantagens, temos alguns pontos até que interessantes:

Pinagem SPI nos Arduinos

Então, igual fizemos sobre o protocolo I2C, vamos identificar quais pinos dos diferentes modelos de Arduino são utilizados para a comunicação com o protocolo SPI. Observe a tabela abaixo retirado do site do Arduino:

Placas MOSI MISSO SCK SS (escravo) SS (mestre) Nível
Uno ou Duemilanove 11 ou ICSP-4 12 ou ICSP-1 13 ou ICSP-3 10 5V
Mega1280 ou Mega2560 51 ou ICSP-4 50 ou ICSP-1 52 ou ICSP-3 53 5V
Leonardo ICSP-4 ICSP-1 ICSP-3 5V
Zero ICSP-4 ICSP-1 ICSP-3 3,3V
101 11 ou ICSP-4 12 ou ICSP-1 13 ou ICSP-3 10 10 3,3V
MKR1000 8 10 9 3,3V

Componentes do Projeto

Montagem do Circuito

Esquema de Ligação no Arduino

Código Comentado

Código Mestre

<code>/* ==================================================================================================

Projeto: Conhecendo o protocolo SPI com Arduino - Master
Autor: Danilo Nogueira
Data: 01/09/2019
Autor: Danilo Nogueira

Referencias:
  - Debounce  - https://www.arduino.cc/en/Tutorial/Debounce
  - SPI       - https://www.arduino.cc/en/reference/SPI

// ==================================================================================================*/
// --- Declaração Bibliotecas ---

#include "SPI.h"

int pino_Button = 4;   // Pino onde conectamos o botão
int pino_Slave  = 10;  // Pino Slave

boolean estado_Button;          // estado atual do botao
boolean ultimo_estado = LOW;    // valor da ultima leitura do botao
boolean estado_led = HIGH;      // valor inicial para o LED

unsigned long lastDebounceTime = 0;  // tempo da ultima modificacao do estado do LED

unsigned long debounceDelay = 60;   // tempo de debounce

// ==================================================================================================*/
// --- Definindo o void setup() ---

void setup() {
  
  SPI.begin();  // iniciando o barramento SPI
  
  // configura o pino do botao como entrada com resistor de pullup interno
  pinMode(pino_Button, INPUT_PULLUP);      
  
  pinMode(pino_Slave, OUTPUT);        // configura o pino seletor do slave como saida
  digitalWrite(pino_Slave, HIGH);     // coloca o seletor como HIGH para bloquear a conexão
}

// ==================================================================================================*/
// --- Definindo o void loop() ---

void loop() {
  
  int leitura = digitalRead(pino_Button);    // Guardando o estado do botão na variável local

  // Quando pressionar o botão, entra na lógica do Debounce
  
  if (leitura != ultimo_estado) {
    
    // reseta o tempo do debounce
    lastDebounceTime = millis();
  }
  
  if ((millis() - lastDebounceTime) &gt; debounceDelay) {

    // se o estado do botao foi alterado:
    if (leitura != estado_Button) {
      estado_Button = leitura;

      // apenas altera o estado do LED se o novo estado do botao for HIGH
      if (estado_Button == HIGH) {
        estado_led = !estado_led;

         // coloca o pino seletor do slave em nivel baixo iniciando a transmissao
        digitalWrite(pino_Slave, LOW);
        
        SPI.transfer(estado_led); // envia um byte contendo o estado do LED
        
        // coloca o pino seletor do slave em nivel alto finalizando a conexão
        digitalWrite(pino_Slave, HIGH); 
      }
    }
  }
  
  // salva a leitura. No proximo laço este sera o ultimo 
  // estado do botao (ultimo_estado)
  ultimo_estado = leitura;
}</code>

Código Slave

<code>/* ==================================================================================================

Projeto: Conhecendo o protocolo SPI com Arduino - Slave
Autor: Danilo Nogueira
Data: 01/09/2019
Autor: Danilo Nogueira

Referencias:
  - Debounce  - https://www.arduino.cc/en/Tutorial/Debounce
  - SPI       - https://www.arduino.cc/en/reference/SPI

// ==================================================================================================*/
// --- Declaração Bibliotecas ---

#include "SPI.h"

int pino_led = 4;     // Pino onde o led está conectado
char dados;           // Variável p/ armazenar os dados recebidos

volatile boolean flag_recebimento;   // flag de recebimento de dado

// ==================================================================================================
// --- Definindo o void setup() ---

void setup() {
  
  // inicia a SPI no modo escravo
  SPCR |= bit (SPE);
  
  // configura o pino MISO como saida
  pinMode(MISO, OUTPUT);
  
  // prepara o flag de recebimento para interrupcao
  flag_recebimento = false;
  
  // liga as interrupcoes
  SPI.attachInterrupt();

  pinMode(pino_led, OUTPUT);  // configura o pino do LED como saida
}

// ==================================================================================================*/
// --- Definindo o void loop() ---

void loop() {
  
  // Caso a flag de recebimento seja verdadeira, então
  
  if (flag_recebimento){
    
    // se o byte recebido for igual a 0, apaga o LED
    if (dados == 0) {
      digitalWrite(pino_led, LOW);
    }

    // se o byte recebido for igual a 1 acende o LED
    if (dados == 1) {
      digitalWrite(pino_led, HIGH);
    }

    // limpa o flag de recebimento
    flag_recebimento = false;
  }
}

// Rotina de interrupcao do SPI
ISR (SPI_STC_vect) {
  
  // le e salva o byte do Registrador de dados da SPI
  dados = SPDR; 

  // seta o flag de recebimento para que o dado recebido 
  // seja processado no proximo loop
  flag_recebimento = true;
}</code>

Explicando o Código

Código Mestre

Iniciamos o código declarando a nossa biblioteca “SPI” e as variáveis definidas para utilizarmos. Em seguida, dentro do void setup temos a inicialização do barramento SPI e colocando o pino de Slave como “HIGH” para no começo, a comunicação esteja fechada.

<code>void setup() {
  
  SPI.begin();  // iniciando o barramento SPI
  
  // configura o pino do botao como entrada com resistor de pullup interno
  pinMode(pino_Button, INPUT_PULLUP);      
  
  pinMode(pino_Slave, OUTPUT);        // configura o pino seletor do slave como saida
  digitalWrite(pino_Slave, HIGH);     // coloca o seletor como HIGH para bloquear a conexão
}</code>

Agora, entrando no void loop(), temos a leitura do estado do sensor de som, verificando o seu estado. Ainda não conhece este sensor? Clique no botão abaixo e confere o post onde explicamos sobre ele.

Depois de alterar o estado, opino de slave é ativado (colocando ele em “LOW”), a transferência do estado do botão é realizada e por fim, o pino slave novamente é desligado.

<code>if (estado_Button == HIGH) {
        estado_led = !estado_led;

         // coloca o pino seletor do slave em nivel baixo iniciando a transmissao
        digitalWrite(pino_Slave, LOW);
        
        SPI.transfer(estado_led); // envia um byte contendo o estado do LED
        
        // coloca o pino seletor do slave em nivel alto finalizando a conexão
        digitalWrite(pino_Slave, HIGH); 
      }</code>

Código Slave

Em seguida, iniciamos o código declarando nossa biblioteca além de criar as variáveis que utilizaremos. Contudo, criamos uma variável do tipo “volatile boolean” para trabalhar com o estado recebido do mestre e tratar ele no código.

<code>#include "SPI.h"

int pino_led = 4;     // Pino onde o led está conectado
char dados;           // Variável p/ armazenar os dados recebidos

volatile boolean flag_recebimento;   // flag de recebimento de dado</code>

Então, em nosso void setup(), temos a linha de comandos “SPCR |= bit (SPE)” a qual serve para inicializar nosso escravo no barramento. Ou seja, estamos dizendo “inicia a comunicação SPI mas saiba que eu sou o escravo”.

Logo depois, colocamos o pino MISO como saída para poder responder os comandos do mestre e inicializamos as interrupções da comunicação.

<code>void setup() {
  
  // inicia a SPI no modo escravo
  SPCR |= bit (SPE);
  
  // configura o pino MISO como saida
  pinMode(MISO, OUTPUT);
  
  // prepara o flag de recebimento para interrupcao
  flag_recebimento = false;
  
  // liga as interrupcoes
  SPI.attachInterrupt();

  pinMode(pino_led, OUTPUT);  // configura o pino do LED como saida
}</code>

Por fim, no void loop(), iniciamos com uma condição a qual vai verificar o estado da nossa variável que recebe o dado enviado pelo mestre. Nisso, quando a variável é chamada, o código entra na função criada no final para armazenar os dados enviados.

Em seguida, colocamos a variável “flag_recebimento” como HIGH e entramos no if, o qual vai comparar o valor armazenado na variável “dados” e realizar a ação de apagar ou acender o led.

<code>ISR (SPI_STC_vect) {
  
  // le e salva o byte do Registrador de dados da SPI
  dados = SPDR; 

  // seta o flag de recebimento para que o dado recebido 
  // seja processado no proximo loop
  flag_recebimento = true;
}</code>

Resultado do Projeto

Por fim, confira o resultado do projeto em nosso canal do Youtube!

https://youtu.be/2XVFm7JAQzo

Mas e ai? Gostou do post? Caso tenha ficado algo incompleto para você, comente abaixo 📝

Dicas? Dúvidas? 🤔 Críticas? 😱 Só comentar abaixo! 👇

Forte abraço e compartilha se gostou! 🤘

Posts relacionados

Controle PID Digital de Luminosidade com Arduino Uno

por Fábio Timbó
3 anos ago

Acionando Cargas por Palmas

por Danilo Nogueira
4 anos ago

Aprenda a Utilizar o Termistor NTC com Arduino

por autocore
7 anos ago
Sair da versão mobile