STM32开发(九)STM32F103 通信 —— I2C通信编程详解
文章目录
- 一、基础知识点
- 二、开发环境
- 三、STM32CubeMX相关配置
- 四、Vscode代码讲解
- GPIO模拟I2C代码
- SHT30相关代码
- main函数中循环代码
- 五、结果演示
- 方式一、示波器分析I2C数据
- 方式2、通过Modbus将获取到的数据传到PC上
一、基础知识点
本实验通过I2C通信获取SHT30温湿度值,显示数码管以及通过modbus协议传送给PC端。
本实验内容知识点:
1、 I2C通信协议
2、温湿度传感器 SHT3x-DIS 手册 解析 、TM1620芯片手册 解析
3、数码管显示 详解
4、RS485 Modbus通信编程详解
5、定时器中断 详解
6、I2C引脚SDA既要输出也要输入,因此要配置为准双向口。先来看下STM32普通GPIO口内部图,了解下准双向口。

将IO口配置为开漏输出时,输出控制器中P-MOS管断开,输出通过N-MOS管决定。
只有输出高电平的时候,N-MOS管截止,这时IO口就能作为输入使用,获取IO口的状态。(P-MOS和N-MOS都截止)
这样I2C SDA引脚就不用重新初始化为输入模式。这也就是准双向口的原理。
准备好了吗?开始实战show time。
二、开发环境
1、硬件开发准备
主控:STM32F103ZET6
温湿度传感器:SHT3X

2、软件开发准备
软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。
该部分可参考:软件开发环境构建
三、STM32CubeMX相关配置
1、STM32CubeMX基本配置
本实验基于CubeMX详解构建基本框架 进行开发。
2、STM32CubeMX I2C相关配置
(1)GPIO配置

