嵌入式开发-STM32硬件I2C驱动OLED屏
创始人
2024-04-05 09:13:29
0

嵌入式开发-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值

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

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

这样再使用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);
}

也就是说,在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();}
}

重复人为模拟干扰

发现显示仍然没有正常,检查发现程序在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();}
}

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

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

完整工程链接

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

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...