一、硬件配置相关
IIC的SCL和SDA都要求接上拉电阻,也即,只要主机和从机都还没开始干活,那么SCL和SDA就都是空闲的(默认的高电平状态被称为“空闲”,也可以称总线被“释放”),同理,SCL或SDA为low时,就称总线被占用或正忙。
IIC协议要求:SCL和SDA必须都是【开漏+上拉】!
简单些一下开漏和推挽的区别:参考《从硬件分析推挽输出和开漏输出详细区别》
(1)推挽:写1时,GPIO被导通到VCC,写0时GPIO被导通到GND
(2)开漏:写1时,GPIO被导通到悬空的漏极,写0时GPIO被导通到GND。
也即IO开漏时,要想写1驱动LED,必须得接上拉电阻,要不然这个IO上写1时,是高阻态,虽然电压表测出了电压,但仍然无法驱动LED,就好像这个IO是悬空似的。
下面讲讲为什么IIC必须要接上拉?
换句话说,IIC的从器件(如24C04等),不具备拉高总线的能力,从机控制SDA上的电平,只能通过令SDA接地或不接地来实现输出0或1,而无法通过接地或接VCC来实现输出0或1。这就要求IIC总线上必须是默认上拉的,否则从机无法令SDA为高。
从另一方面讲:只有开漏上拉,才能满足“线与”逻辑,也即SDA线上多个从机中的任何一个拉低了SDA,就会导致SDA为0。
主机为什么使用开漏?试想,
主机如果使用了推挽,当主机释放SDA时(令SDA=1时),相当于SDA被接通到VCC,这时从机无法控制SDA为0;
主机如果使用了开漏,当主机释放SDA时(令SDA=1时),相当于SDA被悬空上拉到VCC,这时从机可以拉低SDA,使SDA=0,也可以释放SDA使SDA=1,这样从机才能发数据。
void i2c_io_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(IIC_APB1Periph, ENABLE);
GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN | IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ; //输出,读SDA时要改成输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//IIC要求必须上拉
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;//IIC要求必须开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_GPIOx, &GPIO_InitStructure);
GPIO_SetBits(IIC_GPIOx, IIC_SCL_PIN | IIC_SDA_PIN);//开漏写1上拉,释放总线
}
为了能任意控制SCL的频率,建议用IO模拟时,把delay函数的实参定义为宏或变量,以便于修改。一般常用的SCL频率为100KHZ~400KHZ。
二、软件操作相关(硬件IIC)
用的是3.5版本的固件库
固件库中有些宏的名字起得并不好,这里主要做一些注释,方便以后查阅。而且还发现固件库做的各种检查实在是很繁琐,比如检查字节是否发送完毕,库函数不仅会检测字节是否发送完毕,还顺便同时检测5/6个比特位。如果对程序的速度需要优化的话,可以在库函数的基础上自己改改。
每次执行IIC操作后,都要检查操作的结果,检查所用的函数为I2C_CheckEvent(I2Cx, event),其中event为4B,高2B是I2C_SR2寄存器,低2B为I2C_SR1寄存器。
1、发送起始信号
I2C_GenerateSTART(I2C2,ENABLE);//起始信号
while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT));//等待起始信号完毕
其中I2C_EVENT_MASTER_MODE_SELECT宏代表SR2=b_0000_0011,SR1=b_0000_0001,也即SR共有3个bit置位,分别代表:IIC正在通信(忙标志)、当前为主机模式、起始信号已发送完毕。
PS:IIC的忙标志位SR2.1是由硬件自动置位和清除的,只要SCL或SDA中出现低电平,该bit就会被置1。
2、发送片选信号(写)
I2C_Send7bitAddress(I2C2, DB5883_CHIP_ADDR,I2C_Direction_Transmitter);//发送片选地址+写信号
while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//等待片选字节发送完毕,且等待从机的ack
其中I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED代表SR2=b_0000_0111,SR1=B_1000_0010,共5 bits置位,分别代表:数据已发送、正忙标志、是主机模式、数据寄存器已空、片选地址已发送(且已收到从机的ack)
3、发送片选信号(读)
I2C_Send7bitAddress(I2C2, DB5883_CHIP_ADDR,I2C_Direction_Receiver);//发送片选地址+读信号
while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));//等待片选地址发送完成,且等待从机ack
其中I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED代表SR2=b_0000_0011,SR1=B_0000_0010,共3 bits置位,分别代表:正忙标志、是主机模式、片选地址已发送(且已收到从机的ack)
4、发送一个字节的数据
I2C_SendData(I2C2,REG_Address);
while(!I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//等待字节发送完毕,且等待从机ack
I2C_EVENT_MASTER_BYTE_TRANSMITTED代表SR2=b_0000_0111,SR1=B_1000_0100,共5 bits置位,分别代表:数据已发送、正忙标志、是主机模式、数据寄存器已空、收到了从机的ack。
5、设置主机接收到一个字节后要发送Ack还是NoAck
I2C_AcknowledgeConfig(I2C2, DISABLE);//不发送Ack(也即发送NoAck)
6、读取从机发来的字节
//等待读信号(选寄存器)信号发送完成
I2C_AcknowledgeConfig(I2C2, DISABLE);//设置为NoAck模式
while(!(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)));//直到接收完从机发来的字节
REG_data=I2C_ReceiveData(I2C2);//读出寄存器数据
I2C_GenerateSTOP(I2C2,ENABLE);//停止信号
有些iic从机支持被主机连续读多个字节,一般这种设备在被连续读时,主机每收到从机的一个字节,主机都要向从机发送ACK,直到读完最后一个字节,主机向从机发送NoAck,这时从机会停止发送,从机会等待主机发起stop信号或从机自动超时等待新的start
三、软件操作相关(软件模拟IIC)