26、I2C协议的使用与实践

I2C协议的使用与实践

1. I2C通信协议概述

I2C(Inter-Integrated Circuit)是由飞利浦在1982年设计的一种同步串行通信协议,它仅使用两条线:SDA(数据线)和SCL(时钟线),因此有时也被称为TWI(Two-Wire Interface)。I2C总线可以有多个主设备和多个从设备,其简单架构如下图所示:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    Master(主设备):::process -->|SDA| Slave1(从设备1):::process
    Master -->|SCL| Slave1
    Master -->|SDA| Slave2(从设备2):::process
    Master -->|SCL| Slave2
    Master -->|SDA| Slave3(从设备3):::process
    Master -->|SCL| Slave3

与SPI通信系统相比,I2C仅需两条线是其优势,但该协议也有更严格的要求。

2. EEPROM介绍

EEPROM(Electrically Erasable Programmable Read-Only Memory)用于永久存储数据。它最初源于ROM(只读存储器),用于存储系统的BIOS程序,采用非易失性存储器,断电后数据不会丢失。后来发展到PROM(可编程ROM),用户和制造商都可以对其进行编程并永久存储数据。接着是EPROM(可擦除可编程ROM),可以擦除程序或数据以便重新编程,但需要使用紫外线进行擦除。最终发展到EEPROM,可通过发送适当的电信号来擦除数据,既可以永久存储重要数据,又能方便地更改内容,非常实用。

3. 24LC256 EEPROM

3.1 基本信息

24LC256是Microchip生产的一款32k×8位串行EEPROM,符合I2C协议,有八引脚双列直插封装(PDIP)等多种封装形式。

3.2 引脚使用

其引脚使用情况如下表所示:
|引脚编号|用途|
| ---- | ---- |
|1|A0:三个地址线中的第一个|
|2|A1:三个地址线中的第二个|
|3|A2:三个地址线中的第三个|
|4|VSS:接地端|
|5|SDA:数据端,可作为数据输入和输出|
|6|SCL:时钟输入端|
|7|WP:写保护端|
|8|VCC:正电源端|

3.3 通信连接

24LC256与主设备通信仅需两个连接:SCL提供主设备到从设备的时钟信号;SDA在主设备写入时作为数据输入,从设备发送数据时作为数据输出。

3.4 地址设置

它使用A0、A1和A2三条地址线,最多可使八个EEPROM从同一主设备进行单独寻址。WP引脚可防止意外写入,该引脚高电平有效,即逻辑‘1’时,EEPROM不可写入。

3.5 写入操作

向24LC256写入数据的步骤如下:
1. 主设备发送起始位,即在时钟信号为高电平时,SDA线从高到低的转变。
2. 发送控制字节,前四位为控制码1010,接下来三位是EEPROM的地址位A0、A1和A2,必须与实际EEPROM上的三个地址引脚连接的逻辑匹配,最后一位用于确定主设备是写入还是读取操作,写入时该位为逻辑‘0’。
3. 从设备生成确认位,在主设备生成的第九个时钟脉冲为高电平时,将SDA线拉低,每个字节传输后都会产生确认位。
4. 主设备发送要写入或读取的16位地址的高字节,由于是32KB设备,实际上只需要15条地址线,高字节的最高有效位不重要。从设备再次确认该字节。
5. 主设备发送地址的低字节,从设备确认。
6. 主设备发送要写入指定地址的数据字节,从设备确认。
7. 主设备发送停止位,即在SCL线为高电平时,SDA线从低到高的转变。
写入操作流程如下:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(发送起始位):::process --> B(发送控制字节):::process
    B --> C(等待从设备确认):::process
    C --> D(发送地址高字节):::process
    D --> E(等待从设备确认):::process
    E --> F(发送地址低字节):::process
    F --> G(等待从设备确认):::process
    G --> H(发送数据字节):::process
    H --> I(等待从设备确认):::process
    I --> J(发送停止位):::process

