【STM32F4系列】【HAL库】【自制库】模拟IIC主机

news2024/9/26 5:20:35

介绍

本项目是利用GPIO模拟I2C的主机

网上常见的是模拟I2C主机

本项目是作为一个两个单片机之间低速通信的用法

协议介绍请看,传送门

模拟从机请看这里

主机

功能描述

  • I2C按字节(Byte)读写
  • I2C读写寄存器
  • I2C连续读写

编程思路解析

主机是时钟信号的发起方,起始和中止信号均来自主机

I2C的主机编程相对简单

我们只需要按照协议发送信号即可,具体在实现各个功能时细说

HAL初始化

I2C主机需要使用2个开漏上拉输出的GPIO

分别是SDASCL

我们对其操作时序,模拟出I2C协议即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oznuyZH2-1672321697833)(图片/1.png)]

程序设计

GPIO控制

将HAL库的GPIO控制的函数做了一个封装,用于改变GPIO时的快速使用

#define I2C_SCL_GPIOx GPIOA
#define I2C_SCL_Pin GPIO_PIN_0
#define I2C_SDA_GPIOx GPIOA
#define I2C_SDA_Pin GPIO_PIN_1
/**
 * @brief 一段延迟
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-07-27 08:53:30
 */
void I2C_Delay(void)
{
	int z = 0xff;
	while (z--)
		;
}
/**
 * @brief 写SDA
 * @param H_L:高低电平
 * @return 无
 * @author HZ12138
 * @date 2022-10-21 18:07:18
 */
void I2C_Write_SDA(GPIO_PinState H_L)
{
	HAL_GPIO_WritePin(I2C_SDA_GPIOx, I2C_SDA_Pin, H_L);
}
/**
 * @brief 写SCL
 * @param H_L:高低电平
 * @return 无
 * @author HZ12138
 * @date 2022-10-21 18:07:40
 */
void I2C_Write_SCL(GPIO_PinState H_L)
{
	HAL_GPIO_WritePin(I2C_SCL_GPIOx, I2C_SCL_Pin, H_L);
}
/**
 * @brief 读取SDA
 * @param 无
 * @return SDA的状态
 * @author HZ12138
 * @date 2022-10-21 18:07:56
 */
uint16_t I2C_Read_SDA(void)
{
	return HAL_GPIO_ReadPin(I2C_SDA_GPIOx, I2C_SDA_Pin);
}
/**
 * @brief 读取SCL
 * @param 无
 * @return SDA的状态
 * @author HZ12138
 * @date 2022-10-21 18:07:56
 */
uint16_t I2C_Read_SCL(void)
{
	return HAL_GPIO_ReadPin(I2C_SCL_GPIOx, I2C_SCL_Pin);
}

启动中止信号

I2C的启动中止信号是在SCL高电平时,SDA发生跳变

这样只需要先拉高SCL,再改变SDA即可

必要的地方加入了延迟,避免过高的速度出现问题

/**
 * @brief 产生I2C起始信号
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-07-27 08:54:48
 */
void I2C_Start(void)
{
	I2C_Write_SDA(GPIO_PIN_SET);   // 需在SCL之前设定
	I2C_Write_SCL(GPIO_PIN_SET);   // SCL->高
	I2C_Delay();				   // 延时
	I2C_Write_SDA(GPIO_PIN_RESET); // SDA由1->0,产生开始信号
	I2C_Delay();				   // 延时
	I2C_Write_SCL(GPIO_PIN_RESET); // SCL->低
}
/**
 * @brief 产生I2C结束信号
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-07-27 08:57:03
 */
void I2C_End(void)
{
	I2C_Write_SDA(GPIO_PIN_RESET); // 在SCL之前拉低
	I2C_Write_SCL(GPIO_PIN_SET);   // SCL->高
	I2C_Delay();				   // 延时
	I2C_Write_SDA(GPIO_PIN_SET);   // SDA由0->1,产生结束信号
	I2C_Delay();				   // 延时
}

应答信号

对于主机来说,有两种应答,

一个是自己发送应答信号,主机控制SDASCL

另一个是接收从机的应答信号,主机控制SCL,从机控制SDA

这边分成了2个控制函数

注意:ACK信号在很多设备中会被省略,这边主机只保证有第9个周期即可,不用过多关注应答

/**
 * @brief 发送应答码
 * @param ack:0 应答 1 不应达
 * @return 无
 * @author HZ12138
 * @date 2022-07-27 09:03:38
 */
void I2C_Send_ACK(uint8_t ack)
{
	if (ack == 1)
		I2C_Write_SDA(GPIO_PIN_SET); // 产生应答电平
	else
		I2C_Write_SDA(GPIO_PIN_RESET);
	I2C_Delay();
	I2C_Write_SCL(GPIO_PIN_SET);   // 发送应答信号
	I2C_Delay();				   // 延时至少4us
	I2C_Write_SCL(GPIO_PIN_RESET); // 整个期间保持应答信号
}
/**
 * @brief 接受应答码
 * @param 无
 * @return 应答码 0 应答 1 不应达
 * @author HZ12138
 * @date 2022-07-27 09:04:28
 */