配置I2C两个引脚默认高电平:I2C处于空闲状态
配置I2C两个引脚为开漏输出。
四、Vscode代码讲解
GPIO模拟I2C代码
I2C时序由空闲时序、开始时序、停止时序、发送一个字节时序、接收一个字节时序,这四种时序部分组成。GPIO只要模拟出这四种时序就能满足所有的完整I2C时序。因此制作一个结构体来实现所有I2C时序部分接口。其他函数就能直接调用这几个接口构成完整的I2C时序通信。
typedef struct
{void (*Init)(void); //I2C初始化——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读字节时序
} Myi2c_t;Myi2c_t Myi2c =
{Init,start,stop,Write_Byte,Read_Byte
};
实现具体的I2C时序
初始化I2C信号 —— 空闲信号:SCL和SDA信号都为高电平
static void Init(void)
{I2C_SCL_SET; // SCL置高 I2C_SDA_SET; // SDA置高
}
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
static void start(void)
{I2C_SCL_SET; // SCL置高I2C_SDA_SET; // SDA置高I2C_Delay_us(1);I2C_SDA_RESET; // SDA清零I2C_Delay_us(10);I2C_SCL_RESET; // SCL清零I2C_Delay_us(1);
}
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
static void stop(void)
{I2C_SDA_RESET; // SDA清零I2C_SCL_SET; // SCL置高I2C_Delay_us(1);I2C_SDA_SET; // SDA置高I2C_Delay_us(10);
}
I2C写字节时序:1、主机发送1个字节;2、接收从机发来的ACK信号; 3、释放信号线
注:在SCL低变高的时候数据变化,在SCL高电平时保持数据变化
static ACK_Value_t Write_Byte(uint8_t data)
{uint8_t i;ACK_Value_t ACK_Rspond;// When SCL is in low power hours, SDA changes its state. // When SCL is in high power hours, SDA state must be stable.for(i=0;i<8;i++){I2C_SCL_RESET; // SCL清零,准备发送数据位I2C_Delay_us(1);if((data&BIT7) == BIT7) // 要发送的数据最高位,高位先发I2C_SDA_SET;elseI2C_SDA_RESET;I2C_Delay_us(1);I2C_SCL_SET; // SCL置高,发送数据I2C_Delay_us(10);data <<= 1; // 移位,准备下个数据位发送}I2C_SCL_RESET; // SCL清零,准备接收从机发来的ACK信号I2C_SDA_SET; // 将SDA释放信号I2C_Delay_us(1);I2C_SCL_SET; // 接收从机发来的信号I2C_Delay_us(10);ACK_Rspond = (ACK_Value_t)I2C_READ_SDA; // 将信号保存到ACK_RspondI2C_SCL_RESET; // 释放SCLI2C_Delay_us(1);return ACK_Rspond; // 返回ACK信号
}
I2C读字节时序:1、主机接收1个字节;2、接收完成,主机发ACK信号回复从机; 3、释放信号线
注:在SCL低变高的时候数据变化,在SCL高电平时保持数据变化,可采到接收值
static uint8_t Read_Byte(ACK_Value_t ack)
{uint8_t RD_Byte=0,i;for(i=0; i<8; i++) // 循环接收一个字节数据{RD_Byte <<= 1; // 准备接收下一位数据I2C_SCL_RESET; // SCL清零,从机SDA准备数据I2C_Delay_us(10);I2C_SCL_SET; // SCL置高,获取数据I2C_Delay_us(10);RD_Byte |= I2C_READ_SDA; // 将数据保存在RD_Byte缓存里}I2C_SCL_RESET; //SCL清零,主机准备应答信号I2C_Delay_us(1);if(ack == ACK){I2C_SDA_RESET; // 主机SDA清零,回应从机ACK,接收成功}else{I2C_SDA_SET; // 主机SDA置高,回应从机NACK,接收失败}I2C_SCL_SET; // SCL置高,发送ACKI2C_Delay_us(10);//释放SDA数据线//SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号I2C_SCL_RESET; I2C_SDA_SET; I2C_Delay_us(1);return RD_Byte; // 返回接收到的字节
}static void I2C_Delay_us(uint8_t us)
{uint8_t i = 0;//通过示波器测量进行校准while(us--){for(i=0;i<7;i++);}
}
SHT30相关代码
构建一个SHT30结构体并初始化
typedef struct
{float fTemperature_Value; // 保存温度值uint8_t ucHumidity_Value; // 保存湿度值void (*Measure_Period_Mode)(void); // 测量函数
} SHT3X_t;extern SHT3X_t SHT3X;SHT3X_t SHT3X=
{0.0,0,Measure_Period_Mode
};
调用I2C接口实现SHT30时序,获取当前温湿度值
#define SHT3X_ADDR (uint8_t)(0x44 << 1) // SHT30地址,I2C地址位为7位,因此一定要向左移一位。第8位为读写位
#define Write_CMD 0xFE
#define Read_CMD 0x01static void Measure_Period_Mode(void)
{uint8_t SHT32X_RD_array[6] = {0};uint16_t temp_uint = 0;float temp_float = 0;// 1、设备芯片检测模式:启动周期性测量 High repeat , mps = 10Myi2c.start();Myi2c.Write_Byte(SHT3X_ADDR&Write_CMD);Myi2c.Write_Byte(0x27);Myi2c.Write_Byte(0x37);// 2、读当前温湿度值,寄存器0xE000Timer6.SHT30_Measure_Timeout =0; // 使用定时器6 定时测量时间do{if(Timer6.SHT30_Measure_Timeout >= TIMER6_2S) //2s内没获取到数据,退出等待break;Myi2c.start();Myi2c.Write_Byte(SHT3X_ADDR&Write_CMD);Myi2c.Write_Byte(0xE0);Myi2c.Write_Byte(0x00);Myi2c.start();} while (Myi2c.Write_Byte(SHT3X_ADDR | Read_CMD) == NACK);if(Timer6.SHT30_Measure_Timeout < TIMER6_2S){SHT32X_RD_array[0]=Myi2c.Read_Byte(ACK);SHT32X_RD_array[1]=Myi2c.Read_Byte(ACK);SHT32X_RD_array[2]=Myi2c.Read_Byte(ACK);SHT32X_RD_array[3]=Myi2c.Read_Byte(ACK);SHT32X_RD_array[4]=Myi2c.Read_Byte(ACK);SHT32X_RD_array[5]=Myi2c.Read_Byte(NACK);Myi2c.stop();// 3、CRC校验以及温度计算,见手册讲解if(CRC_8(SHT32X_RD_array,2) == SHT32X_RD_array[2]) //CRC-8 校验{temp_uint = SHT32X_RD_array[0]*256+SHT32X_RD_array[1];temp_float = ((float)temp_uint)*0.267032-4500;SHT3X.fTemperature_Value = temp_float*0.01;}// 3、CRC校验以及温度计算,见手册讲解if(CRC_8(&SHT32X_RD_array[3],2) == SHT32X_RD_array[5]) //CRC-8 校验{temp_uint = SHT32X_RD_array[3]*256+SHT32X_RD_array[4];temp_float = ((float)temp_uint)*0.152590;temp_float = temp_float*0.01;SHT3X.ucHumidity_Value = (unsigned char)temp_float; }}
}
main函数中循环代码
while (1)
{float Temp_float = 0;uint16_t Temp_uint = 0;//获取SHT30的温湿度SHT3X.Measure_Period_Mode();//数码管显示//温度if(SHT3X.fTemperature_Value < 0) //负温{Temp_float = 0 - SHT3X.fTemperature_Value;Display.Disp_Other(Disp_NUM_GRID3,0x40,Disp_DP_OFF); //4号数码管显示负号}else{Temp_float = SHT3X.fTemperature_Value;Display.Disp_Other(Disp_NUM_GRID3,0x00,Disp_DP_OFF); //4号数码管关闭}Temp_uint = (uint16_t)(Temp_float*10);Display.Disp(Disp_NUM_GRID4,Temp_uint/100,Disp_DP_OFF);Display.Disp(Disp_NUM_GRID5,Temp_uint%100/10,Disp_DP_ON);Display.Disp(Disp_NUM_GRID6,Temp_uint%10,Disp_DP_OFF);//湿度Display.Disp(Disp_NUM_GRID1,SHT3X.ucHumidity_Value/10,Disp_DP_OFF);Display.Disp(Disp_NUM_GRID2,SHT3X.ucHumidity_Value%10,Disp_DP_OFF);HAL_Delay(500);
}
五、结果演示
方式一、示波器分析I2C数据

