STM32 HAL库实现硬件IIC通信

文章目录

  • 一. 前言
  • 二. 关于IIC通信
  • 三. IIC通信过程
  • 四. STM32实现硬件IIC通信
  • 五. 关于硬件IIC的Bug

一. 前言

最近正在DIY一款智能电池,需要使用STM32F030F4P6和TI的电池管理芯片BQ40Z50进行SMBUS通信。SMBUS本质上就是IIC通信,项目用到STM32CubeMX+HAL库,使用硬件IIC完成MCU对芯片的寄存器数据的读取和改写。

整篇博客主要是梳理一下IIC的通信流程,并记录一下项目实现的过程,如有错的地方,欢迎各位读者批评指正。

二. 关于IIC通信

总线又两条信号线构成一条是数据线SDA ,用于数据传输。另一条则是时钟线SCL ,用于传输CLK信号,始终由主设备控制,主机(Master) 通过控制时钟信号可以实现对从机(Slave) 的控制与读写操作。各设备通过SDA、SCL接入总线,每个连接到IIC总线上的器件都有一个唯一的地址,如上图所示。

SDA和SCL需通过上拉电阻 接至VCC。由于连接到总线上的器件输出级必须是漏极开路或者集电极开路的,因此只要有一个器件任意时刻输出低电平,都将总线上的信号变低;当IIC总线空闲时,SDA和SCL两条线均为高电平,即各器件的SDA和SCL都是线与的关系。

三. IIC通信过程

IIC通信过程由一系列的操作组成:

  1. 起始信号 :通信开始时,主设备发送一个低电平的SCL时钟脉冲,然后再发送一个低电平的SDA数据线脉冲。这个SDA的下降沿表示I2C总线上的一个开始信号。

  2. 从设备地址 :主设备发送从设备地址到I2C总线。I2C地址由7Bit或10Bit组成,取决于使用的设备。

  3. 读/写位传输 :读写位占据 1bit 数据,指定了数据传输的方向:

    a. 主设备发送数据,从设备接受数据,为 0 
    b. 主设备接收数据,从设备发送数据,为 1 
    
  4. 仲裁机制和应答 :应答位由1个Bit的数据组成。主设备通过检测应答位,以确定从设备是否存在。若主设备收到的是NACK,这会让主设备发生重启或停止流程:

     a. 如果从设备存在并正确收到地址,从设备将SDA下拉,产生应答信号 0 (ACK)。
     b. 如果从设备不存在或未正确收到地址,导致SDA空闲,产生非应答信号1(NACK)。
    
  5. 数据传输 :在确认通信目标后,主设备将发送或接收数据。数据传输是在主机产生的每个时钟周期的上升沿或下降沿进行的。

    a. 主设备发送数据: 上图所示,主设备 将要发送的数据位(8位或更多)依次发送到SDA线上,并在每个时钟周期上升沿时更新数据。从设备在每个时钟周期下降沿时接收数据,并在接收之后发送应答位来确认是否接收正确。

    b. 主设备接收数据: 上图所示,当读/写传输位为1时,到了数据传输时,主机从发送变成接收,从机从接收变成发送。从设备 将要发送的数据位(8位或更多)依次发送到SDA线上,并在每个时钟周期上升沿时更新数据。主设备在每个CLK下降沿时接收数据,并在接收之后发送应答位来确认是否接收正确。

  6. 停止信号(Stop Signal) :通信完成后,主设备发送一个停止信号,由一个高电平的SCL时钟脉冲和一个高电平的SDA数据线脉冲组成。这个SDA的上升沿表示I2C总线上的一个停止信号。

四. STM32实现硬件IIC通信

本项目用的是STM32F030F4P6通过Smbus通信和TI的电池管理芯片BQ40Z50通信,读取电池内部的电压、电流及电量信息,并且通过改写电池内部寄存器控制输入输出的Mos管。Smbus本质上就是IIC通信,下面通过访问BQ40Z50内部的电压寄存器的例子来说明模拟IIC的实现过程:

STM32CubeMX配置如下:


1. 主设备: STM32F030F4P6
2. 从设备: BQ40Z50
3. 通信频率: 10-100KHz
4. 从设备地址: 0x16
5. 电压寄存器地址: 0x09

