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;
        else
            I2C_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_Rspond

    I2C_SCL_RESET;                  // 释放SCL
    I2C_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置高,发送ACK
    I2C_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    0x01

static 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 = 10
  Myi2c.start();
  Myi2c.Write_Byte(SHT3X_ADDR&Write_CMD);

  Myi2c.Write_Byte(0x27);
  Myi2c.Write_Byte(0x37);

  // 2、读当前温湿度值,寄存器0xE000
   Timer6.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校验码

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文带你看透前端世界里的日期时间,对就是Date

很高兴我们能够通过不同空间&#xff0c;不同时间&#xff0c;通过这篇博客相识&#xff0c;那一定是一种缘分&#xff0c;一种你和狗哥的缘分。今天我希望通过这篇博客对我所熟知的前端世界里的日期时间做一个汇总&#xff0c;不止是代码上的汇总哦&#xff01; 目录 一、时区…

flex布局优化(两端对齐,从左至右)

文章目录前言方式一 nth-child方式二 gap属性方式三 设置margin左右两边为负值总结前言 flex布局是前端常用的布局方式之一&#xff0c;但在使用过程中&#xff0c;我们总是感觉不太方便&#xff0c;因为日常开发中&#xff0c;大多数时候&#xff0c;我们想要的效果是这样的 …

C++数据结构 —— 哈希表、unordered_map/set封装

目录 1.哈希概念 1.1哈希函数 1.2哈希冲突 2.闭散列实现 3.开散列实现 4.容器的封装 4.1unordered_map 4.2unordered_set 4.3封装过程中遇到的问题 1.哈希概念 顺序结构以及平衡二叉搜索树结构中&#xff0c;在查找一个元素时需要经过比较。顺序查找时间复杂度为O(N…

顺序栈的实现

目录 一、数据结构中的栈 二、接口函数 三、栈的初始化 四、入栈 五、判断栈是否为空 六、出栈 七、栈顶元素及元素总数 八、顺序栈的销毁 一、数据结构中的栈 首先&#xff0c;栈&#xff08;Stack&#xff09;这个词在数据结构和操作系统两个学科中都有出现。 操作系…

图像分割系列(一)

图像分割分类 语义分割 把每个像素都打上标签&#xff08;这个像素点是人&#xff0c;树&#xff0c;背景等&#xff09; &#xff08;语义分割只区分类别&#xff0c;不区分类别中具体单位&#xff09; 实例分割 实例分割不光要区别类别&#xff0c;还要区分类别中每一个…

面向切面编程AOP

1.Spring的AOP简介 1.1什么是AOP AOP为Aspect Oriented Programming的缩写&#xff0c;意思是面向切面编程&#xff0c;是通过预编译和运行期动态代理实现程序功能维护的一种技术 AOP是OOP&#xff08;面向对象&#xff09;的延续&#xff0c;利用AOP可以对业务逻辑的各部分…

5个代码技巧,加速你的Python

5个代码技巧&#xff0c;加速你的Python 人生苦短&#xff0c;快学Python&#xff01; Python作为一种功能强大的编程语言&#xff0c;因其简单易学而受到很多初学者的青睐。它的应用领域又非常广泛&#xff1a;科学计算、游戏开发、爬虫、人工智能、自动化办公、Web应用开发…

蓝桥杯C++组怒刷50道真题(填空题)

&#x1f33c;深夜伤感网抑云 - 南辰Music/御小兮 - 单曲 - 网易云音乐 &#x1f33c;多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐 18~22年真题&#xff0c;50题才停更&#xff0c;课业繁忙&#xff0c;有空就更&#xff0c;2023/3/18/23:01写下 目录 &#x1f44a;填…

【C++】智能指针

文章目录&#x1f4d6; 前言1. 智能指针的引入1.1 内存泄露的危害&#xff1a;1.2 异常安全中的内存泄露&#xff1a;1.3 RAII思想&#xff1a;1.3 拦截异常解决不了的内存泄漏&#xff1a;1.4 智能指针解决&#xff1a;2. 智能指针的拷贝2.1 直接拷贝的问题&#xff1a;2.2 au…

