发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
打印
[牛人杂谈]

我终于玩转了新唐的I2C硬件收发器库函数驱动OLED

[复制链接]
2139|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 gaoyang9992006 于 2022-9-6 21:13 编辑

#申请原创# @21小跑堂

      在网上我想找一个新唐库函数操作OLED获取其他什么的例子都很难找到,至今没找到。都是采用的IO模拟,或者采用了基础库函数(类似寄存器的操作)实现。
那么问题在哪儿呢?
我要点亮一个SSD1306的0.91寸OLED,接口I2C。我用IO很容易点亮。非常方便的移植,但是我是有强迫症的,有硬件的I2C为何大家不用?

用过这个的都应该清楚,通常是要实现以下操作函数
//发送一个字节
//向SSD1306写入一个字节。
//mode:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 mode)
{
        I2C_Start();
        Send_Byte(0x78);
        I2C_WaitAck();
        if(mode){Send_Byte(0x40);}
  else{Send_Byte(0x00);}
        I2C_WaitAck();
        Send_Byte(dat);
        I2C_WaitAck();
        I2C_Stop();
}
厂家通常也是提供这个函数,或者分开,一个是写指令,一个写数据。
void OLED_Write_cmd(uint8_t cmd);
void OLED_Write_data(uint8_t data);

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

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

    /* Send device address */
    I2C_SET_DATA(I2C0, 0x78);
    I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
    I2C_WAIT_READY(I2C0);

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


    /* Send data */
    I2C_SET_DATA(I2C0, cmd);
    I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
    I2C_WAIT_READY(I2C0);

    /* Send STOP */
    I2C_STOP(I2C0);
      
      
}
void OLED_Write_data(uint8_t data)
{
    /* Send START */
    I2C_START(I2C0);
    I2C_WAIT_READY(I2C0);

    /* Send device address */
    I2C_SET_DATA(I2C0, 0x78);
    I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
    I2C_WAIT_READY(I2C0);

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

      
    /* Send data */
    I2C_SET_DATA(I2C0, data);
    I2C_SET_CONTROL_REG(I2C0, I2C_CTL_SI);
    I2C_WAIT_READY(I2C0);

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

    g_I2C_i32ErrCode = 0;

    I2C_START(i2c);                                              /* Send START */
    while(u8Xfering && (u8Err == 0u))
    {
        uint32_t u32TimeOutCount = SystemCoreClock; // 1 second timeout
        I2C_WAIT_READY(i2c)
        {
            u32TimeOutCount--;
            if(u32TimeOutCount == 0)
            {
                g_I2C_i32ErrCode = I2C_TIMEOUT_ERR;
                break;
            }
        }

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

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

    /* Get I2C0 Bus Clock */
    printf("I2C clock %d Hz\n", I2C_GetBusClockFreq(I2C0));
}
好了,看了我这个贴,以后别再抱怨新唐的I2C库函数难用了。最后分享给大家我的测试工程,接线如下图文字描述

游客,如果您要查看本帖隐藏内容请回复




使用特权

评论回复

打赏榜单

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

评论
21小跑堂 2022-9-5 10:22 回复TA
亲测新塘的库函数无问题,但是硬件IIC一直是块难啃的骨头,使用时经常会卡死,关键还是要理解IIC底层和硬件接口的知识。这就是为什么我们不能拘泥于库函数,而是要适当关注底层寄存器的原因。 
评分
参与人数 1威望 +1 收起 理由
想成大牛的小白 + 1 很给力!
沙发
小明的同学| | 2022-9-3 20:11 | 只看该作者
技术高o( ̄▽ ̄)d666

使用特权

评论回复
板凳
昨天| | 2022-9-8 08:57 | 只看该作者
写的好,  新唐的没用过。

使用特权

评论回复
地板
Latin_newday| | 2022-9-8 09:56 | 只看该作者
也用过新塘硬件IIC,参考例程实现的

使用特权

评论回复
5
gaoyang9992006|  楼主 | 2022-9-8 13:54 | 只看该作者
昨天 发表于 2022-9-8 08:57
写的好,  新唐的没用过。

以后可以试试,库函数做的很好。

使用特权

评论回复
6
gaoyang9992006|  楼主 | 2022-9-8 13:54 | 只看该作者
Latin_newday 发表于 2022-9-8 09:56
也用过新塘硬件IIC,参考例程实现的

是的,新唐的例程很丰富,只不过我这次翻车了,是因为信息不对等造成的。不过过程中学到了很多东西。也对比了不同厂家的库函数,找到了共同点和不同点。

使用特权

评论回复
7
panghongfei| | 2023-2-13 08:00 | 只看该作者
技术再牛,不如你的分享更牛

使用特权

评论回复
8
中国龙芯CDX| | 2023-2-13 08:59 | 只看该作者
太好了,结合技术进行自己的提升去用新唐的I2C库函数

使用特权

评论回复
9
lozy| | 2023-3-28 21:22 | 只看该作者
哇,最近真的很需要这个,感谢

使用特权

评论回复
10
nes6502| | 2023-8-23 15:33 | 只看该作者
111,楼主厉害

使用特权

评论回复
11
caoqing| | 2023-12-19 13:03 | 只看该作者
谢谢!下载看看

使用特权

评论回复
12
xuanhuanzi| | 2023-12-19 21:19 | 只看该作者
学习一下如何使用

使用特权

评论回复
13
gsmhacker| | 2024-2-2 08:04 | 只看该作者
新人报到

使用特权

评论回复
14
dongnanxibei| | 2024-2-28 19:55 | 只看该作者
I2C_WriteByteOneReg这个函数很好用。

使用特权

评论回复
15
玛尼玛尼哄| | 2024-2-28 21:13 | 只看该作者
如果移植那个Arduino的库更棒。

使用特权

评论回复
16
734774645| | 2024-2-28 22:33 | 只看该作者
学到了,说明新唐的库函数写的好。

使用特权

评论回复
17
PKM1996| | 2024-3-21 11:14 | 只看该作者
成功点亮,感谢大佬分享

使用特权

评论回复
18
thynet| | 2024-3-21 14:14 | 只看该作者
我需要。

使用特权

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

本版积分规则

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

1961

主题

15931

帖子

208

粉丝