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
/v
x.xx
/h/i2c.h
- Fonte C18:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- Cabeçalho XC8:
installation_path
/v
x.xx
/include/plib/i2c.h
- Fonte XC8:
installation_path
/v
x.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 OpenI2C
funçã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_mode
sã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
GOTO
instruçã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:
- O mestre escreve, o último byte foi o endereço
- Gravações mestras, último byte foram dados
- Leituras principais, o último byte foi o endereço
- Leituras mestras, último byte foram dados
- NACK: fim da transmissão
Você pode verificar em que estado está verificando os bits no SSPSTAT
registro. 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 SSPSTAT
registro (primeiro ANDed 0x2d
para 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.