Começando com I2C em PIC18s

8

Para um projeto, eu gostaria que três PICs (dois escravos PIC18F4620, um mestre PIC18F46K22) se comunicassem pelo barramento I2C. Posteriormente, mais escravos podem ser adicionados (como EEPROM, SRAM, ...). Estou escrevendo o código para esses PICs em C usando o compilador C18. Eu procurei muito na Internet, mas não consegui encontrar bibliotecas para lidar com o (M) SSP periférico. Eu li a folha de dados de ambos os PICs no (M) SSP periférico no modo I2C, mas não consegui descobrir como fazer interface com o barramento.

Então, eu preciso de bibliotecas mestres e escravas.

O que você recomenda? Você tem essa biblioteca em algum lugar? Ele está embutido no compilador e, se sim, onde? Existe um bom tutorial em algum lugar na rede?


fonte
2
Eu tive problemas semelhantes há alguns meses. Você pode ler sobre eles aqui . Aqui estão as bibliotecas para C18 que funcionam com I ^ 2C, mas falta algo importante: você precisa definir a velocidade do barramento manualmente, escrevendo no registro apropriado e isso não é mencionado em nenhum lugar da documentação da biblioteca.
precisa saber é o seguinte
Obrigado, isso foi útil! Porém, apenas a parte principal, não a parte escrava.
Sim, eu não precisava trabalhar com escravos naquela época, então não há exemplos de escravos. Desculpa.
precisa saber é o seguinte
2
Não, tudo bem, foi útil para a parte principal! :-)
por favor, desabilite o analógico nas portas ANSELC = 0;

Respostas:

22

Microchip escreveu notas de aplicação sobre isso:

  • AN734 na implementação de um escravo I2C
  • AN735 na implementação de um mestre I2C
  • Há também uma AN736 mais teórica sobre a configuração de um protocolo de rede para monitoramento ambiental, mas não é necessário para este projeto.

As notas de aplicação estão funcionando com o ASM, mas podem ser portadas para C facilmente.

Os compiladores C18 e XC8 gratuitos da Microchip possuem funções I2C. Você pode ler mais sobre eles na documentação das bibliotecas do compilador , seção 2.4. Aqui estão algumas informações de início rápido:

Configurando

Você já possui o compilador C18 ou XC8 da Microchip. Ambos possuem funções I2C integradas. Para usá-los, você precisa incluir i2c.h:

#include i2c.h

Se você quiser dar uma olhada no código fonte, você pode encontrá-lo aqui:

  • Cabeçalho C18: installation_path/vx.xx/h/i2c.h
  • Fonte C18: installation_path/vx.xx/src/pmc_common/i2c/
  • Cabeçalho XC8: installation_path/vx.xx/include/plib/i2c.h
  • Fonte XC8: installation_path/vx.xx/sources/pic18/plib/i2c/

Na documentação, você pode encontrar em qual arquivo da /i2c/pasta uma função está localizada.

Abrindo a conexão

Se você estiver familiarizado com os módulos MSSP da Microchip, saberá que eles primeiro precisam ser inicializados. Você pode abrir uma conexão I2C em uma porta MSSP usando a OpenI2Cfunção É assim que é definido:

void OpenI2C (unsigned char sync_mode, unsigned char slew);

Com sync_mode, você pode selecionar se o dispositivo é mestre ou escravo e, se é escravo, se deve usar um endereço de 10 ou 7 bits. Na maioria das vezes, 7 bits são usados, especialmente em pequenas aplicações. As opções para sync_modesão:

  • SLAVE_7 - Modo escravo, endereço de 7 bits
  • SLAVE_10 - Modo escravo, endereço de 10 bits
  • MASTER - modo mestre

Com slew, você pode selecionar se o dispositivo deve usar a taxa de giro. Mais sobre o que está aqui: Qual é a taxa de variação para o I2C?

Dois módulos MSSP

Há algo de especial nos dispositivos com dois módulos MSSP, como o PIC18F46K22 . Eles têm dois conjuntos de funções, um para o módulo 1 e outro para o módulo 2. Por exemplo, em vez de OpenI2C(), eles têm OpenI2C1()e openI2C2().

Ok, então você configurou tudo e abriu a conexão. Agora vamos fazer alguns exemplos:

Exemplos

Exemplo de gravação mestre

Se você estiver familiarizado com o protocolo I2C, saberá que uma sequência de gravação principal típica se parece com isso:

Master : START | ADDR+W |     | DATA |     | DATA |     | ... | DATA |     | STOP
Slave  :       |        | ACK |      | ACK |      | ACK | ... |      | ACK |

