Comunicação I2C com Microcontrolador PIC PIC16F877

Tempo de leitura: 9 minutes

Os microcontroladores PIC são uma plataforma poderosa fornecida por microchip para projetos embarcados, sua natureza versátil fez com que encontrassem maneiras em muitas aplicações e a fase ainda está acontecendo. Se você tem seguido nossos tutoriais PIC, então deve ter notado que já cobrimos uma ampla variedade de tutoriais sobre microcontroladores PIC, começando do básico. Desde agora, cobrimos o básico para que possamos entrar em coisas mais interessantes, como o portal de comunicação.

No vasto sistema de aplicativos embarcados, nenhum microcontrolador pode realizar todas as atividades sozinho. Em algum momento, ele precisa se comunicar com outros dispositivos para compartilhar informações, existem muitos tipos diferentes de protocolos de comunicação para compartilhar essas informações, mas os mais usados são USART, IIC, SPI e CAN. Cada protocolo de comunicação tem suas próprias vantagens e desvantagens. Vamos nos concentrar na parte da IIC por enquanto, pois é isso que vamos aprender neste tutorial.

 

O que é o protocolo de comunicação I2C?

O termo IIC significa “Inter Integrated Circuits”. É normalmente denotado como I2C ou I ao quadrado C ou mesmo como protocolo de interface de 2 fios (TWI) em alguns lugares, mas significa o mesmo. I2C é um protocolo de comunicação síncrona, o que significa que ambos os dispositivos que estão compartilhando as informações devem compartilhar um sinal de relógio comum. Ele tem apenas dois fios para compartilhar informações, dos quais um é usado para o sinal do galo e o outro é usado para enviar e receber dados.

Como funciona a comunicação I2C?

A comunicação I2C foi introduzida pela primeira vez por Phillips. Como disse anteriormente, ele tem dois fios, esses dois fios serão conectados em dois dispositivos. Aqui, um dispositivo é chamado de mestre e o outro dispositivo é chamado de escravo. A comunicação deve e sempre ocorrerá entre um Mestre e um Escravo. A vantagem da comunicação I2C é que mais de um escravo pode ser conectado a um mestre.

A comunicação completa ocorre por meio desses dois fios, a saber, Serial Clock (SCL) e Serial Data (SDA).

Serial Clock (SCL): Compartilha o sinal de clock gerado pelo mestre com o escravo

Dados seriais (SDA): Envia os dados de e para o mestre e o escravo.

A qualquer momento, apenas o mestre será capaz de iniciar a comunicação. Como há mais de um escravo no barramento, o mestre deve referir-se a cada escravo usando um endereço diferente. Quando endereçado, apenas o salve com aquele endereço específico irá responder de volta com a informação

 

Onde usar a comunicação I2C?

A comunicação I2C é usada apenas para comunicação de curta distância. É certamente confiável até certo ponto, pois tem um pulso de clock sincronizado para torná-lo inteligente. Este protocolo é usado principalmente para se comunicar com o sensor ou outros dispositivos que precisam enviar informações a um mestre. É muito útil quando um microcontrolador precisa se comunicar com muitos outros módulos escravos usando, no mínimo, fios. Se você estiver procurando por uma comunicação de longo alcance, você deve tentar o RS232 e se estiver procurando por uma comunicação mais confiável, você deve tentar o protocolo SPI.

Como sempre, o melhor lugar para começar é no datasheet. Procure detalhes sobre I2C na ficha técnica e verifique quais registros devem ser configurados. Não vou explicar em detalhes, pois a folha de dados já fez isso para você. Mais abaixo irei explicar as diferentes funções presentes no arquivo de cabeçalho e sua responsabilidade no programa.

void I2C_Initialize()

A função de inicialização é usada para informar ao microcontrolador que vamos usar o protocolo I2C. Isso pode ser feito definindo os bits necessários no registro SSPCON e SSPCON2. O primeiro passo seria declarar os pinos IIC como pinos de entrada, aqui os pinos RC3 e RC4 devem ser usados para comunicação I2C, portanto, nós os declaramos como pinos de entrada. Em seguida, devemos definir o SSPCON e o SSPCON2, que é um registrador de controle MSSP. Estamos operando o PIC no modo mestre IIC com uma frequência de clock de FOSC/(4 * (SSPADD  + 1)). Consulte os números das páginas da folha de dados mencionada nas linhas de comentários abaixo para entender por que esse registro específico é definido dessa forma.

Em seguida, temos que definir a frequência do relógio, a frequência do relógio para diferentes aplicativos pode variar, portanto, obtemos a escolha do usuário por meio da variável feq_k e a usamos em nossas fórmulas para definir o registro SSPADD.

void I2C_Initialize(const unsigned long feq_K) //Comece IIC como mestre
{
  TRISC3 = 1;  TRISC4 = 1;  //Defina os pinos SDA e SCL como pinos de entrada

  SSPCON  = 0b00101000;    //pg84/234
  SSPCON2 = 0b00000000;    //pg85/234

  SSPADD = (_XTAL_FREQ/(4*feq_K*100))-1; //Configurando a velocidade do relógio pg99 / 234
  SSPSTAT = 0b00000000;    //pg83/234
}

 

