1.问题描述
IIC在既有的工作中,用来协调两个外围芯片。一个芯片扩展IO,一个处理显示。仔细核对了IIC的种种配置,并且最终测量了输出的时钟和数据波形,最终的时钟线波形是这样的:
单片机master模式,输出前面的地址部分完毕后, 无回应,电平直接拉高,只传递了一个地址帧,通讯就终止了。为了防止虚焊造成的地址错误,我遍历了0x00~0xff的地址。均无回应。
下面是数据线(因为始终收不到回应帧,我发出了0x00帧——请求地址帧)。
我记不清IIC的时钟频率有没有变动过。这个震动频率看时钟线是93Kz,接近100KHz。数据线,光标所在的那个地方,看起来像是一个应答,对吧?master没有识别到?
2. IIC协议
- Start Bit 是1=》0.IIC地址线和数据线均常1.
- MSB first Transmit.
- Read/Write Bit master 要读取是1,要写入是0.
- ACK is low... to indicate peripheral is ready.
- TransferBit is switched at CLOCK is low. and read at CLK is High.
- Peripheral should ack for each bytes.
2.1识读
所以,上面的示波器数据,时钟震荡时的高位,标志着数据Bits的传输,
前6个Bit,确定无疑,都是0.第7Bit,0.第8Bit1,第9Bit1....9Bit之后,时钟未震荡的第10Bit,数据总线出现0.然后延续大概5个Bits.时钟线复位。
3.分析
上面是一个寄存器读,对比器件的IIC时序:
看这个示例,我的做法似乎不对。
正确的做法是:
Step1.以写模式,输出两个字节:第一个字节:Addr(读写位为0,标志写),第二个字节,寄存器编号。
Step2.以读模式,输出1个字节:第一个字节:Addr(此时读写位为1,标志读),然后读入1个字节,即可。
我的做法,相当于直接:
以读模式,输出1个字节的地址位,然后想要再次写出寄存器编号,没有遇到应答直接报错。
4.纠正
4.1将代码修正为两段模式
双探针不好操作,我单独测了clk和dat,然后人工对齐,大概是上面这张图这样的。
我的实际代码是:
#define PCA9554ADDR (0x70)
#define REG_IN 0
GP_KEYS GetKeyBoardScanCodes(void)
{
uint8_t dumb[2];
dumb[0] = PCA9554ADDR;
dumb[1] = REG_IN;
I2C1_WriteNBytes(PCA9554ADDR, dumb, 2);
dumb[1] = 0xcc;
I2C1_ReadNBytes(PCA9554ADDR, &dumb[1], 1);
return (uint8_t)dumb[1];
}
感觉还是不对。
Write2Bytes时,第一组地址,依稀是0x70。送出后,回执收到没,不确定。那个9Bits之后的凹陷,不知道是什么。第一组的第二个字节,未发出。
第二次发送时,地址部分因为最后一个字节变为1. clk-dat实际对齐后应该是这样,回执肯定是1.
没被确认。
查找代码发现PIC代码里的Addr的定义与我的定义不同。我采用的是左对齐的模式,实际上,7位地址应该右对齐,修改地址字段后,时钟线看起来已经正常:
4.2修改7位地址为右对齐模式:
现在时钟线看起来正常。先发两个字节,然后通知设备要读,设备读完1个字节,连续的8个bit后主机延时应答的第9个bits也是正常的。
但是读到的Bits位不对,试试改为PIC示例代码的寄存器读,时钟线:
4.3改为PIC自带的I2C寄存器读模式
成功:读写均正常。
4.结论
- I2C只有两根线。发生故障时首先查时钟线的反馈是更好的策略。
- I2C的一个完整的Byte传输会有9个bits,最后一个bit因为可能涉及master和slave的交互,如果有交互,时钟线会被master首先设置为浮动高阻,然后slave会拉低,直到准备好拉高回应。
- I2C示波器读取时,Bit位在高电平处读取。
- 时钟和数据线平时常高。
- 大多数地址字节都是在低7Bits。不要弄错。