利用GPIO口模拟I2C总线的时序以及代码实现。可以直接移植!
时间:10-02
整理:3721RD
点击:
/*
作者:天空
日期:2014.5.12
功能:利用GPIO口模拟I2C总线,对传感器寄存器读取数据
注意:如果需要移植些文件到其他设备上,需要更改的函数地方
1、利用GPIO口模拟的数据线SDA和时钟线SCL,注意需要更改GPIO口
2、从设备地址。不同的从设备,地址不一样,注意修改。(在I2C写寄存器和读寄存器的函数中,如果是写,在读的地址上加1.)
经验:利用GPIO口模拟I2C总线时,需要注意的是,在应答信号和读数据的时候,一定要将SDA数据线配置为输入。
*/
#define SCL P0_5//定义P0.5口为时钟线
#define SDA P0_4//定义P0.4口为数据线
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;
/*延时函数*/
void Delay(void)
{
}
/*I2C总线实始化,将P0.4和P0.5配置为输出口,并且拉高*/
/*
I2C总线的开始时序:
1、将数据线SDA和时钟线SCL全部拉高
*/
void I2C_Init(void)
{
P0SEL &= ~0x30;//将P0.4和P0.5配置为GPIO口
P0DIR |= 0x30;//将P0.4和P0.5配置为输出
P0 |= 0x30;//将P0.4和P0.5设置为高电平
}
/*I2C总线的开始信号*/
/*
I2C总线的开始时序:
1、将SDA数据线拉高
2、将SCL时钟线拉高,延时一段时间
3、在SCL为高电平时,拉低SDA,延时一段时间
总结:就是在SCL为高电平时,在SDA上给一个下降沿,表示开始信号。
*/
void I2C_Start(void)
{
SDA = 1;
SCL = 1;
Delay();
SDA = 0;
Delay();
}
/*I2C总线的停止信号*/
/*
I2C总线的停止时序:
1、将SDA数据线拉低
2、将SCL数据线拉高,延时一段时间
3、在SCL为高电平时,将SDA数据线拉高,延时一段时间
总结:在SCL为高电平时,在SDA上给一下上升沿,表示停止信号。
*/
void I2C_Stop(void)
{
SDA = 0;
SCL = 1;
Delay();
SDA = 1;
Delay();
}
/*I2C总线的应答信号*/
/*
I2C总线应答时序:
1、首先是需要将模拟数据线的SDA口配置为输入口。
2、将时钟线SCL拉高,延时一段时间
3、等待从设备应答,从设备应答时,SDA上会产生一个高电平,主设备的GPIO口检测到高电平,则表示应答成功。
4、如果检测到应答信号,则拉SCL时钟线拉低。延时一段时间。
5、将模拟SDA口的GPIO口配置为输出。
*/
void I2C_Ack(void)
{
P0DIR &= ~0x10;//注意这里,数据线应该设置为输入
uint i = 0;
SCL = 1;
Delay();
//等待应答
while((SDA == 1)&&(i<255))
i++;
SCL = 0;
Delay();
P0DIR |= 0x10;//将数据线SDA设置为输出
}
/*I2C总线写数据函数,将要写的数据传递给形参I2CBuf*/
/*
I2C写数据时序:
注意:由于I2C属于串行总线,所以数据都是一位一位的传输
1、将SCL时钟线拉高,准备传输数据,延时一段时间
2、将I2CBuf的最高位传输到SDA数据线上
3、拉低SCL时钟线,延时一段时间
小结:当SCL有一个上升沿时,传输数据。
以上三步表示传输完一位数据。然后将I2CBuf左移一位,再重复以上三步。
最后,将SCL位低,延时一段时间,再将SDA位高,延时一段时间。此操作是为了释放时钟线SCL和数据线SDA.
*/
void I2C_Write(uint I2CBuf)
{
uint iwi;
for(iwi = 0; iwi < 8; iwi++)
{
SCL = 0;
Delay();
if(I2CBuf & 0x80)
SDA = 1;
else
SDA = 0;
SCL = 1;
Delay();
I2CBuf = I2CBuf << 1;
}
SCL = 0;
Delay();
SDA = 1;
Delay();
}
/*I2C总线读数据函数,该函数具有返回值I2CBuf*/
/*
I2C读数据函数的时序:
注意:首先需要将模拟SDA数据线的GPIO口配置为输入
1、将时钟线SCL拉高,延时一段时间
2、读取数据线SDA上的电平,然后写入I2CBuf的最低位。
3、拉低SCL时钟线,延时一段时间
小结:当SCL有一个下降沿时,读取数据。
以上三步表示读取一位数据,读一个字节,需要重复以上操作步骤8次。
*/
uint I2C_Read(void)
{
uint iri;
uint I2CBuf = 0;
P0DIR &= ~0x10;//注意这里,需要将SDA配置为输入。
Delay();
for(iri = 0; iri < 8; iri++)
{
I2CBuf = I2CBuf << 1;//此步不可少
SCL = 1;
Delay();
if(SDA == 1)
I2CBuf |= 0x01;
else
I2CBuf &= 0xfe;
SCL = 0;
Delay();
}
P0DIR |= 0X10;//将SDA配置为输出
return I2CBuf;//将读到的数据存入I2CBuf中,然后返回给主调函数
}
/*
I2C总线写寄存器函数,将寄存器地址传给形参RegAdd,将要写入的数据传递给RegValue。
注意:地址和数据最好都采用十六进制
*/
/*
I2C写寄存器函数的时序:
1、主设备先发出一个开始信号
2、然后写入从设备地址。每一个从设备的地址是唯一的。
3、主设备接收应答信号
4、主设备向从设备写入寄存器的地址
5、主设备接收应答信号
6、主设备向从设备写入数据
7、主设备接收应答信号
8、主设备发出停止信号
*/
void I2C_WriteReg(uint RegAdd, uint RegValue)
{
I2C_Start();
I2C_Write(0xd0);//0xd0是从设置的地址,不同的设备,地址不同。所以移植些函数时,需要更改此处。
I2C_Ack();
I2C_Write(RegAdd);//先写地址,此地址为需要写入数据的寄存器地址
I2C_Ack();
I2C_Write(RegValue);//写入数据
I2C_Ack();
I2C_Stop();
}
/*I2C总线读寄存器函数,将需要读取数据的寄存器的地址传递给形参RegAdd,此函数有返回值*/
/*
I2C读寄存器函数的时序:
1、主设备发出开始信号
2、写入从设备地址
3、主设备接收从设备发来的应答信号
4、写入寄存器地址
5、主设备接收应答信号
6、主设备再次发出开始信号
7、主设备向从设备写入(从设备的地址+1),此时加1表示要从寄存器中读数据
8、从设备给出应答信号
9、从从设备的寄存器中读出数据传给变量
10、主设备发出停止信号
11、返回读出的数据
*/
uint I2C_ReadReg(uint RegAdd)
{
uchar date;
I2C_Start();
I2C_Write(0xd0);//先写入从设备的地址。表示要向从设备中写地址和数据
I2C_Ack();
I2C_Write(RegAdd);//写入寄存器的地址,数据将从些地址中读出
I2C_Ack();
I2C_Start();
I2C_Write(0xd1);//从设备的地址加1,表示要读从设备中寄存器的数据
I2C_Ack();
date = I2C_Read();//从地址RegAdd中读出数据
I2C_Stop();
return date;//将读出的数据存入date中,然后返回给主调函数。
}
感谢分享,回去试一试
好东西啊啊啊啊啊啊啊啊啊啊啊啊啊
国外伺服技术方案转让
编码器采用研发17-bit(160000ppr)高分辨率增量编码器;
转子磁铁采用稀土永磁材料;高速冲压模日本进口矽钢片铁芯
内置位置、速度和扭矩控制模式,支持外部模拟量控制;匹配公司自主研发17-bit高分辨率增量编码器;支持RS232、RS485通讯规格,标准Modbus通讯协议;精密控制、性能稳定、性价比高。
采用抗干扰和防震动设计,适用范围广,性能稳定;类型包含17-bit、20-bit增量式和绝对值编码器。
产品广泛应用于制造业(攻丝机、绕线机、雕铣机、数控车床、数控加工中心、线切割机、铣床、磨床、注塑机、绣花机、纺织机、包装机、印刷机、激光雕刻机、木工机械等),工业自动化(机器人、机械手臂、生产线自动化飞镖机械等),航空业、汽车工业、新能源(风能、电动汽车等)以及其他机械自动化工控领域。
扣扣:二五一二二六二四七一
不好意思,请问您能跟我具体说一下是怎么操作吗?因为我现在有一个传感器,然后需要用到GPIO口模拟I2C,开发板是TQ2440的,在其上移植了Linux系统。所以我的传感器驱动是按照接I2C的样子写还是写寄存器那样的驱动?
这里想请教一下,我看到其他的教程上说从设备ACK应答时,SDA是被拉低的,为什么您的应答时序是SDA是拉高的