Rotinas para interface I²C


Continuaremos hoje com o tópico anterior, tendo o todo conceito em vista, devemos avançar para a implementação, serão ao todo seis rotinas simples (i2c_Ini(), i2c_Start(), i2c_RepeatedStart(), i2c_Stop(), i2c_Write(BYTE), i2c_Read(ACKTYPE)) que farão parte de nossa biblioteca i2c, com toda a inicialização, leitura e escrita no dispositivo,  segue abaixo a implementação destas rotinas com as respectivas descrições.

Função: i2c_Ini()
Propósito: Inicializa a configuração dos registradores do modulo I²C
Parâmetros: Nenhum
Descrição: O modulo é habilitado setando o bit I2CEN(I2CCON<15>), então o modulo ira liberar os pinos SDA e SCL, colocando o barramento no modo ocioso. A lógica de funcionamento para mestre e escravo é ativada simultaneamente e irão responder ao software e eventos no  barramento. As funções mestre irão permanecer em estado ocioso até que o software setar um bit de controle que gere um evento mestre. Habilitamos Entrada/Saída do I²C, configuramos a interrupção e ajustamos o baud rate para operar como mestre no barramento.

void i2c_Ini(void)
{
    // Configura os pinos SCL1/SDA1 como open-drain
    //ODCGbits.ODCG2 = 1;
    //ODCGbits.ODCG3 = 1;
   
    // IFS1 - Interrupt Flag Status register 1
    //               _______________________________________________________________
    // Upper Byte   |U2TXIF |U2RXIF |INT2IF | T5IF  | T4IF  | OC4IF | OC3IF |DMA21IF|
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   | IC8IF | IC7IF | AD2IF |INT1IF | CNIF  | ----- |MI2C1IF|SI2C1IF|
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    // MI2C1IF - I2C1 Master events                   Interrupt Flag status bit
    //
    // limpa o flag de interrupcao do I²C1
    IFS1bits.MI2C1IF = 0;
   

    // IPC4 - Interrupt Priority Control register 4
    //               _______________________________________________________________
    // Upper Byte   | ----- |       CNIP<2:0>       | ----- | ----- | ----- | ----- |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   | ----- |      MI2C1<2:0>       | ----- |      SI2C1<2:0>       |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    // MI2C1<2:0> - I²C Master Events Interrupt Priority bits
    //          011 = prioridade de interrupcao eh 3
    //
//    IPC4bits.MI2C1IP = 3;

    // IEC1 - Interrupt Enable Control register 1
    //               _______________________________________________________________
    // Upper Byte   |U2TXIE |U2RXIE |INT2IE | T5IE  | T4IE  | OC4IE | OC3IE |DMA2IE |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   | IC8IE | IC7IE | AD2IE |INT1IE | CNIE  | ----- |MI2C1IE|SI2C1IE|
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    // MI2C1IE - I2C1 Master events                   Interrupt Enable bit
    //
    // habilita a interrupcao mestre do I²C1
//    IEC1bits.MI2C1IE = 1;

    // Setando o baud rate
    //
    // a seguinte equacao eh usada para calcular o valor de recarga
    // do gerador da taxa de comunicacao:
    //
    //            /  Fcy         Fcy    \.
    //  I2CBRG = |  ------ - ----------- | - 1
    //            \  Fscl     1.111.111 /
    //
    // desejamo comunicar a 400kHz, entaum os valores seraoh:
    //
    //  Fcy = 40 MHz
    //  Fscl = 400 kHz
    //
    //            /  40.000.000     40.000.000 \.
    //  I2CBRG = |  ------------ - ------------ | - 1
    //            \   400.000        1.111.111 /
    //
    //  I2CBRG = (100 - 36 ) - 1
    //
    //  I2CBRG = 63
    //
    I2C1BRG = 63;

    //
    // I2C1CON - I²C 1 CONtrol register
    //               _______________________________________________________________
    // Upper Byte   | I2CEN | ----- |I2CSIDL| SCLREL| IPMIEN|  A10M | DISSLW|  SMEN |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   |  GCEN | STREN | ACKDT | ACKEN | RCEN  |  PEN  | RSEN  |  SEN  |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    // assegura que estah com o valores zerados
    I2C1CON = 0;

    //  A10M: 10-bit Slave Address bit
    //      0 = I2CxADD eh o endereco escravo de  7-bit
    //
    I2C1CONbits.A10M  = 0;

    //  SCLREL: SCLx Release Control bit
    //      1 = Libera o pulso  SCLx
    //      0 = Mantem pulso baixo do SCLx (esticar pulso)
    //          Se STREN = 0:
    //              Bit eh R/S (ou seja, soh pode escrever '1' para liberar o pulso.
    //              Hardware limpa no inicio da transmissao do escravo.
    //
    //  STREN: SCLx Clock Stretch Enable bit
    //      Usado em conjuncao com o bit SCLREL.
    //      1 = Habilita o software ou esticamento do pulso de recepcao
    //      0 = Desabilita o software ou esticamento do pulso de recepcao
    //
    // libera a linha de pulso
    I2C1CONbits.SCLREL = 1;

    // desabilita o o I²C para funcionar como escravo
    I2C1ADD = 0;
    I2C1MSK = 0;


    //  I2CEN: I2Cx Enable bit
    //      1 = Habilita o modulo I²Cx e configura os pinos SDAx e SCLx como porta seriais
    //
    I2C1CONbits.I2CEN = 1;
    IFS1bits.MI2C1IF = 0;
}           