实验结果:
温度:21.4℃ 湿度:47
方式2、通过Modbus将获取到的数据传到PC上
修改Modbus相关代码
diff --git a/MyApplication/Src/Modbus.c b/MyApplication/Src/Modbus.c
index 7665eee..b6caf10 100755
--- a/MyApplication/Src/Modbus.c
+++ b/MyApplication/Src/Modbus.c
@@ -106,24 +106,26 @@ static void Modbus_Read_Register(UART_t* UART)//功能码*(COM_UART->pucSend_Buffer+1) = FunctionCode_Read_Register;//数据长度(字节)
- *(COM_UART->pucSend_Buffer+2) = 2;
+ *(COM_UART->pucSend_Buffer+2) = 6;//发送数据// deep status*(COM_UART->pucSend_Buffer+3) = 0;*(COM_UART->pucSend_Buffer+4) = Deep.Read_Deep();
- *(COM_UART->pucSend_Buffer+5) = 0;
- *(COM_UART->pucSend_Buffer+6) = 0x66;
+ *(COM_UART->pucSend_Buffer+5) = ((uint16_t)(SHT3X.fTemperature_Value*10))/256;
+ *(COM_UART->pucSend_Buffer+6) = ((uint16_t)(SHT3X.fTemperature_Value*10))%256;
+ *(COM_UART->pucSend_Buffer+7) = 0;
+ *(COM_UART->pucSend_Buffer+8) = SHT3X.ucHumidity_Value;//插入CRC
- CRC_16.CRC_Value = CRC_16.CRC_Check(COM_UART->pucSend_Buffer,7); //计算CRC值
+ CRC_16.CRC_Value = CRC_16.CRC_Check(COM_UART->pucSend_Buffer,9); //计算CRC值CRC_16.CRC_H = (uint8_t)(CRC_16.CRC_Value >> 8);CRC_16.CRC_L = (uint8_t)CRC_16.CRC_Value;- *(COM_UART->pucSend_Buffer+7) = CRC_16.CRC_L;
- *(COM_UART->pucSend_Buffer+8) = CRC_16.CRC_H;
+ *(COM_UART->pucSend_Buffer+9) = CRC_16.CRC_L;^M
+ *(COM_UART->pucSend_Buffer+10) = CRC_16.CRC_H;^M//发送数据
- UART3.SendArray(COM_UART->pucSend_Buffer,9);
+ UART3.SendArray(COM_UART->pucSend_Buffer,11);}}
主设备为PC端安装的MThings进行Modbus收发数据。 MThings软件具体操作可参考Modbus通信详解中的结果演示以及报文解析