Inicialmente, enviamos uma condição START. Considere isso pegando o telefone. Em seguida, o endereço com um bit de gravação - discando o número. Nesse ponto, o escravo com o endereço enviado sabe que está sendo chamado. Ele envia uma confirmação ("Olá"). Agora, o dispositivo mestre pode enviar dados - ele começa a falar. Ele envia qualquer quantidade de bytes. Após cada byte, o escravo deve aceitar os dados recebidos ("sim, eu ouvi você"). Quando o dispositivo mestre termina de falar, ele desliga com a condição STOP.

Em C, a sequência de gravação do mestre ficaria assim para o mestre:

IdleI2C();                         // Wait until the bus is idle
StartI2C();                        // Send START condition
IdleI2C();                         // Wait for the end of the START condition
WriteI2C( slave_address & 0xfe );  // Send address with R/W cleared for write
IdleI2C();                         // Wait for ACK
WriteI2C( data[0] );               // Write first byte of data
IdleI2C();                         // Wait for ACK
// ...
WriteI2C( data[n] );               // Write nth byte of data
IdleI2C();                         // Wait for ACK
StopI2C();                         // Hang up, send STOP condition

Exemplo de leitura principal

A sequência de leitura principal é um pouco diferente da sequência de gravação:

Master : START | ADDR+R |     |      | ACK |      | ACK | ... |      | NACK | STOP
Slave  :       |        | ACK | DATA |     | DATA |     | ... | DATA |      |

Novamente, o mestre inicia a chamada e disca o número. No entanto, ele agora quer obter informações. O escravo primeiro atende a chamada e depois começa a falar (enviando dados). O mestre reconhece cada byte até que ele tenha informações suficientes. Então ele envia um Not-ACK e desliga com uma condição STOP.

Em C, isso seria assim para a parte principal:

IdleI2C();                         // Wait until the bus is idle
StartI2C();                        // Send START condition
IdleI2C();                         // Wait for the end of the START condition
WriteI2C( slave_address | 0x01 );  // Send address with R/W set for read
IdleI2C();                         // Wait for ACK
data[0] = ReadI2C();               // Read first byte of data
AckI2C();                          // Send ACK
// ...
data[n] = ReadI2C();               // Read nth byte of data
NotAckI2C();                       // Send NACK
StopI2C();                         // Hang up, send STOP condition

Código escravo

Para o escravo, é melhor usar uma rotina de serviço de interrupção ou ISR. Você pode configurar seu microcontrolador para receber uma interrupção quando seu endereço for chamado. Dessa forma, você não precisa verificar o ônibus constantemente.

Primeiro, vamos configurar o básico para as interrupções. Você precisará ativar as interrupções e adicionar um ISR. É importante que os PIC18s tenham dois níveis de interrupção: alto e baixo. Vamos definir o I2C como uma interrupção de alta prioridade, porque é muito importante responder a uma chamada I2C. O que vamos fazer é o seguinte:

  • Escreva um ISP SSP, para quando a interrupção for uma interrupção SSP (e não outra interrupção)
  • Escreva um ISR de alta prioridade geral, para quando a interrupção for de alta prioridade. Essa função precisa verificar que tipo de interrupção foi acionada e chamar o sub-ISR certo (por exemplo, o SSP ISR)
  • Adicione uma GOTOinstrução ao ISR geral no vetor de interrupção de alta prioridade. Não podemos colocar o ISR geral diretamente no vetor porque ele é muito grande em muitos casos.

Aqui está um exemplo de código:

// Function prototypes for the high priority ISRs
void highPriorityISR(void);

// Function prototype for the SSP ISR
void SSPISR(void);

// This is the code for at the high priority vector
#pragma code high_vector=0x08
void interrupt_at_high_vector(void) { _asm GOTO highPriorityISR _endasm }
#pragma code

// The actual high priority ISR
#pragma interrupt highPriorityISR
void highPriorityISR() {
    if (PIR1bits.SSPIF) {        // Check for SSP interrupt
        SSPISR();            // It is an SSP interrupt, call the SSP ISR
        PIR1bits.SSPIF = 0;  // Clear the interrupt flag
    }
    return;
}

// This is the actual SSP ISR
void SSPISR(void) {
    // We'll add code later on
}

A próxima coisa a fazer é ativar a interrupção de alta prioridade quando o chip é inicializado. Isso pode ser feito com algumas manipulações simples de registro:

RCONbits.IPEN = 1;          // Enable interrupt priorities
INTCON &= 0x3f;             // Globally enable interrupts
PIE1bits.SSPIE = 1;         // Enable SSP interrupt
IPR1bits.SSPIP = 1;         // Set SSP interrupt priority to high

Agora, interrompemos o trabalho. Se você está implementando isso, eu verificaria agora. Escreva um básico SSPISR()para começar a piscar um LED quando ocorrer uma interrupção do SSP.

