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

news2024/10/6 4:06:17

介绍

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

网上常见的是模拟I2C主机

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

协议介绍请看,传送门

模拟主机请看这里

从机

功能

实现I2C从机端读写寄存器

编程思路

I2C的从机实现比起主机来麻烦一些

因为SCL的时序是由主机发送,从机需要响应

注意:整个过程不考虑应答码

思路是检测SCLSDA的边沿(上升沿和下降沿)中断

SDA的边沿检测SCL的电平,如果SCL为高电平,则根据协议开始(SDA上升沿),或结束(SDA下降沿)I2C通信,在SCL低电平则无需动作

SCL上升沿是检测来自SDA的数据(来自主机),SCL下降沿通过SDA发送数据(发给主机)

整体使用状态机的思想:

  1. 在SDA上升沿,SCL高电平时进入空闲态(0),之后转入准备态(1)
  2. 在SCL下降沿时,清空中间变量的数据,转入器件地址解码(2)
  3. 在之后的8个上升沿是数据,第9个上升沿是应答吗,这里用于状态切换,如果是写入器件地址(最低位为0)则转移为寄存器读取态(3),如果是读取器件地址(最低位为1)则转换为数据发送态(5)
  4. 如果寄存器读取态(3)则同样在SCL上升沿动作,读取8个上升沿的数据,当作寄存器地址,并转移为数据读取态(4)
  5. 数据读取态(4),还是在SCL上升沿动作,读取数据放入缓冲区即可
  6. 这边说一下器件地址是读取器件地址时寄存器地址的获取手段,因为发送寄存器地址是按照写入器件地址发送的,之后需要再发送一次读取器件地址,这时又触发了一次起始信号,因此可以即在第一次发送写入器件地址并转移到寄存器读取态(3)读取寄存器地址后被打断了,重新进入了 准备态(1)->器件地址解码(2)->数据发送态(5)
  7. 数据发送态(5),此态是在SCL下降沿动作的(在SCL低电平时改变SDA),按顺序依次发送数据即可
  8. 结束后进入空闲态,等待下次触发

HAL设置

需要设置两个GPIO的上升沿和下降沿中断

如下图,设置为边沿中断,上拉

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

程序

GPIO基本输出函数

#define I2C_Address 0x54
#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);
}

切换GPIO模式

首先我们分析一下GPIO的模式

SCL一直保持边沿中断即可

SDA需要在空闲主机写(从机接收数据)状态保持边沿中断,而在主机读(从机发送数据)边沿中断开漏上拉输出状态切换

为了让通信速率不算太低,此处切换需要较高速率,不建议使用HAL函数

这边选择直接操作寄存器来实现

具体请参考这篇博客,传送门

#define I2C_SDA_Pinx 1 // GPIO_PIN_x 写x
/**
 * @brief 设置SDA为中断模式
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-12-29 19:56:29
 */
void I2C_Slave_Set_SDA_IT(void)
{
	I2C_SDA_GPIOx->MODER &= ~(3 << (I2C_SDA_Pinx * 2));
	I2C_SDA_GPIOx->MODER |= 0 << I2C_SDA_Pinx * 2;
}
/**
 * @brief 设置SDA为开漏上拉输出
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-12-29 19:56:54
 */
void I2C_Slave_Set_SDA_Out(void)
{
	I2C_SDA_GPIOx->MODER &= ~(3 << (I2C_SDA_Pinx * 2));
	I2C_SDA_GPIOx->MODER |= 1 << I2C_SDA_Pinx * 2;
}

SDA和SCL边沿服务函数

本函数主要用于区分上升和下降沿

/**
 * @brief 在SCL中断服务函数中调用
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-12-29 21:28:32
 */
void I2C_Slave_IRQ_SCL(void)
{

	if (I2C_Read_SCL() == GPIO_PIN_SET)
	{ // 上升沿
		I2C_Slave_IRQ_SCL_Rising();
	}
	else
	{ // 下降沿
		I2C_Slave_IRQ_SCL_Falling();
	}
}
/**
 * @brief 在SDA中断服务函数中调用
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-12-29 21:28:32
 */
