STM32开发(九)STM32F103 通信 —— I2C通信编程详解
创始人
2025-05-30 08:00:58
0

文章目录

    • 一、基础知识点
    • 二、开发环境
    • 三、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校验码

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...