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

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屏

  • 9
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: STM32模拟I2C驱动OLED可以通过软件模拟I2C总线来实现。使用STM32的GPIO引脚模拟I2C通信的时钟线(SCL)和数据线(SDA),通过编程控制引脚的输入输出状态来模拟I2C总线的通信。 首先,需要在STM32的开发环境中配置相关的GPIO引脚作为模拟I2C总线的SCL和SDA引脚。然后,在代码中初始化这些引脚的状态,并设置为输出模式。 在实现模拟I2C驱动之前,我们需要了解OLED的通信协议。通常,OLED使用I2C协议进行通信,包括启动信号、地址字节、数据字节等。根据OLED的数据手册,我们可以编写相应的代码来模拟这些通信步骤。 在驱动程序中,需要实现I2C的启动、发送字节、接收字节、停止等操作。这些操作可以通过控制SCL和SDA引脚的电平来实现。例如,启动信号可以通过将SCL引脚置高,然后将SDA引脚从高电平切换到低电平来发送。 然后,我们可以编写一系列函数来实现模拟I2C通信。例如,写入数据的函数可以使用引脚的输入输出状态来模拟发送数据字节,读取数据的函数可以根据引脚电平模拟接收数据字节。这些函数可以在程序中被调用以实现与OLED的通信。 最后,我们可以将模拟I2C驱动OLED的显示代码结合起来,通过模拟I2C总线来控制OLED的初始化、显示内容等操作。这样,我们就能够使用STM32驱动模拟I2COLED显示了。 总之,通过在STM32中模拟I2C总线的通信,我们可以实现驱动OLED的功能。这需要对STM32的GPIO引脚进行适当配置和编程,以模拟I2C通信的时序和协议。 ### 回答2: STM32是一款广泛应用于嵌入式系统开发的微控制器芯片。而模拟I2C驱动是一种基于软件实现的I2C通信协议的方式,可以用来驱动OLED显示。 在使用STM32模拟I2C驱动OLED时,首先需要配置GPIO引脚作为模拟I2C的SCL和SDA线。通常,SCL线连接到芯片的时钟输入引脚,SDA线连接到芯片的数据输入/输出引脚。 然后,需要实现一些软件函数来模拟I2C通信。对于起始信号的生成,可以通过将SCL和SDA引脚置为高电平,再将SDA引脚置为低电平来实现。发送数据时,将数据按照I2C协议的要求进行处理,依次将每个数据位发送出去,并等待接收端返回的ACK信号。接收数据时,根据I2C协议的要求,接收方会返回一个ACK信号,将接收到的数据保存起来。 在驱动OLED显示时,可以根据显示的数据手册,实现针对OLED的控制命令或数据的发送函数。通过模拟I2C驱动,可以将这些命令或数据发送到OLED中,实现对显示内容的控制。 需要注意的是,模拟I2C驱动硬件I2C驱动相比,由于需要通过软件对时序进行精确的控制,所以在速度和准确度上可能会有所降低。因此,在实际应用中,需根据具体要求进行优化。 总之,通过STM32模拟I2C驱动OLED显示,可以实现对显示内容的控制和数据的发送。这种软件实现的方式,适用于一些不支持硬件I2C接口的情况,并且灵活度较高,可以满足不同应用的需求。 ### 回答3: STM32是一种微控制器系列,它具有强大的功能和广泛的应用领域。在实现模拟I2C驱动OLED时,我们可以使用STM32的GPIO和软件I2C库。 首先,我们需要配置STM32的GPIO引脚,用于连接到OLED的SCL(时钟)和SDA(数据)线。我们可以使用CubeMX或编写代码手动配置这些引脚。 接下来,我们需要实现软件I2C的功能。软件I2C是通过控制GPIO引脚的电平变化来模拟硬件I2C的数据传输。可以在STM32的库中找到适用于软件I2C的函数和宏。 在驱动OLED之前,我们需要确定OLED的通信协议和寄存器。通常,OLED使用I2C作为通信接口,并具有一组寄存器用于控制显示内容和属性。 为了驱动OLED,我们需要编写一些函数来发送和接收数据。发送数据时,我们将数据字节写入SDA引脚,并在SCL引脚上产生时钟脉冲。接收数据时,我们将SDA引脚配置为输入,并在SCL引脚上读取时钟脉冲。 最后,我们可以编写一些函数来初始化OLED并控制其显示内容。这些函数将使用软件I2C发送特定的命令和数据字节来设置OLED的显示模式、显示位置和显示内容。 总结起来,实现STM32模拟I2C驱动OLED需要配置GPIO引脚、实现软件I2C功能,编写发送和接收数据的函数,并控制OLED的显示内容。这样,我们就能够通过STM32来控制并显示内容在OLED上。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值