STM32 HAL库 硬件I2C 从机主机防BUG程序

2023-11-21 20:40

本文主要是介绍STM32 HAL库 硬件I2C 从机主机防BUG程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

最近死磕了5天的STM32F1硬件I2C从机的程序,天天早上8点到凌晨,几乎全程心流状态。终于在结合各方资料及自己的思考后,做出了稳定的硬件I2C代码(这个文章中应该是目前为止能查到的最详述可用的硬件I2C代码),经过I2C主机发出的各种奇怪的信号蹂躏后,通讯都可以恢复正常,不会被卡死。证明该方案拥有极高稳定性。

需要注意我这次使用的是 STM32F103C8T6 的兼容型号 GD32F103C8T6 。要问他的兼容性有多强,连I2C bug都能做到一样,哈哈。我当初用GD想着硬件I2C应该能舒服用了,万万没想到,兆易连i2c 硬件BUG都复制了。

大家不要纠结于单片机的型号,我推测应该STM32FXXX 家族硬件I2C应该都是这个样子,具体情况我也无法一一测试,如果大家看了文章在自己的系统上测试成功后别忘了留个言,说下自己系统的配置,方便后来人~

STM32 硬件I2C BUG简述

相信很多接触STM32的用户在尝试使用I2C代码时都被警告过stm32 硬件I2C有bug,那么这个bug具体是什么,又是如何发生的呢,我们具体来分析一下,给我们之后代码解决这个问题来做个铺垫。

首先我们来看一下STM32 I2C的一个神奇的寄存器,SR2BUSY 位,具体他是做什么的参考手册中已经很清晰的描述,我就不多说了,我直接说它的问题。

当STM32 硬件I2C模块在通讯(无论做主机还是从机)中遇到总线被占用时,使得 STM32 在接管总线时发生总线仲裁失败,进而失去对总线的控制,导致BUSY位被置位,且无法通过使用官方驱动库自动清除。而后即表现为锁死状态。

该情况多会出现在I2C通讯错误,或者从机程序接收到了非预定的I2C指令时产生。
关于这个情况,在官方的一篇 《2C 接口进入 Busy 状态不能退出.pdf》 文档中也有描述,也正是这篇文档给了我思路,让我解决了这个问题。
在这里插入图片描述在这里插入图片描述

BUG实例

由于我使用的是硬件I2C从机,所以以下内容均以I2C从机为主要内容讲述,(如果你使用的是主机也请耐心看完,因为主机也可以按照该思路修改后解决)也可能之后我会补充主机内容(主要是I2C做主机时,从机大多时候都成熟器件,想要人为产生BUG情况也蛮困难的)

首先我们来看一段我用逻辑分析仪抓取的硬件系统的波形, 和造成该波形的测试代码(以下I2C写指令由主机发出,主机为其他公司的单片机承担,不建议使用两个STM32 硬件I2C对传测试,否则都出BUG找问题都找不到)
在这里插入图片描述

    while (1){while(HAL_I2C_Slave_Receive_IT(&hi2c1, &i2c_buff, 1) != HAL_OK){}while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY){}delay_ms(10);}
  • 问题分析:

我们可以看到,由于我的代码中只有对一个I2C写指令接收的内容,而后就进入了延时等待状态,从而错过了主机连着发送的第二条写指令,我们的单片机甚至都没有对其做ACK响应。此时我们用调试器查看STM32寄存器,就会发现 BUSY进入了锁死状态。第二次进入的HAL_I2C_Slave_Receive_IT() 函数中会在等待 BUSY 清零的查询操作中超时退出,而后在之后的循环中再次循环这个过程。

而如果我们把循环中的延时去掉,我们会发现I2C的通讯就会变得正常了。即, 由于我们没能对主机发出的第二条的I2C指令及时处理,STM32 I2C就会出问题。而这种情况在使用中是不可能避免的,主机任何一次的误操作或误编程都可能会导致我们的I2C锁死。

解决方案

放上代码

