123下一页
返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

[PIC®/AVR®/dsPIC®产品] 如何解决PIC单片机的硬件I2C在驱动OLED显示屏时候的不好用问题

[复制链接]
 楼主| gaoyang9992006 发表于 2024-1-8 16:36 | 显示全部楼层 |阅读模式
<
#申请原创# @21小跑堂

网友反应在硬件时钟设置较高的时候驱动SSD1306控制器的OLED模块时候无法工作,同样在设置I2C时钟为高速模式时候(1MHz,实际配置为800KHz)也无法工作。
那么这是什么情况呢?
经过我的实验总结如下:
SSD1306通常仅支持100Khz的低速模式和400KHz的中速模式。
查询相关资料内容:SSD1306 i2c的最大速度取决于具体的硬件和软件实现。 一般来说,它支持标准模式(100 kHz)和快速模式(400 kHz)的I2C通信。
所以设置为高速模式无法正常工作。
这是第一个解决的问题,即使用OLED模块时候,硬件I2C的速度推荐设置为400KHz。

接下面分析为何MCU系统时钟设置为高速的时候无法正常工作。
经过阅读MCC的源代码,I2C的通信为非阻塞通信,即不会卡顿在某处等待,如果不满足条件就会跳过去。所以如果系统时钟速度设置过高,导致在I2C从机还没来得及反应的时候就执行完毕了,这个时候总线繁忙,会自动跳过。相关库函数代码如下:
  1. bool I2C1_Write(uint16_t address, uint8_t *data, size_t dataLength)
  2. {
  3.     bool retStatus = false;
  4.     if (!I2C1_IsBusy())
  5.     {
  6.         i2c1Status.busy = true;
  7.         i2c1Status.address = address;
  8.         i2c1Status.switchToRead = false;
  9.         i2c1Status.writePtr = data;
  10.         i2c1Status.writeLength = dataLength;
  11.         i2c1Status.readPtr = NULL;
  12.         i2c1Status.readLength = 0;
  13.         i2c1Status.errorState = I2C_ERROR_NONE;
  14.         I2C1_WriteStart();
  15.         retStatus = true;
  16.     }
  17.     return retStatus;
  18. }
如何解决这个不同步问题呢?修改库函数?NO,我是不主张修改的,因为非阻塞可以防止整个系统卡死在某处。
经过尝试可以在每次调用完该函数后进行一定时间的延时。
经过测试,在系统时钟设置为最高速度64MHz的情况下,每次调用I2C写函数后延时50个系统周期可以正常通信,当低于49后开始不稳定。
  1. void OLED_Write_cmd(uint8_t cmd)
  2. {
  3.     uint8_t cmd2[2];
  4.     cmd2[0] = 0x00;
  5.     cmd2[1] = cmd;
  6.     I2C1_Write(0x3C, cmd2, 2);
  7.     DELAY_microseconds(50);
  8. }
  9. void OLED_Write_data(uint8_t data)
  10. {
  11.     uint8_t data2[2];
  12.     data2[0] = 0x40;
  13.     data2[1] = data;
  14.     I2C1_Write(0x3C, data2, 2);
  15.     DELAY_microseconds(50);
  16. }

这里推荐在64Mhz时钟下延时50,该函数并非是50us经过测试该函数参数对应位System Clock的时钟周期数。
8MHz系统时钟下,设置延时参数为1
16MHz系统时钟下,设置延时参数为6
其他时钟下,请参考以上范围调整到最小合适值即可。
总结:很多人说硬件I2C不好用,其实不是不好用,是很多时候没有能严格按照通信协议规范设置相关参数,比如常用的这个OLED模块,经常用于一些显示,比如文中提到的这个单片机,如果你没能很好的设置相关参数,并对时钟进行同步的延时操作,就无法正常高速点亮屏幕,你会以为是硬件的I2C不好用,其实不是。该文相信可以为很多用I2C硬件不好用的朋友提供一份参考,相信以后的项目中硬件I2C越来越流行。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

打赏榜单

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

评论

文章针对PIC单片机的硬件I2C驱动OLED显示出现的问题给出一个解决方案,通过延时的方式等待数据完整通信,当然延时方式二姨也不认为是个好的方式,但是作者通过与评论区的互动,增加了超时检测,在保证通信完整性的同时防止程序卡死。这是比较好的一种方式,现在大多单片机的硬件I2C通信都会采用的方式。作者积极听取意见并给与改进,态度值得肯定。  发表于 2024-1-11 17:51
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,等同 ...

在写入后,也可以通过库函数检测总线是否在忙,如果忙的时候就是还没写入完整,可以选择等待,这样可以替换掉那个延时函数,但是这么写会导致某些情况程序卡死,所以可以再增加一个局部变量计数,判断两个条件,如果忙,但是超时了,仍然结束程序,这样就可以避免卡死了。
 楼主| gaoyang9992006 发表于 2024-1-9 13:50 | 显示全部楼层