STM32实战项目-触摸按键

前言&#xff1a; 通过触摸按键控制LED灯以及继电器&#xff0c;具体实现功能如下&#xff1a; 1、触摸按键1单击与长按&#xff0c;控制LED1&#xff1b; 2、触摸按键2单击与长按&#xff0c;控制LED2; 3、触摸按键3单击与长按&#xff0c;控制LED3; 4、触摸按键4单击与长…

详解Spring、SpringBoot、SpringCloud三者的联系与区别

一、Spring Spring 是一个轻量级的Java 开发框架&#xff0c;主要依存于SSM 框架&#xff0c;即Spring MVC Spring Mybatis&#xff0c;定位很明确&#xff0c;Spring MVC主要负责view 层的显示&#xff0c;Spring 利用IOC 和AOP 来处理业务&#xff0c;Mybatis则是数据的持…

跨域解决方案

跨域解决方案 1.跨域基本介绍 文档&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS 跨域问题是什么&#xff1f; 一句话&#xff1a;跨域指的是浏览器不能执行其他网站的脚本&#xff0c;它是由浏览器的同源策略造成的&#xff0c;是浏览器对 javascr…

数据结构 | 栈的中缀表达式求值

目录 什么是栈&#xff1f; 栈的基本操作 入栈操作 出栈操作 取栈顶元素 中缀表达式求值 实现思路 具体代码 什么是栈&#xff1f; 栈是一种线性数据结构&#xff0c;具有“先进后出”&#xff08;Last In First Out, LIFO&#xff09;的特点。它可以看作是一种受限的…

“国产版ChatGPT”文心一言发布会现场Demo硬核复现

文章目录前言实验结果一、文学创作问题1 :《三体》的作者是哪里人&#xff1f;问题2&#xff1a;可以总结下三体的核心内容吗&#xff1f;如果要续写的话&#xff0c;可以从哪些角度出发&#xff1f;问题3&#xff1a;如何从哲学角度来进行续写&#xff1f;问题4&#xff1a;电…

学习28个案例总结

学习前 对于之前遇到的问题没有及时总结&#xff0c;导致做什么事情都是新的一样。没有把之前学习到接触到的内容应用上。通过这次对28个案例的学习。把之前遇到的问题总结成自己的经验&#xff0c;在以后的开发过程中避免踩重复性的坑。多看帮助少走弯路。 学习中 对28个案例…

2023年安徽省中职网络安全跨站脚本攻击

B-4&#xff1a;跨站脚本攻击 任务环境说明&#xff1a; √ 服务器场景&#xff1a;Server2125&#xff08;关闭链接&#xff09; √ 服务器场景操作系统&#xff1a;未知 √ 用户名:未知 密码&#xff1a;未知 1.访问服务器网站目录1&#xff0c;根据页面信息完成条件&am…

Shader基础

参考文章:Unity着色器介绍 Shader基础 Properties 声明格式 [optional: attribute] name(“display text in Inspector”, type name) default value 属性类型 Color&#xff1a;颜色属性&#xff0c;表示 RGBA 颜色值。Range&#xff1a;范围属性&#xff0c;表示一个在…

基于微信小程序的校园二手交易平台小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…

22讲MySQL有哪些“饮鸩止渴”提高性能的方法

短连接风暴 是指数据库有很多链接之后只执行了几个语句就断开的客户端&#xff0c;然后我们知道数据库客户端和数据库每次连接不仅需要tcp的三次握手&#xff0c;而且还有mysql的鉴权操作都要占用很多服务器的资源。话虽如此但是如果连接的不多的话其实这点资源无所谓的。 但是…

Web自动化——前端基础知识(二)

1. Web前端开发三要素 web前端开发三要素 什么是HTMl&#xff1f; Html是超文本标记语言&#xff0c;是用来描述网页的一种标记语言HTML是一种标签规则的形式将内容呈现在浏览器中可以以任意编辑器创建&#xff0c;其文件扩展名为.html或.htm保存即可 什么是CSS&#xff1f;…