STM32实战总结:HAL之I2C

news2025/1/14 0:49:11

I2C基础知识参考:

嵌入式常见接口协议总结_路溪非溪的博客-CSDN博客

电路图

扩展的I2C接口,可以连接支持I2C的设备。常见于传感器等。

参考手册

目前大部分MCU都带有IIC总线接口,STM32F1也不例外。但是这里我们不使用STM32F1的硬件IIC,而是通过软件模拟。原因是ST为了规避飞利浦IIC专利问题,将STM32的硬件IIC设计的比较复杂,且稳定性不怎么好。
用软件模拟IIC,最大的好处就是方便移植, 同一个代码兼容所有MCU,任何一个单片机只要有IO口,就可以很快的移植过去,而且不需要特定的IO口。而硬件IIC,则换一款 MCU,基本上就得重新搞一次,移植是比较麻烦的,这是我们推荐使用软件模拟IIC的另外一个原因。

自行阅读参考手册,大概了解下STM32的IIC。

此处通过SHT30的使用来学习IIC协议。

电路图如下:

注意,这里的引脚连接没有连到硬件IIC上。

所以,就算想用硬件IIC也用不起来。

STN30

STN30是一款温湿度传感器芯片,关键性能如下:

Wide supply voltage range, from 2.4 to 5.5 V

I2C Interface with communication speeds up to 1MHz and two user selectable addresses Typical accuracy of 2%RH and 0.3°C

The sensor shows best performance when operated within recommended normal temperature and humidity range of 5 – 60 °C and 20 – 80 %RH

饮片引脚如下:

 

设备地址:

上面的原理图中,ADDR接了地,所以这里的地址是0x44.

为了提高准确度,该芯片采用多次测量取均值的策略。所以,当重复度越高的时候,所用的时间会越长。

更多自行阅读手册。

GPIO配置

因为是用软件I2C,所以只用配置对应的两个GPIO口,即PG11和PG12。

要注意的一点是:

我们在配置时,需要将端口配置成输出或者输入,但是,在I2C工作过程中,既需要输出,又需要输入,通过半双工来完成整个过程。

那怎么办呢?怎么能让一个IO口既是输出也是输入呢?

当引脚配置成输出,并且是开漏模式时,引脚对输入的数据也能进行读取。

不过,读取时需要先输出一个高电平,关闭内部的NMOS管,才能作为输入使用。

这就是STM32的准双向口。

关键代码

I2C.c

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/
//置位与清零SCL管脚
#define	SET_SCL	HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_SET) 
#define	CLR_SCL	HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_RESET)
//置位与清零SDA管脚
#define	SET_SDA	HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_SET)
#define	CLR_SDA	HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_RESET)
//读SDA管脚状态
#define READ_SDA	HAL_GPIO_ReadPin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin)

/* Private variables----------------------------------------------------------*/
void Init(void);  //I2C初始化
void Start(void); //I2C起始信号
void Stop(void);  //I2C停止信号
ACK_Value_t Write_Byte(uint8_t);      //I2C写字节
uint8_t     Read_Byte (ACK_Value_t);  //I2C读字节

/* Public variables-----------------------------------------------------------*/
I2C_Soft_t I2C_Soft = 
{
	Init,
	Start,
	Stop,
	Write_Byte,
	Read_Byte
};

/* Private function prototypes------------------------------------------------*/      
static void I2C_Delay_us(uint8_t);

/*
	* @name   Init
	* @brief  I2C初始化
	* @param  None
	* @retval None      
*/
static void Init(void)
{
	SET_SCL;
	SET_SDA;
}

/*
	* @name   Start
	* @brief  I2C起始信号
	* @param  None
	* @retval None      
*/
static void Start(void)
{
	//SCL为高电平,SDA的下降沿为I2C起始信号
	SET_SDA;
	SET_SCL;
	I2C_Delay_us(1);
	
	CLR_SDA;
	I2C_Delay_us(10);
	
	CLR_SCL;
	I2C_Delay_us(1);
}

/*
	* @name   Stop
	* @brief  I2C停止信号
	* @param  None
	* @retval None      
*/
static void Stop(void)
{
	//SCL为高电平,SDA的上升沿为I2C停止信号
	CLR_SDA;
	SET_SCL;
	I2C_Delay_us(1);
		
	I2C_Delay_us(10);
	SET_SDA;
}