Função: i2c_Start()
Propósito:  Condição de inicio para usar o protocolo do barramento I²C
Parâmetros: Nenhum
Descrição: Após o barramento estar em estado ocioso, uma transição de alto para baixo da linha SDA, enquanto o relógio SCL estiver alto, determina uma condição de inicio. Todos os dados transferidos devem começar com a condição de inicio.

void i2c_Start(void)
{
    //
    // I2C1CON - I²C 1 CONtrol register
    //               _______________________________________________________________
    // Upper Byte   | I2CEN | ----- |I2CSIDL| SCLREL| IPMIEN|  A10M | DISSLW|  SMEN |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   |  GCEN | STREN | ACKDT | ACKEN | RCEN  |  PEN  | RSEN  |  SEN  |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    //  SEN: Start Condition Enable bit (quando o I²C opera como mestre)
    //      1 = Inicia condicao de inicio nos pinos SDAx e SCLx.
    //      Hardware limpa no final da sequencia de inicio do mestre
    //      0 = Condicao de inicio nao estah em progresso
    //
    // Start Condition
    I2C1CONbits.SEN = 1;

    // IFS1 - Interrupt Flag Status register 1
    //               _______________________________________________________________
    // Upper Byte   |U2TXIF |U2RXIF |INT2IF | T5IF  | T4IF  | OC4IF | OC3IF |DMA21IF|
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   | IC8IF | IC7IF | AD2IF |INT1IF | CNIF  | ----- |MI2C1IF|SI2C1IF|
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    // MI2C1IF - I2C1 Master events                   Interrupt Flag status bit
    //
    // espera pelo fim do START
    while (!IFS1bits.MI2C1IF);
   
    // limpa o flag
    IFS1bits.MI2C1IF = 0;
}


Função: i2c_RepeatedStart()
Propósito:  Condição de inicio repetido para usar o protocolo do barramento I²C
Parâmetros: Nenhum
Descrição: O Modulo joga o pino SCL para baixo. Quando o modulo amostra o pino SCL baixo, irá liberar o pino SDA pela contagem de um baud rate(Tbrg). Quando o gerador de baud rate esgota o tempo, se a amostra do pino SDA for alta, o modulo libera o pino SCL. Quando o modulo amostrar o pino SCL alto, o gerador de baud rate recarrega e inicia a contagem. Os pinos SDA e SCL devem ser amostrados alto por um Tbgr. Esta ação é seguida pela declaração do pino SDA para baixo por um Tbgr enquanto o pino SCL estiver alto.

