news 2026/8/14 20:41:39

12. 基于TI MSPM0G3507开发板的I2C协议详解与SHT20温湿度传感器驱动实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
12. 基于TI MSPM0G3507开发板的I2C协议详解与SHT20温湿度传感器驱动实战

12. 基于TI MSPM0G3507开发板的I2C协议详解与SHT20温湿度传感器驱动实战

最近在做一个环境监测的小项目,用到了TI的MSPM0G3507开发板来读取SHT20温湿度传感器的数据。很多刚开始接触嵌入式或者准备电赛的朋友都问过我,I2C协议到底怎么用?软件模拟和硬件I2C有什么区别?今天我就结合这个实战项目,把I2C从原理到代码实现,手把手给大家讲清楚。

咱们这篇教程会从I2C协议的基础讲起,然后带你用MSPM0G3507的GPIO引脚模拟I2C时序(也就是软件I2C),最后驱动SHT20传感器读取温湿度数据。整个过程我会把原理、配置、代码和常见问题都讲明白,保证你跟着做一遍就能掌握。

1. I2C协议到底是什么?

1.1 两线制的“聊天”协议

I2C(Inter-Integrated Circuit)也叫IIC,你可以把它想象成设备之间“聊天”的一种方式。它最大的特点就是简单——只需要两根线就能让多个设备互相通信。

这两根线分别是:

  • SDA(Serial Data Line):数据线,用来传输实际的数据
  • SCL(Serial Clock Line):时钟线,用来同步数据传输的节奏

在I2C总线上,设备有主从之分。主设备就像是聊天群里的群主,负责发起对话、控制聊天节奏;从设备就是群成员,只能等群主@自己才能说话。一个主设备可以同时和多个从设备通信,每个从设备都有一个唯一的“电话号码”(地址),主设备通过这个地址来指定要和谁通信。

1.2 硬件上的门道:为什么需要上拉电阻?

I2C在硬件实现上有个很重要的特点:它用的是开漏输出模式。这是什么意思呢?

你可以把开漏输出想象成一个只能“拉低”不能“推高”的开关。当设备要输出低电平时,它就把开关闭合,把线拉到地(0V);当要输出高电平时,它就把开关断开,让线“悬空”。

注意:开漏输出本身不能产生高电平,所以必须在SDA和SCL两根线上都接一个上拉电阻(通常2.2kΩ到10kΩ),这样当所有设备都不拉低时,电阻就把线拉到了高电平。

为什么要这么设计?主要有三个原因:

  1. 防止信号打架:如果多个设备同时用推挽模式输出,一个输出高一个输出低,就直接短路了
  2. 避免干扰:开漏模式下,只有拉低操作,不会出现多个高电平叠加干扰
  3. 实现“线与”:任何设备都能把总线拉低,方便实现多主机仲裁

I2C有不同的速度模式,咱们常用的有:

  • 标准模式:100 kbps(每秒10万位)
  • 快速模式:400 kbps
  • 高速模式:更快,但用得少

速度越快,能用的线就越短。标准模式大概能到1米,快速模式就只有0.3米左右了。

1.3 软件I2C vs 硬件I2C:怎么选?

这是很多初学者困惑的地方,我简单对比一下:

软件I2C

  • 原理:用普通的GPIO引脚,通过程序控制引脚电平变化来模拟I2C时序
  • 优点:不挑芯片,只要有GPIO就能用;引脚可以随便选,灵活
  • 缺点:占用CPU资源,时序要靠延时函数控制,速度慢且不稳定
  • 适用场景:低速通信、引脚资源紧张、芯片没有硬件I2C模块

硬件I2C

  • 原理:芯片内部有专门的I2C硬件模块,自动处理时序
  • 优点:不占用CPU,通信稳定可靠,速度有保障
  • 缺点:必须用芯片指定的引脚,不是所有芯片都有
  • 适用场景:高速稳定通信、需要DMA传输、多任务系统

MSPM0G3507其实是有硬件I2C模块的,支持100k、400k、1Mbps三种速率,还有8字节的FIFO(数据缓冲区)。但今天咱们为了彻底理解I2C原理,先用软件方式实现一遍。

2. 手把手搭建MSPM0G3507的I2C工程

2.1 工程准备与引脚配置

首先,咱们需要一个干净的工程模板。如果你用的是TI的SDK,找到G3507的空白工程(empty工程),复制到SDK根目录下,然后用Keil打开。

提示:工程必须放在SDK根目录,否则编译时会找不到相关文件。