如果要连续写入多个地址,可在第一个数据确认后、主设备发送停止位前发送更多数据,EEPROM会自动打开下一个存储位置。

3.6 读取操作

从24LC256读取数据的步骤如下:
1. 主设备发送起始位,接着发送包含从设备地址的控制字节,最后一位设置为逻辑‘0’,这仍是写入操作,因为主设备需要先写入要开始读取的地址。
2. 从设备确认控制字节后,主设备发送要开始读取的地址的高字节和低字节,从设备发送确认位。
3. 主设备发送新的起始位,接着发送相同的控制和从设备地址信息,但新控制字节的最后一位设置为逻辑‘1’,表示主设备要从刚发送的地址读取数据。
4. 从设备确认新控制字节后,将请求的数据放在SDA总线上,随后是一个非确认位,主设备发送停止位。
读取操作流程如下:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(发送起始位):::process --> B(发送控制字节):::process
    B --> C(等待从设备确认):::process
    C --> D(发送地址高字节):::process
    D --> E(等待从设备确认):::process
    E --> F(发送地址低字节):::process
    F --> G(等待从设备确认):::process
    G --> H(发送新起始位):::process
    H --> I(发送控制字节,最后一位为1):::process
    I --> J(等待从设备确认):::process
    J --> K(从设备发送数据):::process
    K --> L(主设备发送非确认位):::process
    L --> M(主设备发送停止位):::process

同样,如果要连续读取多个地址,可在发送非确认位和停止位前请求更多数据,EEPROM会自动打开下一个存储位置。

4. TC74温度传感器

4.1 基本信息

TC74是Microchip生产的温度传感器,有五引脚TO22封装和五引脚SOT - 23A封装两种形式。

4.2 引脚使用

其引脚使用情况如下表所示:
|引脚编号|用途|
| ---- | ---- |
|1|NC:无连接|
|2|SDA:数据输入和输出|
|3|GND:接地(TO - 220封装的外标签也接地)|
|4|SCL:时钟输入|
|5|VCC:正电源|

4.3 通信方式

TC74的通信与EEPROM类似,通常为读取操作,但有时也需要写入。

4.4 读取温度操作

读取TC74当前获取的温度的步骤如下:
1. 主设备向从设备发送起始位。
2. 主设备发送TC74的七位地址,默认地址为1001 101,还有其他七个地址需由Microchip设置。
3. 主设备将地址字节的第八位设置为逻辑‘0’,表示正在向TC74写入。
4. 主设备等待从设备的确认位。
5. 主设备发送命令字节0X00。
6. 主设备等待从设备的确认位。
7. 主设备发送另一个起始位。
8. 接着发送地址字节1001 101,但这次第八位设置为逻辑‘1’,表示主设备要从TC74读取数据。
9. 主设备等待确认位。
10. TC74发送表示温度读数的八位数据。
11. 主设备用非确认位和停止位响应。
读取温度操作流程如下:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(发送起始位):::process --> B(发送七位地址):::process
    B --> C(设置第八位为0):::process
    C --> D(等待确认位):::process
    D --> E(发送命令字节0X00):::process
    E --> F(等待确认位):::process
    F --> G(发送新起始位):::process
    G --> H(发送地址字节,第八位为1):::process
    H --> I(等待确认位):::process
    I --> J(TC74发送温度数据):::process
    J --> K(主设备发送非确认位):::process
    K --> L(主设备发送停止位):::process

5. I2C协议代码实现

以下是使用I2C协议与24LC256 EEPROM和TC74温度传感器通信的代码:

