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:

  • MISO (Master In Slave Out) – Uso do barramento Slave para enviar dados do escravo para o mestre.
  • MOSI (Master Out Slave In) – Uso do barramento Master para enviar dados do Mestre para os periféricos (escravos).
  • SCLK (Serial Clock) – O relógio de pulsos que sincroniza a transmissão de dados gerada pelo mestre.

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:

Protocolo-SPI
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:

  • Qual é a velocidade máxima de comunicação que ele pode usar? Isso é controlado pelo primeiro parâmetro no SPISettings . Se você estiver usando um chip classificado em 15 MHz, use 15000000. 
  • Como é o deslocamentos de dados? Bit Mais Significativo (MSB) ou Bit Mais Significativo (LSB)? Isso é controlado pelo segundo parâmetro do SPISettings, MSBFIRST ou LSBFIRST. 
  • O clock de dados está inativo quando alto ou baixo? Os dados estão na borda de subida ou descida dos pulsos do relógio? Esses modos são controlados pelo terceiro parâmetro no SPISettings.

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:

Protocolo-SPI
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:

  • Comunicação Full duplex; 
  • Flexibilidade no protocolo completo para os bits transferidos; 
  • Normalmente requer menor energia do que o I2C devido a menos circuitos;
  • Não necessita de resistores de pull-up;
  • Os escravos não precisa de um endereço único;

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

  • Requer mais pinos do CI;
  • Não há reconhecimento do escravo (o mestre poder enviar a lugar nenhum e ele nem saberia disso);
  • Suporta apenas um dispositivo mestre;
  • Nenhum protocolo de verificação de erros é definido;

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

Protocolo-SPI
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! 🤘

5.00 avg. rating (94% score) - 1 vote