打开工程后,咱们要配置两个引脚作为I2C的SDA和SCL。根据原文,我们选择:

  • SDA:PA0引脚
  • SCL:PA1引脚

在Keil里打开empty.syscfg文件,然后会弹出SYSCONFIG的图形化配置界面。这里我们需要做两件事:

  1. 配置GPIO引脚

    • 点击ADD添加GPIO配置组
    • 把PA0和PA1都配置为GPIO功能
    • 注意:软件I2C不需要配置为I2C外设模式,就用普通的GPIO就行
  2. 配置串口

    • 因为我们要通过串口把温湿度数据打印到电脑上查看
    • 选择任意一个UART引脚(比如PA2、PA3)配置为UART功能

配置完成后保存,Keil会自动生成ti_msp_dl_config.h文件,里面就有我们刚才配置的引脚定义。

2.2 工程文件管理

为了让代码结构清晰,咱们在工程里新建一个BSP文件夹(Board Support Package,板级支持包),所有自己写的驱动文件都放这里。

BSP文件夹下创建两个文件:

  • bsp_i2c.c- I2C驱动实现文件
  • bsp_i2c.h- I2C驱动头文件

然后在Keil的工程管理窗口:

  1. 新建一个工程分组,命名为BSP
  2. bsp_i2c.c添加到这个分组
  3. 在工程设置里添加BSP文件夹到头文件搜索路径

注意:如果你把工程名从empty改成了别的名字,记得更新头文件路径,把empty改成你的新工程名。

3. 用GPIO模拟I2C时序

3.1 引脚操作的宏定义

软件I2C的核心就是控制GPIO引脚的电平。为了代码清晰易读,咱们先定义一组宏:

// bsp_i2c.h #ifndef __BSP_I2C_H__ #define __BSP_I2C_H__ #include "ti_msp_dl_config.h" #include "stdio.h" // 设置SDA为输出模式(主机发送数据时用) #define SDA_OUT() { \ DL_GPIO_initDigitalOutput(I2C_SDA_IOMUX); \ DL_GPIO_setPins(I2C_PORT, I2C_SDA_PIN); \ DL_GPIO_enableOutput(I2C_PORT, I2C_SDA_PIN); \ } // 设置SDA为输入模式(主机接收数据时用) #define SDA_IN() { DL_GPIO_initDigitalInput(I2C_SDA_IOMUX); } // 读取SDA引脚电平(接收数据时用) #define SDA_GET() ( ( ( DL_GPIO_readPins(I2C_PORT,I2C_SDA_PIN) & I2C_SDA_PIN ) > 0 ) ? 1 : 0 ) // 控制SDA和SCL引脚输出高低电平 #define SDA(x) ( (x) ? (DL_GPIO_setPins(I2C_PORT,I2C_SDA_PIN)) : (DL_GPIO_clearPins(I2C_PORT,I2C_SDA_PIN)) ) #define SCL(x) ( (x) ? (DL_GPIO_setPins(I2C_PORT,I2C_SCL_PIN)) : (DL_GPIO_clearPins(I2C_PORT,I2C_SCL_PIN)) ) #endif

这几个宏是软件I2C的基石:

  • SDA_OUT()SDA_IN()用于切换SDA引脚的方向(输出或输入)
  • SDA_GET()用于读取SDA引脚的电平状态
  • SDA(x)SCL(x)用于设置引脚电平,x为1输出高,为0输出低

3.2 I2C的四大基本时序

I2C通信有四个最基本的时序信号,咱们一个一个来实现。

1. 起始信号(Start)起始信号告诉所有设备:“我要开始通信了”。它的时序是:SCL为高电平时,SDA从高变低。