uint8_t I2C_Get_ACK(void)
{
	uint8_t ret;				 // 用来接收返回值
	I2C_Write_SDA(GPIO_PIN_SET); // 电阻上拉,进入读
	I2C_Delay();
	I2C_Write_SCL(GPIO_PIN_SET); // 进入应答检测
	I2C_Delay();				 // 至少延时4us
	ret = I2C_Read_SDA();		 // 保存应答信号
	I2C_Write_SCL(GPIO_PIN_RESET);
	return ret;
}

写一个字节

控制SDA向外部发送1Byte数据

从高位到低位发送,SDA的电平改变需要在SCL低电平时

发送8个bit后,第9个周期需要读取ASK信号

所以编程思路如下:

搞一个循环,从0到7做计数,每次发送数据的最高位,并将数据向右移动一位,同时在SDA改变后发送一个SCL周期

在发送完成后,读取ASK信号并保存下来

/**
 * @brief I2C写1Byte
 * @param dat:1Byte数据
 * @return 应答结果 0 应答 1 不应达
 * @author HZ12138
 * @date 2022-07-27 09:05:14
 */
uint8_t I2C_SendByte(uint8_t dat)
{
	uint8_t ack;
	for (int i = 0; i < 8; i++)
	{
		// 高在前低在后
		if (dat & 0x80)
			I2C_Write_SDA(GPIO_PIN_SET);
		else
			I2C_Write_SDA(GPIO_PIN_RESET);
		I2C_Delay();
		I2C_Write_SCL(GPIO_PIN_SET);
		I2C_Delay(); // 延时至少4us
		I2C_Write_SCL(GPIO_PIN_RESET);
		dat <<= 1; // 低位向高位移动
	}

	ack = I2C_Get_ACK();

	return ack;
}

读一个字节

控制SDA向外部发送1Byte数据

从高位到低位读取,SCL高电平时,读取SDA

发送8个bit后,第9个周期需要发送ASK信号

和写一样,同样搞一个循环

SCL高电平时读取SDA即可

在读取完1Byte后,向外发送ASK信号

/**
 * @brief I2C读取1Byte数据
 * @param ack:应答 0 应答 1 不应达
 * @return 接受到的数据
 * @author HZ12138
 * @date 2022-07-27 09:06:13
 */
uint8_t I2C_ReadByte(uint8_t ack)
{
	uint8_t ret = 0;
	I2C_Write_SDA(GPIO_PIN_SET);
	for (int i = 0; i < 8; i++)
	{
		ret <<= 1;
		I2C_Write_SCL(GPIO_PIN_SET);
		I2C_Delay();
		// 高在前低在后
		if (I2C_Read_SDA())
		{
			ret++;
		}
		I2C_Write_SCL(GPIO_PIN_RESET);
		I2C_Delay();
	}

	I2C_Send_ACK(ack);

	return ret;
}

连续写

用于写大量数据给从机

流程:

  1. 发送起始信号
  2. 发送器件地址(最低位是0)
  3. 发送寄存器地址
  4. 将缓冲区数据按顺序发送出去
  5. 发送结束信号
/**
 * @brief I2C连续写
 * @param addr:器件地址
 * @param reg:寄存器地址
 * @param len:长度
 * @param buf:缓冲区地址
 * @return 状态 0成功 其他失败
 * @author HZ12138
 * @date 2022-08-08 15:47:11
 */
uint8_t I2C_Write_Len(uint8_t reg, uint8_t len, uint8_t *buf)
{
	uint8_t i;
	I2C_Start();
	I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令
	I2C_SendByte(reg);			   // 写寄存器地址
	for (i = 0; i < len; i++)
	{
		I2C_SendByte(buf[i]); // 发送数据
	}
	I2C_End();
	return 0;
}

连续读

用于从从机读取大量数据

流程:

  1. 发送起始信号
  2. 发送器件地址(最低为为0)
  3. 发送寄存器地址
  4. 发送起始信号
  5. 发送器件地址(最低为为1)
  6. 按顺序读取数据写入缓冲区
  7. 发送结束信号
/**
 * @brief I2C连续读
 * @param addr:器件地址
 * @param reg:寄存器地址
 * @param len:长度
 * @param buf:缓冲区地址
 * @return 状态 0成功 其他失败
 * @author HZ12138
 * @date 2022-08-08 15:47:11
 */