void i2c_reset()
{/* 开漏输出,关闭I2C输入通道,并尝试将总线拉高 */GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_SET);// SCL PB8 拉高for (uint8_t i = 0; i < 10; i++) {if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET){rt_kprintf("retry %d\n", i);break;}/* 该延时循环的周期和时长,请根据你的实际主机对I2C通讯出错的处理来修改 */rt_thread_mdelay(10);}/* 归还总线控制权 */GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* 复位I2C */hi2c1.Instance->CR1 |= I2C_CR1_SWRST;hi2c1.Instance->CR1 &= ~I2C_CR1_SWRST;/* 重新初始化I2C */MX_I2C1_Init();
}/** I2C最大的问题在于如果主机随意发送内容,而stm32作为从机时, 接收到程序意图之外的通讯内容后会卡死BUSY位。* 发生该情况后,可配置SCL SDA为开漏输出,关闭I2C输入通道防止错误再次被触发,而后尝试拉高SCL SDA线,或者* 等待总线被主机释放,这里的情况依据具体主机的不同可根据实际操作判断,最终我们需要等待I2C总线为空闲即SCL* SDA线为高电平保持时。将SCL SDA配回IIC,释放总线控制权 ,使用 SWRST复位I2C,清除全部I2C寄存器内容,* (BUSY位也会在该操作中被清除) 并重新初始化I2C总线。*/
void i2c_salve_thread(void *parameter)
{while (1){if(HAL_I2C_Slave_Receive_IT(&hi2c1, &i2c_buff, 1) != HAL_OK){// I2C设备出现故障无法开启接收i2c_reset();}// 检测标志位,防止I2C被二次开启,导致BUGwhile (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY){rt_thread_mdelay(1);}rt_thread_mdelay(10);}
}void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{direction = TransferDirection;
}

如代码中注释,我们在检测 HAL_I2C_Slave_Receive_IT() 返回超时或错误时,使用 i2c_reset() 函数来复位I2C硬件模块以重启I2C,使得STM32可以在主机恢复发送正常设定的I2C命令后可以恢复通讯。

i2c_reset() 函数核心操作思路如下:

  1. 配置 对应IO为开漏输出,以关闭I2C模块的硬件输入通道,防止后续的通讯继续触发BUG,并尝试将总线拉高。

  2. 尝试让从机释放总线,或等待主机释放总线(此步骤根据你的实际系统为准,最好查一下波形,看看具体故障表现是什么,如果STM32是从机,那么主机在通讯失败后,大部分会在一定时间后超时并释放总线。所以我的代码中只是在一定时间内检测总线是否被释放。如果你是主机的话则可以按照 《2C 接口进入 Busy 状态不能退出.pdf》 文档中的方法,在SCL线上发送脉冲来使得从机释放I2C总线)还是一句话:该步骤根据你的实际系统为准,我们在这步中核心需求就是让总线被释放。

  3. 将引脚配置回I2C的模式,将总线归还STM32的I2C模块

  4. 将CR1寄存器的SWRST置位后再清零,以复位I2C模块,在此过程中I2C除DR寄存器外所有寄存器都会被清空,包括BUSY位 (此处记忆有些模糊,不确定DR寄存器是否会被清空。DR为I2C收发数据缓存寄存器)

  5. 重新初始化I2C,原因是我们上一步的复位操作清空了I2C的配置

加入这些代码后,我们可以再次使用逻辑分析仪观察波形,或通过串口打印来检测程序运行。(由于逻辑分析仪波形太长,图片放不下就不做展示了)我们可以看到,不管主机如何发送诱发错误的信息,我们的代码都能让 stm32在i2c复位后的第一次接收时工作正常。而如果主机按照预定协议,间隔发送指令时,通讯就会完全恢复正常,不会触发i2c_reset() 函数。

小计

  1. 如果使用 HAL_I2C_Slave_Receive_IT() 函数接收了主机发送的读取指令,并不会触发BUG,主机会读取回 0x00的数据。

  2. 例子中我使用了 RT-thread 实时操作系统,所以延时不大一样。使用RTOS的延时时还可以让我们在等待时,将CPU调度到其他线程使用,防止一个I2C占用全部CPU周期。

  3. 有了故障处理程序后,我们就可以使用 HAL 库中自带的 I2C_TwoBoards_AdvComIT 例程来处理收发数据了。我使用的官方HAL库例程路径:

     D:\ST\STM32Cube\Repository\STM32Cube_FW_F1_V1.7.0\Projects\STM32F103RB-Nucleo\Examples\I2C\I2C_TwoBoards_AdvComIT
    
  4. i2c_reset() 核心操作思路的步骤顺序有严格要求,随意变更1~5操作的前后顺序会导致BUG再次触发

  5. IIC总线调试具有特殊性,我们最好还是准备一个逻辑分析仪来抓取波形,结合DEBUG时对寄存器的查看来分析解决故障

  6. 接上一条,在调试IIC前,我们应该确保自己对STM IIC的各个寄存器和位功能以及I2C波形时序的熟悉,以求在出现问题时能够找到问题所在,新版的HAL库IIC内容很清晰,只要了解寄存器,通过DEBUG+查看波形 的方法可以很快定位解决错误。

  7. 示例代码仅提供思路,具体使用需要修改为你的配置。

  8. 注意一定要使用新版本的HAL库,我已经被旧版的有明显错误的HAL I2C库坑过了(居然直接对只读的标志位赋值,来想要清除标志位)

总结

STM32的IIC模块确实存在BUG, 具体表现就是在我们代码没有处理预设之外的IIC指令或数据时会发生由于总线仲裁失败,导致BUSY位锁死的问题。出现这种问题,只要我们能够用代码监测到此情况的发生,并使用上述的 i2c_reset()核心操作思路就可以解决,从而让我们在实际工程应用中使用。毕竟软件也许可以方便的模拟100khz的IIC主机,但对400khz I2c通讯来说,无论是主机还是从机都很难实现(从机的软件模拟技巧性很强,且CPU消耗也大)。

最后

如果这个文章,解决了你的问题,请留个言交流下,因为我目前精力有限也只是在自己的硬件上测试了这个内容,让我知道你的问题被解决会让我很开心,也会让之后的朋友不在为这个问题困扰~

因为目前我的工程涉及到自己公司的产品,不方便直接发出,之后有空了,我会做一个示例工程挂到 githubgitee 上,方便大家修改使用。

这篇关于STM32 HAL库 硬件I2C 从机主机防BUG程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/404971

相关文章

python编写朋克风格的天气查询程序

《python编写朋克风格的天气查询程序》这篇文章主要为大家详细介绍了一个基于Python的桌面应用程序,使用了tkinter库来创建图形用户界面并通过requests库调用Open-MeteoAPI... 目录工具介绍工具使用说明python脚本内容如何运行脚本工具介绍这个天气查询工具是一个基于 Pyt

Ubuntu设置程序开机自启动的操作步骤

《Ubuntu设置程序开机自启动的操作步骤》在部署程序到边缘端时,我们总希望可以通电即启动我们写好的程序,本篇博客用以记录如何在ubuntu开机执行某条命令或者某个可执行程序,需要的朋友可以参考下... 目录1、概述2、图形界面设置3、设置为Systemd服务1、概述测试环境:Ubuntu22.04 带图

Python程序打包exe,单文件和多文件方式

《Python程序打包exe,单文件和多文件方式》:本文主要介绍Python程序打包exe,单文件和多文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python 脚本打成exe文件安装Pyinstaller准备一个ico图标打包方式一(适用于文件较少的程

Python程序的文件头部声明小结

《Python程序的文件头部声明小结》在Python文件的顶部声明编码通常是必须的,尤其是在处理非ASCII字符时,下面就来介绍一下两种头部文件声明,具有一定的参考价值,感兴趣的可以了解一下... 目录一、# coding=utf-8二、#!/usr/bin/env python三、运行Python程序四、

无法启动此程序因为计算机丢失api-ms-win-core-path-l1-1-0.dll修复方案

《无法启动此程序因为计算机丢失api-ms-win-core-path-l1-1-0.dll修复方案》:本文主要介绍了无法启动此程序,详细内容请阅读本文,希望能对你有所帮助... 在计算机使用过程中,我们经常会遇到一些错误提示,其中之一就是"api-ms-win-core-path-l1-1-0.dll丢失

SpringBoot后端实现小程序微信登录功能实现

《SpringBoot后端实现小程序微信登录功能实现》微信小程序登录是开发者通过微信提供的身份验证机制,获取用户唯一标识(openid)和会话密钥(session_key)的过程,这篇文章给大家介绍S... 目录SpringBoot实现微信小程序登录简介SpringBoot后端实现微信登录SpringBoo

uniapp小程序中实现无缝衔接滚动效果代码示例

《uniapp小程序中实现无缝衔接滚动效果代码示例》:本文主要介绍uniapp小程序中实现无缝衔接滚动效果的相关资料,该方法可以实现滚动内容中字的不同的颜色更改,并且可以根据需要进行艺术化更改和自... 组件滚动通知只能实现简单的滚动效果,不能实现滚动内容中的字进行不同颜色的更改,下面实现一个无缝衔接的滚动

Java使用WebView实现桌面程序的技术指南

《Java使用WebView实现桌面程序的技术指南》在现代软件开发中,许多应用需要在桌面程序中嵌入Web页面,例如,你可能需要在Java桌面应用中嵌入一部分Web前端,或者加载一个HTML5界面以增强... 目录1、简述2、WebView 特点3、搭建 WebView 示例3.1 添加 JavaFX 依赖3

防止SpringBoot程序崩溃的几种方式汇总

《防止SpringBoot程序崩溃的几种方式汇总》本文总结了8种防止SpringBoot程序崩溃的方法,包括全局异常处理、try-catch、断路器、资源限制、监控、优雅停机、健康检查和数据库连接池配... 目录1. 全局异常处理2. 使用 try-catch 捕获异常3. 使用断路器4. 设置最大内存和线

使用Python创建一个功能完整的Windows风格计算器程序

《使用Python创建一个功能完整的Windows风格计算器程序》:本文主要介绍如何使用Python和Tkinter创建一个功能完整的Windows风格计算器程序,包括基本运算、高级科学计算(如三... 目录python实现Windows系统计算器程序(含高级功能)1. 使用Tkinter实现基础计算器2.