ÍNDICE DE CONTEÚDO
- Comunicação SPI – Parte 1
- Comunicação SPI – Parte 2
- Comunicação SPI – Parte 3 – Microcontrolador AT89S8253 + EEPROM 25LC256
No artigo anterior, Comunicação SPI – Parte 1, entendemos o que é uma comunicação SPI e suas principais características elétricas. Aqui vamos entender como é a implementação básica das instruções SPI. Também vamos verificar como ela se dá nos principais microcontroladores com o uso de bibliotecas. Para nossos exemplos, vamos sempre trabalhar com o Master da comunicação.
Leia também a continuação desse artigo, o Comunicação SPI – Parte 3. Nele apresentamos um exemplo de comunicação entre o microcontrolador AT89S8253 e a memória 25LC256.
Comunicação Half-Duplex e Full-Duplex
Quando falamos de comunicação, normalmente pensamos em dados sendo transmitidos e recebidos. Essa troca de dados pode ser feita de maneira simultânea ou não. Quando transmitimos e recebemos os dados simultaneamente, damos o nome de full-duplex. Quando é necessário aguardar um dado ser transmitido para outro ser recebido (ou vice-versa) damos o nome de half-duplex.
Comunicações com RS-485 utilizam o mesmo canal para fazer a transmissão e a recepção de dados. Dessa forma, ou se transmite, ou se recebe, mas nunca simultaneamente. Essa comunicação é half-duplex. A mesma coisa acontece nos protocolos de comunicação I2C e OneWire, que serão abordados em artigos futuros.
Embarcados Experience 2024: Evento Presencial
Participe do Embarcados Experience 2024 em São Paulo. Conhecimento técnico, palestras, workshops e oportunidade de networking com profissionais experientes.
Em uma comunicação serial RS-232 padrão, há um canal para transmitir (TXD) e um para receber (RXD). Dessa forma, o envio e o recebimento de informação podem ser feitas simultaneamente, e não há a necessidade de esperar o fim de um para o início do outro. Essa comunicação é full-duplex.
O SPI não apenas permite que os dados sejam transmitidos e recebidos simultaneamente, como é uma exigência de hardware. Cada bit de dado enviado do Master para o Slave, transfere também um bit de dado do Slave para o Master. Isso porque o fundamento do SPI é um circuito shift-register, como visto no artigo passado.
Dessa forma, ao final da transmissão de um byte do Master para o Slave, haverá um byte transferido do Slave para o Master. Sendo assim, não há como transmitir um dado sem outro recebido. Analogamente, não há como receber um dado sem transmitir algo em troca.
Para entender melhor como esse processo funciona, vamos fazer a simulação da troca de dados entre um dispositivo Master e um Slave. No nosso caso, o Master possui carregado no seu registrador o dado 3Ch, e o Slave possui carregado 5Ah. Master deseja enviar o valor 3Ch para o Slave, e receber do slave o 5Ah.
No final dos 8 pulsos no SCLK, o valor 3Ch foi completamente transferido para o registrador da SPI do Slave. Da mesma forma, o valor 5Ah foi transferido para o Master na mesma troca de bits. Percebe-se, então, que o Master vai receber o dado que estiver no buffer do Slave, mesmo que este não seja desejado. Também entende-se que, para receber algo do Slave, é necessário enviar algo.
Sendo assim, não faz muito sentido partirmos para uma solução do tipo envia() e outra recebe(). Quando pensamos em uma rotina para fazer o envio e recebimento de dados simultaneamente, pensamos em algo mais parecido com transfere(). Para a SPI, teremos uma única instrução de troca de dados. A prototipagem é algo como:
uint8_t SPI_transfere( uint8_t data ) ;
Para a prototipagem apresentada, o dado passado como parâmetro é enviado pela SPI (uma vez que somos o Master da comunicação). A comunicação irá receber o dado de resposta do Slave e passará de retorno dessa função.
No entanto, para toda a regra há sempre uma exceção. Existem componentes que vão utilizar a comunicação SPI como half-duplex. Para esses casos, o que se faz é enviar um byte nulo (normalmente 00h) para ter um dado recebido, ou receber algum valor inútil quando mandamos algo. A comunicação será sempre full-duplex, mesmo que o dado enviado ou recebido seja desnecessário.
Slave Select
Outro conceito que devemos entender é o uso do SS (Slave Select). Como o próprio nome diz, trata-se da “Seleção do Escravo“, ou a seleção do dispositivo com o qual se deseja trabalhar. Seu funcionamento é idêntico ao Chip Select, muito comum em memórias e outros semicondutores. Uma vez que receba um 0 (uma vez que é sempre barrado) o dispositivo fica ativo para a troca de dados sincronizado pelo clock enviado pelo Master. Caso esse pino esteja mantido com 1, o MISO (saída do dispositivo Slave) é colocado em alta impedância e não irá interferir na comunicação.
Logo, esse pino só faz sentido se o dispositivo for um Slave. Como o Master pode ter conectado mais de um Slave no mesmo barramento SPI, é interessante haver vários SS, um para controlar cada dispositivo. Dessa forma, o pino SS dos microcontroladores normalmente tem utilidade apenas quando este é o Slave da comunicação. Quando ele é o Master da comunicação, esse pino não é utilizado e pode ser usado como um GPIO padrão. Esse é o caso dos nossos exemplos neste artigo.
Um outro ponto bastante comum quanto ao SS é a situação de possuir apenas um dispositivo Slave. É muito comum acreditar que, uma vez que este componente é o único Slave e é ativo com 0, pode-se economizar um IO e ligá-lo diretamente ao terra. No entanto, muitos Slaves utilizam o sinal de SS como “sincronismo de frame”. Um início de um novo frame será sempre marcado quando o sinal passa de 1 para 0, e ligá-lo diretamente ao GND pode fazer com o Slave perca o sincronismo do frame após algumas trocas de bytes.
Utilizando a Comunicação SPI no Arduino UNO
Nosso primeiro exemplo de configuração será com o Arduino UNO. Seu microcontrolador, o ATmega328P possui um periférico de SPI integrado. Também já existe uma biblioteca pronta para trabalhar com ele, o SPI.H. Com ela, uma série de métodos ficam disponíveis para o uso.
- Begin()
Este método inicializa o objeto SPI com a configuração padrão da biblioteca do Arduino. Após essa instrução, o periférico já está ativo e pronto para ser utilizada.
- setBitOrder()
Define qual será o primeiro bit da comunicação, se será o mais significativo (MSB) ou o menos significativo (LSB). Seus parâmetros são:
LSBFIRST
MSBFIRST
- setClockDivide()
Configura o divisor do clock principal. O Arduino trabalha com 16MHz e o valor padrão para a configuração é SPI_CLOCK_DIV4, o que significa que, por padrão, o SCK irá comunicar a 4MHz. No entanto, os valores possíveis vão de 2 a 128, permitindo clocks respectivos de 8MHz a 125KHz. Os parâmetros que podem ser aplicados são:
SPI_CLOCK_DIV2
SPI_CLOCK_DIV4
SPI_CLOCK_DIV8
SPI_CLOCK_DIV16
SPI_CLOCK_DIV32
SPI_CLOCK_DIV64
SPI_CLOCK_DIV128
- setDataMode()
Este método define o modo de comunicação, conforme vimos no artigo passado. Cada modo possui uma borda diferente e uma fase e uma polaridade diferente. Pode-se trabalhar com os seguintes parâmetros:
SPI_MODE0
SPI_MODE1
SPI_MODE2
SPI_MODE3
- transfer()
Finalmente, este é o método responsável pela transferência. Irá transmitir qualquer valor passado como parâmetro e, conforme o padrão da SPI, retornará o valor recebido de volta.
Para nosso exemplo, vamos imaginar transmitir o valor 00h 3Ch 5Ah para um Slave conectado ao pino 3. O código seria apresentado dessa forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <SPI.H> #define SS_SLAVE 3 void setup( void ) { pinMode( SS_SLAVE , OUTPUT ) ; SPI.begin() ; // Inicializa a SPI do periférico. } void loop( void ) { unsigned char retorno[ 3 ] ; // Retorno para cada byte. digitalWrite( SS_SLAVE , LOW ) ; // Habilita o SS. retorno[ 0 ] = SPI.transfer( 0x00 ) ; // Envia 00h e recebe o retorno. retorno[ 1 ] = SPI.transfer( 0x3C ) ; // Envia 3Ch e recebe o retorno. retorno[ 2 ] = SPI.transfer( 0x5A ) ; // Envia 5Ah e recebe o retorno. digitalWrite( SS_SLAVE , HIGH ) ; // Desabilita o SS. delay( 1000 ) ; } |
Neste exemplo de comunicação, o sistema baixou o SS controlando o IO 3 e transferiu os três bytes esperados. Para cada byte houve um retorno, que foi carregado em uma respectiva posição da matriz retorno[].
Utilizando a Comunicação SPI no Mbed
O Mbed é um sistema que engloba hardwares de prateleira e uma plataforma online que permite desenvolver firmwares de forma intuitiva para microcontroladores ARM. É mantido, testado e administrado pela própria ARM. A intenção principal é remover todas as barreiras que existem de configuração de registradores, manipulação de periféricos e instalação de softwares.
É possível, dessa forma, atender diversos tipos de público, o que, com certeza, ajuda a divulgar os projetos de protótipos de sistemas eletrônicos de baixo consumo e permite o desenvolvimento de sistemas de controle ou monitoramento caseiros ou para testes. Sendo assim, se apresenta de forma mais democrática entre os sistemas com microcontroladores.
Disponibiliza atualmente para desenvolvimento de software as linguagens C e C++ e é conforme com CMSIS, e funciona com os microcontroladores dos fabricantes Freescale, Nordic, NXP, u-blox e ST
Tão simples quanto o Arduino, o Mbed já possui uma biblioteca pronta para a comunicação SPI. Neste caso, há a necessidade de indicar quais os pinos MISO, MOSI e SCK do dispositivo. Como trabalharemos como Master de comunicação, a seleção do Slave é feito por um pino digital padrão. No entanto, vale identificar que a documentação do Mbed sempre apresenta este como CS, e não como SS. No entanto, possui a mesma função. Os pinos de comunicação são passados no construtor da classe, e para o nosso exemplo são:
MOSI | p5 |
MISO | p6 |
SCK | p7 |
- format()
Essa instrução faz a configuração do número de bits (de 4 a 16 bits) e do modo (de 0 a 3), através de seus parâmetros.
- frequency()
Configura a frequência em Hz do SCK. O valor default é 1MHz.
- write()
Este é a instrução de transferência. Apensar do nome trazer a ideia apenas de escrita, este método envia o dado atribuído ao parâmetro e retorna o dado recebido.
Como exemplo, vamos configurar o dispositivo como modo 3 e definir o SS no pino p8. Da mesma forma, vamos transferir os valores 00h 3Ch e 5Ah.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include "mbed.h" SPI spi( p5 , p6 , p7 ) ; // MOSI, MISO e SCK. DigitalOut cs( p8 ) ; int main( void ) { unsigned char retorno[ 3 ] ; // Desabilita SS. cs = 1 ; // Configura SPI com 8 bits de dados e modo 3. spi.format( 8 , 3 ) ; // Configura clock em 1MHz. spi.frequency( 1000000 ) ; // Habilita o SS. cs = 0 ; retorno[ 0 ] = spi.write( 0x00 ) ; // Envia 0x00 e recebe o retorno. retorno[ 1 ] = spi.write( 0x3C ) ; // Envia 0x3C e recebe o retorno. retorno[ 2 ] = spi.write( 0x5A ) ; // Envia 0x5A e recebe o retorno. // Desabilita SS. cs = 1 ; } |
Utilizando a Comunicação SPI via Software
Uma outra saída muito comum é quando o microcontrolador não possui SPI por software é escrever uma rotina que gera esses sinais através da interface GPIO. Apesar de exigir processamento para a troca de dados, essa saída é bastante barata e eficaz. Para nosso exemplo, vamos imaginar você já possui acesso ao GPIO e que possui as seguintes instruções implementadas.
- escreve_mosi()
Instrução que escreve o parâmetro no pino MOSI, aceitando apenas 0 ou 1 para o sinal digital.
- escreve_sck()
Da mesma forma que a instrução anterior, escreve o parâmetro 0 ou 1 no pino SCK.
- le_miso()
Faz a leitura do bit digital do pino MISO.
A partir dessas instruções, a função de troca para o modo 0 é dada da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
unsigned char SPI_transfer( unsigned char data ) { unsigned char SPI_count ; // Contagem da transação SPI. unsigned char ret = 0x00 ; // Retorno do SPI. for( SPI_count = 8 ; SPI_count > 0 ; SPI_count-- ) { escreve_mosi( data & 0x80 == 0x80 ) ; // Escreve BIT no MOSI. SPI_byte <<= 1 ; // Faz o shift para o próximo bit. ret <<= 1 ; escreve_sck( 1 ) ; // Escreve 1 no SCK. ret |= le_miso() // Captura o bit recebido no MISO. escreve_sck( 0 ) ; // Escreve 0 no SCK. } return( ret ) ; // Retorna o byte recebido. } |
Alguns pontos são importante para esse tipo de solução. Primeiramente, não há controle da velocidade do dispositivo. Isso pode ser um problema para Slaves mais lentos ou Masters de núcleo mais rápido. O segundo ponto importante é que essa rotina pode ser interrompida por uma interrupção, o que não é necessariamente um problema para a troca de dados, mas irá deformar a forma de onda.
Seguindo nosso exemplo, o código para a escrita dos mesmos valores fazendo uso de nossa nova instrução seria conforme a seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void main( void ) { unsigned char retorno[ 3 ] ; // Bytes de retorno da SPI. configura_saida_mosi() ; // Configura IO MOSI como saída. configura_saida_sck() ; // Configura IO SCK como saída. configura_saida_SS() ; // Configura IO SS como saída. configura_entrada_miso() ; // Configura IO MISO como entrada. escreve_ss( 0 ) ; // Habilita SS. retorno[ 0 ] = SPI_transfer( 0x00 ) ; // Envia 00h e recebe o retorno. retorno[ 1 ] = SPI_transfer( 0x3C ) ; // Envia 3Ch e recebe o retorno. retorno[ 2 ] = SPI_transfer( 0x5A ) ; // Envia 5Ah e recebe o retorno. escreve_ss( 1 ) ; // Desabilita SS. } |
Conclusão – Comunicação SPI Parte 2
Conseguimos perceber como se dá o processo de comunicação SPI e quais as principais técnicas de implementação desse dispositivo. Também podemos ver como ele é aplicado em alguns microcontroladores de mercado e como fazer a troca de dados em cada um deles.
Referências
Arduino Uno – Fábio Souza | https://embarcados.com.br/arduino-uno/ |
Mbed – Thiago Lima | https://embarcados.com.br/mbed/ |
AT89S52 | https://www.atmel.com/images/doc1919.pdf |
AT89S8253 Primer | https://www.atmel.com/Images/doc3655.pdf |
AN128 | https://www.silabs.com/Support%20Documents/TechnicalDocs/an128.pdf |
Coding SPI Software | |
Arduino Uno | https://arduino.cc/en/Main/ArduinoBoardUno |
SPI Handbook MBed | https://mbed.org/handbook/SPI |
Muito boa a série de artigos, parabéns.
Olá Francesco, você tem algum material sobre como comunicar um dispositivo i2C com um pic18F? Encontrei vários materiais para o compilador XC8 e como estou usando o C18, ao reescrever as funções para este segundo compilador a comunicação não é estabelecida.
Ola Renato,
Já existe um biblioteca interna para acesso a hardware no MPLAB C18, o documento que descreve as funções, você pode baixar aqui: https://ww1.microchip.com/downloads/en/DeviceDoc/MPLAB_C18_Libraries_51297f.pdf
Olá vinifr, obrigado pelo retorno. Estou usando esta lib, porém não consigo iniciar a comunicação. Estou usando um dispositivo fisico PN532.. também tentei com uma simulação no Protheus usando o DS1307 e nada :/
Caramba, RFID é um pesadelo :D, já mexi com ele e perdi feio. Tem como você passar o código que você está testando o RTC DS1307?
Psé vinifr, na verdade eu preciso trabalhar com NFC (muito parecido com RFID) mas eu quero validar a comunicação i2c com o DS1307 para fins de validação.. segue código
#define address 0x68
OpenI2C(MASTER, SLEW_OFF);
SSPADD = 0x3F;
IdleI2C();
StartI2C(); // começar a comunicação I2C
IdleI2C();
WriteI2C(address); // envia o endereço para o Dispositivo
IdleI2C();
WriteI2C(0x00); // envia o endereço para o Dispositivo
IdleI2C();
RestartI2C();
IdleI2C();
WriteI2C(address + 1);
tmp = ReadI2C(); // Retorna o byte MSB
IdleI2C();
AckI2C();
IdleI2C();
tmp = ReadI2C();
IdleI2C();
NotAckI2C();
IdleI2C();
StopI2C();
IdleI2C();
while (1);
Delay100TCYx(200);
Se você deseja apenas validar a I2C, era mais fácil usar uma memoria EEPROM I2C. Aqui tem um exemplo: https://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1824&appnote=en023445
Olha lá no final da página, AN991(Using the C18 Compiler and the MSSP to Interface I2C? EEPROMs with PIC18 Devices)
Olá vinifr, Validei o I2C com a EEPROM no protheus e está ok, porem agora estou tentando comunicar o PIC18F4550 com o leitor RFID/NFC PN532 e a função para no getsI2C.. Saber de alguma config específica para fazer essa comunicação? ps. Controlbyte – comando de escrita address – endereço do leitor NFC (0x48) data – onde será armazenado o dado lido length – tamanho do dado a ser lido Segue o código da função abaixo: unsigned char ByteReadI2C(unsigned char ControlByte, unsigned char address, unsigned char *data, unsigned char length) { IdleI2C(); // ensure module is idle StartI2C(); // initiate START… Leia mais »
Ola Renato,
Como disse, RFID/NFC é um pouco complicado. Mas o que eu estava fazendo era bastante complicado. Designaram-me para implementar o próprio leitor em si! Putz, muito foda.
Quanto ao seu problema, o firwmare para nessa função provavelmente porque não está lendo nada do NFC. Você precisa seguir o datasheet à risca. Principalmente na parte de inicialização do módulo.
Eu achei esse links, espero que ajude:
https://linksprite.com/wiki/index.php5?title=PN532_RFID_Module
https://www.libnfc.org/api/
Parabéns pelo artigo Francesco! Só um pequeno ajuste no código do Mbed LPC1768 linha 26, seria “ss” em vez de “cs”
Olá Marcelo, obrigado pelo retorno.
Realmente, ficou diferente da documentação que o Mbed costuma colocar.
Já fiz as alterações.
Obrigado mais uma vez.
Francesco
Ficou show de bola esse artigo! Parabéns Francesco.
Série de artigos muito esclarecedora. Estou tentando fazer uma comunicação entre dois microcontroladores via protocolo SPI de maneira que o escravo rode um programa no loop infinito execute um comando ou retorne algum valor para o mestre apenas quando requisitado. Você tem ideia de como consigo rodar um programa no loop infinito como monitoramento de um ambiente e aceite a comunicação SPI quando requisitado pelo mestre.
Grato
Wender
Olá Wenderson, Você levantou um ponto interessante, a comunicação entre dois microcontroladores utilizando o canal SPI. É um bom ponto, pois levanta alguns comportamentos que discutimos no artigo, como o SPI ser full-duplex por padrão. Um ponto para se ter em mente é que você não é obrigado a retornar uma informação a cada troca de informações. Apesar a comunicação fazer a transferência dos dados em ambas as direções, você pode enviar um dado útil apenas do mestre para o escravo. O próximo dado útil poderia ser do escravo para o mestre, e por aí vai. Isso já configura em… Leia mais »
Olá Francesco Sacco, parabéns pelo seu trabalho. Gostaria de tirar uma dúvida com você, se for possivel. Então a dúvida é, eu tenho 4 ADC mcp3201, que é um adc de 12 bits com uma unica entrada e saída serial SPI. O problema é o seguinte, estou precisando fazeer um sistema de aquisição de dados com 4 entradas simultâneas com resolução de 12 bits. eu sei que ja existem ci com essa proposta, mas o problema é não encontro no mercado brasileiro. Desse modo, eu usei os 4 adcs com o atmega328 com a comunicação spi, entretanto não deu pra… Leia mais »
Muito show o artigo, parabéns. só um ajuste ao invés de setClockDivide, é setClockDivider. faltou um “r”.
tou com um problema com o arduino e não consigo resolve-lo… tenho um arduino uno, uma wifi shield e um datalogger arduino. https://www.arduino.cc/en/Main/ArduinoWiFiShield https://www.adafruit.com/product/1141 Quando ponho no arduino a wifi shield+datalogger shield, a wifi shield deixa de ser reconhecida…. ambas usam o protocolo SPI, mas nao sei implementar bem isso. Já li sobre o SPI, mas não tou a ver como posso resolver isto… deve ser fácil, mas ainda não tive sucesso. o slave select (SS) da wifi é o pino 10, e para a datalogger tambem era, mas alterei para o pino 5 para não ocorrer conflito. Eles dizem… Leia mais »