void I2C_Slave_IRQ_SDA(void)
{
	if (I2C_Slave_SDA_IRQ_EN == 1)
	{
		if (I2C_Read_SCL() == GPIO_PIN_SET)
		{
			if (I2C_Read_SDA() == GPIO_PIN_SET)
			{					   // SDA上升沿
								   // 完成态
				I2C_Slave_Ins = 0; // 到空闲态
				I2C_Slave_Set_SDA_IT();
			}
			else
			{ // SDA下降沿
				I2C_Slave_Set_SDA_IT();
				if (I2C_Slave_Ins == 0) // 空闲态
					I2C_Slave_Ins = 1;	// 到准备态
				else
				{
					I2C_Slave_Ins = 1;
				}
			}
		}
	}
}

SCL上升沿服务函数

本函数是核心之一

根据编程思路一节的内容来编写上升沿服务函数

/**
 * @brief SCL上升沿服务函数
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-12-29 21:29:43
 */
void I2C_Slave_IRQ_SCL_Rising(void)
{ // SCL上升沿
	switch (I2C_Slave_Ins)
	{
	case 2: // 器件地址解码

		I2C_Slave_zj <<= 1;
		I2C_Slave_zj |= I2C_Read_SDA();
		I2C_Slave_num++;
		if (I2C_Slave_num == 8) // 数据码
		{
			I2C_Slave_Add = I2C_Slave_zj;
			I2C_Slave_zj = 0;
		}
		else if (I2C_Slave_num == 9) // 应答码
		{
			I2C_Slave_num = 0;
			if (I2C_Slave_Add == (I2C_Address & 0xfe))
				I2C_Slave_Ins = 3; // 到寄存器地址读取态
			else if (I2C_Slave_Add == (I2C_Address | 0x01))
			{
				I2C_Write_SDA(GPIO_PIN_SET);
				I2C_Slave_Set_SDA_Out();
				I2C_Slave_Ins = 5;
				I2C_Slave_zj = 0xaa;
				I2C_Slave_num = 0;
				I2C_Slave_SDA_IRQ_EN = 0;
			}
		}

		break;
	case 3: // 寄存器地址读取

		I2C_Slave_zj <<= 1;
		I2C_Slave_zj |= I2C_Read_SDA();
		I2C_Slave_num++;
		if (I2C_Slave_num == 8) // 数据码
		{
			Reg_Add = I2C_Slave_zj;
		}
		else if (I2C_Slave_num == 9) // 应答码
		{
			I2C_Slave_Ins = 4; // 数据读取
			I2C_Slave_zj = 0;
			I2C_Slave_num = 0;
		}
		break;
	case 4: // 数据读取(主机写)
		I2C_Slave_zj <<= 1;
		I2C_Slave_zj |= I2C_Read_SDA();
		I2C_Slave_num++;
		if (I2C_Slave_num == 9) // 应答码
		{
			I2C_Slave_zj = 0;
			I2C_Slave_num = 0;
		}
		break;

	default:
		break;
	}
}

SCL上升沿服务函数

本函数是核心之一

根据编程思路一节的内容来编写下降沿服务函数

/**
 * @brief SCL下降沿服务函数
 * @param 无
 * @return 无
 * @author HZ12138
 * @date 2022-12-29 21:29:43
 */
void I2C_Slave_IRQ_SCL_Falling(void)
{ // SCL下降沿
	switch (I2C_Slave_Ins)
	{
	case 1: // 准备态
		I2C_Slave_zj = 0;
		I2C_Slave_num = 0;
		I2C_Slave_Ins = 2; // 到器件地址解码
		break;
	case 5: // 数据发送(主机读)

		if (I2C_Slave_zj & 0x80)
			I2C_Write_SDA(GPIO_PIN_SET);
		else
			I2C_Write_SDA(GPIO_PIN_RESET);
		I2C_Slave_zj <<= 1;
		I2C_Slave_num++;
		if (I2C_Slave_num == 9) // 应答码
		{
			I2C_Slave_num = 0;
			I2C_Write_SDA(GPIO_PIN_SET);
			I2C_Slave_Set_SDA_IT();
			I2C_Slave_SDA_IRQ_EN = 1;
			I2C_Slave_Ins = 0;
		}
		break;
	default:
		break;
	}
}

