• STM32 硬件IIC 控制OLED I2C卡死问题


    #更新通知:2023-09-06 STM32L151 固件库 使用I2C 太难了,又宕机了,建议不要在固件库版本上尝试硬件IIC 了,一般人真用不了,直接使用软件模拟的,或者不要使用固件库了,用HAL 库吧,据说HAL 库没这么多问题,不死心的我还是死心了,等有空再研究吧

    1. STM32L151C8T6 硬件IIC 控制OLED 屏,OLED 驱动IC CH1116G, 查阅OLED 数据手册

    在这里插入图片描述

    2. STM32 硬件IIC 初始化,用的标准库,固件库

    // stm32l151c8t6 as master, oled control ic (CH1116G) as slave, and communicate by master iic2
    void STM32L151C8T6_IIC_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct;
        I2C_InitTypeDef I2C_InitStruct;
    
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
    
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // GPIO_OType_OD, GPIO_OType_PP
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
        GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_400KHz;
        GPIO_Init(GPIOB, &GPIO_InitStruct); // IIC2 SCL - PB10, SDA - PB11
    
        GPIO_ResetBits(GPIOB, GPIO_Pin_11);
        delay_xms(20);
    
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_I2C2); // set PB10 as IIC2 SCL
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_I2C2); // set PB11 as IIC2 SDA
    
        I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
        I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
        I2C_InitStruct.I2C_ClockSpeed = iic_clockSpeed_400Khz; // must be less than 100 Khz
        I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
        I2C_InitStruct.I2C_Mode = I2C_Mode_SMBusHost; // 这里很重要
        I2C_InitStruct.I2C_OwnAddress1 = IIC2_NOT_USE_OWN_ADDR; // do not use own address
    
        I2C_Init(I2C2, &I2C_InitStruct);
    
        I2C_Cmd(I2C2, ENABLE);
    
        I2C_AcknowledgeConfig(I2C2, ENABLE);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    3. GPIO 引脚速率要和 I2C 速率匹配,这很重要

    在这里插入图片描述

    3.1 I2C模式,我这里选的是主机模式,选其它模式就会出问题

    在这里插入图片描述

    4. OLED 硬件I2C 发送函数封装,给OLED发送的东西分两种:①发的是数据,②发的是命令

    // master (STM32L151C8T6) send cmd instruction to oled screen control ic (CH1116G)
    void OLED_SendCmd(uint8_t cmd)
    {
        WaitFor_IIC_ReadyToWorking();
    
        I2C_GenerateSTART(I2C2, ENABLE); // iic start signal
    
        IIC_SendStartSignal_CheckEvent();
    
        I2C_Send7bitAddress(I2C2, OLED_ADDRESS, I2C_Direction_Transmitter); // send device addr and write bit
    
        I2C_SendDeviceAddrWaitAck();
    
        IIC_SendByteToOLED(iic_transmitType_Cmd);
    
        IIC_Delay(IIC_TIMEOUT_COUNTER);
        I2C_SendData(I2C2, cmd);
        I2C_SendByteDataWaitAck();
    
        I2C_GenerateSTOP(I2C2, ENABLE);
        IIC_Delay(IIC_TIMEOUT_COUNTER);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5. IIC 发送数据之前先检查I2C 是否有空,STM32 为了规避飞利浦的发明专利,把硬件I2C 搞的很复杂,导致固件库会有卡死的问题,据说HAL 库解决了这问题,本人没用过HAL库

    static void WaitFor_IIC_ReadyToWorking(void)
    {
        while (I2C2->SR2 & 0x02)
        {
            INFO_LOG("[WaitFor_IIC_ReadyToWorking] i2c2 is busy\r\n");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6. 发送完起始信号后,要检查起始信号事件, 不要用固件库检查事件函数,会卡死的

    static void IIC_SendStartSignal_CheckEvent(void)
    {
        while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0001)))
        {
            printf("[IIC_SendStartSignal_CheckEvent][] I2C_SR1=0x%04x, I2C2_SR2=0x%04x\r\n", I2C2->SR1, I2C2->SR2);
        }
        while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0003)) == 0x0003)
        {
            printf("[IIC_SendStartSignal_CheckEvent][] I2C_SR1=0x%04x, I2C2_SR2=0x%04x\r\n", I2C2->SR1, I2C2->SR2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    7. 发送完设备地址也要检查发送设备地址这个事件,同样不能用固件库函数

    static void I2C_SendDeviceAddrWaitAck(void)
    {
        while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0082)) == 0x0082)
        {
        }
        while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0007)) == 0x0007)
        {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    8. 发送命令给OLED 屏,这里要发两次,我也暂时没弄明白为什么,这个地方搞死我了,好惨

    static void IIC_SendByteToOLED(uint8_t mode)
    {
        IIC_Delay(IIC_TIMEOUT_COUNTER);
        I2C_SendData(I2C2, mode); // 0x00, high 8-bits, cmd code, iic_transmitType_Cmd
        I2C_SendByteDataWaitAck();
    
        IIC_Delay(IIC_TIMEOUT_COUNTER);
        I2C_SendData(I2C2, mode); // 0x00, low 8-bits, cmd code
        I2C_SendByteDataWaitAck();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    9. 发送完命令后要检查标志位, while 里面可以啥都不写,空转,死等,也可以加一个超时退出,但是超时退出只能解决卡死的问题,并不能解决I2C busy 卡死导致发不出去数据的问题,治标不治本,这一点被别人说的坑死了,网上有人说超时退出可以解决卡死问题,但仅仅只解决了卡死问题,I2C 还是没有工作起来,我们的最终目的是让I2C 工作起来

    static void I2C_SendByteDataWaitAck(void)
    {
        while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0080)) == 0x0080)
        {
        }
        while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0007)) == 0x0007)
        {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    10. OLED 初始化,不同的驱动IC 会有所区别,但是可以先用我这个套用一下试试,我是在STM32L151C8T6 芯片上还跑了FreeRTOS 实时操作系统的

    void OLED_Init(void)
    {
        delay_xms(20); // oled startup slowly than stm32l151c8t6
        INFO_LOG("[OLED_Init] init start\r\n");
    
        OLED_SendCmd(0xAE); // display off
    
        OLED_SendCmd(0x02); // set colum start address
        OLED_SendCmd(0x10); // set colum end address
    
        OLED_SendCmd(0x40); // set start line (first row)
    
        OLED_SendCmd(0xB0); // set page address
    
        OLED_SendCmd(0x81); // set contrast ratio
        OLED_SendCmd(0xCF); // 128
    
        OLED_SendCmd(0xA1); // set segment remapping, from right to left
    
        OLED_SendCmd(0xA6); // forward display, normal or reverse
    
        OLED_SendCmd(0xA8); // multiple reuse rate, multiple ratio
        OLED_SendCmd(0x3F); // duty = 1 / 64
    
        OLED_SendCmd(0xAD); // set charge pump enable
        OLED_SendCmd(0x8B); // enable DC-DC
    
        OLED_SendCmd(0x33); // set VPP = 10V
    
        OLED_SendCmd(0xC8); // set output scan direction, COM[N - 1] to COM[0], COM scan direction
    
        OLED_SendCmd(0xD3); // set display offset
        OLED_SendCmd(0x00); // 0x00
    
        OLED_SendCmd(0xD5); // set internal clock frequence, set osc frequency
        OLED_SendCmd(0xC0);
    
        OLED_SendCmd(0xD9); // set pre-charge period
        OLED_SendCmd(0x1F); // 0x22
    
        OLED_SendCmd(0xDA); // set COM pins, pin layout
        OLED_SendCmd(0x12);
    
        OLED_SendCmd(0xDB); // set electrical level, set VCOMH
        OLED_SendCmd(0x40);
    
        OLED_SendCmd(0xAF); // enable display, display on
    
        INFO_LOG("[OLED_Init] init complete\r\n");
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    11. OLED 测试函数封装,B 站博主keysking

    void OLED_Test(void)
    {
        OLED_SendCmd(0xB0); // page 0
        OLED_SendCmd(0x00); // colume 0 low 4-bits
        OLED_SendCmd(0x10); // colume 0 high 8-bits
    
        OLED_SendCmd(0x40);
        OLED_SendCmd(0xAA);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    12. 主函数调用OLED 初始化函数和测试函数,先延时一会再初始化,因为STM32 比OLED的控制IC 起来的快很多,CH1116G

    int main(void)
    {
    	delay_xms(1000);
        OLED_Init();
        OLED_Test();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    13. IIC_Delay 函数

    #define IIC_TIMEOUT_COUNTER    0x1000 // iic transmit timeout
    
    static void IIC_Delay(uint32_t delay_time)
    {
        uint32_t delayTime;
        for (delayTime = 0; delayTime < delay_time; delayTime++)
        {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    14. 用逻分仪抓的数据看不出来细节,可以用示波器抓一下波形,看细节,这个波形都变形了,查看原理图,上面接了两个电容,把SCL 和SDA 当成高频信号,给我滤掉了,导致这波形乱七八糟的,直接把原理图上的两个对地电容去掉,或者更好其它容量的电容,我这里是直接去掉了

    在这里插入图片描述

    15. 接了两个对地电容,把正常信号当成高频滤掉了

    在这里插入图片描述

    16. 正常波形,应该是这样的,方波才对,正弦波是有问题的,这里还有一个小台阶,待处理

    在这里插入图片描述

  • 相关阅读:
    【索引】常见的索引、B+树结构、什么时候需要使用索引、优化索引方法、索引主要的数据结构、聚簇索引、二级索引、创建合适的索引等重点知识汇总
    什么是内部类?
    Kotlin File FileTreeWalk walkTopDown onEnter onLeave
    Redis的入门之路
    SMC IRV系列手动真空调节器低压控制性能考核试验
    排序-选择类排序
    Exception in thread “main“ java.io.NotSerializableException:
    easyPubMed
    设计模式-01简单工厂模式详解 详细代码对比
    TDDL介绍及原理
  • 原文地址:https://blog.csdn.net/weixin_50212044/article/details/132686559