补充,如果不使用延时函数,那么可以使用总线繁忙检测函数来作为是否完全写入的结束标志。如下:
  1.     uint8_t cmd2[2];
  2.     cmd2[0] = 0x00;
  3.     cmd2[1] = cmd;
  4.     I2C1_Write(0x3C, cmd2, 2);
  5.     while(I2C1_IsBusy());
 楼主| gaoyang9992006 发表于 2024-1-9 13:54 | 显示全部楼层
增加双重判断,写入后检测是否写入完成(总线是否忙),同时通过一个临时变量计数,设置超时检测。
  1. void OLED_Write_data(uint8_t data)
  2. {
  3.     uint8_t i=0;
  4.     uint8_t data2[2];
  5.     data2[0] = 0x40;
  6.     data2[1] = data;
  7.     I2C1_Write(0x3C, data2, 2);
  8.     while(I2C1_IsBusy()&&i<10)
  9.     {
  10.         i++;
  11.     }
  12. }


734774645 发表于 2024-1-10 15:33 | 显示全部楼层
解决了我的困惑
xuanhuanzi 发表于 2024-1-11 17:54 | 显示全部楼层
这个经验在其他单片机上应该也是受用的,收藏了。
suncat0504 发表于 2024-1-11 18:22 | 显示全部楼层
我也用过I2C设备驱动OLED,中间要做的事儿多,比较麻烦,远不如软件模拟来得方便。要发送数据,处理中断,检查通讯状态,按照指定步骤发送数据等,中间有一个环节出了问题,就无法正确执行显示。另外还有担心处理中其他中断打断处理过程,导致I2C时序被打乱。反正做过一次,我是宁愿用软件模拟,也不用I2C处理OLED的显示了。倒是用I2C进行简单数据通讯,比如一个字节这样的,可能更方便一些。
meeagle 发表于 2024-1-14 15:51 | 显示全部楼层
一直是用硬件I2C来驱动SS1306的,它只发送不接收所以没必要用中断的,我是用查询的方式,另外SS1306有RAM的也不怕程序的其他中断干扰
 楼主| gaoyang9992006 发表于 2024-1-15 09:33 | 显示全部楼层
meeagle 发表于 2024-1-14 15:51
一直是用硬件I2C来驱动SS1306的,它只发送不接收所以没必要用中断的,我是用查询的方式,另外SS1306有RAM的 ...

ST的库是在发送函数最后实现了我帖子中补充的这个内容,就是通过判断是否忙,另外增加了一个等待超时结束的功能。
meeagle 发表于 2024-1-15 16:59 | 显示全部楼层
gaoyang9992006 发表于 2024-1-15 09:33
ST的库是在发送函数最后实现了我帖子中补充的这个内容,就是通过判断是否忙,另外增加了一个等待超时结束 ...

是的,发送然后查询I2C发送完成标志就可以,只靠软件延时当频率变动了自然就可能出错
guijial511 发表于 2024-1-16 07:46 来自手机 | 显示全部楼层
说不好用的一般是没有掌握精髓
 楼主| gaoyang9992006 发表于 2024-1-16 10:56 | 显示全部楼层
guijial511 发表于 2024-1-16 07:46
说不好用的一般是没有掌握精髓

是的,所以这个帖子就是讲觉得不好用的原因,如何让它好用。
 楼主| gaoyang9992006 发表于 2024-1-16 10:57 | 显示全部楼层
meeagle 发表于 2024-1-15 16:59
是的,发送然后查询I2C发送完成标志就可以,只靠软件延时当频率变动了自然就可能出错 ...

是的,所以延时的用法一般只能用于定频工作模式,如果变频模式就要用到后面的方法,发送完成后,要判断是否发送成功,另外做超时处理。
21mengnan 发表于 2024-1-27 20:45 | 显示全部楼层
涨姿势了,原来这样啊。
小灵通2018 发表于 2024-1-27 21:16 | 显示全部楼层
库函数只实现了写,但是没确定写完了没。另外就是如果忙,他不写,就过去了。
643757107 发表于 2024-1-28 13:03 | 显示全部楼层
这个方法应该在驱动其他芯片上也是一样的。好贴。
643757107 发表于 2024-1-28 17:08 | 显示全部楼层
大佬什么时候出个讲解SPI的。
643757107 发表于 2024-1-28 17:09 | 显示全部楼层
就是SPI只用发送线怎么配置,让接收线作为普通的IO。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:如果你觉得我的分享或者答复还可以,请给我点赞,谢谢。

2045

主题

16350

帖子

222

粉丝
快速回复 在线客服 返回列表 返回顶部