IIC协议
IIC协议
STM32—IIC协议
IIC 简介
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。
它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。
在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。
IIC物理层
IIC总线的硬件,是由一根数据线SDA,一根时钟线SCL构成。不同的器件,都是并联接在这两条线上,I2C总线上的每个设备都自己一个唯一的地址,来确保不同设备之间访问的准确性。
SDA(Serial data)是数据线,D代表Data也就是数据,Send Data 也就是用来传输数据的
SCL(Serial clock line)是时钟线,C代表Clock 也就是时钟 也就是控制数据发送的时序的
IIC总线通过上拉电阻接到电源VCC,电阻大小要根据设备实际测量。
总线空闲的时候,SDA和SCL都是高电平。
当其中一个设备拉低总线,整条线就全是低电平,器件与器件之间“与”关系。
IIC特点
通常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(控制SCL的电平高低变换)谁就是主设备。
- IIC主设备功能:主要产生时钟,产生起始信号和停止信号
- IIC从设备功能:可编程的IIC地址检测,停止位检测
- IIC的一个优点是它支持多主控(multimastering),其中任何一个能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
- 支持不同速率的通讯速度,标准速度(最高速度100kHZ),快速(最高400kHZ)
- SCL和SDA都需要接上拉电阻 (大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。
- IIC是半双工,而不是全双工 ,同一时间只可以单向通信,IIC协议首先是发送从机硬件地址,然后发送命令,再发送数据/寄存器编号或者读取数据。IIC协议可以多字节连续读写数据。
- 各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。
IIC协议层
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答
信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,
表示已收到数据。 CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号, CPU 接
收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为
受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
IIC 总线时序图
IIC基本读写过程
起始信号S有主机的IIC接口产生,这时连接到主机的所有设备都会接收到这个信号。
起始信号产生后,所有从机开始等待主机广播下来的从机的地址信号,在总线上,所有从机的地址都是唯一的,当主机广播的地址与设备的地址匹配后,这个设备就被选中,地址不匹配的设备会忽略之后的数据信号。从机的地址信号可以时7位和10位的。
主机发送完地址信号后接着发送传输方向的选择位,0表示数据的传输方向由主机传至从机
,即主机写数据,1表示数据的传输方向由从机传至从机
,及主机读数据。
从机匹配地址后,会返回一个应答(ACK)或非应答信号(NACK),主机只有接收到从机的应答信号后,主机才能继续向从机发送数据。(写数据过程) 读数据过程即主机要向从机发送应答或非应答信号,从机只有收到主机发送的应答信号,才能继续向主机发送数据
写数据
当配置的方向传输位为“读数据”方向时,主机广播完地址,并接收到从机的应答信号时,主机开始正式向从机传输数据,一次传输的 数据大小为8位,主机每发送完一个字节后,都要等待从机的应答信号,可以重复这个过程。当像停止发送数据时,主机向从机发送一个停止信号(P),表示不在传输数据。
读数据
当配置的方向传输位为“读数据”方向时,主机广播完地址,并接收到从机的应答信号 后,从机开始向主机发送数据,数据的大小为8位,从机每发送完一个数据,都会等待主机的应答信号,可重复此过程。当主机向停止接收数据时,就向从机发送一个非应答信号(NACK),则主机自动停止发送数据。
读和写数据
IIC更常用的是复合格式,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过地址找到设备后,发送一段数据,这段数据通常用于表示该设备内部寄存器或存储器的地址;第二次传输中,则对该地址进行读或写。 (第一次告诉从机要读写的地址,第二次才真正进行数据的传输)
信号的起始(S)和停止信号(P)
当SCL为高电平SDA来一个下降沿意味着通讯的开始。当SCL为高电平SDA来一个上升沿代表通讯的结束。
初始(空闲)状态
因为IIC的 SCL 和SDA 都需要接上拉电阻,保证空闲状态的稳定性
所以IIC总线在空闲状态下SCL 和SDA都保持高电平
开始信号
SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。
//产生IIC起始信号
//1.先拉高SDA,再拉高SCL,空闲状态
//2.拉低SDA
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1; //首先把时钟线拉高
delay_us(5);
IIC_SCL=1; //首先把时钟线拉高
delay_us(5);
IIC_SDA=0;//START:在SCL为高时拉低SDA线,即为起始信号
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
停止信号
停止信号:SCL保持高电平。SDA由低电平变为高电平。
//产生IIC停止信号
//1.先拉低SDA,再拉低SCL
//2.拉高SCL
//3.拉高SDA
//4.停止接收数据
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4); //STOP:当SCL高时,数据由低变高
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
数据有效性
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。
也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输
地址及数据方向
设备地址可以是7位或11位,数据传输方向位和7位地址位一起传送。读数据方向时(相当于从机向主机写数据)由从机控制SDA线,写数据方向时,SDA由主机控制。
响应
当设备接收数据后,若希望对方继续发送,则向对方发送应答信号,发送方会继续发送下一个数据;若接收方不想接收数据时,则向对方发送非应答信号,发送方接收到非应答信号后会产生一个停止信号,结束信号传输。
传输时主机产生时钟,通讯开始后,发送端发送完地址及传输方向位信号时(前8位),第九位时数据发送端会释放SDA的控制权,由数据接收端控制SDA ,若SDA为高电平,表示产生非应答信号,低电平表示产生应答信号。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
//主机产生应答信号ACK //1.先拉低SCL,再拉低SDA //2.拉高SCL //3.拉低SCL void IIC_Ack(void) { IIC_SCL=0; //先拉低SCL,使得SDA数据可以发生改变 SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(5); IIC_SCL=0; }
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
//主机不产生应答信号NACK //1.先拉低SCL,再拉高SDA //2.拉高SCL //3.拉低SCL void IIC_NAck(void) { IIC_SCL=0;//先拉低SCL,使得SDA数据可以发生改变 SDA_OUT(); IIC_SDA=1;//拉高SDA,不产生应答信号 delay_us(2); IIC_SCL=1; delay_us(5); IIC_SCL=0; }
等待应答信号
//等待应答信号到来 //返回值:1,接收应答失败 // 0,接收应答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA)// #define READ_SDA PBin(7) //输入SDA { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//时钟输出0 return 0; }
IIC数据传输
数据传送格式
SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB)
,每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK, 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。
IIC发送一个字节
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=txd&0x80; //获取最高位
//获取数据的最高位,然后数据左移一位
//如果某位为1,则SDA为1,否则相反
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
IIC读取一个字节数据
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
IIC代码
IIC.h
#ifndef __IIC_H
#define __IIC_H
#include "sys.h"
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif
IIC.c
#include "IIC.h"
#include "delay.h"
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
IIC协议的数据读写三种方式
IIC协议单向发送数据
比如显示设备,一般情况下只需要写入数据,传输过程如下:
- 首先是起始信号S
- 然后是发送7位从机地址+传输方向位,0写
- SCL拉高,判断ACK,再拉低SCL
- 发送第1个字节,一般情况下都是指令,或者寄存器地址
- SCL拉高,判断ACK,再拉低SCL
- 发送第2个字节,一般情况下都是数据
- SCL拉高,判断ACK,再拉低SCL
- 可以继续传输数据,或者停止
IIC协议发送地址后立即读取数据
比如传感器设备,一般情况下只需要读出数据,传输过程如下:
- 首先是起始信号S
- 然后是发送7位从机地址+传输方向位,1读
- SCL 拉高,判断ACK,再拉低SCL
- 读取第1个字节,一般情况下都是指令,或者寄存器地址
- 发送应答ACK
- 读取第2个字节,一般情况下都是数据
- 发送应答ACK,或者不应答NACK
- 可以继续传输数据,或者停止
IIC协议先制定寄存器地址,再读取该寄存器的数据
比如存储芯片,一般情况下要先制定存储地址,再读取数据。传输过程如下:
- 首先是起始信号S
- 然后是发送7位从机地址+传输方向位,0写
- SCL 拉高,判断ACK,再拉低SCL
- 写数据,一般是写某个寄存器地址或者指令
- SCL拉高,判断ACK,再拉低SCL
- 再次起始信号S
- 然后是发送7位从机地址+传输方向位,1读
- SCL拉高,判断ACK,再拉低SCL
- 读取
第四步
写的寄存器地址的数据 - 发送应答ACK,或者不应答NACK
- 可以继续传输数据,或者停止。
IIC实现的方式
IIC实现的方式主要有两种,硬件IIC和软件模拟IIC
软件IIC是使用程序控制SCL,SDA线输出高低电平,模拟i2c协议的时序。一般较硬件IIC稳定,但是程序较为繁琐,但不难。
硬件IIC程序员只要调用IIC的控制函数即可,不用直接的去控制SCL,SDA高低电平的输出。但是有些单片机的硬件IIC不太稳定,调试问题较多。
硬件IIC、软件模拟IIC的区别
模拟IIC | 硬件IIC | |
---|---|---|
用法 | 流程更清楚一些 | 用法比较复杂 |
速度 | 速度快、效率高、可以使用DMA | |
适用管脚 | 任何管脚 | 固定管脚 |
稳定性 | 稳定 | 不太稳定 |
给那些看完的朋友,奖励一个 赤赤博客-后端+前端,觉得不错的话可以推荐给身边的朋友哟!