成品

		I2C_Write_SDA(GPIO_PIN_SET);
	else
		I2C_Write_SDA(GPIO_PIN_RESET);
	I2C_Slave_zj <<= 1;
	I2C_Slave_num++;
	if (I2C_Slave_num == 9) // 应答码
	{
		I2C_Slave_num = 0;
		I2C_Write_SDA(GPIO_PIN_SET);
		I2C_Slave_Set_SDA_IT();
		I2C_Slave_SDA_IRQ_EN = 1;
		I2C_Slave_Ins = 0;
	}
	break;
default:
	break;
}

}


# 成品

[GitHub](https://github.com/HZ1213825/HAL_STM32F4_IIC)

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

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

相关文章

【nowcoder】笔试强训Day12

目录 一、选择题 二、编程题 2.1二进制插入 2.2 查找组成一个偶数最接近的两个素数 一、选择题 1.以下方法&#xff0c;哪个不是对add方法的重载? public class Test {public void add( int x,int y,int z){} } A. public int add(int x,int y,float z){return 0;} B.…

Go语言设计与实现 -- WaitGroup, Once, Cond

WaitGroup 我们可以通过 sync.WaitGroup 将原本顺序执行的代码在多个 Goroutine 中并发执行&#xff0c;加快程序处理的速度。 我们来看一下sync.WaitGroup的结构体&#xff1a; type WaitGroup struct {//保证WaitGroup不会被开发者通过再赋值的方式复制noCopy noCopy// 64-…

重学redux之Redux-Thunk高级使用(三)

这是第三篇了,哥们,如果没看过前两篇,可以去看看之前的两篇,有基础的可以直接看,不多说,直接开讲 默认情况下,Redux 的动作是同步调度的,对于任何需要与外部 API 通信或执行副作用的应用程序来说都是一个问题。 Redux 允许中间件位于被分派的动作和到达 reducer 的动…

抖音本地生活的蓬勃发展,离不开服务商的推波助澜

抖音本地生活&#xff0c;已经势不可挡01 抖音公布本地生活成绩单&#xff0c;交易额增长30倍抖音经过6年时间的演变&#xff0c;产品功能日益丰富&#xff0c;已经从内容消费&#xff0c;延续到线上购物、线下团购等领域&#xff0c;从最初的记录美好生活&#xff0c;成为一种…

统计分析工具-FineReport配置SQL Server外接数据库(2)

1. 配置外接数据库 1.1 外接数据库配置入口 外接数据库的配置入口&#xff0c;有三种形式&#xff1a; 1&#xff09;超级管理员第一次登录数据决策系统时&#xff0c;即可为系统配置外接数据库。如下图所示&#xff1a; 2&#xff09;对于使用内置数据库的系统&#xff0c;管…

站点能源低碳目标网,助力网络碳中和 | 华为发布站点能源十大趋势

2022年12月29日&#xff0c;华为今天举办站点能源十大趋势发布会并重磅发布白皮书。发布会上&#xff0c;华为站点能源领域总裁尧权全面解读了能源数字化、低碳网络、站点供电绿色化等站点能源十大趋势。 尧权表示&#xff0c;2022年是不平凡的一年&#xff0c;全球能源危机背…

十、通过网络服务将esp8266引脚状态显示在网页中

ESP8266在服务器模式运行时&#xff0c;我们可以使用浏览器来显示它的引脚状态。 1、实现目标 学习如何通过esp8266建立基本网站&#xff0c;在该网站上实时显示esp8266的引脚值。 2、原理图 FLASH按键与D3引脚连接&#xff0c;可以通过FLASH按键改变D3引脚的电平。当没有按…

中型企业适合用什么样的CRM管理软件,求推荐?

中型企业适合用什么样的CRM管理软件&#xff0c;求推荐&#xff1f; CRM管理软件是现代企业必不可少的管理软件之一&#xff0c;很多企业都会选择CRM管理软件来经营客户资源&#xff0c;但能够精准地选择到适合自己企业的CRM管理软件则是困难的。 中型企业需要与自己业务流程…

数据可视化之finebi和tableau电力系统分析实现对比

通过一个电力系统简单案例&#xff0c;尝试实际执行finebi和Tableau数据可视化设计的各项基本步骤&#xff0c;以熟悉Tableau和finebi数据可视化设计技巧&#xff0c;提高大数据可视化应用能力。 一、工具/准备工作 在开始本实验之前&#xff0c;请认真阅读课程的相关内容。 …

写给小白的TensorFlow的入门课

文章目录前言学习AI的必要性和业务的关系最简单的例子要做什么&#xff1f;数据图形化展示构建计算图形计算图形最小化误差MacOS 中配置运行环境安装验证安装简单模型训练识别数字图片的模型训练Softmax Regression算法大概步骤大致算法实现结语参考链接前言 深度学习就是从大…

抖音电商发布2023年食品健康行业8大趋势,新减负、新养生等成为关键词

2022抖音电商食品健康峰会暨年货盛典在杭州成功举行。抖音电商食品健康行业还联合欧睿共同发布了《2023年度食品健康行业趋势洞察报告》。图片来源&#xff1a;抖音电商抖音电商食品健康行业负责人白华在会上透露&#xff0c;过去一年&#xff0c;抖音电商食品健康行业呈现出有…

虚拟机数据库改密码ERROR 1396 (HY000): Operation ALTER USER failed for ‘root‘@‘localhost‘

注&#xff1a;原因为MySql 8.0.11 换了新的身份验证插件&#xff08;caching_sha2_password&#xff09;, 原来的身份验证插件为&#xff08;mysql_native_password&#xff09;。而客户端工具Navicat Premium12 中找不到新的身份验证插件&#xff08;caching_sha2_password&a…

Java实现多线程

目录 基本概念 1、程序、进程、线程 2、使用线程的优点 3、线程的分类 4、线程的生命周期 多线程的实现方法 1、继承Thread类 2、实现Runnable接口 3、实现Callable接口 4、使用线程池 线程同步 1、同步代码、同步方法 2、同步机制中的锁 3、锁&#xff08;Lock&…

【电商】电商后台---采购管理模块

从供应商的管理到合同的管理&#xff0c;再到商品系统的模块的介绍、商品价格与税率维护策略&#xff0c;不知不觉已经完成了几篇文章&#xff0c;前期的准备工作完成后&#xff0c;接下来就应该进入到采购管理模块了。 几天来一直在构思如何写&#xff0c;写的内容让大家看过觉…

使用天地图加载Geoserver的图层

一、写在前面 在项目中往往使用地图作为底图(比如 天地图卫星图等)&#xff0c;再其上覆盖你的通过geoserver发布自定义图层。本文记录了我的实现方法。 二、过程 2.1 我遇到的难题 遇到难题1&#xff1a;使用无人机拍摄制作的正射影像图有几百MB甚至1个G&#xff0c;直接展示图…

YOLO系列目标检测算法——PP-YOLOE

YOLO系列目标检测算法目录 - 文章链接 YOLO系列目标检测算法总结对比- 文章链接 YOLOv1- 文章链接 YOLOv2- 文章链接 YOLOv3- 文章链接 YOLOv4- 文章链接 Scaled-YOLOv4- 文章链接 YOLOv5- 文章链接 YOLOv6- 文章链接 YOLOv7- 文章链接 PP-YOLO- 文章链接 …

深入浅出面向对象设计模式(Java)

设计模式是什么 设计模式是面向对象的一种思想。 设计模式的基本原则&#xff1f; 单一职责原则开放封闭原则里氏替换原则接口隔离原则依赖翻转原则 基本分类和为什么分为3类&#xff1f; 创建型&#xff08;怎么优雅创建对象&#xff09; 结构性&#xff08;对象的结构&am…

巧用Hibernate 完成多数据库的DDL脚本创建

巧用Hibernate 完成多数据库的DDL脚本创建 spring boot jpa 默认的orm框架就是Hibernate。 由hibernate完成数据库的读写也是主流的方式之一。但是不同数据库之间&#xff0c;建表、建索引的方言语句都有较多差别&#xff0c;很难做到一套SQL在所有数据库上进行执行。 那么Hibe…

C++11之线程库

文章目录一、thread二、mutex三、lock_guard 与 unique_lock1. lock_guard2. unique_lock四、atomic五、condition_variable在 C11 之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如 Windows 和 Linux 下各有自己的接口&#xff0c;这使得代码的…

PHP另类判断 - 数组是一维还是二维

之前有一个需求&#xff0c;需要判断一个数组是一维还是二维数组&#xff0c;如果是二维的话就要使用foreach循环来处理 在网上搜了一下给出来的都是下面所写的方式&#xff1a; if(count($updata) count($updata,1)) {// 一维 } else {// 二维 }首先我要说的是&#xff0c;上…