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

[牛人杂谈] 我终于玩转了新唐的I2C硬件收发器库函数驱动OLED

[复制链接]
3796|20
 楼主| gaoyang9992006 发表于 2022-9-3 19:18 | 显示全部楼层 |阅读模式
本帖最后由 gaoyang9992006 于 2022-9-6 21:13 编辑

#申请原创# @21小跑堂

      在网上我想找一个新唐库函数操作OLED获取其他什么的例子都很难找到,至今没找到。都是采用的IO模拟,或者采用了基础库函数(类似寄存器的操作)实现。
那么问题在哪儿呢?
我要点亮一个SSD1306的0.91寸OLED,接口I2C。我用IO很容易点亮。非常方便的移植,但是我是有强迫症的,有硬件的I2C为何大家不用?
670976313355da6f5b.png
用过这个的都应该清楚,通常是要实现以下操作函数
  1. //发送一个字节
  2. //向SSD1306写入一个字节。
  3. //mode:数据/命令标志 0,表示命令;1,表示数据;
  4. void OLED_WR_Byte(u8 dat,u8 mode)
  5. {
  6.         I2C_Start();
  7.         Send_Byte(0x78);
  8.         I2C_WaitAck();
  9.         if(mode){Send_Byte(0x40);}
  10.   else{Send_Byte(0x00);}
  11.         I2C_WaitAck();
  12.         Send_Byte(dat);
  13.         I2C_WaitAck();
  14.         I2C_Stop();
  15. }
厂家通常也是提供这个函数,或者分开,一个是写指令,一个写数据。
  1. void OLED_Write_cmd(uint8_t cmd);
  2. void OLED_Write_data(uint8_t data);

而惯常的用法就是采用IO模拟,你只需要在头文件选择使用哪两个IO就行了,多么的简单!
而后我用过STM32驱动它,采用了STM32 HAL的一个库函数,如下所示,请注意这里的从机地址我填写的是0x78,这也是很多卖OLED模块提供例子中给的地址
  1. void OLED_Write_cmd(uint8_t cmd)
  2. {
  3. HAL_I2C_Mem_Write(&hi2c2, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 0x100);
  4. }
  5. void OLED_Write_data(uint8_t data)
  6. {
  7. HAL_I2C_Mem_Write(&hi2c2, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, &data, 1, 0x100);
  8. }
然后我照猫画虎在新唐的BSP库函数中找到了类似的函数移植
  1. void OLED_Write_cmd(uint8_t cmd)
  2. {
  3. I2C_WriteByteOneReg(I2C0,0x78,0x00,cmd);
  4. }
  5. void OLED_Write_data(uint8_t data)
  6. {
  7. I2C_WriteByteOneReg(I2C0,0x78,0x40,data);

  8. }
本想着不出意外的情况下就点亮了,结果出意外了,没亮。。。
然后我摸索了一天没找到原因啊,头大。
然后我问了新唐的技术,技术给我的建议是让我看I2C的STATUS状态寄存器,这个寄存器在每次操作后会更新状态刚好库函数是根据这个状态选择下一步操作的。
发现,发起START信号是正常的,正确的返回了0x08状态,然后下一步竟然跳过了本应出现的0x18,直接给了个0x20状态。
然后我开始自己着手不用这个组合库函数,而采用基于寄存器的底层操作函数直接操作
  1. void OLED_Write_cmd(uint8_t cmd)
  2. {
  3.     /* Send START */
  4.     I2C_START(I2C0);
  5.     I2C_WAIT_READY(I2C0);

  6.     /* Send device address */
  7.     I2C_SET_DATA(I2C0, 0x78);
  8.     I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
  9.     I2C_WAIT_READY(I2C0);

  10.     /* Send register number and MSB of data */
  11.     I2C_SET_DATA(I2C0, 0x00);
  12.     I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
  13.     I2C_WAIT_READY(I2C0);


  14.     /* Send data */
  15.     I2C_SET_DATA(I2C0, cmd);
  16.     I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
  17.     I2C_WAIT_READY(I2C0);

  18.     /* Send STOP */
  19.     I2C_STOP(I2C0);
  20.       
  21.       
  22. }
  23. void OLED_Write_data(uint8_t data)
  24. {
  25.     /* Send START */
  26.     I2C_START(I2C0);
  27.     I2C_WAIT_READY(I2C0);

  28.     /* Send device address */
  29.     I2C_SET_DATA(I2C0, 0x78);
  30.     I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
  31.     I2C_WAIT_READY(I2C0);

  32.     /* Send register number and MSB of data */
  33.     I2C_SET_DATA(I2C0, 0x40);
  34.     I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
  35.     I2C_WAIT_READY(I2C0);

  36.       
  37.     /* Send data */
  38.     I2C_SET_DATA(I2C0, data);
  39.     I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
  40.     I2C_WAIT_READY(I2C0);

  41.     /* Send STOP */
  42.     I2C_STOP(I2C0);
  43.       
  44. }