uint8_t I2C_Read_Len(uint8_t reg, uint8_t len, uint8_t *buf)
{
	I2C_Start();
	I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令
	I2C_SendByte(reg);			   // 写寄存器地址
	I2C_Start();
	I2C_SendByte(I2C_Address | 1); // 发送器件地址+读命令
	while (len)
	{
		if (len == 1)
			*buf = I2C_ReadByte(1); // 读数据,发送nACK
		else
			*buf = I2C_ReadByte(0); // 读数据,发送ACK
		len--;
		buf++;
	}
	I2C_End(); // 产生一个停止条件
	return 0;
}

写寄存器

写寄存器的一个字节

流程:

  1. 发送起始信号
  2. 发送器件地址(最低位是0)
  3. 发送寄存器地址
  4. 将缓冲区数据发送出去
  5. 发送结束信号
/**
 * @brief I2C写一个字节
 * @param reg:寄存器地址
 * @param data:数据
 * @return 状态 0成功 其他失败
 * @author HZ12138
 * @date 2022-08-08 15:47:11
 */
uint8_t I2C_Write_Reg(uint8_t reg, uint8_t data)
{
	I2C_Start();
	I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令
	I2C_SendByte(reg);			   // 写寄存器地址
	I2C_SendByte(data);			   // 发送数据
	I2C_End();
	return 0;
}

读寄存器

读取寄存器的一个字节

流程:

  1. 发送起始信号
  2. 发送器件地址(最低为为0)
  3. 发送寄存器地址
  4. 发送起始信号
  5. 发送器件地址(最低为为1)
  6. 读取数据写入缓冲区
  7. 发送结束信号
/**
 * @brief I2C读一个字节
 * @param reg:寄存器地址
 * @return 读取到的数据
 * @author HZ12138
 * @date 2022-08-08 15:47:11
 */
uint8_t I2C_Read_Reg(uint8_t reg)
{
	uint8_t res;
	I2C_Start();
	I2C_SendByte(I2C_Address | 0); // 发送器件地址+写命令
	I2C_SendByte(reg);			   // 写寄存器地址
	I2C_Start();
	I2C_SendByte(I2C_Address | 1); // 发送器件地址+读命令
	res = I2C_ReadByte(1);		   // 读取数据,发送nACK
	I2C_End();					   // 产生一个停止条件
	return res;
}

成品

GitHub

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python百日进阶-数据分析】Day141 - plotly桑基图:plotly.graph_objects.Sankey()

文章目录一、语法二、参数三、返回值四、实例4.1 基本桑基图4.2 桑基图的hovertemplate和customdata4.3 定义节点位置4.4 带有彩色链接的更复杂的桑基图4.5 Dash中的桑基图4.6 风格桑基图一、语法 用于网络流量数据分析的桑基图。节点在 中指定&#xff0c;nodes源和目标之间的…

Flutter GetX系列教程---BottomSheet

安装 将 GetX 添加到你的 pubspec.yaml 文件中 dependencies:get: ^4.6.5在需要用到的文件中导入&#xff0c;它将被使用。 import package:get/get.dart;BottomSheet介绍 BottomSheet 是底部弹出的一个组件&#xff0c;常用于单选、验证码二次校验弹窗等&#xff0c;GetX的…

Go语言设计与实现 -- singleflight

这个东西很重要&#xff0c;可以经常用在项目当中&#xff0c;所以我们单独拿出来进行讲解。 在使用它之前我们需要导包&#xff1a; go get golang.org/x/sync/singleflightgolang/sync/singleflight.Group 是 Go 语言扩展包中提供了另一种同步原语&#xff0c;它能够在一个服…

【NCC】之二:积分图加速均值计算

文章目录<center> 积分图 integral image1. 原理&#xff1a;2. 示例3. 计算区域均值4. 计算区域方差5. 积分图示例6. 计算积分图的源码7. 用积分图加速NCC参考积分图 integral image1. 原理&#xff1a; Summed Area Table是一种数据结构和算法&#xff0c;用于快速有效…

【math】大规模对称正定稀疏线性方程组的求解与代数多重网格

大规模对称正定稀疏线性方程组的求解与代数多重网格代数多重网格问题定义迭代法的优畧几何多重网格代数多重网格代数多重网格 你好&#xff01;代数多重网格一个很有意思的话题。 问题定义 很多问题都可以抽象为求解下列优化的问题&#xff1a; 对于图像问题&#xff0c;一…

安全、稳定的工业蜂窝路由器具有怎样的特性?

一、前言 传统路由器通过电缆或光纤线路访问Internet&#xff0c;在很多场景或区域下存在着很大的局限性&#xff0c;例如在行驶的火车上&#xff0c;在固定电话稀缺或没有其他接入方式的地区都是十分受限的。随着科技的发展&#xff0c;很多行业应用都需要具有更强大功能的路…

3 高级面向对象编程实例