/*
* File:   12cEEPROMPIC18F4525Prog.c
Author: Mr H. H. Ward
*
Created on 25 July 2018, 13:19
*/
#include <xc.h>
#include <stdio.h>
#include <conFigInternalOscNoWDTNoLVP.h>
#include <4BitLCDDemoBoard.h>
// declare any variables
unsigned char a,b, i, disData, temp, tempr, thigh, tlow, adhigh, adlow;
float sysTemperature;
unsigned char rxData[12];
unsigned char dataout [8] =
{
0x41,
0x6E,
0x6E,
0x2C,
0x57,
0x61,
0x72,
0x64,
};
//declare any subroutines
void ms13delay (unsigned char (t))
{
for (n = 0; n <t; n++)
{
TMR0 = 0;
while (TMR0 < 255);
}
}
void MSSP2CInit()
{
SSPCON1 = 0x28;
SSPCON2 = 0x00;
SSPSTAT = 0x00;
SSPADD =  0x13;
TRISCbits.RC3 = 1;
TRISCbits.RC4 = 1;
}
void MSSP2Cidle()
{
while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
void MSSP2Cwait()
{
while(!PIR1bits.SSPIF);
PIR1bits.SSPIF=0;
}
void MSSP2CStart()
{
MSSP2Cidle();
SSPCON2bits.SEN = 1;
}
void MSSP2CStop()
{
MSSP2Cidle();
SSPCON2bits.PEN = 1;
}
void MSSP2CRestart()
{
MSSP2Cidle();
SSPCON2bits.RSEN = 1;
}
void MSSP2CNACK()
{
MSSP2Cidle();
SSPCON2bits.ACKDT = 1;
SSPCON2bits.ACKEN = 1;
}
void MSSP2CWrite(unsigned char data)
{
MSSP2Cidle();
SEND: SSPBUF=data;
MSSP2Cidle();
while(SSPCON2bits.ACKSTAT)
{
SSPCON2bits.RSEN=1;
MSSP2Cwait();
goto SEND;
}
}
unsigned char MSSP2Cread()
{
SSPCON2bits.RCEN=1;
while(!BF);
SSPCON2bits.RCEN=0;
return SSPBUF;
}
unsigned char MSSP2CreadNAck()
{
SSPCON2bits.RCEN=1;
while(!BF);
SSPCON2bits.RCEN=0;
SSPCON2bits.ACKDT=1;
SSPCON2bits.ACKEN=1;
MSSP2Cwait();
return SSPBUF;
}
void writeToEEPROM(unsigned char adhigh, unsigned char adlow, unsigned char edata)
{
MSSP2Cidle();
MSSP2CStart();
MSSP2CWrite(0xA0);
MSSP2CWrite(adhigh);
MSSP2CWrite(adlow);
MSSP2CWrite(edata);
MSSP2CStop();
//ms13delay (2);
}
unsigned char readFromEEPROM(unsigned char adhigh, unsigned char adlow)
{
MSSP2Cidle();
MSSP2CStart();
MSSP2CWrite(0XA0);
MSSP2CWrite(adhigh);
MSSP2CWrite(adlow);
MSSP2CRestart();
MSSP2CWrite(0xA1);
temp = MSSP2CreadNAck ();
MSSP2CStop();
return temp;
}
void displayTemp(float dp)
{
sprintf(str, "%.1f OC   ", dp);
writeString(str);
}
void displayTempNeg(float dp)
{
sprintf(str, "-%.1f OC   ", dp);
writeString(str);
}
void main()
{
PORTA = 0;
PORTB = 0;
PORTC = 0;
PORTD = 0;
TRISA = 0XFF;
TRISB = 0;
TRISC = 0;
TRISD = 0;
ADCON0 = 0;
ADCON1 = 0X0F;
OSCCON = 0X74;
T0CON = 0XC7;
setUpTheLCD ();
MSSP2CInit();
writeString ("Hello EEPROM");
line2 ();
adhigh = 0x01;
for(adlow=0,b=0;adlow<8;adlow++)
{
a = dataout [b];
writeToEEPROM(adhigh,adlow, a);
ms13delay (3);
b++;
}
for(adlow=0, b=0;adlow<8;adlow++)
{
disData = readFromEEPROM(adhigh,adlow);
rxData [b] = disData;
ms13delay (10);
lcdData = rxData[b];
lcdOut ();
}
ms13delay(30);
while (1)
{
clearTheScreen ();
writeString ("Temp is");
line2 ();
MSSP2Cidle ();
MSSP2CStart ();
MSSP2CWrite (0x9A);
MSSP2CWrite (0x00);
MSSP2CRestart();
MSSP2CWrite(0x9B);
thigh = MSSP2CreadNAck ();
MSSP2CStop();
if (thigh & 0b10000000)
{
thigh = (~thigh + 1);
sysTemperature = thigh;
displayTempNeg(sysTemperature);
}
else
{     sysTemperature = thigh;
displayTemp(sysTemperature);
}
ms13delay(61);
}
}

6. 代码分析

6.1 代码结构

  • 注释部分(第1 - 6行) :为标准注释,包含文件信息和创建时间。
  • 头文件包含(第7 - 10行) :包含了常用的头文件,其中 stdio.h 用于 sprintf 函数,用于在LCD上显示温度。
  • 变量声明(第12 - 15行) :设置了一些 unsigned char 类型的变量和一个 float 类型的变量 sysTemperature ,用于存储温度值。还设置了一个空数组 rxData[12] 用于存储从EEPROM读取的数据,以及一个包含八个ASCII值的数组 dataout [8] ,用于存储要写入EEPROM的数据。
  • 子函数声明与实现
    • ms13delay 函数(第27 - 34行) :创建一个可变延迟的子函数,最小延迟时间为13ms。
    • MSSP2CInit 函数(第35 - 43行) :初始化MSSP(主同步串行端口)用于I2C操作。
    • MSSP2Cidle 函数(第44 - 47行) :检查I2C模块是否空闲,避免总线冲突。
    • MSSP2Cwait 函数(第48 - 52行) :使PIC等待直到MSSP2模块完成操作。
    • MSSP2CStart 函数(第53 - 57行) :生成发送数据到从设备所需的起始位。
    • MSSP2CStop 函数(第58 - 62行) :生成停止位。
    • MSSP2CRestart 函数(第63 - 67行) :生成重启位。
    • MSSP2CNACK 函数(第68 - 73行) :生成主设备在传输结束时发送给从设备的非确认信号。
    • MSSP2CWrite 函数(第74 - 85行) :向从设备写入一个字节的数据。
    • MSSP2Cread 函数(第87 - 93行) :读取I2C总线上的数据。
    • MSSP2CreadNAck 函数(第94 - 103行) :读取从设备的数据并发送非确认信号。
    • writeToEEPROM 函数(第104 - 114行) :向EEPROM写入数据。
    • readFromEEPROM 函数(第115 - 127行) :从EEPROM读取数据。
    • displayTemp 函数(第128 - 132行) :将 float 类型的温度值显示在LCD上。
    • displayTempNeg 函数(第133 - 137行) :显示负温度值。

6.2 主函数分析

  • 初始化部分(第139 - 153行) :对端口、寄存器进行初始化,设置LCD和MSSP2C模块。
  • 写入EEPROM部分(第156 - 163行) :将数组 dataout 中的数据写入EEPROM的指定地址。
  • 读取EEPROM部分(第164 - 171行) :从EEPROM读取数据并显示在LCD上。
  • 温度监测部分(第173 - 198行) :进入无限循环,不断监测TC74温度传感器的温度,并根据温度正负情况显示在LCD上。

通过以上代码和分析,我们可以实现使用I2C协议与24LC256 EEPROM和TC74温度传感器进行通信,完成数据的读写和温度的监测。

7. 关键子函数详细解析

7.1 MSSP2CInit 函数

void MSSP2CInit()
{
    SSPCON1 = 0x28;
    SSPCON2 = 0x00;
    SSPSTAT = 0x00;
    SSPADD =  0x13;
    TRISCbits.RC3 = 1;
    TRISCbits.RC4 = 1;
}
  • 功能 :初始化MSSP(主同步串行端口)用于I2C操作。
  • 具体操作
    1. SSPCON1 = 0x28 :将 SSPCON1 寄存器加载为 0b00101000 ,使 SSPEN 位为逻辑‘1’,开启MSSP模块; CKP 位在I2C模式中不使用;第3位为逻辑‘1’,第2、1、0位为逻辑‘0’,将MSSP设置为I2C模式,时钟频率为振荡器除以 (4 × (SSPADD + 1))
    2. SSPCON2 = 0x00 :将 SSPCON2 寄存器的所有位加载为逻辑‘0’,关闭相关功能。
    3. SSPSTAT = 0x00 :将 SSPSTAT 寄存器的所有位加载为逻辑‘0’,关闭相关功能。
    4. SSPADD = 0x13 :对于24LC256 EEPROM,时钟信号最大频率为100kHz或400kHz,这里使用100kHz。振荡器频率设置为8Mhz,要将其分频到100kHz,需除以80,即 4 × (SSPADD + 1)=80 ,所以 SSPADD 寄存器的值为19(十六进制为0x13)。
    5. TRISCbits.RC3 = 1 TRISCbits.RC4 = 1 :将相应位设置为输入。

7.2 MSSP2Cidle 函数

void MSSP2Cidle()
{
    while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
  • 功能 :检查I2C模块是否空闲,避免总线冲突。
  • 具体操作
    • (SSPSTAT & 0x04) :检查主设备是否正在传输数据。
    • (SSPCON2 & 0x1F) :检查确认、接收使能、停止、重复起始或起始是否激活。如果任何一个测试结果为真,则PIC必须等待,直到这些条件都不满足(即I2C模块空闲)。

7.3 MSSP2CWrite 函数

void MSSP2CWrite(unsigned char data)
{
    MSSP2Cidle();
    SEND: SSPBUF=data;
    MSSP2Cidle();
    while(SSPCON2bits.ACKSTAT)
    {
        SSPCON2bits.RSEN=1;
        MSSP2Cwait();
        goto SEND;
    }
}
  • 功能 :向从设备写入一个字节的数据。
  • 具体操作
    1. MSSP2Cidle() :确保I2C模块空闲。
    2. SSPBUF=data :将数据加载到 SSPBUF 寄存器,准备发送给从设备。
    3. MSSP2Cidle() :再次确保I2C模块空闲。
    4. while(SSPCON2bits.ACKSTAT) :检查从设备是否发送了确认信号。如果 ACKSTAT 位为逻辑‘1’,表示未收到确认信号,执行以下操作:
      • SSPCON2bits.RSEN = 1 :使能PIC重新发送相同的数据。
      • MSSP2Cwait() :调用 MSSP2Cwait 子函数,使PIC等待操作完成。
      • goto SEND :跳转到 SEND 标签,再次尝试发送数据。

7.4 MSSP2Cread 函数

unsigned char MSSP2Cread()
{
    SSPCON2bits.RCEN=1;
    while(!BF);
    SSPCON2bits.RCEN=0;
    return SSPBUF;
}
  • 功能 :读取I2C总线上的数据。
  • 具体操作
    1. SSPCON2bits.RCEN = 1 :使能MSSP接收I2C总线上的数据。
    2. while(!BF) :等待 SSPSTAT 寄存器的 BF (缓冲区满)标志变为逻辑‘1’,表示 SSPBUF 寄存器已充满从设备发送的数据。
    3. SSPCON2bits.RCEN = 0 :关闭MSSP的接收功能。
    4. return SSPBUF :返回 SSPBUF 寄存器中的数据。

8. 实际应用流程

8.1 写入数据到EEPROM

  1. 初始化 :调用 MSSP2CInit 函数初始化MSSP模块。
  2. 设置地址 :设置要写入的地址的高字节和低字节,例如 adhigh = 0x01 adlow 通过循环从0递增到7。
  3. 写入数据 :调用 writeToEEPROM 函数,将数据写入指定地址。具体步骤如下:
    • 调用 MSSP2Cidle 函数确保I2C模块空闲。
    • 调用 MSSP2CStart 函数发送起始位。
    • 调用 MSSP2CWrite(0xA0) 发送控制字节。
    • 调用 MSSP2CWrite(adhigh) MSSP2CWrite(adlow) 发送地址的高字节和低字节。
    • 调用 MSSP2CWrite(edata) 发送要写入的数据。
    • 调用 MSSP2CStop 函数发送停止位。

8.2 从EEPROM读取数据

  1. 初始化 :调用 MSSP2CInit 函数初始化MSSP模块。
  2. 设置地址 :设置要读取的地址的高字节和低字节。
  3. 读取数据 :调用 readFromEEPROM 函数,从指定地址读取数据。具体步骤如下:
    • 调用 MSSP2Cidle 函数确保I2C模块空闲。
    • 调用 MSSP2CStart 函数发送起始位。
    • 调用 MSSP2CWrite(0XA0) 发送控制字节。
    • 调用 MSSP2CWrite(adhigh) MSSP2CWrite(adlow) 发送地址的高字节和低字节。
    • 调用 MSSP2CRestart 函数发送重启位。
    • 调用 MSSP2CWrite(0xA1) 发送控制字节,指示要读取数据。
    • 调用 MSSP2CreadNAck 函数读取数据并发送非确认信号。
    • 调用 MSSP2CStop 函数发送停止位。

8.3 读取TC74温度传感器的温度

  1. 初始化 :调用 MSSP2CInit 函数初始化MSSP模块。
  2. 发送命令 :按照以下步骤发送命令读取温度:
    • 调用 MSSP2Cidle 函数确保I2C模块空闲。
    • 调用 MSSP2CStart 函数发送起始位。
    • 调用 MSSP2CWrite(0x9A) 发送TC74的地址,指示要写入数据。
    • 调用 MSSP2CWrite(0x00) 发送命令字节。
    • 调用 MSSP2CRestart 函数发送重启位。
    • 调用 MSSP2CWrite(0x9B) 发送地址,指示要读取数据。
    • 调用 MSSP2CreadNAck 函数读取温度数据。
    • 调用 MSSP2CStop 函数发送停止位。
  3. 处理温度数据 :根据温度数据的正负情况,调用 displayTemp displayTempNeg 函数在LCD上显示温度。

9. 总结

通过上述内容,我们详细介绍了I2C通信协议的原理、24LC256 EEPROM和TC74温度传感器的使用方法,以及如何使用代码实现与它们的通信。在实际应用中,我们可以按照以下步骤进行操作:
1. 了解I2C协议的基本原理和特点,掌握其通信方式和数据传输规则。
2. 熟悉24LC256 EEPROM和TC74温度传感器的引脚使用和通信流程。
3. 根据实际需求编写代码,实现数据的读写和温度的监测。
4. 对代码进行详细分析和调试,确保程序的正确性和稳定性。

通过合理运用I2C协议和相关设备,我们可以实现高效、稳定的数据通信和设备控制。以下是整个操作流程的mermaid流程图:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(初始化系统):::process --> B(初始化MSSP模块):::process
    B --> C{选择操作}:::process
    C -->|写入EEPROM| D(设置地址和数据):::process
    D --> E(写入数据到EEPROM):::process
    C -->|读取EEPROM| F(设置地址):::process
    F --> G(从EEPROM读取数据):::process
    C -->|读取TC74温度| H(发送读取温度命令):::process
    H --> I(读取温度数据):::process
    I --> J(处理并显示温度):::process
    E --> K(结束操作):::process
    G --> K
    J --> K

在实际开发中,我们可以根据具体需求对代码进行扩展和优化,例如增加错误处理机制、提高数据传输的效率等。同时,要注意I2C总线的电气特性和通信规范,避免出现通信故障。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值