发新帖本帖赏金 50.00元(功能说明)我要提问
12下一页
返回列表
打印
[PIC®/AVR®/dsPIC®产品]

如何解决PIC单片机的硬件I2C在驱动OLED显示屏时候的不好用问题

[复制链接]
1074|40
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创# @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越来越流行。


使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2024-01-11
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-1-11 17:51 回复TA
文章针对PIC单片机的硬件I2C驱动OLED显示出现的问题给出一个解决方案,通过延时的方式等待数据完整通信,当然延时方式二姨也不认为是个好的方式,但是作者通过与评论区的互动,增加了超时检测,在保证通信完整性的同时防止程序卡死。这是比较好的一种方式,现在大多单片机的硬件I2C通信都会采用的方式。作者积极听取意见并给与改进,态度值得肯定。 
沙发
lcczg| | 2024-1-9 11:50 | 只看该作者
本帖最后由 lcczg 于 2024-1-9 11:52 编辑

我的理解应该是写入时间间隔的问题。在你的例子中,400KHz的速率下写入三个字节需要400/3/8=16.7KHz,等同于60uS.这应该就是你的50uS延迟加大概10uS的程序执行时间。延迟短就发送不完整,导致丢数据。
可以检测I2C1_Write函数的返回状态位,在上一次发送完成后再启动下一次,这样应该可行。

使用特权

评论回复
板凳
gaoyang9992006|  楼主 | 2024-1-9 13:36 | 只看该作者
lcczg 发表于 2024-1-9 11:50
我的理解应该是写入时间间隔的问题。在你的例子中,400KHz的速率下写入三个字节需要400/3/8=16.7KHz,等同 ...

看了那个库函数,增加延时应该是最好的解决方法。不然不同步就导致部分数据发送失败就跳过去了。

使用特权

评论回复
地板
gaoyang9992006|  楼主 | 2024-1-9 13:49 | 只看该作者
lcczg 发表于 2024-1-9 11:50
我的理解应该是写入时间间隔的问题。在你的例子中,400KHz的速率下写入三个字节需要400/3/8=16.7KHz,等同 ...

在写入后,也可以通过库函数检测总线是否在忙,如果忙的时候就是还没写入完整,可以选择等待,这样可以替换掉那个延时函数,但是这么写会导致某些情况程序卡死,所以可以再增加一个局部变量计数,判断两个条件,如果忙,但是超时了,仍然结束程序,这样就可以避免卡死了。

使用特权

评论回复
5
gaoyang9992006|  楼主 | 2024-1-9 13:50 | 只看该作者
补充,如果不使用延时函数,那么可以使用总线繁忙检测函数来作为是否完全写入的结束标志。如下:
    uint8_t cmd2[2];
    cmd2[0] = 0x00;
    cmd2[1] = cmd;
    I2C1_Write(0x3C, cmd2, 2);
    while(I2C1_IsBusy());

使用特权

评论回复
6
gaoyang9992006|  楼主 | 2024-1-9 13:54 | 只看该作者
增加双重判断,写入后检测是否写入完成(总线是否忙),同时通过一个临时变量计数,设置超时检测。
void OLED_Write_data(uint8_t data)
{
    uint8_t i=0;
    uint8_t data2[2];
    data2[0] = 0x40;
    data2[1] = data;
    I2C1_Write(0x3C, data2, 2);
    while(I2C1_IsBusy()&&i<10)
    {
        i++;
    }
}


使用特权

评论回复
7
734774645| | 2024-1-10 15:33 | 只看该作者
解决了我的困惑

使用特权

评论回复
8
xuanhuanzi| | 2024-1-11 17:54 | 只看该作者
这个经验在其他单片机上应该也是受用的,收藏了。

使用特权

评论回复
9
suncat0504| | 2024-1-11 18:22 | 只看该作者
我也用过I2C设备驱动OLED,中间要做的事儿多,比较麻烦,远不如软件模拟来得方便。要发送数据,处理中断,检查通讯状态,按照指定步骤发送数据等,中间有一个环节出了问题,就无法正确执行显示。另外还有担心处理中其他中断打断处理过程,导致I2C时序被打乱。反正做过一次,我是宁愿用软件模拟,也不用I2C处理OLED的显示了。倒是用I2C进行简单数据通讯,比如一个字节这样的,可能更方便一些。

使用特权

评论回复
10
meeagle| | 2024-1-14 15:51 | 只看该作者
一直是用硬件I2C来驱动SS1306的,它只发送不接收所以没必要用中断的,我是用查询的方式,另外SS1306有RAM的也不怕程序的其他中断干扰

使用特权

评论回复
11
gaoyang9992006|  楼主 | 2024-1-15 09:33 | 只看该作者
meeagle 发表于 2024-1-14 15:51
一直是用硬件I2C来驱动SS1306的,它只发送不接收所以没必要用中断的,我是用查询的方式,另外SS1306有RAM的 ...

ST的库是在发送函数最后实现了我帖子中补充的这个内容,就是通过判断是否忙,另外增加了一个等待超时结束的功能。

使用特权

评论回复
12
meeagle| | 2024-1-15 16:59 | 只看该作者
gaoyang9992006 发表于 2024-1-15 09:33
ST的库是在发送函数最后实现了我帖子中补充的这个内容,就是通过判断是否忙,另外增加了一个等待超时结束 ...

是的,发送然后查询I2C发送完成标志就可以,只靠软件延时当频率变动了自然就可能出错

使用特权

评论回复
13
guijial511| | 2024-1-16 07:46 | 只看该作者
说不好用的一般是没有掌握精髓

使用特权

评论回复
14
gaoyang9992006|  楼主 | 2024-1-16 10:56 | 只看该作者
guijial511 发表于 2024-1-16 07:46
说不好用的一般是没有掌握精髓

是的,所以这个帖子就是讲觉得不好用的原因,如何让它好用。

使用特权

评论回复
15
gaoyang9992006|  楼主 | 2024-1-16 10:57 | 只看该作者
meeagle 发表于 2024-1-15 16:59
是的,发送然后查询I2C发送完成标志就可以,只靠软件延时当频率变动了自然就可能出错 ...

是的,所以延时的用法一般只能用于定频工作模式,如果变频模式就要用到后面的方法,发送完成后,要判断是否发送成功,另外做超时处理。

使用特权

评论回复
16
21mengnan| | 2024-1-27 20:45 | 只看该作者
涨姿势了,原来这样啊。

使用特权

评论回复
17
小灵通2018| | 2024-1-27 21:16 | 只看该作者
库函数只实现了写,但是没确定写完了没。另外就是如果忙,他不写,就过去了。

使用特权

评论回复
18
643757107| | 2024-1-28 13:03 | 只看该作者
这个方法应该在驱动其他芯片上也是一样的。好贴。

使用特权

评论回复
19
643757107| | 2024-1-28 17:08 | 只看该作者
大佬什么时候出个讲解SPI的。

使用特权

评论回复
20
643757107| | 2024-1-28 17:09 | 只看该作者
就是SPI只用发送线怎么配置,让接收线作为普通的IO。

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:西安公路研究院南京院
简介:主要工作从事监控网络与通信网络设计,以及从事基于嵌入式的通信与控制设备研发。擅长单片机嵌入式系统物联网设备开发,音频功放电路开发。

1896

主题

15632

帖子

198

粉丝