void IIC_Start(void) { SDA_OUT(); // SDA设置为输出模式 SCL(0); // 先确保SCL为低 SDA(1); // SDA拉高 SCL(1); // SCL拉高 delay_us(5); // 保持一段时间 SDA(0); // SDA从高变低,产生起始信号 delay_us(5); SCL(0); // SCL拉低,为后续数据传输做准备 delay_us(5); }

2. 停止信号(Stop)停止信号表示:“通信结束”。时序是:SCL为高电平时,SDA从低变高。

void IIC_Stop(void) { SDA_OUT(); SCL(0); // 先确保SCL为低 SDA(0); // SDA拉低 SCL(1); // SCL拉高 delay_us(5); SDA(1); // SDA从低变高,产生停止信号 delay_us(5); }

3. 发送一个字节发送数据时,每个bit都是在SCL为低电平时改变SDA,在SCL为高电平时保持稳定。

void IIC_Send_Byte(uint8_t dat) { int i = 0; SDA_OUT(); SCL(0); // 确保SCL为低 // 从最高位开始,依次发送8个bit for(i = 0; i < 8; i++) { // 取出最高位(0x80是1000 0000) SDA((dat & 0x80) >> 7); delay_us(1); SCL(1); // SCL拉高,从机在这时采样数据 delay_us(5); SCL(0); // SCL拉低,准备发送下一位 delay_us(5); dat <<= 1; // 左移一位,准备发送下一位 } }

4. 接收一个字节接收数据时,主机控制SCL产生时钟,从机在SCL为高时把数据放到SDA上。

unsigned char IIC_Read_Byte(void) { unsigned char i, receive = 0; SDA_IN(); // SDA设置为输入模式 for(i = 0; i < 8; i++) { SCL(0); delay_us(5); SCL(1); // SCL拉高,从机在这时放置数据 delay_us(5); receive <<= 1; // 左移一位,为接收新bit腾出位置 if(SDA_GET()) // 读取SDA电平 { receive |= 1; // 如果为高,最低位置1 } SCL(0); // SCL拉低,准备接收下一位 } return receive; }

3.3 应答机制:确认收到

I2C有个很重要的机制叫应答(ACK)。每传输完一个字节(8位数据),接收方都要在第9个时钟周期给出应答信号:

  • 应答(ACK):接收方把SDA拉低,表示“我收到了”
  • 非应答(NACK):接收方保持SDA为高,表示“我没收到”或“别发了”

主机发送应答/非应答:

void IIC_Send_Ack(uint8_t ack) { SDA_OUT(); SCL(0); SDA(0); // 先拉低,准备发送应答 delay_us(5); if(!ack) SDA(0); // ack=0,发送应答(低电平) else SDA(1); // ack=1,发送非应答(高电平) SCL(1); // SCL拉高,从机在这时采样应答信号 delay_us(5); SCL(0); SDA(0); // 恢复SDA为低 }

主机等待从机应答:

uint8_t IIC_Wait_Ack(void) { char ack = 0; unsigned char ack_flag = 10; // 超时计数 SDA_IN(); // SDA设置为输入,准备读取从机应答 SDA(1); // 释放SDA线(加上拉电阻会拉高) delay_us(5); SCL(1); // SCL拉高 delay_us(5); // 等待从机拉低SDA(应答) while((SDA_GET() == 1) && (ack_flag)) { ack_flag--; delay_us(5); } if(ack_flag <= 0) // 超时,没有收到应答 { IIC_Stop(); // 发送停止信号 return 1; // 返回1表示无应答 } else // 收到了应答 { SCL(0); // SCL拉低 SDA_OUT(); // SDA改回输出模式 } return ack; // 返回0表示有应答 }

这里有个细节要注意:等待应答时,主机要先释放SDA线(设置为输入模式),让从机有机会拉低它。如果从机不拉低,SDA会被上拉电阻拉到高电平,这就是非应答。

4. 驱动SHT20温湿度传感器

4.1 SHT20传感器简介

SHT20是一款数字式温湿度传感器,精度很高:

  • 温度精度:±0.3℃
  • 湿度精度:±3%RH
  • 工作电压:2.1~3.6V(正好适合咱们的3.3V开发板)
  • 通信接口:I2C

它的I2C地址是0x80(写操作)或0x81(读操作)。这里解释一下:I2C地址一般是7位,SHT20的7位地址是0b1000_000(0x40)。但实际发送时,我们要把读写位加上:

  • 写操作:地址左移1位,最低位写0 → 0x80
  • 读操作:地址左移1位,最低位写1 → 0x81

4.2 硬件连接

接线非常简单,只有4根线:

SHT20引脚开发板引脚
SCLPA1
SDAPA0
GNDGND
VCC3V3

注意:SHT20的工作电压是2.1~3.6V,一定要接3.3V,不要接5V!

4.3 SHT20的通信流程

SHT20支持两种工作模式,咱们用的是非主机模式(No Hold Master)。这种模式下,传感器测量时不会占用I2C总线,主机可以干别的事。

测量命令:

  • 测量温度:0xF3
  • 测量湿度:0xF5

读取数据的完整流程,我画了个示意图帮你理解:

开始通信: 1. 主机发送起始信号 2. 主机发送器件地址+写(0x80) 3. 从机应答 4. 主机发送测量命令(0xF3或0xF5) 5. 从机应答 等待测量完成(需要延时一段时间) 读取数据: 6. 主机发送起始信号(重复起始) 7. 主机发送器件地址+读(0x81) 8. 从机应答 9. 主机读取数据高字节 10. 主机发送应答 11. 主机读取数据低字节 12. 主机发送应答 13. 主机读取校验字节(可选) 14. 主机发送非应答 15. 主机发送停止信号

4.4 完整的SHT20驱动函数

把上面的流程用代码实现:

float SHT20_Read(uint8_t regaddr) { unsigned char data_H = 0; // 数据高8位 unsigned char data_L = 0; // 数据低8位 float temp = 0; // 第一步:开始通信,发送器件地址(写) IIC_Start(); IIC_Send_Byte(0x80 | 0); // 0x80是写地址 if(IIC_Wait_Ack() == 1) // 等待应答 { printf("error -1\r\n"); // 无应答,可能是接线问题 return 0; } // 第二步:发送测量命令 IIC_Send_Byte(regaddr); // 0xF3测温度,0xF5测湿度 if(IIC_Wait_Ack() == 1) { printf("error -2\r\n"); return 0; } // 第三步:等待测量完成,然后发送读地址 do { delay_us(10); // 给传感器一点测量时间 IIC_Start(); // 重复起始信号 IIC_Send_Byte(0x80 | 1); // 0x81是读地址 } while(IIC_Wait_Ack() == 1); // 如果传感器没准备好,会返回非应答,需要重试 delay_us(20); // 再等一小会儿 // 第四步:读取数据 data_H = IIC_Read_Byte(); // 读取高8位 IIC_Send_Ack(0); // 发送应答,告诉传感器继续发 data_L = IIC_Read_Byte(); // 读取低8位 IIC_Send_Ack(1); // 发送非应答,告诉传感器别发了 IIC_Stop(); // 发送停止信号,结束通信 // 第五步:数据处理 // 数据低字节的最后两位是状态位,需要清零 uint16_t dat = (data_H << 8) | data_L; dat &= ~(0x03); // 清除最后两位状态位 // 第六步:数据换算 if(regaddr == 0xf3) // 温度数据 { // 温度转换公式:T = -46.85 + 175.72 * (S(T) / 65536) temp = (dat / 65536.0) * 175.72 - 46.85; } if(regaddr == 0xf5) // 湿度数据 { // 湿度转换公式:RH = -6 + 125 * (S(RH) / 65536) temp = (dat / 65536.0) * 125.0 - 6; } return temp; }

这里有几个关键点需要注意:

  1. 状态位清除:SHT20返回的16位数据中,最低两位是状态位(bit0无意义,bit1表示测量类型),计算前要清除
  2. 数据换算:传感器返回的是原始数据,需要按公式转换成实际值
  3. 浮点运算:公式里有除法和小数,记得用浮点变量存储结果

4.5 主函数调用

最后,在main函数里调用我们的驱动函数:

#include "ti_msp_dl_config.h" #include "stdio.h" #include "bsp_i2c.h" #define delay_ms(X) delay_cycles((CPUCLK_FREQ/1000)*(X)) #define T_ADDR 0xf3 // 温度命令 #define PH_ADDR 0xf5 // 湿度命令 int main(void) { SYSCFG_DL_init(); // 系统初始化 printf("SHT20 Start!!\r\n"); while(1) { float TEMP = SHT20_Read(T_ADDR); // 读取温度 float PH = SHT20_Read(PH_ADDR); // 读取湿度 printf("Temp = %.2f C\r\n", TEMP); printf("Humi = %.2f %%RH\r\n", PH); printf("\n"); delay_ms(1000); // 每秒读取一次 } }

重要提示:printf输出浮点数需要开启microLIB库,并在工程设置里调整堆栈大小。具体操作:Options for Target → Target → 勾选"Use MicroLIB",然后调整Stack Size为0x800(2KB)。

5. 调试技巧与常见问题

5.1 如果通信失败怎么办?

  1. 检查接线:这是最常见的问题,确保SDA、SCL、GND、VCC四根线都接对了
  2. 检查上拉电阻:软件I2C必须接上拉电阻(4.7kΩ或10kΩ),否则SDA和SCL无法被拉高
  3. 用示波器看波形:如果有条件,用示波器看SDA和SCL的波形,检查时序是否符合要求
  4. 调整延时时间:不同MCU速度不同,可能需要调整delay_us()的时间
  5. 检查地址:确认传感器地址是否正确,SHT20是0x80(写)/0x81(读)

5.2 软件I2C的时序优化

软件I2C的时序全靠延时函数控制,如果发现通信不稳定:

  • 缩短延时:如果MCU速度快,可以减小delay_us()的参数
  • 增加超时判断:在等待应答的地方增加超时机制,避免死等
  • 禁用中断:在关键时序操作期间禁用中断,避免被打断

5.3 从软件I2C迁移到硬件I2C

当你掌握了软件I2C的原理后,可以尝试用MSPM0G3507自带的硬件I2C模块。硬件I2C更稳定,不占用CPU,而且支持DMA传输。迁移时主要改三部分:

  1. 引脚配置改为I2C功能模式
  2. 初始化硬件I2C外设
  3. 调用TI提供的I2C驱动库函数

不过说实话,对于初学者来说,先搞懂软件I2C的原理非常重要。我见过很多直接调库的同学,一旦通信出问题就完全不知道怎么调试。自己实现一遍软件I2C,你对时序的理解会深刻很多。

好了,关于MSPM0G3507的I2C协议和SHT20驱动就讲到这里。实际做项目时,你可能还会遇到I2C总线冲突、从设备无响应、数据校验错误等问题,但只要你理解了基本原理,这些都能通过逻辑分析仪或示波器找到原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 15:58:15

ZYNQ实战:手把手教你用AXI-CAN在Linux下搭建CAN通信(附完整测试命令)

ZYNQ实战&#xff1a;从零构建Linux下的AXI-CAN通信系统 在嵌入式系统开发中&#xff0c;CAN总线因其高可靠性和实时性被广泛应用于汽车电子、工业控制等领域。本文将带你完整实现基于ZYNQ平台的AXI-CAN通信系统&#xff0c;从硬件配置到Linux驱动调试&#xff0c;最后通过实际…

作者头像 李华
网站建设 2026/8/14 20:41:05

【20年架构师总结】Linux服务类日志分析和策略003篇

文章目录 Linux日志分析全链路实践:从基础运维到AIOps智能运维 一、传统运维阶段:响应式故障处理 1.1 核心工作流程 1.2 实用工具链与示例 1.2.1 基础命令精要 1.2.2 高级日志分析脚本 二、SRE阶段:工程化与可靠性驱动 2.1 SRE四大黄金指标的可观测性实现 2.1.1 基于Prometh…

作者头像 李华
网站建设 2026/8/14 20:41:38

从零造轮子:用Java手撸一个简化版数据库MYDB(附完整源码解析)

从零造轮子&#xff1a;用Java手撸一个简化版数据库MYDB&#xff08;附完整源码解析&#xff09; 在当今数据驱动的时代&#xff0c;数据库作为信息系统的核心组件&#xff0c;其重要性不言而喻。但对于开发者而言&#xff0c;仅仅会使用MySQL、PostgreSQL等成熟数据库还远远不…

作者头像 李华
网站建设 2026/7/14 15:58:27

RTX 4090专属!Qwen2.5-VL-7B-Instruct图文助手保姆级部署教程

RTX 4090专属&#xff01;Qwen2.5-VL-7B-Instruct图文助手保姆级部署教程 1. 准备工作与环境配置 1.1 硬件要求 本教程针对配备RTX 4090 24GB显存显卡的本地环境设计&#xff0c;确保能够充分发挥Flash Attention 2极速推理优势。最低系统要求如下&#xff1a; 显卡&#x…

作者头像 李华
网站建设 2026/7/14 15:58:26

Qwen All-in-One功能体验:无需切换,输入即得情感标签+对话

Qwen All-in-One功能体验&#xff1a;无需切换&#xff0c;输入即得情感标签对话 你有没有遇到过这样的场景&#xff1f;想给聊天机器人加个情感分析功能&#xff0c;结果发现要部署两个模型——一个专门做情感分类&#xff0c;一个负责对话生成。不仅部署麻烦&#xff0c;内存…

作者头像 李华
网站建设 2026/7/14 15:58:28

在泰山派3M-RK3576开发板上部署YOLO11目标检测模型实战指南

在泰山派3M-RK3576开发板上部署YOLO11目标检测模型实战指南 最近有不少朋友在问&#xff0c;怎么把最新的YOLO11模型部署到泰山派3M-RK3576开发板上跑起来。正好我最近也在做这个项目&#xff0c;今天就把完整的部署流程分享给大家&#xff0c;从环境搭建、模型转换到最终在开发…

作者头像 李华