Ok, então você tem suas interrupções funcionando. Agora vamos escrever um código real para a SSPISR()função. Mas primeiro alguma teoria. Distinguimos cinco tipos diferentes de interrupção I2C:

  1. O mestre escreve, o último byte foi o endereço
  2. Gravações mestras, último byte foram dados
  3. Leituras principais, o último byte foi o endereço
  4. Leituras mestras, último byte foram dados
  5. NACK: fim da transmissão

Você pode verificar em que estado está verificando os bits no SSPSTATregistro. Este registro é o seguinte no modo I2C (os bits não utilizados ou irrelevantes são omitidos):

  • Bit 5: D / NOT A: Data / Not address: defina se o último byte for dados, limpo se o último byte for um endereço
  • Bit 4: P: Bit de parada: define se uma condição STOP ocorreu pela última vez (não há operação ativa)
  • Bit 3: S: Bit de início: defina se uma condição START ocorreu pela última vez (há uma operação ativa)
  • Bit 2: R / NOT W: Read / Not write: definido se a operação for uma Leitura Mestre, limpo se a operação for uma Gravação Mestre
  • Bit 0: BF: Buffer Cheio: defina se houver dados no registro SSPBUFF, limpo se não houver

Com esses dados, é fácil ver como ver em que estado o módulo I2C está:

State | Operation | Last byte | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 0
------+-----------+-----------+-------+-------+-------+-------+-------
1     | M write   | address   |   0   |   0   |   1   |   0   |   1
2     | M write   | data      |   1   |   0   |   1   |   0   |   1
3     | M read    | address   |   0   |   0   |   1   |   1   |   0
4     | M read    | data      |   1   |   0   |   1   |   1   |   0
5     | none      | -         |   ?   |   ?   |   ?   |   ?   |   ?

No software, é melhor usar o estado 5 como padrão, assumido quando os requisitos para os outros estados não são atendidos. Dessa forma, você não responde quando não sabe o que está acontecendo, porque o escravo não responde a um NACK.

De qualquer forma, vamos dar uma olhada no código:

void SSPISR(void) {
    unsigned char temp, data;

    temp = SSPSTAT & 0x2d;
    if ((temp ^ 0x09) == 0x00) {            // 1: write operation, last byte was address
        data = ReadI2C();
        // Do something with data, or just return
    } else if ((temp ^ 0x29) == 0x00) {     // 2: write operation, last byte was data
        data = ReadI2C();
        // Do something with data, or just return
    } else if ((temp ^ 0x0c) == 0x00) {     // 3: read operation, last byte was address
        // Do something, then write something to I2C
        WriteI2C(0x00);
    } else if ((temp ^ 0x2c) == 0x00) {     // 4: read operation, last byte was data
        // Do something, then write something to I2C
        WriteI2C(0x00);
    } else {                                // 5: slave logic reset by NACK from master
        // Don't do anything, clear a buffer, reset, whatever
    }
}

Você pode ver como pode verificar o SSPSTATregistro (primeiro ANDed 0x2dpara que tenhamos apenas os bits úteis) usando máscaras de bits para ver que tipo de interrupção temos.

É seu trabalho descobrir o que você deve enviar ou fazer quando responde a uma interrupção: depende da sua inscrição.

Referências

Mais uma vez, gostaria de mencionar as notas de aplicação que a Microchip escreveu sobre o I2C:

  • AN734 na implementação de um escravo I2C
  • AN735 na implementação de um mestre I2C
  • AN736 na configuração de um protocolo de rede para monitoramento ambiental

Há documentação para as bibliotecas do compilador: Documentação das bibliotecas do compilador

Ao configurar algo você mesmo, verifique a folha de dados do seu chip na seção (M) SSP para comunicação I2C. Usei o PIC18F46K22 para a parte principal e o PIC18F4620 para a parte escrava.

Comunidade
fonte
3

Em primeiro lugar, eu recomendaria mudar para o compilador XC8 simplesmente porque é o mais recente. Existem bibliotecas periféricas disponíveis, mas nunca as usei muito. Verifique no site da Microchips para obter detalhes e a documentação.

Ok, eu tenho algumas rotinas básicas muito antigas aqui para comunicações I2C eeprom que usei há muito tempo com um PIC16F e o antigo compilador Microhip de gama média (pode ter sido o Hi-Tech), mas acho que eles podem funcionar bem com o PIC18, pois acho que o periférico é o mesmo. De qualquer forma, você descobrirá muito rapidamente se tudo é diferente.
Eles faziam parte de um arquivo maior que foi usado em um projeto de registrador de temperatura, então eu rapidamente retirei todas as outras funções não relacionadas e salvei como os arquivos abaixo, então é possível que eu tenha feito uma bagunça, mas espero que você será capaz de ter uma idéia do que é necessário (pode até funcionar, você nunca sabe ;-))

