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);
}

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);
}

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");
    }
}

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);
    }
}

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

复制代码
static void I2C_SendDeviceAddrWaitAck(void)
{
    while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0082)) == 0x0082)
    {
    }
    while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0007)) == 0x0007)
    {
    }
}

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();
}

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)
    {
    }
}

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");
}

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);
}

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

复制代码
int main(void)
{
	delay_xms(1000);
    OLED_Init();
    OLED_Test();
}

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++)
    {
    }
}

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

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

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

相关推荐
HQB202611 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
果壳science13 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn13 小时前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
沉淀的.晴天13 小时前
can总线
stm32
桀人14 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯15 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C15 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路15 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc15 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
碧海银沙音频科技研究院16 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法