Void I2C_Hold()

A próxima função importante é a função I2C_hold, que é usada para manter a execução do dispositivo até que a operação I2C atual seja concluída. Teríamos que verificar se as operações I2C devem ser realizadas antes de iniciar qualquer nova operação. Isso pode ser feito verificando o registro SSPSTAT e SSPCON2. O SSPSTAT contém as informações sobre o status do barramento I2C.

O programa pode parecer um pouco complicado, pois envolve um operador “e” e um “ou”. Quando você o quebra como

SSPSTAT & 0b00000100
SSPCON2 & 0b00011111

 

Isso significa que estamos nos certificando de que o 2nd bit em SSPSTAT é zero e, da mesma forma, os bits de 0 a 4 são zero em SSPCON2. Então combinamos tudo isso para verificar se o resultado é zero. Se o resultado for zero, o programa continuará, caso contrário, ele permanecerá lá até chegar a zero, pois é usado em um loop while.

void I2C_Hold()
{
    while (   (SSPCON2 & 0b00011111)  ||  (SSPSTAT & 0b00000100)   ) ; //verifique isso nos registros para ter certeza de que o IIC não está em andamento
}

Void I2C_Begin() e void I2C_End()

Cada vez que escrevermos ou lermos quaisquer dados usando o barramento I2C, devemos começar e terminar a conexão I2C. Para iniciar uma comunicação I2C, temos que definir o bit SEN e, para encerrar a comunicação, temos que definir o bit de status PEN. Antes de alternar qualquer um desses bits, também devemos verificar se o barramento I2C está ocupado usando a função I2C_Hold conforme discutido acima.

void I2C_Begin()
{
  I2C_Hold();  //Segure o programa se I2C está ocupado
  SEN = 1;     //Comece IIC pg85/234
}
void I2C_End()
{
  I2C_Hold(); //Segure o programa se I2C está ocupado
  PEN = 1;    //Fim da IIC pg85/234
}

Void I2C_Write()

A função de gravação é usada para enviar quaisquer dados do módulo mestre para o módulo salve. Esta função é normalmente usada após uma função inicial I2C e é seguida por uma função I2C End. Os dados que devem ser gravados no barramento IIC são transmitidos pelos dados variáveis. Esses dados são carregados no registrador de buffer SSPBUF para enviá-los pelo barramento I2C.

Normalmente, antes de escrever um dado, um endereço será escrito, então você terá que usar a função de escrita duas vezes, uma para definir o endereço e a outra para enviar os dados reais.

void I2C_Write(unsigned data)
{
  I2C_Hold();      //Segure o programa se I2C está ocupado
  SSPBUF = data;   //pg82/234
}

unsigned short I2C_Read()

A função final que precisamos conhecer é a função I2C_Read. Esta função é usada para ler os dados que estão atualmente no barramento I2C. É usado após solicitar a um escravo que escreva algum valor no barramento. O valor que for recebido estará no SSPBUF, podemos transferir esse valor para qualquer variável do nosso funcionamento.

Durante uma comunicação I2C, o escravo após enviar os dados solicitados pelo Mestre enviará um outro bit que é o bit de reconhecimento, este bit também deve ser verificado pelo mestre para garantir que a comunicação foi bem-sucedida. Depois de verificar o bit ACKDT para confirmação, ele deve ser habilitado configurando o bit ACKEN.

unsigned short I2C_Read(unsigned short ack)
{
  unsigned short incoming;
  I2C_Hold();
  RCEN = 1;

  I2C_Hold();
  incoming = SSPBUF;      //obtém os dados salvos em SSPBUF

  I2C_Hold();
  ACKDT = (ack)?0:1;    //verifique se o bit de confirmação foi recebido
  ACKEN = 1;            //pg 85/234

  return incoming;
}

Ou seja, essas funções devem ser suficientes para configurar uma comunicação I2C e escrever ou ler dados de um dispositivo. Observe também que existem muitas outras funcionalidades que a comunicação I2C pode executar, mas por uma questão de simplicidade, não as discutiremos aqui. Você sempre pode consultar a ficha técnica para saber o funcionamento completo do

O código completo com arquivo de cabeçalho para comunicação PIC16F877A I2C pode ser baixado do link.

 

Programação usando os arquivos de cabeçalho I2C:

Agora que aprendemos como funciona uma comunicação I2C e como podemos usar o arquivo de cabeçalho criado para ela, vamos fazer um programa simples no qual usaremos o arquivo de cabeçalho e escreveremos alguns valores nas linhas I2C. Em seguida, simularemos este programa e verificaremos se esses valores estão sendo escritos no barramento.

Como sempre, o programa começa com a configuração dos bits de configuração e a definição da frequência do relógio para 20 MHz, conforme mostrado abaixo

