Microchip为此编写了应用笔记:
该应用笔记可与ASM一起使用,但可以轻松移植到C。
Microchip的免费C18和XC8编译器具有I2C功能。您可以在编译器文档第2.4节中了解有关它们的更多信息。以下是一些快速入门信息:
配置
您已经具有Microchip的C18或XC8编译器。它们都具有内置的I2C功能。要使用它们,您需要包括i2c.h
:
#include i2c.h
如果您想看一下源代码,可以在这里找到:
- C18接头:
installation_path
/v
x.xx
/h/i2c.h
- C18来源:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- XC8标头:
installation_path
/v
x.xx
/include/plib/i2c.h
- XC8来源:
installation_path
/v
x.xx
/sources/pic18/plib/i2c/
在文档中,您可以找到/i2c/
函数位于文件夹中的哪个文件中。
打开连接
如果您熟悉Microchip的MSSP模块,您将知道它们首先必须初始化。您可以使用该OpenI2C
功能在MSSP端口上打开I2C连接。这是它的定义方式:
void OpenI2C (unsigned char sync_mode, unsigned char slew);
使用sync_mode
,您可以选择设备是主机还是从机,如果是从机,则选择使用10位还是7位地址。大多数情况下,使用7位,尤其是在小型应用程序中。选项sync_mode
包括:
SLAVE_7
-从模式,7位地址
SLAVE_10
-从模式,10位地址
MASTER
-主模式
使用slew
,您可以选择设备是否应使用压摆率。有关此处内容的更多信息:I2C的压摆率是多少?
两个MSSP模块
具有两个MSSP模块的器件有一些特殊之处,例如PIC18F46K22。它们具有两组功能,一组用于模块1,另一组用于模块2。例如OpenI2C()
,它们具有OpenI2C1()
和来代替openI2C2()
。
好的,您已经全部设置好并打开了连接。现在让我们做一些例子:
例子
大师写的例子
如果您熟悉I2C协议,就会知道典型的主写序列如下所示:
Master : START | ADDR+W | | DATA | | DATA | | ... | DATA | | STOP
Slave : | | ACK | | ACK | | ACK | ... | | ACK |
首先,我们发送一个START条件。考虑一下拿起电话。然后,用写位的地址-拨打号码。此时,具有发送地址的从站知道自己正在被呼叫。他发送一个确认(“ Hello”)。现在,主设备可以发送数据了-他开始讲话。他发送任意数量的字节。在每个字节之后,从机应确认接收到的数据(“是的,我听到了”)。主设备完成通话后,他会挂断STOP条件。
在C语言中,母版的写入顺序对于母版如下所示:
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
大师阅读示例
主读序列与写序列略有不同:
Master : START | ADDR+R | | | ACK | | ACK | ... | | NACK | STOP
Slave : | | ACK | DATA | | DATA | | ... | DATA | |
主机再次发起呼叫并拨打该号码。但是,他现在想获取信息。从机首先应答呼叫,然后开始通话(发送数据)。主机确认每个字节,直到获得足够的信息为止。然后,他发送Not-ACK并以STOP条件挂断。
在C语言中,对于主部分,它看起来像这样:
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
从站代码
对于从机,最好使用中断服务程序或ISR。您可以将微控制器设置为在调用地址时接收中断。这样,您就不必经常检查公共汽车。
首先,让我们为中断设置基础。您必须启用中断并添加ISR。PIC18具有两个级别的中断很重要:高电平和低电平。我们将I2C设置为高优先级中断,因为答复I2C呼叫非常重要。我们要做的是以下几点:
- 编写一个SSP ISR,用于该中断是一个SSP中断(而不是另一个中断)时
- 当中断为高优先级时,编写一个通用的高优先级ISR。此功能必须检查触发了哪种中断,并调用正确的子ISR(例如SSP ISR)
GOTO
在高优先级中断向量上向通用ISR 添加一条指令。我们不能将通用ISR直接放在向量上,因为在许多情况下它太大了。
这是一个代码示例:
// 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
}
下一步是在芯片初始化时启用高优先级中断。这可以通过一些简单的寄存器操作来完成:
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
现在,我们正在处理中断。如果您要实现此功能,我现在将对其进行检查。编写基本信息,SSPISR()
以在发生SSP中断时开始使LED闪烁。
好的,您的中断工作了。现在让我们为该SSPISR()
函数编写一些实际代码。但是首先是一些理论。我们区分五种不同的I2C中断类型:
- 主机写入,最后一个字节为地址
- 主机写入,最后一个字节为数据
- 主机读取,最后一个字节是地址
- 主机读取,最后一个字节为数据
- NACK:传输结束
您可以通过检查SSPSTAT
寄存器中的位来检查自己处于什么状态。在I2C模式下,该寄存器如下(忽略未使用或无关的位):
- 位5:D / NOT A:数据/非地址:如果最后一个字节是数据,则设置;如果最后一个字节是地址,则清除
- 位4:P:停止位:如果最后一次发生STOP条件(没有有效操作),则置1
- 位3:S:起始位:如果最后一次发生START条件(有一个有效的操作)则置位
- 位2:R / NOT W:读/不写:如果操作是主读,则置位;如果操作是主写,则清零
- 位0:BF:缓冲区已满:如果SSPBUFF寄存器中有数据,则置1;否则,清0。
有了这些数据,很容易看到如何查看I2C模块处于什么状态:
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 | - | ? | ? | ? | ? | ?
在软件中,最好使用状态5作为默认值,这是在不满足其他状态的要求时假定的。这样,当您不知道发生了什么时您就不会回复,因为从站不会响应NACK。
无论如何,让我们看一下代码:
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
}
}
您可以看到如何使用位掩码检查SSPSTAT
寄存器(首先与进行“与”操作,0x2d
以便我们只有有用的位),以便查看我们拥有的中断类型。
找出响应中断时必须发送或执行的操作是您的工作:这取决于您的应用程序。
参考文献
同样,我想提及Microchip编写的有关I2C的应用笔记:
有有关编译器库的文档:编译器库文档
自己进行设置时,请在(M)SSP部分上检查芯片的数据表以进行I2C通信。我将PIC18F46K22用作主器件,将PIC18F4620用作从器件。