/*
	* @name   Write_Byte
	* @brief  I2C写字节
	* @param  WR_Byte -> 待写入数据
	* @retval ACK_Value_t -> 从机应答值      
*/
static ACK_Value_t Write_Byte(uint8_t WR_Byte)
{
	uint8_t i;
	ACK_Value_t  ACK_Rspond;
	
	//SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
	//数据按8位传输,高位在前,利用for循环逐个接收
	for(i=0;i<8;i++)
	{
		//SCL清零,主机SDA准备数据
		CLR_SCL;
		I2C_Delay_us(1);
		if((WR_Byte&BIT7) == BIT7)
		{
			SET_SDA;
		}
		else
		{
			CLR_SDA;
		}
		I2C_Delay_us(1);
		//SCL置高,传输数据
		SET_SCL;
		I2C_Delay_us(10);
		
		//准备发送下一比特位
		WR_Byte <<= 1;
	}
	
	CLR_SCL;	
	//释放SDA,等待从机应答
	SET_SDA;
	I2C_Delay_us(1);
	
	SET_SCL;
	I2C_Delay_us(10);
	
	ACK_Rspond = (ACK_Value_t)READ_SDA;
	
	CLR_SCL;
	I2C_Delay_us(1);
	
	//返回从机的应答信号
	return ACK_Rspond;
}

/*
	* @name   Write_Byte
	* @brief  I2C写字节
	* @param  ACK_Value -> 主机回应值
	* @retval 从机返回值      
*/
static uint8_t Read_Byte(ACK_Value_t ACK_Value)
{
	uint8_t RD_Byte = 0,i;
		
	接收数据
	//SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
	//数据按8位传输,高位在前,利用for循环逐个接收
	for(i=0;i<8;i++)
	{
		//准备接收下一比特位
		RD_Byte <<= 1;
		
		//SCL清零,从机SDA准备数据
		CLR_SCL;
		I2C_Delay_us(10);
		
		//SCL置高,获取数据
		SET_SCL;
		I2C_Delay_us(10);	

		RD_Byte |= READ_SDA;		
	}
	
	
	//SCL清零,主机准备应答信号
	CLR_SCL;
	I2C_Delay_us(1);
	
	//主机发送应答信号	
	if(ACK_Value == ACK)
	{
		CLR_SDA;
	}
	else
	{
		SET_SDA;	
  }	
	I2C_Delay_us(1);
	
	
	SET_SCL; 	
	I2C_Delay_us(10);
	
	//Note:
  //释放SDA数据线
	//SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号
	CLR_SCL;
  SET_SDA; 	
	I2C_Delay_us(1);

	//返回数据
	return RD_Byte;
}

/*
	* @name   I2C_Delay
	* @brief  I2C延时
	* @param  None
	* @retval None      
*/
static void I2C_Delay_us(uint8_t us)
{
	uint8_t i = 0;
	
	//通过示波器测量进行校准
	while(us--)
	{
		for(i=0;i<7;i++);
	}
}
/********************************************************
  End Of File
********************************************************/

SHT30.c

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/
void Measure_Period_Mode(void);  //周期测量模式

/* Public variables-----------------------------------------------------------*/
SHT30_t SHT30 = 
{
	0.0,
	0,
	Measure_Period_Mode
};

/* Private function prototypes------------------------------------------------*/      
static uint8_t CRC_8(uint8_t *,uint8_t);


/*
	* @name   Measure_Period_Mode
	* @brief  周期测量模式
	* @param  None
	* @retval None      
*/
static void Measure_Period_Mode(void)
{
  uint8_t   temp_array[6] = {0};
	uint16_t  temp_uint     = 0;
	float     temp_float    = 0;

	//启动周期性测量
	I2C_Soft.Start();
	I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
	I2C_Soft.Write_Byte(0x27); //High repeat , mps = 10
	I2C_Soft.Write_Byte(0x37);
	
	Timer6.SHT30_Measure_Timeout = 0;
	//发送接收数据命令
	do
	{		
		if(Timer6.SHT30_Measure_Timeout >= TIMER6_2S) //2s内没获取到数据,退出等待
			break;
		
		I2C_Soft.Start();
		I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
		I2C_Soft.Write_Byte(0xE0);
		I2C_Soft.Write_Byte(0x00);
		
		I2C_Soft.Start();
	}
	while(I2C_Soft.Write_Byte(SHT30_ADDR | Read_CMD) ==NACK);
		
	//开始接收测量数据,并计算
	if(Timer6.SHT30_Measure_Timeout < TIMER6_2S)
	{
		temp_array[0] = I2C_Soft.Read_Byte(ACK);
		temp_array[1] = I2C_Soft.Read_Byte(ACK);
		temp_array[2] = I2C_Soft.Read_Byte(ACK);
		temp_array[3] = I2C_Soft.Read_Byte(ACK);
		temp_array[4] = I2C_Soft.Read_Byte(ACK);
		temp_array[5] = I2C_Soft.Read_Byte(NACK);
		I2C_Soft.Stop();
		
		//计算温度,精度0.1
		if(CRC_8(temp_array,2) == temp_array[2]) //CRC-8 校验
		{
			temp_uint         = temp_array[0]*256+temp_array[1];
			temp_float        = ((float)temp_uint)*0.267032-4500;
			SHT30.fTemperature = temp_float*0.01;
	  }
		
		//计算湿度,精度1%RH
		if(CRC_8(&temp_array[3],2) == temp_array[5]) //CRC-8 校验
		{
			temp_uint      = temp_array[3]*256+temp_array[4];
			temp_float     = ((float)temp_uint)*0.152590;
			temp_float     = temp_float*0.01;
			SHT30.ucHumidity = (unsigned char)temp_float;  
	  }
	}
}