#pragma config FOSC = HS    //Bits de seleção do oscilador (oscilador HS)
#pragma config WDTE = OFF   //Bit de ativação do temporizador de watchdog (WDT desativado)
#pragma config PWRTE = ON   //Bit de ativação do temporizador de inicialização (PWRT ativado)
#pragma config BOREN = ON   //Bit de habilitação de redefinição de Brown-out (BOR habilitado)
#pragma config LVP = OFF    //Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 é digital I / O, HV em MCLR deve ser usado para programação)
#pragma config CPD = OFF    //Bit de proteção de código de memória EEPROM de dados (proteção de código EEPROM de dados desativada)
#pragma config WRT = OFF    //Bits de habilitação de gravação da memória do programa Flash (proteção contra gravação desligada; toda a memória do programa pode ser gravada pelo controle EECON)
#pragma config CP = OFF     //Bit de proteção de código de memória de programa Flash (proteção de código desativada)

#define _XTAL_FREQ 20000000

A próxima etapa seria adicionar o arquivo de cabeçalho que acabamos de discutir. O arquivo de cabeçalho é denominado PIC16F877a_I2C.h e pode ser baixado do link que discutimos acima. Certifique-se de que o arquivo de cabeçalho seja adicionado ao arquivo de cabeçalho de sua lista de projetos, sua estrutura de arquivos de projeto deve ser semelhante a esta

Depois de se certificar de que o arquivo de cabeçalho foi adicionado ao seu arquivo de projeto, inclua o arquivo de cabeçalho no arquivo C principal

#include <xc.h>
#include "PIC16F877a_I2C.h"

Dentro do loop while, começaremos a comunicação I2C, escreveremos alguns valores aleatórios no barramento I2C e então terminaremos a comunicação I2C. Os valores aleatórios que escolhi são D0, 88 e FF. Você pode inserir os valores que desejar. Mas lembre-se desses valores, pois iremos verificá-los em nossa simulação.

while(1)
  {
   I2C_Begin();      
   I2C_Write(0xD0);
   I2C_Write(0x88);
   I2C_Write(0xFF);
   I2C_End();
   __delay_ms(1000);
  }

O programa completo pode ser encontrado no final da página, você pode usar ou baixar o arquivo zip completo do programa aqui. Depois de obter o programa, compile-o e prepare-se para a simulação.

 

Simulação Proteus:

Proteus tem um bom instrumento chamado depurador I2C que pode ser usado para ler os dados em um barramento I2C, então vamos construir um circuito usando-o e verificar se os dados estão sendo escritos com sucesso. O diagrama completo do circuito é mostrado abaixo

Carregue o arquivo hex que foi gerado pelo nosso programa clicando duas vezes no Microcontrolador. Em seguida, simule o programa. Você notará uma janela pop-up que exibirá todas as informações sobre o barramento I2C. A janela do nosso programa é mostrada abaixo.

Se você observar de perto os dados que estão sendo gravados, poderá notar que eles são os mesmos que escrevemos em nosso programa. Os valores são D0, 88 e FF. Os valores são gravados a cada 1 segundo, portanto, o tempo também é atualizado conforme mostrado abaixo. A seta azul indica que está escrito de mestre para escravo, caso contrário, estaria apontando na direção oposta. Uma visão mais detalhada dos dados enviados é mostrada abaixo.

Este é apenas um vislumbre do que o I2C pode fazer, ele também pode ler e gravar dados em vários dispositivos. Cobriremos mais sobre I2C em nossos próximos tutoriais através da interface de vários módulos que funcionam com o protocolo I2C.

Espero que você tenha entendido o projeto e aprendido algo útil com ele. Se você tiver alguma dúvida, poste-os na seção de comentários abaixo ou use os fóruns para obter ajuda técnica.

O código completo foi fornecido abaixo; você pode baixar arquivos de cabeçalho com todo o código daqui.

Código

/*
 * File:   IIC with PIC16F
 */
#pragma config FOSC = HS // Bits de seleção do oscilador (oscilador HS)
#pragma config WDTE = OFF // Bit de ativação do temporizador de watchdog (WDT desativado)
#pragma config PWRTE = ON // Bit de ativação do temporizador de inicialização (PWRT ativado)
#pragma config BOREN = ON // Bit de habilitação de redefinição de Brown-out (BOR habilitado)
#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 é digital I / O, HV em MCLR deve ser usado para programação)
#pragma config CPD = OFF // Bit de proteção de código de memória EEPROM de dados (proteção de código EEPROM de dados desativada)
#pragma config WRT = OFF // Bits de habilitação de gravação da memória do programa Flash (proteção contra gravação desligada; toda a memória do programa pode ser gravada pelo controle EECON)
#pragma config CP = OFF // Bit de proteção de código de memória de programa Flash (proteção de código desativada)

#define _XTAL_FREQ 20000000

#include <xc.h>
#include "PIC16F877a_I2C.h"

int main()
{
    I2C_Initialize(100); //Inicializa I2C Master com clock de 100KHz

    while(1)
    {
     I2C_Begin();       
     I2C_Write(0xD0); 
     I2C_Write(0x88); 
     I2C_Write(0xFF); 
     I2C_End();
   
     __delay_ms(1000);

   }
}