读取寄存器数据:

  1. 主机(STM32)首先产生Start信号。
  2. 然后紧跟着发送从机设备地址(0x16),此时读写位为0,表明是向从机写命令;
  3. 这时候主机等待从机(BQ40Z50)的应答信号(ACK)。
  4. 当主机收到应答信号时,发送要访问的寄存器地址(0x09),继续等待从机的应答信号;
  5. 当主机收到应答信号后,主机要改变通信模式,主机将由发送变为接收,从机将由接收变为发送。接着主机重新发送一个开始Start信号,然后紧跟着发送从机地址(0x16),注意此时读写位为1,表明将主机设置成接收模式开始读取数据。
  6. 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收从机发送来的寄存器数据。
  7. 主机产生停止信号,结束传送过程。

虽然通信过程比较复杂,但是实现非常简单。因为大部分步骤HAL库已经封装好。直接调用现成的接口即可:

HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,
                                   uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
                                   /**
* @brief  Read an amount of data in blocking mode from a specific memory address
* @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
*                the configuration information for the specified I2C.
* @param  DevAddress Target device address: The device 7 bits address value
*         in datasheet must be shifted to the left before calling the interface
* @param  MemAddress Internal memory address
* @param  MemAddSize Size of internal memory address
* @param  pData Pointer to data buffer
* @param  Size Amount of data to be sent
* @param  Timeout Timeout duration
* @retval HAL status
*/

比如,读取BQ40Z50的电压:

Uint8_t Rxbuffer[2];//用于接受数据的数组
			 
HAL_I2C_Mem_Read(hi2cx,0x16,0x09,I2C_MEMADD_SIZE_8BIT,Rxbuffer,2,OxFF);//IIC接受从机数据

voltage = Rxbuffer[1]<<8|Rxbuffer[0];//对接受数据进行解析

同样的,写入寄存器数据:

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,
                                    uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
/**
  * @brief  Write an amount of data in blocking mode to a specific memory address
  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
  *                the configuration information for the specified I2C.
  * @param  DevAddress Target device address: The device 7 bits address value
  *         in datasheet must be shifted to the left before calling the interface
  * @param  MemAddress Internal memory address
  * @param  MemAddSize Size of internal memory address
  * @param  pData Pointer to data buffer
  * @param  Size Amount of data to be sent
  * @param  Timeout Timeout duration
  * @retval HAL status
  */

比如:向0x00写入0x270C和0x430D可以关闭BQ40Z50的Mos管:

uint8_t Mos_off [2];
Mos_off [0] = 0x0C;
Mos_off [1] = 0x27;
if(HAL_I2C_Mem_Write(hi2cx,0x16,0x00,I2C_MEMADD_SIZE_8BIT,Mos_off ,2,0xFF)==HAL_OK)
{
	Mos_off [0] = 0x3D;
	Mos_off [1] = 0x04;
	HAL_I2C_Mem_Write(hi2cx,0x16,0x00,I2C_MEMADD_SIZE_8BIT,Mos_off ,2,0xFF);
}

五. 关于硬件IIC的Bug

在开发过程中,IIC经常会遇到很多莫名其妙的Bug,比如反复超时,陷入I2C_WaitOnFlagUntilTimeout()死循环中,或者是一直处于HAL_I2C_STATE_BUSY_RX直接卡死,看了网络上很多的帖子,但是都没有找到好的解决办法。

这里的解决方式非常简单粗暴,就在卡死的地方直接对硬件IIC进行重置,重新初始化,亲测有效。

各位有什么更好的方法,欢迎在评论区留言,一起探讨。

