学习目标
- 了解I2C通讯协议
- 理解I2C工作原理
-
学习内容
基本原理
I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在集成电路之间进行数据交换。它最初由飞利浦公司(Philips)开发,现已成为一种通用的串行通信协议,被广泛应用于各种电子设备和嵌入式系统中。
总线结构
I2C总线包括两根信号线:SDA(串行数据线)和SCL(串行时钟线)。这两根信号线共用一个总线,因此在总线上可以连接多个设备。在I2C总线上,每个设备都有一个唯一的地址,用于标识设备。
SCL线是时钟线,用于控制数据传输的速度和时序;SDA线是数据线,用于传输实际的数据.
设备的地址通常是由设备制造商确定的,并在设备的数据手册中公布。
总之我们搞明白几个关键名词就可以: Master: 主设备。通常是主控MCU
- Slave:从设备。通常是功能芯片,例如RTC时钟,陀螺仪,温湿度等等。
- SCL:时钟线,控制数据传输的速度和时序。
- SDA:数据线。传输数据的。
地址:从设备地址。主设备通过地址进行访问。在总线中,每个从设备地址唯一。
上拉电阻
在I2C总线中,上拉电阻的大小通常是由以下几个因素决定的:
总线长度:总线长度越长,上拉电阻的阻值就应该越小,以保证信号的稳定性。这是因为,总线长度越长,线路上的电容就越大,需要更多的电流来充电和放电,因此上拉电阻的阻值也应该相应地减小。
- 总线上的设备数量:总线上连接的设备数量越多,需要更大的电流来充电和放电,以确保信号的稳定性。因此,当总线上连接的设备数量增加时,上拉电阻的阻值也应该相应地减小。
- 总线上设备的最高工作频率:I2C总线的时钟频率通常在100kHz到400kHz之间。如果总线上的设备需要使用更高的时钟频率,则上拉电阻的阻值应该相应地减小,以确保设备能够在规定的时间内完成数据的传输。
总的来说,I2C总线中上拉电阻的大小需要根据具体的情况来确定,以保证总线的稳定性和可靠性。一般来说,上拉电阻的阻值应该在1kΩ到10kΩ之间。
信号电平
I2C总线的信号电平是基于器件的供电电压而定的,通常为3.3V或5V。在I2C总线上,SDA和SCL信号线都是开漏模式,因此需要外接上拉电阻,以确保信号电平的不确定性。
速度
I2C总线的速度是由其时钟频率决定的。I2C总线的时钟频率通常在100kHz到400kHz之间,其中100kHz是标准模式(Standard Mode),400kHz是快速模式(Fast Mode)。
- 在标准模式下,I2C总线的时钟频率为100kHz,数据传输速率最高可以达到每秒约10kbps。标准模式适用于大多数的应用场景,可以满足许多设备的数据传输需求。
- 在快速模式下,I2C总线的时钟频率为400kHz,数据传输速率最高可以达到每秒约40kbps。快速模式适用于一些需要更高速度的应用场景,例如传感器数据采集等。
此外,I2C总线还支持更高速度的高速模式(High Speed Mode)和超高速模式(Ultra-Fast Mode),它们的时钟频率分别为1MHz
和5MHz
。这些高速模式通常用于一些需要非常高速数据传输的应用场景。
需要注意的是,总线的速度不仅受时钟频率的影响,还受到总线长度、电容负载、上拉电阻大小等因素的影响。因此,在实际应用中,需要根据具体情况来确定总线的速度以确保数据传输的稳定性和可靠性。
STC8H芯片I2C引脚
STC8H内置了一组I2C接口。
I2C接口 | SCL | SDA |
---|---|---|
I2C1 | P1.5 | P1.4 |
P2.5 | P2.4 | |
P3.2 | P3.3 |
I2C开发流程
总结起来,I2C总线编程开发步骤为以下:
- 引脚功能配置
- I2C配置
- 总线数据读取或写入
I2C引脚配置为开漏(OD)模式。
基本上所有的芯片平台都是这种流程,具体的代码写法可能有所差异,但是道理相通。
STC8H的I2C配置
记得开启扩展寄存器使能:
EAXSFR();
配置IO口为开漏模式
P3_MODE_OUT_OD(GPIO_Pin_2 | GPIO_Pin_3);
以下是STC8H的I2C配置代码。
I2C.c
I2C.h
NVIC.c
NVIC.h
Switch.h
/**************** I2C初始化函数 *****************/
void I2C_config(void)
{
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_Master; //主从选择 I2C_Mode_Master, I2C_Mode_Slave
I2C_InitStructure.I2C_Enable = ENABLE; //I2C功能使能, ENABLE, DISABLE
I2C_InitStructure.I2C_MS_WDTA = DISABLE; //主机使能自动发送, ENABLE, DISABLE
I2C_InitStructure.I2C_Speed = 13; //总线速度=Fosc/2/(Speed*2+4),
// 400k, 24M => 13
// 100k, 24M => 58
I2C_Init(&I2C_InitStructure);
NVIC_I2C_Init(I2C_Mode_Master,DISABLE,Priority_0); //主从模式, I2C_Mode_Master, I2C_Mode_Slave; 中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
I2C_SW(I2C_P33_P32); //I2C_P14_P15,I2C_P24_P25,I2C_P33_P32
}
- I2C_MODE:模式,当前是Master还是Slave。
- I2C_Speed: 速度。100k或者400k,通过
总线速度=Fosc/2/(Speed*2+4)
公式计算。STC8H的I2C读取
stc8h提供了库函数,对I2C进行读取和写入。
标准的读数据帧:
库函数中的读取I2C_ReadNbyte
```c //======================================================================== // 函数: void I2C_ReadNbyte(u8 dev_addr, u8 mem_addr, u8 p, u8 number) // 描述: I2C读取数据函数. // 参数: dev_addr: 设备地址, mem_addr: 存储地址, p读取数据存储位置, number读取数据个数. // 返回: none. // 版本: V1.0, 2020-09-15 //======================================================================== void I2C_ReadNbyte(u8 dev_addr, u8 mem_addr, u8 p, u8 number) / DeviceAddress,WordAddress,First Data Address,Byte lenth */ { Start(); //发送起始命令 SendData(dev_addr); //发送设备地址+写命令 RecvACK(); SendData(mem_addr); //发送存储地址 RecvACK(); Start(); //发送起始命令 SendData(dev_addr|1); //发送设备地址+读命令 RecvACK(); do {
} while(—number); SendNAK(); //send no ACK*p = RecvData();
p++;
if(number != 1) SendACK(); //send ACK
Stop(); //发送停止命令 }
<a name="TY8R3"></a>
#### STC8H的I2C写入
标砖的写数据帧:<br />![image.png](https://cdn.nlark.com/yuque/0/2023/png/27903758/1694368626425-7c374bbb-1fb2-4f1b-b648-b67dbe1bb7a3.png#averageHue=%23c5bc7d&clientId=u4bb90af7-1a73-4&from=paste&height=348&id=u8dec805d&originHeight=348&originWidth=1426&originalType=binary&ratio=1&rotation=0&showTitle=false&size=183473&status=done&style=none&taskId=u039e3311-12e8-45f2-9ce8-ca08ac64628&title=&width=1426)<br />库函数中的写入 `I2C_WriteNbyte`
```c
//========================================================================
// 函数: void I2C_WriteNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number)
// 描述: I2C写入数据函数.
// 参数: dev_addr: 设备地址, mem_addr: 存储地址, *p写入数据存储位置, number写入数据个数.
// 返回: none.
// 版本: V1.0, 2020-09-15
//========================================================================
void I2C_WriteNbyte(u8 dev_addr, u8 mem_addr, u8 *p, u8 number) /* DeviceAddress,WordAddress,First Data Address,Byte lenth */
{
Start(); //发送起始命令
SendData(dev_addr); //发送设备地址+写命令
RecvACK();
SendData(mem_addr); //发送存储地址
RecvACK();
do
{
SendData(*p++);
RecvACK();
}
while(--number);
Stop(); //发送停止命令
}
I2C地址问题
开发过程中,经常有address
问题。由于翻译问题,和一些程序员编码命名问题,导致我们经常把address
概念混淆。
通常我们关心的地址有:
- 设备地址:具体说法就是从设备的访问地址。
-
设备地址
设备地址其实包含了两个地址,一个是读取从设备时的地址,一个是向从设备写入数据时的地址。这两个地址还不一样。这两个地址的来源需要翻看
从设备
的芯片手册,进行查看。
设备地址是8个位的,最后一位表示读还是写,1表示读,0表示写。(1读0写这个是默认的,但也不排除一些奇葩厂商芯片设计自定义反向操作,一切以实际为准)
上图中,就是一个I2C的从设备地址,最后一位决定是读还是写。
我们在查询用户手册的过程中,必须确认这个地址,但通常会碰到一些问题: 只提供一个地址。
通常会明确说是读地址还是写地址,一定要查阅清楚。还有就是只提供了一个地址,没有明确说明通常是前七位组成的地址。
- 1种类型的从设备多个串联。一种类型的芯片通常地址是相同的,但是要访问具体的从设备需要唯一地址,否则不能正常工作。这个时候需要芯片支持地址扩展。
- 多种类型的从设备地址相同。这个就需要从设备可以配置改地址的方式。
从设备寄存器地址
通常我们通过I2C总线要去写入或者读取的就是这些寄存器地址。对于寄存器地址数据含义,需要阅读芯片手册。
I2C通讯流程(了解)
对于一些已经提供了库函数的芯片平台,使用I2C是非常容易的,不要从底层做起,因为有良好的API支持。
但是对于没有支持的,或者是需要清楚的了解过程的,需要去理解这个流程。
I2C通信流程如下:
- 主设备发送起始信号(Start)。
- 主设备发送从设备地址和读/写位,请求与从设备建立通信。
- 从设备返回应答信号(ACK)。
- 主设备发送要读/写的数据。
- 从设备返回应答信号(ACK)。
- 通信结束时,主设备发送停止信号(Stop)。
下面是每个步骤的详细说明:
- 起始信号(Start):主设备通过拉低SDA线而将SDA线从高电平转换成低电平,同时将SCL线拉高。这表示通信开始了。
- 从设备地址和读/写位:主设备发送从设备地址和读/写位,告诉从设备要进行读或写操作。I2C总线支持多个从设备,因此地址是从设备的标识符。地址的最低位表示通信模式,0表示写模式,1表示读模式。
- 应答信号(ACK):从设备接收到地址后,会发送应答信号(ACK)表示已经准备好接收或发送数据。如果没有设备响应主设备发送的地址,则通信失败。
- 发送数据:主设备向从设备发送要写入的数据。
- 应答信号(ACK):从设备接收到数据后,发送应答信号(ACK)表示已经成功接收数据。如果从设备没有成功接收到数据,将发送非应答信号(NACK)。
- 停止信号(Stop):通信结束后,主设备发送停止信号(Stop),将SDA线从低电平拉到高电平,同时将SCL线拉高。这表示通信结束了。
需要注意的是,在步骤2和步骤4中,如果主设备发送的数据超过了从设备的缓存,从设备将发送非应答信号(NACK)表示数据传输失败。主设备在收到非应答信号(NACK)后将终止通信。
举例: