I2C介绍
I2C(集成电路总线),由Philips公司(2006年迁移到NXP)在1980年代初开发的一种简单、双线双向的同步串行总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。
I²C最重要的功能包括:
只需要两条总线;
没有严格的波特率要求,例如使用RS232,主设备生成总线时钟;
所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;
I²C是真正的多主设备总线,可提供仲裁和冲突检测;
传输速度;
标准模式:Standard Mode = 100 Kbps
快速模式:Fast Mode = 400 Kbps
高速模式: High speed mode = 3.4 Mbps
超快速模式: Ultra fast mode = 5 Mbps
最大主设备数:无限制;
最大从机数:理论上是127;
I2C 的IO初始化
SDA SCL都设置成推挽输出,并都设置成高电平
void IIC_Init(void)
{
SDA_OUT();
SCL_OUT();
SDA_SET();
SCL_SET();
}
I2C开始时序
void IIC_Start(void)
{
SDA_OUT(); //由于上一个SCL状态是0或者是1,要让SDA稳定输出,都可以设置为1。
SDA_SET();
Delay_us(2);
SCL_SET(); //让SCL发生变化,为0不变,为1则变化,表明数据可以变动了。
Delay_us(2);
SDA_RESET(); //SDA线数据变化,从而可以保证发出的是start信号。
Delay_us(2);
SCL_RESET(); //将I2C总线钳住,下一个时间SDA可以输出高低电平。
Delay_us(4);
}
I2C结束时序
void IIC_Stop(void)
{
SDA_OUT();
SDA_RESET(); //上一个状态的SCL=0,这个状态设置SDA=0,目的是让状态能反转
Delay_us(2);
SCL_SET(); //该时刻设置SCL=1,然后就可以让数据稳定在改状态下。
Delay_us(2);
SDA_OUT();
Delay_us(4);
}
I2C等待应答信号
uint8_t IIC_Wait_Ack(void)
{
uint16_t ucErrTime = 0;
SCL_RESET();
Delay_us(1);
SDA_IN(); //SDA设置为输入
while (READ_SDA())
{
ucErrTime++;
if (ucErrTime > 250)
{
IIC_Stop();
return 0; //超时,表明数据传输有问题
}
}
SCL_SET();
Delay_us(1);
SCL_RESET(); //时钟输出0
Delay_us(2);
return 1;
}
产生ACK应答
void IIC_Ack(void)
{
SCL_RESET();
Delay_us(2);
SDA_OUT();
SDA_RESET();
Delay_us(1);
SCL_SET();
Delay_us(1);
SCL_RESET();
Delay_us(4);
}
不产生应答
void IIC_NAck(void)
{
SDA_OUT();
SCL_RESET();
Delay_us(2);
SDA_OUT();
Delay_us(2);
SCL_SET();
Delay_us(2);
SCL_RESET();
Delay_us(2);
}
发送8位数据
void SendByte(uint8_t c)
{
uint8_t BitCnt;
for (BitCnt = 0; BitCnt < 8; BitCnt++) /*要传送的数据长度为8位*/
{
SDA_OUT();
if ((c << BitCnt) & 0x80)
SDA_SET(); /*判断发送位*/
else
SDA_RESET();
Delay_us(2);
SCL_SET(); /*置时钟线为高,通知被控器开始接收数据位*/
Delay_us(4);
SCL_RESET();
}
}
接收8位数据
uint8_t RcvByte()
{
uint8_t retc;
uint8_t BitCnt;
retc = 0;
// SDA_OUT();
// SDA_OUT();
SDA_IN(); //SDA设置为输入
for (BitCnt = 0; BitCnt < 8; BitCnt++)
{
Delay_us(2);
SCL_RESET(); /*置时钟线为低,准备接收数据位*/
Delay_us(4);
SCL_SET(); /*置时钟线为高使数据线上数据有效*/
Delay_us(4);
retc = retc << 1;
if (READ_SDA() == 1)
retc = retc + 1; /*读数据位,接收的数据位放入retc中 */
Delay_us(2);
}
SCL_RESET();
Delay_us(2);
return (retc);
}
是否应答
void Ack_I2c(uint8_t a)
{
SDA_OUT();
if (a == 0)
SDA_RESET(); /*在此发出应答或非应答信号 */
else
SDA_OUT();
Delay_us(2);
SCL_SET();
Delay_us(4);
SCL_RESET(); /*清时钟线,钳住I2C总线以便继续接收*/
Delay_us(2);
}
I2C发送单个字节
uint8_t ISendByte(uint8_t sla, uint8_t c)
{
IIC_Start(); /*启动总线*/
SendByte(sla); /*发送器件地址*/
if (ack == 0)
return (0);
SendByte(c); /*发送数据*/
if (ack == 0)
return (0);
IIC_Stop(); /*结束总线*/
return (1);
}
I2C发送两个字节
uint8_t ISendStr(uint8_t sla, uint8_t suba, uint8_t s)
{
uint8_t i;
uint8_t no = 1;
IIC_Start(); /*启动总线*/
SendByte(sla); /*发送器件地址*/
if (IIC_Wait_Ack() == 0)
return (0);
SendByte(suba); /*发送器件子地址*/
if (IIC_Wait_Ack() == 0)
return (0);
//for(i=0;i<no;i++)
{
SendByte(s); /*发送数据*/
if (IIC_Wait_Ack() == 0)
return (0);
// s++;
}
IIC_Stop(); /*结束总线*/
return (1);
}
使用I2C读取数据
uint8_t IRcvStr(uint8_t sla, uint8_t suba)
{
uint8_t i;
uint8_t s;
uint8_t no = 1;
IIC_Start(); /*启动总线*/
SendByte(sla); /*发送器件地址*/
if (IIC_Wait_Ack() == 0)
return (0);
SendByte(suba); /*发送器件子地址*/
if (IIC_Wait_Ack() == 0)
return (0);
IIC_Start();
SendByte(sla + 1);
if (IIC_Wait_Ack() == 0)
return (0);
s = RcvByte(); /*发送数据*/
Ack_I2c(1); /*发送非应位*/
IIC_Stop(); /*结束总线*/
return s;
}
NCA9555端口初始化,并把16个端口设置成输入模式,读取按键值
#define PCA9555 0x40
IIC_Init();
ISendStr(PCA9555,0x06,0xff);// p0设置为输入,1为输入
ISendStr(PCA9555,0x07,0xff);// p1设置为输入//默认输出高
uint8_t buff8=0,buff9;
buff8 = ~IRcvStr(PCA9555,0x00);
buff9 = ~IRcvStr(PCA9555,0x01);
部分参考https://blog.csdn.net/u010632165/article/details/109188507?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170053238516800227447903%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=170053238516800227447903&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-2-109188507-null-null.142v96pc_search_result_base7&utm_term=I2C&spm=1018.2226.3001.4187