Você precisará verificar se o arquivo principal do cabeçalho está correto e verificar / alterar os pinos para garantir que sejam os pinos periféricos I2C corretos e os nomes dos registros, se forem do compilador Hi-Tech, que fez as coisas de maneira um pouco diferente referente à convenção de nomenclatura de registros.

Arquivo I2C.c:

#include "I2C.h"
#include "delay.h"
#include <pic.h>

#define _XTAL_FREQ 20000000


void write_ext_eeprom(unsigned int address, unsigned char data)
 {
    unsigned char a0 = ((address & 0x8000) >> 14);  
    unsigned char msb = (address >> 8);
    unsigned char lsb = (address & 0x00FF);


   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_write(data);
   i2c_stop();
   DelayMs(11);
}

/******************************************************************************************/

unsigned char read_ext_eeprom(unsigned int address)
{
   unsigned char a0 = ((address & 0x8000) >> 14);  
   unsigned char data;
   unsigned char msb = (address >> 8);
   unsigned char lsb = (address & 0x00FF);

   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_repStart();
   i2c_write(0xa1 | a0);
   data=i2c_read(0);
   i2c_stop();
   return(data);
}

void i2c_init()
{
 TRISC3=1;           // set SCL and SDA pins as inputs
 TRISC4=1;

 SSPCON = 0x38;      // set I2C master mode
 SSPCON2 = 0x00;

 //SSPADD = 9;          // 500kHz bus with 20MHz xtal 
 SSPADD = 49;           // 100kHz bus with 20Mhz xtal

 CKE=0;     // use I2C levels      worked also with '0'
 SMP=1;     // disable slew rate control  worked also with '0'

 PSPIF=0;      // clear SSPIF interrupt flag
 BCLIF=0;      // clear bus collision flag
}

/******************************************************************************************/

void i2c_waitForIdle()
{
 while (( SSPCON2 & 0x1F ) | RW ) {}; // wait for idle and not writing
}

/******************************************************************************************/

void i2c_start()
{
 i2c_waitForIdle();
 SEN=1;
}

/******************************************************************************************/

void i2c_repStart()
{
 i2c_waitForIdle();
 RSEN=1;
}

/******************************************************************************************/

void i2c_stop()
{
 i2c_waitForIdle();
 PEN=1;
}

/******************************************************************************************/

int i2c_read( unsigned char ack )
{
 unsigned char i2cReadData;

 i2c_waitForIdle();

 RCEN=1;

 i2c_waitForIdle();

 i2cReadData = SSPBUF;

 i2c_waitForIdle();

 if ( ack )
  {
  ACKDT=0;
  }
 else
  {
  ACKDT=1;
  }
  ACKEN=1;               // send acknowledge sequence

 return( i2cReadData );
}

/******************************************************************************************/

unsigned char i2c_write( unsigned char i2cWriteData )
{
 i2c_waitForIdle();
 SSPBUF = i2cWriteData;
//if(ACKSTAT)
{
//while(ACKSTAT);
}
 return ( ! ACKSTAT  ); // function returns '1' if transmission is acknowledged
}

Arquivo de cabeçalho I2C.h:

extern void i2c_init();
extern void i2c_waitForIdle();
extern void i2c_start();
extern void i2c_repStart();
extern void i2c_stop();
extern int i2c_read( unsigned char ack );
extern unsigned char i2c_write( unsigned char i2cWriteData );
Oli Glaser
fonte
Obrigado, essa é a parte principal e, de fato, provavelmente o mesmo que o PIC18. Também obrigado pela nota do compilador :-) Perguntar um pouco deu-me algumas notas de aplicação, então eu as adicionarei como resposta.
Você deve adicionar uma seção sobre a configuração do gerador de taxa de transmissão. O código normalmente parece SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;para 1KHz, etc.
Jesse Craig
1

Os compiladores XC8 e XC16 incluem bibliotecas para I2C.

O problema que encontrei é que a documentação não é muito boa! Se você usar os exemplos da documentação do Microchip, estará sem sorte. Mesmo o suporte ao Microchip não pode ajudá-lo. Eu mesmo estava lá.

Há algum tempo, trabalhei com o microcontrolador da série PIC24EP512GP e a biblioteca não funcionou para mim, conforme documentado pelo Microchip.

Chetan Bhargava
fonte
Ah, que pena! Então o que você fez?
Improvisou o meu, infelizmente.
Chetan Bhargava
1
Eles são úteis para os outros também? Eu gostaria de vê-los!