这次没出意外,点亮了。好奇怪,我开始比对那个组合型库函数与我上面这个操作的区别
I2C_WriteByteOneReg();
  1. uint8_t I2C_WriteByteOneReg(I2C_T *i2c, uint8_t u8SlaveAddr, uint8_t u8DataAddr, uint8_t data)
  2. {
  3.     uint8_t u8Xfering = 1u, u8Err = 0u, u8Ctrl = 0u;
  4.     uint32_t u32txLen = 0u;

  5.     g_I2C_i32ErrCode = 0;

  6.     I2C_START(i2c);                                              /* Send START */
  7.     while(u8Xfering && (u8Err == 0u))
  8.     {
  9.         uint32_t u32TimeOutCount = SystemCoreClock; // 1 second timeout
  10.         I2C_WAIT_READY(i2c)
  11.         {
  12.             u32TimeOutCount--;
  13.             if(u32TimeOutCount == 0)
  14.             {
  15.                 g_I2C_i32ErrCode = I2C_TIMEOUT_ERR;
  16.                 break;
  17.             }
  18.         }

  19.         switch(I2C_GET_STATUS(i2c))
  20.         {
  21.         case 0x08u:
  22.             I2C_SET_DATA(i2c, (uint8_t)(u8SlaveAddr << 1u | 0x00u));    /* Send Slave address with write bit */
  23.             u8Ctrl = I2C_CTL_SI;                           /* Clear SI */
  24.             break;
  25.         case 0x18u:                                           /* Slave Address ACK */
  26.             I2C_SET_DATA(i2c, u8DataAddr);                   /* Write Lo byte address of register */
  27.             break;
  28.         case 0x20u:                                           /* Slave Address NACK */
  29.         case 0x30u:                                           /* Master transmit data NACK */
  30.             u8Ctrl = I2C_CTL_STO_SI;                       /* Clear SI and send STOP */
  31.             u8Err = 1u;
  32.             break;
  33.         case 0x28u:
  34.             if(u32txLen < 1u)
  35.             {
  36.                 I2C_SET_DATA(i2c, data);
  37.                 u32txLen++;
  38.             }
  39.             else
  40.             {
  41.                 u8Ctrl = I2C_CTL_STO_SI;                   /* Clear SI and send STOP */
  42.                 u8Xfering = 0u;
  43.             }
  44.             break;
  45.         case 0x38u:                                           /* Arbitration Lost */
  46.         default:                                             /* Unknow status */
  47.             I2C_SET_CONTROL_REG(i2c, I2C_CTL_STO_SI);      /* Clear SI and send STOP */
  48.             u8Ctrl = I2C_CTL_SI;
  49.             u8Err = 1u;
  50.             break;
  51.         }
  52.         I2C_SET_CONTROL_REG(i2c, u8Ctrl);                        /* Write controlbit to I2C_CTL register */
  53.     }
  54.     return (u8Err | u8Xfering);                                  /* return (Success)/(Fail) status */
  55. }
注意以上新唐库函数中实现中的那个左移操作。
终于让我给找到了,我们知道I2C从机元件通常是有7位地址和10位地址之分的,我想要使用的这个库函数是7位地址的,而它就认为你要输入的地址是原始的7位地址,而我认为是地址的地址0x78,我以为就是那个地址,因为我没去看OLED的驱动芯片原始手册,认为大家都是用的这个0x78就真的是它的7位地址了,而I2C_WriteByteOneReg函数中是把它当作7位的原始地址处理的,所以给左移了一位,在末尾补0表示写操作。
问题就出在这,实际上OLED的原本7位地址是0x3C 而不是0x78,通常使用IO模拟的那些网上的操作函数是手动给移位后作为它的写入地址用了。
因此问题就在这里了。
现在我已经可以通过
  1. void OLED_Write_cmd(uint8_t cmd)
  2. {
  3. //        HAL_I2C_Mem_Write(&hi2c2, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 0x100);

  4. I2C_WriteByteOneReg(I2C0,0x3C,0x00,cmd);
  5.         
  6.         
  7. }
  8. void OLED_Write_data(uint8_t data)
  9. {
  10. //        HAL_I2C_Mem_Write(&hi2c2, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, &data, 1, 0x100);
  11. I2C_WriteByteOneReg(I2C0,0x3C,0x40,data);
  12.         
  13. }