void i2c_RepeatedStart(void)
{
    //
    // I2C1CON - I²C 1 CONtrol register
    //               _______________________________________________________________
    // Upper Byte   | I2CEN | ----- |I2CSIDL| SCLREL| IPMIEN|  A10M | DISSLW|  SMEN |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   |  GCEN | STREN | ACKDT | ACKEN | RCEN  |  PEN  | RSEN  |  SEN  |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    //  RSEN: Repeated Start Condition Enable bit (quando o I²C opera como mestre)
    //      1 = Inicia condicao de inicio repetido nos pinos SDAx e SCLx.
    //       Hardware limpa no final da sequencia de inicio repetido do mestre
    //      0 = Condicao de inicio repetido nao estah em progresso
    //
    // Start Condition
    I2C1CONbits.RSEN = 1;

    // IFS1 - Interrupt Flag Status register 1
    //               _______________________________________________________________
    // Upper Byte   |U2TXIF |U2RXIF |INT2IF | T5IF  | T4IF  | OC4IF | OC3IF |DMA21IF|
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   | IC8IF | IC7IF | AD2IF |INT1IF | CNIF  | ----- |MI2C1IF|SI2C1IF|
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    // MI2C1IF - I2C1 Master events                   Interrupt Flag status bit
    //
    // espera pelo fim do REPEATED START
    while (!IFS1bits.MI2C1IF);
   
    // limpa o flag
    IFS1bits.MI2C1IF = 0;
}


Função: i2c_Stop()
Propósito: Condição de parada ao termino dos dados enviados no barramento
Parâmetros: Nenhum
Descrição: Uma transição de baixo para alto da linha SDA enquanto a linha de relógio SCL estiver alta determina uma condição de parada. Toda a transferência de dados deve terminar com a condição de parada.

void i2c_Stop(void)
{
    //
    // I2C1CON - I²C 1 CONtrol register
    //               _______________________________________________________________
    // Upper Byte   | I2CEN | ----- |I2CSIDL| SCLREL| IPMIEN|  A10M | DISSLW|  SMEN |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   |  GCEN | STREN | ACKDT | ACKEN | RCEN  |  PEN  | RSEN  |  SEN  |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    //
    //  PEN: Stop Condition Enable bit (quando o I²C opera como mestre)
    //      1 = Inicia a condicao de parada nos pinos SDAx e SCLx.
    //          Hardware limpa no final da sequencia de parada do mestre
    //      0 = Condicao de parada nao estah em progresso
    //
    //
   
    // Stop Condition
    I2C1CONbits.PEN = 1;
   
    // espera pelo fim do STOP
    while (!IFS1bits.MI2C1IF);
   
    // limpa o flag
    IFS1bits.MI2C1IF = 0;
}


Função: i2c_Write(BYTE)
Propósito:  Enviar um byte pelo barramento I²C
Parâmetros: btData - Informação a ser transmitida pela I²C
Retorno: 1 - Recebeu confirmação do dispositivo (ACK), 0 - O dispositivo escravo não respondeu.
Descrição: Para cada byte de dado transmitido pelo mestre, o escravo deve confirmar cada um com o ACK.