/*
	* @name   CRC_8
	* @brief  CRC-8校验
	* @param  Crc_ptr -> 校验数据首地址
						LEN     -> 校验数据长度
	* @retval CRC_Value -> 校验值      
*/
static uint8_t CRC_8(uint8_t *Crc_ptr,uint8_t LEN)
{
	uint8_t CRC_Value = 0xFF;
	uint8_t i = 0,j = 0;

	for(i=0;i<LEN;i++)
	{
		CRC_Value ^= *(Crc_ptr+i);
		for(j=0;j<8;j++)
		{
			if(CRC_Value & 0x80)
				CRC_Value = (CRC_Value << 1) ^ 0x31;
			else
				CRC_Value = (CRC_Value << 1);
		}
	}
	return CRC_Value;
}

/********************************************************
  End Of File
********************************************************/

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

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

相关文章

Linux查看磁盘、文件系统、文件夹、文件大小的命令(lsblk、df、du、ll)

记录&#xff1a;325 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;使用lsblk命令查看磁盘大小和磁盘挂载情况&#xff1b;使用df查看文件系统大小和挂载情况&#xff1b;使用du命令查看文件夹(目录)大小&#xff1b;使用ll和ls查看文件大小。 版本&#xff1a; 操作…

XXL-JOB任务有效期支持实现方案

概述 在做数据产品或平台系统时&#xff0c;经常会遇到类似如下截图中&#xff0c;有截至日期的定时调度任务的需求。即定时任务只在指定的开始日期-截至日期里指定的时间里执行。具体的业务需求场景&#xff0c;如营销活动的看板数据的订阅邮件&#xff0c;推送名单的活动&am…

实验(五):外部中断实验

一、实验目的与任务 实验目的&#xff1a; 1&#xff0e;掌握外部中断的工作原理&#xff1b; 2&#xff0e;学会中断程序设计。 任务&#xff1a; 1&#xff0e;运行Keil开发环境&#xff0c;完成外部中断响应软件编程&#xff1b; 2&#xff0e;外部中断接口分别接按键K1、K2…

Hibernate基本使用

注&#xff1a;本文使用maven创建项目。 目录&#xff1a;Hibernate简介&#xff1a;Hibernate使用&#xff1a;一、手动创建&#xff1a;1.建表&#xff1a;2.pom.xml中导入相关依赖&#xff1a;3.创建Hibernate核心配置文件hibernate.cfg.xml&#xff1a;4.创建实体类UserEnt…

Ubuntu系统、CentOS系统双网卡的配置

虚机双网卡配置前言一、CentOS系统1.配置网卡信息1.1编辑eth0网卡1.2查看eth0网卡信息1.3编辑eth1网卡1.4查看eth1网卡信息2.关闭网卡arp代答和rp_filter校验2.1编辑配置文件2.2查看配置文件3.重启网络服务4.配置路由4.1 配置路由4.2 查看路由二、Ubuntu系统1.配置网卡信息1.1.…

微信小程序运行机制和生命周期

一. 运行机制 首先了解下小程序的运行机制&#xff0c;小程序从启动到最终被销毁&#xff0c;会经历很多不同的状态&#xff0c;小程序在不同状态下会有不同的表现。大致运行机制如下图。 小程序生命周期图 接下来我们是图中概念讲解&#xff0c;项目中也会经常遇到。 1&…

etf动量轮动+大盘择时:年化30%的策略

原创文章第111篇&#xff0c;专注“个人成长与财富自由、世界运作的逻辑&#xff0c; AI量化投资”。 今天重点来探索一下elegantRL。 昨天的文章金融强化学习与finRL开发包里介绍了finRL的源码结构&#xff0c;背后的强化学习框架是elegantRL。 聚宽平台上有一个“动量轮动…

Java#18(面向对象三大特征之一:继承)

目录 一.继承 1.Java中提供了关键字extends,可以让一个类和另一个类建立起继承关系 2.继承的好处 3.什么时候使用继承? 二.继承的特点 java只支持单继承,不支持多继承,但支持多层继承 三.子类到底能继承父类中的哪些内容? 四.继承中成员变量和成员方法的访问特点 1. 继…

Apache Jmeter压力测试与性能监控,监测cpu、内存、磁盘、网络

