做盗文网站,吉林省科瑞建设项目管理有限公司网站,wordpress基础服务器,中国最大的做网站公司目录硬知识I2C 介绍I2C 物理层I2C 协议层数据有效性规定起始和停止信号应答响应总线的寻址方式数据传输示例程序Soft_I2C.cSoft_I2C.h普中51-单核-A2 STC89C52 Keil uVision V5.29.0.0 PK51 Prof.Developers Kit Version:9.60.0.0 硬知识 摘自《普中 51 单片机开发攻略》
I2…
目录硬知识I2C 介绍I2C 物理层I2C 协议层数据有效性规定起始和停止信号应答响应总线的寻址方式数据传输示例程序Soft_I2C.cSoft_I2C.h普中51-单核-A2 STC89C52 Keil uVision V5.29.0.0 PK51 Prof.Developers Kit Version:9.60.0.0 硬知识 摘自《普中 51 单片机开发攻略》
I2C 介绍 I2CInterIntegrated Circuit总线是由 PHILIPS 公司开发的两线式串行总线用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式具有接口线少控制方式简单 器件封装形式小通信速率较高等优点。I2C 总线只有两根双向信号线。一根是数据线 SDA另一根是时钟线 SCL。由于其管脚少硬件实现简单可扩展性强等特点因此被广泛的使用在各大集成芯片内。下面我们就从 I2C 的物理层与协议层来了解 I2C。
I2C 物理层 I2C 通信设备常用的连接方式如下图所示 它的物理层有如下特点
它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在 一个 I2C 通讯总线中可连接多个 I2C 通讯设备支持多个通讯主机及多个通讯从机。一个 I2C 总线只使用两条总线线路一条双向串行数据线(SDA)一条串行时钟线(SCL)。数据线即用来表示数据时钟线用于数据收发同步。每个连接到总线的设备都有一个独立的地址主机可以利用这个地址进行不同设备之间的访问。总线通过上拉电阻接到电源。当 I2C 设备空闲时会输出高阻态而当所有设备都空闲都输出高阻态时由上拉电阻把总线拉成高电平。多个主机同时使用总线时为了防止数据冲突会利用仲裁方式决定由哪个设备占用总线。具有三种传输模式标准模式传输速率为 100kbit/s快速模式为 400kbit/s高速模式下可达 3.4Mbit/s但目前大多 I2C 设备尚不支持高速模式。连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
I2C 总线常用的一些术语
主机启动数据传送并产生时钟信号的设备从机被主机寻址的器件多主机同时有多于一个主机尝试控制总线但不破坏传输主模式用 I2CNDAT 支持自动字节计数的模式 位 I2CRM,I2CSTT,I2CSTP 控制数据的接收和发送从模式发送和接收操作都是由 I2C 模块自动控制的仲裁是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使传输不被破坏的过程同步两个或多个器件同步时钟信号的过程发送器发送数据到总线的器件接收器从总线接收数据的器件。
I2C 协议层 I2C 的协议定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
数据有效性规定 I2C 总线进行数据传送时时钟信号为高电平期间数据线上的数据必须保持稳定只有在时钟线上的信号为低电平期间数据线上的高电平或低电平状态才允许变化。如下图 每次数据传输都以字节为单位每次传输的字节数不受限制。
起始和停止信号 SCL 线为高电平期间SDA 线由高电平向低电平的变化表示起始信号SCL 线为高电平期间SDA 线由低电平向高电平的变化表示终止信号。如下图 起始和终止信号都是由主机发出的在起始信号产生后总线就处于被占用的状态在终止信号产生后总线就处于空闲状态。
应答响应 每当发送器件传输完一个字节的数据后后面必须紧跟一个校验位这个校验位是接收端通过控制 SDA数据线来实现的以提醒发送端数据我这边已经接收完成数据传送可以继续进行。这个校验位其实就是数据或地址传输过程中的响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后若希望对方继续发送数据则需要向对方发送“应答(ACK)”信号即特定的低电平脉冲 发送方会继续发送下一个数据若接收端希望结束数据传输则向对方发送“非应答(NACK)”信号即特定的高电平脉冲发送方接收到该信号后会产生一个停止信号结束信号传输。应答响应时序图如下 每一个字节必须保证是 8 位长度。数据传送时先传送最高位MSB每一个被传送的字节后面都必须跟随一位应答位即一帧共有 9 位。 由于某种原因从机不对主机寻址信号应答时如从机正在进行实时性的处理工作而无法接收总线上的数据它必须将数据线置于高电平而由主机产生一个终止信号以结束总线的数据传送。如果从机对主机进行了应答但在数据传送一段时间后无法继续接收更多的数据时从机可以通过对无法接收的第一个数据字节的“非应答”通知主机主机则应发出终止信号以结束数据的继续传送。当主机接收数据时它收到最后一个数据字节后必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后从机释放 SDA 线以允许主机产生终止信号。这些信号中起始信号是必需的结束信号和应答信号都可以不要。
总线的寻址方式 I2C 总线寻址按照从机地址位数可分为两种一种是 7 位另一种是 10 位。采用 7 位的寻址字节寻址字节是起始信号后的第一个字节的位定义如下 D7D1 位组成从机的地址。D0 位是数据传送方向位为“ 0”时表示主机向从机写数据为“1”时表示主机由从机读数据。 10 位寻址和 7 位寻址兼容而且可以结合使用。10 位寻址不会影响已有 的 7 位寻址有 7 位和 10 位地址的器件可以连接到相同的 I2C 总线。这里以 7 位寻址为例进行介绍。 当主机发送了一个地址后总线上的每个器件都将头 7 位与它自己的地址比较如果一样器件会判定它被主机寻址其他地址不同的器件将被忽略后面的数据信号。至于是从机接收器还是从机发送器都由 R/W 位决定的。从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的 7 位寻址位有 4 位是固定位3 位是可编程位这时仅能寻址 8 个同样的器件即可以有 8 个同样的器件接入到该 I2C 总线系统中。
数据传输 I2C 总线上传送的数据信号是广义的既包括地址信号又包括真正的数据信号。在起始信号后必须传送一个从机的地址7 位第 8 位是数据的传送方向位R/W用“ 0”表示主机发送写数据W“ 1”表示主机接收数据R。每次数据传送总是由主机产生的终止信号结束。但是若主机希望继续占用总线进行新的数据传送则可以不产生终止信号马上再次发出起始信号对另一从机进行寻址。 在总线的一次数据传送过程中可以有以下几种组合方式
主机向从机发送数据数据传送方向在整个传送过程中不变 注意有阴影部分表示数据由主机向从机传送无阴影部分则表示数据由从机向主机传送。A 表示应答A 非表示非应答高电平。S 表示起始信号P 表 示终止信号。主机在第一个字节后立即从从机读数据 在传送过程中当需要改变传送方向时起始信号和从机地址都被重复产生一次但两次读/写方向位正好相反
示例程序 由于89C5x 单片机没有硬件 IIC 接口这里给出的是IO模拟的I2C程序 stdint.h见【51单片机快速入门指南】1基础知识和工程创建
Soft_I2C.c
修改自普中的例程原版应答后未把SDA恢复高电平且起始和结束的时序有误将导致通信错误。 加入了对器件特定寄存器的连续读写函数。
#include Soft_I2C.h#define I2C_TIMEOUT_TIMES 100 //超时倍数//延时 用于等待应答时的超时判断 移植时需修改
void i2c_timeout_delay(void)
{}void i2c_delay() //每步的间隔 用于等待电平稳定和控制通讯速率
{}//SCL拉高 移植时需修改
void I2C_SCL_H(void)
{I2C_SCL 1;
}//SCL拉低 移植时需修改
void I2C_SCL_L(void)
{I2C_SCL 0;
}//SDA拉高 移植时需修改
void I2C_SDA_H(void)
{I2C_SDA 1;
}//SDA拉低 移植时需修改
void I2C_SDA_L(void)
{I2C_SDA 0;
}//读取SDA 移植时需修改
uint8_t I2C_SDA_Read(void)
{return I2C_SDA;
}/*******************************************************************************
* 函 数 名 : i2c_start
* 函数功能 : 产生I2C起始信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void i2c_start(void)
{I2C_SDA_H();I2C_SCL_H();i2c_delay();I2C_SDA_L(); //当SCL为高电平时SDA由高变为低i2c_delay();I2C_SCL_L(); //钳住I2C总线准备发送或接收数据
}/*******************************************************************************
* 函 数 名 : i2c_stop
* 函数功能 : 产生I2C停止信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void i2c_stop(void)
{I2C_SDA_L();I2C_SCL_H();i2c_delay();I2C_SDA_H(); //当SCL为高电平时SDA由低变为高i2c_delay();
}/*******************************************************************************
* 函 数 名 : i2c_ack
* 函数功能 : 产生ACK应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void i2c_ack(void)
{I2C_SCL_L();I2C_SDA_L(); //SDA为低电平i2c_delay();I2C_SCL_H();i2c_delay();I2C_SCL_L();I2C_SDA_H();
}/*******************************************************************************
* 函 数 名 : i2c_nack
* 函数功能 : 产生NACK非应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void i2c_nack(void)
{I2C_SCL_L();I2C_SDA_H(); //SDA为高电平i2c_delay();I2C_SCL_H();i2c_delay();I2C_SCL_L();
}/*******************************************************************************
* 函 数 名 : i2c_wait_ack
* 函数功能 : 等待应答信号到来
* 输 入 : 无
* 输 出 : 1接收应答失败0接收应答成功
*******************************************************************************/
uint8_t i2c_wait_ack(void)
{uint16_t time_temp 0;I2C_SCL_H();i2c_delay();while(I2C_SDA_Read()) //等待SDA为低电平{time_temp;i2c_timeout_delay();if(time_temp I2C_TIMEOUT_TIMES) //超时则强制结束I2C通信{i2c_stop();return 1;}}I2C_SCL_L();return 0;
}/*******************************************************************************
* 函 数 名 : i2c_write_byte
* 函数功能 : I2C发送一个字节
* 输 入 : dat发送一个字节
* 输 出 : 无
*******************************************************************************/
void i2c_write_byte(uint8_t dat)
{uint8_t i 0;I2C_SCL_L();for(i 0; i8; i) //循环8次将一个字节传出先传高再传低位{if((dat 0x80) 0)I2C_SDA_H();elseI2C_SDA_L();dat 1;i2c_delay();I2C_SCL_H();i2c_delay();I2C_SCL_L();i2c_delay();}
}/*******************************************************************************
* 函 数 名 : i2c_read_byte
* 函数功能 : I2C读一个字节
* 输 入 : ack 1时发送ACKack 0发送nACK
* 输 出 : 应答或非应答
*******************************************************************************/
uint8_t i2c_read_byte(uint8_t ack)
{uint8_t i 0, receive 0;for(i 0; i 8; i ) //循环8次将一个字节读出先读高再传低位{I2C_SCL_L();i2c_delay();I2C_SCL_H();receive 1;if(I2C_SDA_Read())receive;i2c_delay();}if (!ack)i2c_nack();elsei2c_ack();return receive;
}/*******************************************************************************
* 函 数 名 : i2c_mem_write
* 函数功能 : I2C对指定器件、指定寄存器连续写入
* 输 入 : 器件地址、器件寄存器地址、待输入数据首地址、待输入数据长度
* 输 出 : 0: 成功 1失败
*******************************************************************************/
uint8_t i2c_mem_write(uint8_t DevAddress, uint8_t MemAddress, uint8_t *pData, uint16_t Len)
{i2c_start();i2c_write_byte(DevAddress 1);if(i2c_wait_ack())return 1;i2c_write_byte(MemAddress);if(i2c_wait_ack())return 1;while(Len--){i2c_write_byte(*pData);if(i2c_wait_ack())return 1;}i2c_stop();return 0;
}/*******************************************************************************
* 函 数 名 : i2c_mem_read
* 函数功能 : I2C对指定器件、指定寄存器连续读取
* 输 入 : 器件地址、器件寄存器地址、数据缓冲区首地址、数据长度
* 输 出 : 0: 成功 1失败
*******************************************************************************/
uint8_t i2c_mem_read(uint8_t DevAddress, uint8_t MemAddress, uint8_t *pBuffer, uint16_t Len)
{ i2c_start(); i2c_write_byte(DevAddress 1); //发送写命令 if(i2c_wait_ack())return 1;i2c_write_byte(MemAddress); //发送字地址 if(i2c_wait_ack())return 1; i2c_start(); i2c_write_byte(DevAddress 1 | 1); //进入接收模式 if(i2c_wait_ack())return 1;while(Len--){*pBuffer i2c_read_byte(Len!0); //读取字节 }i2c_stop(); //产生一个停止条件 return 0;
}/**写入8位寄存器的一个位。
* 参数 DevAddress I2C从器件地址
* 参数 addr I2C从器件内部地址
* 参数 bitNum 写入的比特位(0-7)
* 参数 data 写入数据
* 返回值 返回状态 (0成功)
*/
uint8_t i2c_write_bit(uint8_t DevAddress, uint8_t addr, uint8_t bitNum, uint8_t Data)
{uint8_t b;if (!i2c_mem_read(DevAddress, addr, b, 1)){b (Data ! 0) ? (b | (1 bitNum)) : (b ~(1 bitNum));return i2c_mem_write(DevAddress, addr, b, 1); //写入数据}elsereturn 1;
}/**写入8位寄存器的多个位。
* 参数 DevAddress I2C从器件地址
* 参数 addr I2C从器件内部地址
* 参数 bitStart 第一位的写入位置0-7
* 参数 length 写的比特数(不超过8)
* 参数 Data 写入数据
* 返回值 返回状态 (0成功)
*/
uint8_t i2c_write_bits(uint8_t DevAddress, uint8_t addr, uint8_t bitStart, uint8_t length, uint8_t Data)
{// 010 要写入的值// 76543210 比特位// xxx args: bitStart4, length3// 00011100 掩码字节// 10101111 原始值样本// 10100011 原始值 ~掩码// 10101011 掩码 | 原始值uint8_t b, mask 0;if (!i2c_mem_read(DevAddress, addr, b, 1)){mask (((1 length) - 1) (bitStart - length 1)); //掩码Data (bitStart - length 1); //把写入的数据移动到位Data mask;b ~(mask);b | Data;return i2c_mem_write(DevAddress, addr, b, 1); //写入数据}elsereturn 1;
}
/**读取一个位从8位器件的寄存器。
* 参数 DevAddress I2C从器件地址
* 参数 addr I2C从器件内部地址
* 参数 bitNum 位的位置来读取0-7
* 参数 *data 数据存储地址
* 返回值0成功
*/
uint8_t i2c_read_bit(uint8_t DevAddress, uint8_t addr, uint8_t bitNum, uint8_t *Data)
{uint8_t b;if (!i2c_mem_read(DevAddress, addr, b, 1)){*Data b (1 bitNum);return 0;}else{return 1;}
}
/**读取8位寄存器的多个位。
* 参数 DevAddress I2C从器件地址
* 参数 addr I2C从器件内部地址
* 参数 bitStart第一位的位置读取0-7
* 参数 length 位读取参数长度数不超过8
* 参数 *data 数据存储地址即101任何bitStart位置读取将等于0X05
* 返回值0成功
*/
uint8_t i2c_read_bits(uint8_t DevAddress, uint8_t addr, uint8_t bitStart, uint8_t length, uint8_t *Data)
{// 01101001 读取字节// 76543210 比特位// xxx args: bitStart4, length3// 010 masked// - 010 shifteduint8_t b, mask 0;if (!i2c_mem_read(DevAddress, addr, b, 1)){mask ((1 length) - 1) (bitStart - length 1);b mask;b (bitStart - length 1);*Data b;return 0;}elsereturn 1;
}Soft_I2C.h
#ifndef SOFT_I2C_H_
#define SOFT_I2C_H_#include STC89C5xRC.H
#include stdint.h//定义I2C控制脚
sbit I2C_SCL P2^1; //SCL时钟线
sbit I2C_SDA P2^0; //SDA数据线//I2C所有操作函数
void i2c_start(void); //发送I2C开始信号
void i2c_stop(void); //发送I2C停止信号
uint8_t i2c_wait_ack(void); //I2C等待ACK信号
void i2c_ack(void); //I2C发送ACK信号
void i2c_nack(void); //I2C不发送ACK信号void i2c_write_byte(uint8_t txd); //I2C发送一个字节
uint8_t i2c_read_byte(uint8_t ack); //I2C读取一个字节
uint8_t i2c_mem_write(uint8_t DevAddress, uint8_t MemAddress, uint8_t *pData, uint16_t Len); //I2C对指定器件、指定寄存器连续写入
uint8_t i2c_mem_read(uint8_t DevAddress, uint8_t MemAddress, uint8_t *pBuffer, uint16_t Len); //I2C对指定器件、指定寄存器连续读取
uint8_t i2c_write_bit(uint8_t DevAddress, uint8_t addr, uint8_t bitNum, uint8_t Data); //写入8位寄存器的一个位
uint8_t i2c_write_bits(uint8_t DevAddress, uint8_t addr, uint8_t bitStart, uint8_t length, uint8_t Data);//写入8位寄存器的多个位
uint8_t i2c_read_bit(uint8_t DevAddress, uint8_t addr, uint8_t bitNum, uint8_t *Data); //读取一个位从8位器件的寄存器
uint8_t i2c_read_bits(uint8_t DevAddress, uint8_t addr, uint8_t bitStart, uint8_t length, uint8_t *Data);//读取8位寄存器的多个位#endif