I2C_SCL 时钟抖动问题之“if条件判断分支” 软件优化
I2C_SCL 时钟抖动问题之“if条件判断分支” 软件优化
一开始,我先给出一个比较常见的GPIO模拟的I2C的I2C_writeByte的代码,代码是从南京沁恒CH450的网站上download下来的推荐代码。
void CH450_I2c_WrByte(unsigned char dat) //写一个字节数据
{
unsigned char i;
CH450_SDA_D_OUT; /* 设置SDA为输出方向 */
for(i=0;i!=8;i++) // 输出8位数据
{
if(dat&0x80) {CH450_SDA_SET;}
else {
CH450_SDA_CLR;
}
DELAY_0_1US; // 可选延时,这边对应的为一个指令周期
CH450_SCL_SET;
dat<<=1;
DELAY_0_1US; // 可选延时,这边对应的为一个指令周期
CH450_SCL_CLR;
}
CH450_SDA_D_IN; /*设置SDA为输入方向 */
CH450_SDA_SET;
DELAY_0_1US;
CH450_SCL_SET; //接收应答
DELAY_0_1US;
CH450_SCL_CLR;
}
使用keil的逻辑分析工具,查看I2C波形,SCL一个周期内放大,发现仿真输出结果有差异。
SDA为高变低的时候,CLK从之前拉高到之后拉高的周期时间,经历了14个指令周期(如上图所示,水平方向,1Grid=0.5us,即24M条件下的一个指令周期)
SCL从拉高,保持高,到拉低:5个指令周期
SCL从拉低(保持低)~SDA拉低,7个指令周期
SCL继续保持低~SCL拉高:2个指令周期
SDA为高变低的时候,CLK从之前拉高到之后拉高的周期时间,经历了16个指令周期(如上图所示,水平方向,1Grid=0.5us,即24M条件下的一个指令周期)
SCL从拉高,保持高,到拉低:5个指令周期
SCL从拉低(保持低)~SDA拉低,7个指令周期SCL继续保持低
SCL拉高:4个指令周期
对比两个情况的差异:SDA在SCL拉高以后第(5+7)个指令周期以后采样SDA的值时:
如果为低,则只执行2个汇编指令周期,
如果为高则后面运行4个汇编指令周期,
【原因分析】:汇编代码
中间的区别就是在判断分支上面的差异问题;
从上图中我们可以看到如果SDA检测为高,拉高SDA候,会运行SJMP C:095F指令,这个指令为两个指令周期,而else 也就是SDA为低时,拉低SDA候,程序自动就定位到C:095F 位置上,因此会导致差异!
【可能造成的不良影响】
实际上,虽然差异只有2个指令周期的时间,在24M时钟的情况下,Tclk的差异只有1us。但是这个CLK jitter是程序差异引入的,不是器件本身导致的,所以这个jitter是可以避免的。
另外,采样时,SDA高时,Tclk=14指令周期=7us; 对应T=142.857K
SDA低时,TCLK=16指令周期=8us;对应T=125K
差异,实际上已经很明显了!
更为关键的是,这个时钟抖动,其实跟晶振震荡关系不大,不管单片机用的是12M,或者11.0592M,亦或是24M,33M等,CLK抖动都是2个指令周期,以16个指令周期来算,变化率达到了1/8,12.5%的变化抖动,示波器应该是能够看出差异的。
【结论】
从上面分析的过程来看,由于单片机判断语句的分支差异,(我给出的示例if条件判断两个分支差异在汇编语句中,就差一个SJMP语句,也就是两个指令周期的时间)在驱动代码中的差异其实是不可忽略的,它会导致CLK产生时钟抖动的问题;
【解决方法&改善对策】
既然if条件判断,分支差异导致了2个指令周期的时间差异,我将SDA为低时的else语句中,增加了2个_NOP_()语句,从而弥补了差异。
这样,不管条件判断为真还是假,TCLK的时钟周期都是一样的,实际仿真的结果应该是一样的。