[2023-03-18 15:45:34-919]COM38-发送:01 03 9c 41 00 03 7b 8f
0x01:主机要查询的从设备地址
0x03:功能码 查询读操作
0x9c 0x41:寄存器地址0x9c41转十进制地址为40,001
0x00 0x02:读取三个数据(一个数据3字节)
0x7b 0x8f:CRC校验码
[2023-03-18 15:45:34-941]COM38-接收:01 03 06 00 00 00 d6 00 2e 91 40
0x01:告诉主机自己从设备地址
0x03:功能码 读操作
0x00 0x00:读出第一个数据为0x01,当前蜂鸣器关闭状态
0x00 0xd6:读取第二个数据为0xd6,温度值=214/10=21.4℃ (发送代码将值乘以10,这里需要除以10获取真实温度值)
0x00 0x2e:读取第二个数据为0x2e,湿度值=46
0x91 0x40:CRC校验码
相关文章:
STM32开发(九)STM32F103 通信 —— I2C通信编程详解
文章目录一、基础知识点二、开发环境三、STM32CubeMX相关配置四、Vscode代码讲解GPIO模拟I2C代码SHT30相关代码main函数中循环代码五、结果演示方式一、示波器分析I2C数据方式2、通过Modbus将获取到的数据传到PC上一、基础知识点 本实验通过I2C通信获取SHT30温湿度值ÿ…...
手撕数据结构—栈
Tips不得不再次提一下这个语法问题,当数组创建的时候,进行初始化的时候,分为全部初始化或者说部分初始化,对于不完全初始化而言,剩下的部分就全部默认为零。现在比如说你想对整型数组的1万个元素把它全部变成-1&#x…...
【java刷题】排序子序列
这里写目录标题问题描述解决思路实现代码问题描述 牛牛定义排序子序列为一个数组中一段连续的子序列,并且这段子序列是非递增或者非递减排序的。牛牛有一个长度为n的整数数组A,他现在有一个任务是把数组A分为若干段排序子序列,牛牛想知道他最少可以把这个数组分为几段排序子序…...
Springboot怎么快速集成Mybatis和thymeleaf?
前言有时候做方案,需要模拟一些业务上的一些场景来验证方案的可行性,基本上每次都是到处百度如何集成springbootmybatisthymeleaf这些东西的集成平时基本上一年也用不了一次,虽然比较简单,奈何我真得记不住详细的每一步࿰…...
shell常见面试题一
(1)、set //查看系统变量 (2)、chsh -s /bin/zsh test //修改用户登录shell (3)、2>&1 //标准错误重定向到标准输出 &> //同样可以将标准错误重定向到标准输出 如下: ls test.…...
python如何快速采集美~女视频?无反爬
人生苦短 我用python~ 这次康康能给大家整点好看的不~ 环境使用: Python 3.8 Pycharm mou歌浏览器 mou歌驱动 —> 驱动版本要和浏览器版本最相近 <大版本一样, 小版本最相近> 模块使用: requests >>> pip install requests selenium >>> pip …...
kali内置超好用的代理工具proxychains
作者:Eason_LYC 悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。 一个人的价值,在于他所拥有的。所以可以不学无术,但不能一无所有! 技术领域:WEB安全、网络攻防 关注WEB安全、网络攻防。…...
Java栈和队列·下
Java栈和队列下2. 队列(Queue)2.1 概念2.2 实现2.3 相似方法的区别2.4 循环队列3. 双端队列 (Deque)3.1 概念4.java中的栈和队列5. 栈和队列面试题大家好,我是晓星航。今天为大家带来的是 Java栈和队列下 的讲解!😀 继上一个讲完的栈后&…...
b01lers CTF web 复现
warmup 按照提示依次 base64 加密后访问,可以访问 ./flag.txt,也就是 Li9mbGFnLnR4dA 。 from base64 import b64decode import flaskapp flask.Flask(__name__)app.route(/<name>) def index2(name):name b64decode(name)if (validate(name))…...
三月份跳槽了,历经字节测开岗4轮面试,不出意外,被刷了...
大多数情况下,测试员的个人技能成长速度,远远大于公司规模或业务的成长速度。所以,跳槽成为了这个行业里最常见的一个词汇。 前几天,我看到有朋友留言说,他在面试字节的测试开发工程师的时候,灵魂拷问三小…...
springboot+vue驾校管理系统 idea科目一四预约考试,练车
加大了对从事道路运输经营活动驾驶员的培训管理力度,但在实际的管理过程中,仍然存在以下问题:(1)管理部门内部人员在实际管理过程中存在人情管理,不进行培训、考试直接进行发证。(2)从业驾驶员培训机构不能严格执行管理部门的大纲…...
【pytorch】使用deepsort算法进行目标跟踪,原理+pytorch实现
目录deepsort流程一、匈牙利算法二、卡尔曼滤波车速预测例子动态模型的概念卡尔曼滤波在deepsort中的动态模型三、预测值及测量值的含义deepsort在pytorch中的运行deepsort流程 DeepSORT是一种常用的目标跟踪算法,它结合了深度学习和传统的目标跟踪方法。DeepSORT的…...
Python 基础教程【3】:字符串、列表、元组
本文已收录于专栏🌻《Python 基础》文章目录🌕1、字符串🥝1.1 字符串基本操作🍊1.1.1 字符串创建🍊1.1.2 字符串元素读取🍊1.1.3 字符串分片🍊1.1.4 连接和重复🍊1.1.5 关系运算&…...
(数据结构)八大排序算法
目录一、常见排序算法二、实现1. 直接插入排序2.🌟希尔排序3. 选择排序4.🌟堆排序5. 冒泡排序7. 🌟快速排序7.1 其他版本的快排7.2 优化7.3 ⭐非递归7. 🌟归并排序7.1 ⭐非递归8. 计数排序三、总结1. 分析排序 (Sorting) 是计算机…...
构建GRE隧道打通不同云商的云主机内网
文章目录1. 环境介绍2 GRE隧道搭建2.1 华为云 GRE 隧道安装2.2 阿里云 GRE 隧道安装3. 设置安全组4. 验证GRE隧道4.1 在华为云上 ping 阿里云云主机内网IP4.2 在阿里云上 ping 华为云云主机内网IP5. 总结1. 环境介绍 华为云上有三台云主机,内网 CIDR 是 192.168.0.0…...
48天C++笔试强训 001
作者:小萌新 专栏:笔试强训 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:讲解48天笔试强训第一天的题目 笔试强训 day1选择题12345678910编程题12选择题 1 以下for循环的执行次数是(ÿ…...
Android 11新增系统服务
1.编写.aidl文件存放位置:frameworks/base/core/java/android/ospackage android.os;interface ISystemVoiceServer {void setHeightVoice(int flag);void setBassVoice(int flag);void setReverbVoice(int flag);}2.将.aidl文件添加到frameworks/base/Android.bp f…...
“你要多弄弄算法”
开始瞎掰 ▽ 2月的第一天,猎头Luna给我推荐了字节的机会,菜鸡我呀,还是有自知之明的,赶忙婉拒:能力有限,抱歉抱歉。 根据我为数不多的和猎头交流的经验,一般猎头都会稍微客套一下:…...
【数据结构】千字深入浅出讲解队列(附原码 | 超详解)
🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 📣系列专栏:C语言实现数据结构 💬总结:希望你看完…...
vue面试题(day04)
vue面试题vue插槽?vue3中如何获取refs,dom对象的方式?vue3中生命周期的和vue2中的区别?说说vue中的diff算法?说说 Vue 中 CSS scoped 的原理?vue3中怎么设置全局变量?Vue中给对象添加新属性时&a…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...
