• 嵌入式开发-STM32硬件I2C驱动OLED屏


    嵌入式开发-STM32硬件I2C驱动OLED屏

    I2C简介

    I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
    主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。

    STM32的I2C

    坊间流传STM32的硬件I2C很容易死机,所以不能使用硬件I2C,正点原子也在教程中强调了这一点。个人猜想由于Philips拥有专利,而ST为了绕开专利,而将硬件I2C弄得异常复杂(从相关的寄存器数量及设置可见一斑),造成硬件I2C很是难用,也容易出现异常死机。

    今天我就来挑战一下。尝试使用STM32F103C8T6用硬件I2C的方式来驱动OLED屏。

    MCU与OLED的硬件连接

    都是常规设置不啰嗦:开启外部时钟,开启SWD调试接口,开启I2C2,配置默认即可,LED可用可不用。
    在这里插入图片描述

    OLED驱动函数

    这个是参照正点原子的软件驱动I2C例程修改的,将其中的软件驱动IO口电平的相关代码改为HAL_I2C_Mem_Write()函数来驱动。

    GRAM的定义

    有一个问题,正点原子的代码中,GRAM的定义有点问题,如下:

    OLED_GRAM[144][8];	//行定义是Y值,列定义是X值
    
    • 1

    144是X的值,8是Y的值,符合常规,没有问题。
    OLED的驱动时,是要求连续发送X的值,一行发完再发一行,正点原子的代码中也是这样做的,他不是按数组的行来取的,而是按数组的列来取值,对于软件I2C来说没有问题。
    但是通过HAL_I2C_Mem_Write()函数连续发送数据时,其发送过程是提供数组首地址,然后地址自增,也就是先发送Y的那8个数据,再连续发送整列,因为数组就是这样配置的,这样就不能用。
    于是将X和Y的定义换一下,成下面这样

    OLED_GRAM[8] [144];  //行定义改为X值,列定义改为Y值
    
    • 1

    这样再使用HAL_I2C_Mem_Write()函数连续发送数据时,就正常了。

    当然,画点画线写字符等所有相关函数都要做配合性的修改,这里不再一一列出,需要的可以下载完整的工程文档,文末有链接。

    显示内容

    显示的内容很简单,就是交替显示2行字符,这样如果程序死机的话画面就肯定不动了
    在这里插入图片描述
    在这里插入图片描述

    人为模拟干扰

    将SCL和SDA两根线经由120欧电阻引出,经过120欧电阻可以对该引脚强制拉高或拉低,方便测试。然后分别对地进行触碰,和互相碰触,绝对不可以直接焊线引出然后直接碰触,否则会由于电源对地短路烧电源。
    可以发现一旦强制拉低,画面立刻不再切换显示,或者直接黑屏,这取决于卡死点的程序运行位置。
    但是此时程序仍然在正常运行,可以打断点和跟踪调试,于是判断故障为刚才的强制拉低导致OLED屏幕运行错乱。

    修改代码

    检查代码发现,是下面这个语句出了问题

    void oled_write_onebyte(u8 data, u8 cmd)
    {
      HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, 0, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
    }
    
    
    void oled_write_bytes(u8* data, u8 len, u8 cmd)
    {
      HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, cmd, I2C_MEMADD_SIZE_8BIT, data, len, 1000);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    也就是说,在STM32往OLED寄存器中写入数据时,由于人为操作导致数据异常,OLED内部寄存器参数乱了,导致工作不正常。
    针对性的解决方法也是简单粗暴,将OLED重新初始化,并重新上电,改代码如下:

    void oled_write_onebyte(u8 data, u8 cmd)
    {
      u32 ret;
      ret = HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, 0, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
      if(ret!=0)
      {
        oled_init();
        OLED_DisPlay_Off();
        HAL_Delay(10);
        OLED_DisPlay_On();
      }
    }
    
    void oled_write_bytes(u8* data, u8 len, u8 cmd)
    {
      u32 ret;
      ret = HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, cmd, I2C_MEMADD_SIZE_8BIT, data, len, 1000);
      if(ret!=0)
      {
        oled_init();
        OLED_DisPlay_Off();
        HAL_Delay(10);
        OLED_DisPlay_On();
      }
    }
    
    • 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

    重复人为模拟干扰

    发现显示仍然没有正常,检查发现程序在I2C_RequestMemoryWrite()这个函数出了问题,简单粗暴的直接把I2C再来一次初始化搞定。

    void oled_write_onebyte(u8 data, u8 cmd)
    {
      u32 ret;
      ret = HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, 0, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
      if(ret!=0)
      {
        MX_I2C2_Init();
        oled_init();
        OLED_DisPlay_Off();
        HAL_Delay(10);
        OLED_DisPlay_On();
      }
    }
    
    void oled_write_bytes(u8* data, u8 len, u8 cmd)
    {
      u32 ret;
      ret = HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, cmd, I2C_MEMADD_SIZE_8BIT, data, len, 1000);
      if(ret!=0)
      {
        MX_I2C2_Init();
        oled_init();
        OLED_DisPlay_Off();
        HAL_Delay(10);
        OLED_DisPlay_On();
      }
    }
    
    • 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

    著名的HardFault_Handler错误

    此时程序工作基本正常,可以从故障中恢复,但长时间维持人为异常状态(十多秒)后,仍有小几率卡死,程序会跳转到HardFault_Handler,这个排查起来花了点时间,最后发现是
    堆栈溢出,见下图:
    在这里插入图片描述

    注意看箭头所指的这个拖动条,oled_init和oled_write_onebyte这两个函数已经重复了N次,继续下去肯定是堆栈溢出跑不了的。
    这个问题是由于在oled_init函数中调用了oled_write_onebyte这个函数,而在这个函数运行时,如果仍然处于故障状态的话,就又会调用oled_init,如此便会形成嵌套,嵌套多了就会导致堆栈溢出。
    解决起来还是简单粗暴,加延时。
    你不是时间长了,重复次数多,导致堆栈溢出么,我给你加延时,让你在几十分钟内跑不了那么多次不就行了。
    当然这个搞法治标不治本,但是谁没事把个OLED屏幕短路那么久。如果哪位有更好的解决思路,请在下方留言讨论。

    void oled_write_onebyte(u8 data, u8 cmd)
    {
      u32 ret;
      ret = HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, 0, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
      if(ret!=0)
      {
        MX_I2C2_Init();
    HAL_Delay(100);
    oled_init();
        OLED_DisPlay_Off();
        HAL_Delay(100);
        OLED_DisPlay_On();
      }
    }
    
    void oled_write_bytes(u8* data, u8 len, u8 cmd)
    {
      u32 ret;
      ret = HAL_I2C_Mem_Write(&hi2c2, OLED_ADDR, cmd, I2C_MEMADD_SIZE_8BIT, data, len, 1000);
      if(ret!=0)
      {
        MX_I2C2_Init();
        HAL_Delay(100);
        oled_init();
        OLED_DisPlay_Off();
        HAL_Delay(100);
        OLED_DisPlay_On();
      }
    }
    
    • 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

    至此故障解决。
    咦,传说中的I2C不好用的问题在哪里呀?出了问题复位重来不就行了么?ST这么大的公司,如果连个I2C都做得不能用的话,他的片子还能卖得这么火么?可能做到每年几百亿的销售额么?
    所以不要人云亦云,有问题还是自己过一遍,尝试一下也许就自己解决了。

    完整工程链接

    完整工程,包括CubeMX工程,Keil工程,链接如下:嵌入式开发-STM32硬件I2C驱动OLED屏

  • 相关阅读:
    本地项目如何设置https——2024-04-19
    设计模式 -- 适配器模式(Adapter Pattern)
    多层索引MultiIndex
    Edu Codeforces Round 133 A. 2-3 Moves
    Spring MVC相关异常类
    零基础写框架(3): Serilog.NET 中的日志使用技巧
    卷积神经网络的算法过程,卷积神经网络算法实现
    云计算(二):负载均衡概述
    Java中位运算符优先级低于算术运算符
    玄机-第一章 应急响应-Linux日志分析
  • 原文地址:https://blog.csdn.net/13011803189/article/details/127728172