IIC总线
7位I2C总线可以挂接127个不同地址的I2C设备,0号"设备"作为群呼地址.
第一个字节(为slave address)由7位地址和一位R/W读写位组成的,这字节是个器件地址。
常用IIC接口通用器件的器件地址是由种类型号,及寻址码组成的,共7位。
如格式如下:
D7 D6 D5 D4 D3 D2 D1 D0
1-器件类型由:D7-D4 共4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的。
2-用户自定义地址码:D3-D1共3位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IIC的3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。
所以为什么同一IIC总线上同一型号的IIC只能最多共挂8片同种类芯片的原因了。
3-最低一位就是R/W位。
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
起始和终止信号 :SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
数据传送格式
(1)字节传送与应答
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。
最后一位R/W为告诉从机下一字节数据是要读还是写,0为写入,1为读出。
任一地址读取数据格式
硬件IIC与模拟IIC
硬件IIC中程序要等接口芯片的应答电平,等不到就死等,所以会卡死(估计未加超时处理所致),模拟IIC中的等待电平是cpu自己产生的,错过了就错过了,下次再来。如果说移植性,那完全在你的程序是否写得好了,和接口形式没关系。看IIC协议先:两条线可以挂多个设备。IIC设备(稍微有点智能的)里有个固化的地址。只有在两条线上传输的值等于我(IIC设备)的地址时,我才作出响应。
开始信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号它就知道处理器要发送数据了。
停止信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号它就知道处理器已经结束了数据传输,我们就可以各忙各个的了,如休眠等。
再看数据怎么传:SDA上传输的数据必须在SCL为高电平期间保持稳定:因为外接IIC设备在SCL为高电平的期间采集数据方知SDA是高或低电平。SDA上的数据只能在SCL为低电平期间翻转变化。
响应信号(ACK):处理器把数据发给外接IIC设备,如何知道IIC设备数据已经收到呢?就需要外接IIC设备回应一个信号给处理器。处理器发完8bit数据后就不再驱动总线了(SDA引脚变输入),而SDA和SDL硬件设计时都有上拉电阻,所以这时候SDA变成高电平。那么在第8个数据位,如果外接IIC设备能收到信号的话接着在第9个周期把SDA拉低,那么处理器检测到SDA拉低就能知道外接IIC设备数据已经收到。
IIC数据从最高位开始传输。
再进一步说:IIC总线是允许挂载多个设备的,如何访问其中一个设备而不影响其他设备呢?
用7bit表示从地址,那么可以挂载的从设备数是2的7次方128个。处理器想写的话:先发送起始位,再发一个8bit数据:前7bit表示从地址,第8bit表示读或者写。0 write是处理器往IIC从设备发,1read是IIC从设备往处理器发。第9个时钟周期回复响应信号。
下面就以AT24Cxx为例详细说明一下:
首先发出一个start信号,从设备地址,R/W(0,写),回应ACK表示有这个从设备存在。这时候是处理器从指定的从设备读数据的从设备里8bit存储地址的指定。所以这里R/W是0为写。ACK回应有这个设备的话,处理器把要访问的从设备里的8bit存储地址写好。ACK对方回应。继续一个start信号+从设备地址,最低位是高电平表示读数据,回应ACK表示有这个从设备存在。在读数据的时候,每发出一个时钟,处理器会SDA上的数据存起来。那么发出8个时钟后处理器就能得到8位的数据。这时候若想连续读就不断回应ACK信号否则就发出停止信号。
读的过程:start信号,从设备地址,写,待读取存储地址,再一个start信号,从设备地址,读,8个时钟,从设备就把对应的数据反馈给处理器。
start信号,哪一个设备地址,写,紧跟连续两个字节的数据:要写的地址,对方收到8bit地址后回应ACK,再8bit数据发给从设备,对方收到8bit数据后回应ACK,处理器写完后发送停止信号。
1.传感器读取
MPU6050传感器与控制器的通信方式是I2C总线协议。该协议有两根信号线,分别是数据线和时钟线,时钟线是为数据的存储位置做基准,数据线根据时钟线的第几个时钟沿确定该位是第几位数据,从而达到接收和发送数据的目的。I2C总线时序图4.4所示。
图4.4 IIC总线时序图
STM32系列控制器芯片对传感器MPU6050的数据读取包括加速度和角速度两部分,控制器内虽然已经有了硬件I2C接口,但是为了方便程序的可移植性和便于调试,本设计使用普通I/O口的模拟I2C,即根据I2C的协议自己编写软件协议程序控制时序图。具体的读取数据指令如下:
IICreadBytes(MPU_ADDRESS,MPU6050_RA_ACCEL_XOUT_H,14,buffer)
MPU6050_Lastax=(((int16_t)buffer[0]) << 8) | buffer[1];
MPU6050_Lastay=(((int16_t)buffer[2]) << 8) | buffer[3];
MPU6050_Lastaz=(((int16_t)buffer[4]) << 8) | buffer[5];
//跳过温度ADC
MPU6050_Lastgx=(((int16_t)buffer[8]) << 8) | buffer[9];
MPU6050_Lastgy=(((int16_t)buffer[10]) << 8) | buffer[11];
MPU6050_Lastgz=(((int16_t)buffer[12]) << 8) | buffer[13];
其中MPU_ADDRESS为 MPU-6050 的 I2C 总线地址,MPU_RA_ACCEL_XOUT_H则为 X 轴加速度高位寄存器在 MPU-6050 中的地址,微控制器操作 I2C 总线从开始地址为MPU_RA_ACCEL_XOUT_H的地方读取 14个字节存入缓存器buffer中,这14个字节的前6个字节是3个轴的加速度数据,后6个字节是3个轴的角速度数据,中间的2个字节是温度传感器数据,我们用不到所以舍弃。将得到的数据根据高位在前,低位在后的原则进行数据合成,得到了三个轴的16bit的原始传感器测量值。
//产生IIC起始信号
voidIIC_Start(void)
{
SDA_OUT(); //sda线输出
delay_us(1);
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA changeform high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
voidIIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA changeform low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8IIC_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应答
voidIIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
voidIIC_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,无应答
voidIIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{ IIC_SDA=(txd&0x80)>>7;
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
u8IIC_Read_Byte(unsigned char ack)
{
Unsignedchar 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;
}
/*功能:读取指定设备 指定寄存器的一个值输入I2C_Addr 目标设备地址addr寄存器地址返回读出来的值*/
Unsignedchar
I2C_ReadOneByte(unsignedchar I2C_Addr,unsigned char addr)
{
unsigned char res=0;
IIC_Start();
IIC_Send_Byte(I2C_Addr); //发送写命令
res++;
IIC_Wait_Ack();
IIC_Send_Byte(addr); res++; //发送地址
IIC_Wait_Ack();
//IIC_Stop();//产生一个停止条件
IIC_Start();
IIC_Send_Byte(I2C_Addr+1); res++; //进入接收模式
IIC_Wait_Ack();
res=IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
return res;
}
u8MPU_Read_Byte(u8 reg)
{
u8 res;
IIC_Start();
IIC_Send_Byte((0x68<<1)|0);//发送器件地址+写命令
IIC_Wait_Ack(); //等待应答
IIC_Send_Byte(reg); //写寄存器地址
IIC_Wait_Ack(); //等待应答
IIC_Start();
IIC_Send_Byte((0x68<<1)|1);//发送器件地址+读命令
IIC_Wait_Ack(); //等待应答
res=IIC_Read_Byte(0);//读取数据,发送nACK
IIC_Stop(); //产生一个停止条件
return res;
}
/*
u8IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)
*功能:读取指定设备 指定寄存器的 length个值
输入 dev 目标设备地址
reg寄存器地址length 要读的字节数*data 读出的数据将要存放的指针返回 读出来的字节数量*/
u8IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)
{
u8 count = 0;
IIC_Start();
IIC_Send_Byte(dev); //发送写命令IIC_Wait_Ack();
IIC_Send_Byte(reg); //发送地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(dev+1); //进入接收模式
IIC_Wait_Ack();
for(count=0;count<length;count++)
{ if(count!=length-1)
data[count]=IIC_Read_Byte(1);//带ACK的读数据
else data[count]=IIC_Read_Byte(0);//最后一个字节NACK}
IIC_Stop();//产生一个停止条件
return count;
}
/*将多个字节写入指定设备 指定寄存器输入dev目标设备地址reg寄存器地址length 要写的字节数*data 将要写的数据的首地址返回是否成功
*/
u8IICwriteBytes(u8 dev, u8 reg, u8 length, u8* data)
{
u8count = 0;
IIC_Start();
IIC_Send_Byte(dev); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(reg); //发送地址
IIC_Wait_Ack();
for(count=0;count<length;count++)
{IIC_Send_Byte(data[count]);
IIC_Wait_Ack();
}
IIC_Stop();//产生一个停止条件
return 1; //status == 0;
}
/*读取指定设备 指定寄存器的一个值输入dev目标设备地址reg 寄存器地址*data读出的数据将要存放的地址返回1
*/
u8IICreadByte(u8 dev, u8 reg, u8 *data)
{*data=I2C_ReadOneByte(dev,reg);
return 1;
}
Unsignedchar
IICwriteByte(unsignedchar dev, unsigned char reg, unsigned char data){
return IICwriteBytes(dev, reg, 1,&data);
}
/**************************实现函数********************************************
*函数原型: u8 IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8length,u8 data)
*功 能: 读修改 写 指定设备 指定寄存器一个字节 中的多个位
输入 dev 目标设备地址
reg 寄存器地址
bitStart 目标字节的起始位
length 位长度
data 存放改变目标字节位的值
返回 成功 为1
失败为0
*******************************************************************************/
u8IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8 length,u8 data)
{
u8 b;
if (IICreadByte(dev, reg, &b) != 0) {
u8 mask = (0xFF << (bitStart +1)) | 0xFF >> ((8 - bitStart) + length - 1);
data <<= (8 - length);
data >>= (7 - bitStart);
b &= mask;
b |= data;
return IICwriteByte(dev, reg, b);
} else {
return 0;
}
}
/**************************实现函数********************************************
*函数原型: u8 IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8data)
*功 能: 读修改 写 指定设备 指定寄存器一个字节 中的1个位
输入 dev 目标设备地址
reg 寄存器地址
bitNum 要修改目标字节的bitNum位
data 为0时,目标位将被清0 否则将被置位
返回 成功 为1
失败为0
*******************************************************************************/
u8IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8 data){
u8 b;
IICreadByte(dev, reg, &b);
b = (data != 0) ? (b | (1 << bitNum)): (b & ~(1 << bitNum));
return IICwriteByte(dev, reg, b);
}
//------------------Endof File----------------------------