void User_I2C_ErrorInit(I2C_HandleTypeDef *hi2c)
{
	HAL_I2C_DeInit(hi2c);
	
	hi2c->Instance = I2C1;
	hi2c->Init.Timing = 0x2000090E;
	hi2c->Init.OwnAddress1 = 0;
	hi2c->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
	hi2c->Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
	hi2c->Init.OwnAddress2 = 0;
	hi2c->Init.OwnAddress2Masks = I2C_OA2_NOMASK;
	hi2c->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
	hi2c->Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;

    hi2c->Instance->CR1 |= I2C_CR1_SWRST;
    hi2c->Instance->CR1 &= ~I2C_CR1_SWRST;

	if (HAL_I2C_Init(hi2c) != HAL_OK)
	{
		User_Error_Handler();
	}

	/** Configure Analogue filter
	*/
	if (HAL_I2CEx_ConfigAnalogFilter(hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
	{
		User_Error_Handler();
	}

	/** Configure Digital filter
	*/
	if (HAL_I2CEx_ConfigDigitalFilter(hi2c, 0) != HAL_OK)
	{
		User_Error_Handler();
	}
}

static void User_Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

各位有什么更好的方法,欢迎在评论区留言,一起探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/773064.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法库应用-顺序串(串比较)

学习贺利坚老师博客 数据结构例程——串的顺序存储应用_使用顺序串存储身份证号-CSDN博客 本人详细解析博客 串的顺序存储结构应用_(1)假设串采用顺序串存储,设计一个算法程序,按顺序比较两个串s和t的大小。请-CSDN博客 版本日志 V1.0: 利用顺序串, 进行简单的判断比较, 也算是…

JavaScript中闭包的理解

闭包&#xff08;Closure&#xff09;概念&#xff1a;一个函数对周围状态的引用捆绑在一起&#xff0c;内层函数中访问到其外层函数的作用域。简单来说;闭包内层函数引用外层函数的变量&#xff0c;如下图&#xff1a; 外层在使用一个函数包裹住闭包是对变量的保护&#xff0c…

Linux--V4L2摄像头驱动框架及UVC浅析

一、前言 对于一个usb摄像头&#xff0c;它的内核驱动源码位于/drivers/media/usb/uvc/ 核心层&#xff1a;V4L2_dev.c文件 硬件相关层&#xff1a; uvc_driver.c文件 本篇记录基于对6.8.8.8内核下vivid-core.c文件&#xff08;虚拟视频驱动程序&#xff09;的分析&#xff…

【数据库】仓库管理数据库(练习样例)

某连锁超市需要设计实现一个仓库管理系统&#xff0c;要求每个仓库可以有多名仓库管理员&#xff0c;每个仓库管理员只负责管理一个仓库&#xff0c;同时每个仓库都配备了一名仓库主管&#xff1b;不同的仓库存放的是不同类型的货品&#xff0c;每种货品只存放在固定的仓库中&a…

Os-hackNos

下载地址 https://download.vulnhub.com/hacknos/Os-hackNos-1.ova 环境配置如果出现&#xff0c;扫描不到IP的情况&#xff0c;可以尝试vulnhub靶机检测不到IP地址解决办法_vulnhub靶机扫描不到ip-CSDN博客 信息收集 确定靶机地址&#xff1a; 探测到存活主机192.168.111.…

modelscope可控细节的长文档摘要

modelscope可控细节的长文档摘要尝试 本文的想法来自今年OpenAI cookbook的一篇实践&#xff1a;summarizing_long_documents&#xff0c;目标是演示如何以可控的细节程度总结大型文档。 如果我们想让大语言模型总结一份长文档&#xff08;例如 10k 或更多tokens&#xff09;&…

【MySQL】 NDB 集群概述

MySQL NDB&#xff08;Network Database&#xff09;是MySQL的一个存储引擎&#xff0c;也称为NDB Cluster存储引擎。它主要用于构建高可用性、高可扩展性和高性能的分布式数据库集群。NDB Cluster是MySQL的一个特殊版本&#xff0c;专门设计用于处理大规模的分布式数据存储和处…

【MySQL】MySQL 9.0悄悄的来了

MySQL 9.0.0 中的变化 MySQL 9.0 中的新功能 JavaScript 存储程序 MySQL 企业版现在支持用 JavaScript 编写的存储程序&#xff0c;例如使用 CREATE FUNCTION下面显示的语句和 JavaScript 代码创建的这个简单示例&#xff1a; CREATE FUNCTION gcd(a INT, b INT) RETURNS …

SpringBoot-第一天学习

SpringBoot介绍-约定大于配置 SpringBoot是在Spring4.0基础上开发的&#xff0c;不是替代Spring的解决方案&#xff0c;而是和Spring框架结合并进一步简化Spring搭建和开发过程的。 如何简化&#xff1f;就是通过提供默认配置等方式让我们更容易&#xff0c;集成了大量常用的…

泛微开发修炼之旅--29用计划任务定时发送邮件提醒

文章链接&#xff1a;29用计划任务定时发送邮件提醒

嵌入式Linux系统编程 — 6.7 实时信号

目录 1 什么是实时信号 2 sigqueue函数 3 sigpending()函数 1 什么是实时信号 等待信号集只是一个掩码&#xff0c;它并不追踪信号的发生次数。这意味着&#xff0c;如果相同的信号在被阻塞的状态下多次产生&#xff0c;它只会在信号集中被记录一次&#xff0c;并且在信号集…

Django文档简化版——Django快速入门——创建一个基本的投票应用程序

Django快速入门——创建一个基本的投票应用程序 准备工作1、创建虚拟环境2、安装django 1、请求和响应&#xff08;1&#xff09;创建项目&#xff08;2&#xff09;用于开发的简易服务器&#xff08;3&#xff09;创建投票应用&#xff08;4&#xff09;编写第一个视图1、编写…

FreeRTOS的任务间通信

文章目录 4 FreeRTOS任务间通信4.1 队列4.1.1 队列的使用4.1.2 队列的创建&#xff0c;删除&#xff0c;复位4.1.3 队列的发送&#xff0c;接收&#xff0c;查询 4.2 邮箱&#xff08;mailbox&#xff09;4.2.1 任务中读写邮箱4.2.2 中断中读写邮箱 4.3 队列集4.3.1 队列集的创…

linux19:程序替换

一&#xff1a;最简单的看看程序替换是什么样的&#xff08;单个进程版&#xff09; 1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 int main()5 {6 printf("Before : I am a process , myPid:%d,myPPid:%d\n",getpid(),getpp…

【Ubuntu】详细说说Parallels DeskTop安装和使用Ubuntu系统

希望文章能给到你启发和灵感~ 如果觉得文章对你有帮助的话,点赞 + 关注+ 收藏 支持一下博主吧~ 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境二、Ubuntu系统的使用2.1 系统的下载2.2 系统的安装2.3 安装桌面版(可选)2.3.1 安装/更新apt2.3.2 安装桌面版2.3…

算法day02 回文 罗马数字转整数

回文 搞错了String类型的indexOf方法&#xff0c;理解成获取对应下标的值&#xff0c;实际上是在找对应值的下标。 4ms 耗时最少的方法尽量不会去调用jdk提供的方法&#xff0c;而是直接使用对应的数学逻辑关系来处理&#xff0c; 甚至用 代替equals方法。 罗马数字转整数 考…

Simulink中示波器连续运行的方法

1.在Simulink中,经常要使用到示波器,默认示波器是定时运行的,只能观察到一小部分运行的波形;实际调试过程中,经常要连续运行,因此,需要设置示波器为连续运行模式,下面将介绍示波器连续运行的方法。 打开Simulink仿真软件,找到仿真设置按钮,点击设置: 2.将其停止时间…

Oracle 解决4031错误

一、问题描述 什么是4031错误和4031错误产生的原因: 简单一个句话概括: 由于服务器一直在执行大量的硬解析,导致Oracle 的shared pool Free空间碎片过多,大的chunk不足, 当又一条复杂的sql语句要硬解析时, 缺少1个足够大的Free chunk, 通常就会报4031错误. 二、解决方法 临…

JVM原理(十五):JVM虚拟机静态分配与动态分配

1. 分派 本节讲解的分派调用过程将会揭示多态性特征的一-些最基本的体现&#xff0c;如“重载”和“重写”在Java虚拟机之中是如何实现的。 1.1. 静态分派 案例&#xff1a; 我们先来看一段代码: Human mannew Man(); 我们把上面代码中的“Human"称为变量的“静态类型…

9 redis,memcached,nginx网络组件

课程目标: 1.网络模块要处理哪些事情 2.reactor是怎么处理这些事情的 3.reactor怎么封装 4.网络模块与业务逻辑的关系 5.怎么优化reactor? io函数 函数调用 都有两个作用:io检测 是否就绪 io操作 1. int clientfd = accept(listenfd, &addr, &len); 检测 全连接队列…