成功点亮我的OLED屏幕了,在此分享给大家,以后可以大胆的使用硬件I2C的库函数驱动这些外设了,使用的时候注意一下这个地址到底是不是那个原本的地址,不要用它的“小名”。。。o(* ̄︶ ̄*)o
补充:
在系统初始化函数中增加以下相关内容,用于使能I2C的时钟模块,以及配置使用哪些IO连接到内部的I2C收发器的时钟与数据端口
  1. void SYS_Init(void)
  2. {
  3.     /* Enable I2C0 peripheral clock */
  4.     CLK_EnableModuleClock(I2C0_MODULE);
  5.                
  6.     /* Set I2C0 multi-function pins */
  7.     SYS->GPG_MFPL = (SYS->GPG_MFPL & ~(SYS_GPG_MFPL_PG0MFP_Msk | SYS_GPG_MFPL_PG1MFP_Msk)) |
  8.                     (SYS_GPG_MFPL_PG1MFP_I2C0_SDA | SYS_GPG_MFPL_PG0MFP_I2C0_SCL);
  9. }
另外就是设置I2C的速率,一般有三种选择100K,400K,1M
  1. void I2C0_Init(void)
  2. {
  3.     /* Open I2C module and set bus clock */
  4.     I2C_Open(I2C0, 100000);

  5.     /* Get I2C0 Bus Clock */
  6.     printf("I2C clock %d Hz\n", I2C_GetBusClockFreq(I2C0));
  7. }
好了,看了我这个贴,以后别再抱怨新唐的I2C库函数难用了。最后分享给大家我的测试工程,接线如下图文字描述
4654163133d3b18d96.png
游客,如果您要查看本帖隐藏内容请回复




打赏榜单

21小跑堂 打赏了 50.00 元 2022-09-05
理由:恭喜通过原创文章审核!请多多加油哦!

评论

亲测新塘的库函数无问题,但是硬件IIC一直是块难啃的骨头,使用时经常会卡死,关键还是要理解IIC底层和硬件接口的知识。这就是为什么我们不能拘泥于库函数,而是要适当关注底层寄存器的原因。  发表于 2022-9-5 10:22

评分

参与人数 1威望 +1 收起 理由
想成大牛的小白 + 1 很给力!

查看全部评分

小明的同学 发表于 2022-9-3 20:11 | 显示全部楼层
技术高o( ̄▽ ̄)d666
昨天 发表于 2022-9-8 08:57 | 显示全部楼层
写的好,  新唐的没用过。
Latin_newday 发表于 2022-9-8 09:56 | 显示全部楼层
也用过新塘硬件IIC,参考例程实现的
 楼主| gaoyang9992006 发表于 2022-9-8 13:54 | 显示全部楼层
昨天 发表于 2022-9-8 08:57
写的好,  新唐的没用过。

以后可以试试,库函数做的很好。
 楼主| gaoyang9992006 发表于 2022-9-8 13:54 | 显示全部楼层
Latin_newday 发表于 2022-9-8 09:56
也用过新塘硬件IIC,参考例程实现的

是的,新唐的例程很丰富,只不过我这次翻车了,是因为信息不对等造成的。不过过程中学到了很多东西。也对比了不同厂家的库函数,找到了共同点和不同点。
panghongfei 发表于 2023-2-13 08:00 | 显示全部楼层
技术再牛,不如你的分享更牛
中国龙芯CDX 发表于 2023-2-13 08:59 | 显示全部楼层
太好了,结合技术进行自己的提升去用新唐的I2C库函数
lozy 发表于 2023-3-28 21:22 | 显示全部楼层
哇,最近真的很需要这个,感谢
nes6502 发表于 2023-8-23 15:33 | 显示全部楼层
111,楼主厉害
caoqing 发表于 2023-12-19 13:03 | 显示全部楼层
谢谢!下载看看
xuanhuanzi 发表于 2023-12-19 21:19 | 显示全部楼层
学习一下如何使用
gsmhacker 发表于 2024-2-2 08:04 | 显示全部楼层
新人报到
dongnanxibei 发表于 2024-2-28 19:55 | 显示全部楼层
I2C_WriteByteOneReg这个函数很好用。
玛尼玛尼哄 发表于 2024-2-28 21:13 | 显示全部楼层
如果移植那个Arduino的库更棒。
734774645 发表于 2024-2-28 22:33 | 显示全部楼层
学到了,说明新唐的库函数写的好。
PKM1996 发表于 2024-3-21 11:14 | 显示全部楼层
成功点亮,感谢大佬分享
thynet 发表于 2024-3-21 14:14 | 显示全部楼层
我需要。
TDS528011 发表于 2025-9-2 16:00 | 显示全部楼层
我用硬件iic实现了,软件iic怎么实现呢,用逻辑分析仪分析软件iic时序是没问题的但是屏幕就是不亮
我趴在云边 发表于 2025-9-14 13:39 | 显示全部楼层
恭喜!用新唐 I2C 硬件库函数驱动 OLED,先初始化 I2C(配置时钟、引脚、从机地址),再调用库函数发 OLED 初始化指令(如设置显示模式、对比度),最后用 I2C_WriteByte 等函数传显示数据。硬件 I2C 比软件模拟更高效,稳定实现字符 / 图形显示,后续还可拓展动态显示功能,进一步挖掘 OLED 潜力。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

2052

主题

16403

帖子

222

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