1.官网下载Jmeter 解压&#xff0c;bin目录下 Windows 运行jmeter.bat 、Linux运行jmeter.sh 2.jmeter-plugins-manager 插件 测试机下载放置Jmeter的apache-jmeter-5.5\lib\ext 目录下,重新jmeter。 3.ServerAgent-2.2.3.zip下载 下载好放服务器端&#xff0c;给可执行文…

FPGA精简版UDP协议实现板间网线传输视频,提供3套工程源码

目录1.FPGA精简版UDP介绍2.网线板间视频传输---精简版UDP再次精简3.网线板间视频传输---实现方案4.网线板间视频传输---发送端方案5.网线板间视频传输---接收端方案6.工程1介绍---Artix7(RTL8211)双网口环回7.工程2介绍---Artix7发送--->Kintex7(B50610)接收8.工程3介绍---K…

RabbitMQ的广播模式(fanout)在(基于xml配置)项目中使用

项目结构 添加相关的jar包&#xff1a; pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&qu…

@敏捷组织从业者,开放敏捷架构O-AA™标准考试及认证项目重磅上线!

数字化转型和敏捷转型需同时进行&#xff0c; O-AA™标准更强调组织和文化的转型。 认证项目 重磅发布 发布物资源 标准中文从业认证考试 标准讲师认证培训课程 ALL IN ∨ 开放敏捷架构O-AA™标准采用了基于结果、以产品为中心的方法&#xff0c;使企业能够以灵活和敏捷的…

[ros2实操]1-ros2的安装(ubuntu1804)与运行

参考链接: Recording and playing back data — ROS 2 Documentation: Galactic documentation 使用docker创建了一个ubuntu1804镜像: docker run -it --gpus all \-p 8860:8860 \-v /tmp/.X11-unix:/tmp/.X11-unix \-v /home/lbw/temp_dir:/temp_dir \-e DISPLAYunix$DISPL…

软件测试行业5年经验,薪资不如刚入行的应届生,真是日了狗了,问题究竟出在哪里?

最近公司新招了一位刚入行的应届生&#xff0c;作为组长我深刻体验到新人入行的痛楚&#xff0c;对此我十分照顾他&#xff0c;都是手把手教他公司业务流程。直到15号他工资到账15400元短信提示音响起&#xff0c;我才想起这是多么幼稚的行为&#xff0c;凭什么我在公司待了五年…

实验三 静态路由配置

计算机网络实验实验三 静态路由配置一、实验目的二、实验目的三、实验步骤3.1 连接实验拓扑结构3.2 配置路由器IP地址和掩码3.3 配置PC机IP地址、网关地址3.4 配置路由器的路由表四、思考题实验三 静态路由配置 一、实验目的 掌握手工配置路由表的方法 掌握读懂路由表的能力 …

Charles安装配置

目录 一、工作原理 二、主要功能 三、主要优点&#xff08;对比Fiddler&#xff09; 四、安装与配置 1、安装 2、组件介绍 1&#xff09;主导航栏 2&#xff09;请求栏 3&#xff09;请求数据栏 3、设置 1&#xff09;代理设置 2&#xff09;访问控制 4、客户端设置 …

(C语言)P1002 [NOIP2002 普及组] 过河卒

[NOIP2002 普及组] 过河卒 一、题目描述 棋盘上 AAA 点有一个过河卒&#xff0c;需要走到目标 BBB 点。卒行走的规则&#xff1a;可以向下、或者向右。同时在棋盘上 CCC 点有一个对方的马&#xff0c;该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦…

Redis实战——短信登录

目录 1 基于Seesion实现短信登录 1.1 发送短信验证码 1.2 登录功能 2 使用Redis进行短信验证码校验登录 2.1 Seesion方法存在的问题 2.2 发送短信验证码 2.3 验证码校验及登录功能 3.拦截器优化 1 基于Seesion实现短信登录 1.1 发送短信验证码 发送验证码请求路径 /u…

2、skywalking-安装(Docker-Compose方式)

1、服务器环境介绍 两台服务器均为Centos7.6 172.16.128.129 vm1 装载skywalking(采用es作为数据库) 172.16.128.130 vm2 项目服务器&#xff0c;然后向vm1集成 2、准备工作 2.1、由于安装skywalking是通过docker-compose方式安装在vm1且项目到时候是直接build成镜像然后发布&a…

行业安全解决方案 | 零售企业如何做好安全建设对抗黑灰产?

随着各行各业信息化、数字化、智能化进程不断加快&#xff0c;零售行业新业态也正在蓬勃发展。然而&#xff0c;随着转型深入推进及业务量的上升&#xff0c;随处可见的安全威胁逐渐成为零售企业的首要难题。 其主要原因在于该类企业一般涵盖大量用户个人数据&#xff0c;当企…