高级OOP 1 继承 是一种基于已有类创建新类的机制 class 子类名 extends 父类{类体; }public class Extends_v1 {public static void main(String[] args) {Extendsclass01 ex new Extendsclass01();} } class Baseclass01{public int num;public void setNum(int n){num n…

java之线程死锁和ThreadLocal的使用

线程死锁&#xff1a; 线程死锁是指两个或者两个以上的线程在执行过程中&#xff0c;由于竞争资源或者彼此通信而造成的一种阻塞的现象,若无外力的作用,它们都将无法继续执行下去。 此时应用系统就处于了死锁状态&#xff0c;这些永远在互相等待的线程称为死锁线程。 如下图…

文本中按规则分组区段随机抽样

【问题】 This is a bit complex, and I greatly appreciate any help! I am trying to randomly sample rows from a .csv file. Essentially, I want a resulting file of unique locations (Locations are specified by Easting and Northing columns of the data file, be…

ServletContext和过滤器

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

BM30 二叉搜索树与双向链表

题目 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表。如下图所示&#xff1a; 数据范围&#xff1a;输入二叉树的节点数0≤n≤1000&#xff0c;二叉树中每个节点的值0≤val≤1000. 要求&#xff1a;空间复杂度O(1)&#xff08;即在原树上操作&#x…

低代码对比分析,从工程化上看产品的优劣

低代码算是这几年在IT行业内越来越尖锐的讨论了&#xff0c;而且随着这两年大厂的大量裁员&#xff0c;更是亲者痛仇者快的事情&#xff0c;因为很多大厂发现把一些低端的研发岗位干掉了&#xff0c;反而整个体系在工具的辅助运转下&#xff0c;效率更高&#xff0c;执行力更优…

【Python数据分析】Python模拟登录(一) requests.Session应用

最近由于某些原因&#xff0c;需要用到Python模拟登录网站&#xff0c;但是以前对这块并不了解&#xff0c;而且目标网站的登录方法较为复杂&#xff0c; 所以一下卡在这里了&#xff0c;于是我决定从简单的模拟开始&#xff0c;逐渐深入地研究下这块。 注&#xff1a;本文仅为…

Python学习基础笔记五十九——封装和@property

1、私有属性的一个用法&#xff1a; class Room:def __init__(self, name, length, width):self.name nameself.__length lengthself.__width widthdef area(self):return self.__length * self.__widthwei Room(Wei, 2, 1) print(wei.area()) 2、getter和setter&#xf…

Hi3861鸿蒙物联网项目实战:智能照明灯

华清远见FS-Hi3861开发套件&#xff0c;支持HarmonyOS 3.0系统。开发板主控Hi3861芯片内置WiFi功能&#xff0c;开发板板载资源丰富&#xff0c;包括传感器、执行器、NFC、显示屏等&#xff0c;同时还配套丰富的拓展模块。开发板配套丰富的学习资料&#xff0c;包括全套开发教程…

第十篇 1+X考证 Web前端测试题(新)

单选题 1、关于HTML和CSS以下说法错误的是&#xff08; D &#xff09; A、HTML标签中属性的值一定要用双引号或单引号括起来B、HTML空元素要有结束的标签或于开始的标签后加上"/"C、结构与样式完全分离时&#xff0c;结构代码中不涉及任何的样式元素&#xff0c;如f…

Qt之软键盘的实现

文章目录前言一、基于中文汉字数据库1、核心代码2、效果二、基于谷歌拼音输入引擎1、核心代码2、效果前言 Qt5.8版本开始推出了基于QML实现的软键盘功能&#xff0c;在此之前&#xff0c;并没有官方版本的软键盘。本篇主要介绍Qt实现软键盘的两种方案&#xff0c;一种基于中文汉…

[python][GUI]pyside6

------------------------------------------------------------------------------------------------------------------ #非常好资料和教程&#xff1a; 1. Module Index - Qt for Python 2. muziing/PySide6-Code-Tutorial: 可能是最好的PySide6中文教程&#xff01;用代…

Spring boot 日志直接推送到elasticsearch上

Spring boot 日志直接推送到elasticsearch前言核心依赖elasticsearch配置文件1.url格式如下2.index索引前缀 "xxx"3.maxMessageSize参数数据内容最大值&#xff0c;本文未使用&#xff08;默认值-1全部数据接收&#xff09;如下4.BasicAuthentication.java 重写该类用…

详解opencv库函数ellipse()

opencv库函数ellipse()函数可以画扇形&#xff0c;也可以画椭圆。画扇形时只需要将椭圆的长短轴长度设为相同并给定扇形的圆心角即可。 # 参数 1.目标图片 2.椭圆圆心 3.长短轴长度 4.偏转角度 5.圆弧起始角度 6.终止角度 7.颜色 8.是否填充 cv2.ellipse(img_p, (500, 2…