#申请原创# @21小跑堂
网友反应在硬件时钟设置较高的时候驱动SSD1306控制器的OLED模块时候无法工作,同样在设置I2C时钟为高速模式时候(1MHz,实际配置为800KHz)也无法工作。
那么这是什么情况呢?
经过我的实验总结如下:
SSD1306通常仅支持100Khz的低速模式和400KHz的中速模式。
查询相关资料内容:SSD1306 i2c的最大速度取决于具体的硬件和软件实现。 一般来说,它支持标准模式(100 kHz)和快速模式(400 kHz)的I2C通信。
所以设置为高速模式无法正常工作。
这是第一个解决的问题,即使用OLED模块时候,硬件I2C的速度推荐设置为400KHz。
接下面分析为何MCU系统时钟设置为高速的时候无法正常工作。
经过阅读MCC的源代码,I2C的通信为非阻塞通信,即不会卡顿在某处等待,如果不满足条件就会跳过去。所以如果系统时钟速度设置过高,导致在I2C从机还没来得及反应的时候就执行完毕了,这个时候总线繁忙,会自动跳过。相关库函数代码如下:
bool I2C1_Write(uint16_t address, uint8_t *data, size_t dataLength)
{
bool retStatus = false;
if (!I2C1_IsBusy())
{
i2c1Status.busy = true;
i2c1Status.address = address;
i2c1Status.switchToRead = false;
i2c1Status.writePtr = data;
i2c1Status.writeLength = dataLength;
i2c1Status.readPtr = NULL;
i2c1Status.readLength = 0;
i2c1Status.errorState = I2C_ERROR_NONE;
I2C1_WriteStart();
retStatus = true;
}
return retStatus;
}
如何解决这个不同步问题呢?修改库函数?NO,我是不主张修改的,因为非阻塞可以防止整个系统卡死在某处。
经过尝试可以在每次调用完该函数后进行一定时间的延时。
经过测试,在系统时钟设置为最高速度64MHz的情况下,每次调用I2C写函数后延时50个系统周期可以正常通信,当低于49后开始不稳定。
void OLED_Write_cmd(uint8_t cmd)
{
uint8_t cmd2[2];
cmd2[0] = 0x00;
cmd2[1] = cmd;
I2C1_Write(0x3C, cmd2, 2);
DELAY_microseconds(50);
}
void OLED_Write_data(uint8_t data)
{
uint8_t data2[2];
data2[0] = 0x40;
data2[1] = data;
I2C1_Write(0x3C, data2, 2);
DELAY_microseconds(50);
}
这里推荐在64Mhz时钟下延时50,该函数并非是50us经过测试该函数参数对应位System Clock的时钟周期数。
8MHz系统时钟下,设置延时参数为1
16MHz系统时钟下,设置延时参数为6
其他时钟下,请参考以上范围调整到最小合适值即可。
总结:很多人说硬件I2C不好用,其实不是不好用,是很多时候没有能严格按照通信协议规范设置相关参数,比如常用的这个OLED模块,经常用于一些显示,比如文中提到的这个单片机,如果你没能很好的设置相关参数,并对时钟进行同步的延时操作,就无法正常高速点亮屏幕,你会以为是硬件的I2C不好用,其实不是。该文相信可以为很多用I2C硬件不好用的朋友提供一份参考,相信以后的项目中硬件I2C越来越流行。
|
文章针对PIC单片机的硬件I2C驱动OLED显示出现的问题给出一个解决方案,通过延时的方式等待数据完整通信,当然延时方式二姨也不认为是个好的方式,但是作者通过与评论区的互动,增加了超时检测,在保证通信完整性的同时防止程序卡死。这是比较好的一种方式,现在大多单片机的硬件I2C通信都会采用的方式。作者积极听取意见并给与改进,态度值得肯定。