BOOL i2c_Write(BYTE btData)
{
    // IWCOL: Write Collision Detect bit
    //1 = Tentativa de escrever no registrador I2CxTRN falhou pq o modulo I²C estava ocupado.
    //0 = Sem colisao
    //Hardware setado por uma escrita em I2CxTRN enquanto ocupado(limpo por software).
    //
    //while (I2C1STATbits.IWCOL);
   
    // envia o dado
    I2C1TRN = btData;

    // espera pelo fim do Operacao de escrita
    while (!IFS1bits.MI2C1IF);
   
    // limpa o flag
    IFS1bits.MI2C1IF = 0;

    // I2C1STAT - I²C1 STATus register
    //               _______________________________________________________________
    // Upper Byte   |ACKSTAT| TRSTAT| ----- | ----- | ----- |  BCL  | GCSTAT| ADD10 |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   | IWCOL | I2COV |  D_A  |   P   |   S   |  R_W  |  RBF  |  TBF  |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
    //  ACKSTAT: Acknowledge Status bit
    //      (quando o I²C opera como mestre, aplicavel durante a transmissao do mestre)
    //      1 = recebido do escravo um NACK
    //      0 = recebido do escravo um ACK
    //       Hardware setado ou limpo ao fim da confirmacao do escravo.
    //
    // se o ACK nao retornou, indica erro
    if (I2C1STATbits.ACKSTAT)
        return FALSE;
    else
        return TRUE;    
}


Função: i2c_Read(BYTE)
Propósito: Ler o dado no barramento I²C passado pelo dispositivo escravo.
Parâmetros: ackType - Tipo de confirmação a ser enviado(ACK ou NACK).
Retorno: O byte lido do dispositivo escravo
Descrição: Para cada byte que o escravo transmitir para o mestre, deve ser confirmado com ACK, o ultimo byte transmitido pelo escravo, o mestre deve enviar um NACK, para encerrar a transmissão.

BYTE i2c_Read(ACKTYPE ackType)
{
    //
    // I2C1CON - I²C 1 CONtrol register
    //               _______________________________________________________________
    // Upper Byte   | I2CEN | ----- |I2CSIDL| SCLREL| IPMIEN|  A10M | DISSLW|  SMEN |
    //              |__15___|__14___|__13___|__12___|__11___|__10___|___9___|___8___|
    //               _______________________________________________________________
    // Lower Byte   |  GCEN | STREN | ACKDT | ACKEN | RCEN  |  PEN  | RSEN  |  SEN  |
    //              |__ 7___|___6___|___5___|___4___|___3___|___2___|___1___|___0___|
    //
   
    BYTE btInfo;
   
    //RCEN: Receive Enable bit (quando o I²C opera como mestre)
    //1 = Habilita a recepcao do I²C. Hardware limpa ao final do oitavo bit do byte recebido
    I2C1CONbits.RCEN = 1;

    // espera pela indicacao de dado recebido
    while (!IFS1bits.MI2C1IF);
   
    // limpa o flag
    IFS1bits.MI2C1IF = 0;

    // recebe o dado
    btInfo = I2C1RCV;

    //  ACKDT: Acknowledge Data bit (quando o I²C opera como mestre, aplicavel durante a recepcao)
    //      Valor que sera transmitido quando o software inciar uma sequencia de confirmacao.
    //      1 = Envia NACK durante a confirmacao
    //      0 = Envia  ACK durante a confirmacao
    //
    // seleciona a confirmacao passada para a funcao (ACK ou NACK)
    I2C1CONbits.ACKDT = ackType;     

    //ACKEN: Acknowledge Sequence Enable bit
    //1 = Incia a sequencia de confirmacao nos pinos SDAx e SCLx e transmite o bit de dados ACKDT.
    //   Hardware limpa ao final da sequencia de confirmacao do mestre.
    //
    // envia a confirmacao escolhida
    I2C1CONbits.ACKEN = 1;

    // espera pela transmissao da confirmacao
    while (!IFS1bits.MI2C1IF);
   
    // limpa o flag
    IFS1bits.MI2C1IF = 0;


    return btInfo;
}

               Com isso finalizamos a rotinas necessárias para comunicar com um dispositivo I²C, lembre-se de incluir no arquivo de cabeçalho do projeto a seguinte declaração.

typedef enum tagACKTYPE
        {
            ACK,
            NACK
        } ACKTYPE;


void i2c_Ini();
void i2c_Start();
void i2c_RepeatedStart();
void i2c_Stop();
BOOL i2c_Write(BYTE);
BYTE i2c_Read(ACKTYPE);

               E na próxima parte iremos comunicar o microcontrolador com outro dispositivo pela I²C.