STM32用IIC驱动SHT20温湿度传感器并在OLED上显示
简介这是一份IIC驱动0.96寸OLED显示SHT20温湿度数据的完整工程Demo面向STM32嵌入式开发者和物联网爱好者演示了从底层IIC通信到传感器数据解析、OLED界面刷新的全流程。压缩包内共149个文件约3.45MB主要包含C/H源码、Keil工程配置文件以及hex、axf等可烧录固件和编译中间文件工程目录清晰便于直接打开、编译和下载验证。Demo实现了IIC初始化、OLED初始化、SHT20温度湿度读取、数据格式处理和动态显示更新等关键模块同时包括IIC时序配置、SHT20命令读取与CRC校验解析、温湿度单位换算以及通信异常处理代码结构完整可作为实际项目改造的基础。目前已有847人学习下载适合用来快速掌握IIC总线协议、OLED显示驱动以及数字温湿度传感器的典型应用。1. 用IIC把SHT20的温湿度搬到0.96寸OLED上如果你手头有一颗SHT20、一块0.96寸SSD1306 OLED又不想在串口助手里盯着十六进制数据发呆那么这个IIC驱动的DEMO就是你需要的起点。它做的事情很直白通过IIC总线读取SHT20的温湿度原始值经过公式换算成十进制温湿度再用字模的方式显示在OLED屏幕上。整条链路只有两根信号线SCL、SDA加上电源地却把传感器采集、数据解析、显示驱动一次跑通。这个DEMO适合刚接触IIC协议的人也适合要用STM32或ESP32快速验证传感器和显示模块的工程师。我先讲明白SHT20的IIC行为再给可复现的代码和参数最后把最常见的坑列出来。2. SHT20的IIC时序与温湿度换算先把协议层吃透SHT20是Sensirion的数字温湿度传感器出厂前校准过走标准IICI2C接口。它区别于DHT11的关键在于SHT20的数据读取是主控主动发起测量命令然后等待一段时间再读取结果测量过程中传感器会拉低SDA来表示忙主控需要支持时钟拉伸clock stretching或者用延时来解决。很多初学者在这里翻车——用IIC读DHT11的思路去读SHT20结果永远读到0xFF。2.1 SHT20的设备地址和寄存器命令SHT20默认7位从机地址是0x40二进制1000000加上读写位后写地址为0x80读地址为0x81。注意不同厂家的模块可能把地址引脚ADDR接了不同电位如果地址不对可以通过跳线改成0x41。建议初始化时写一条软件复位命令0xFE让传感器回到已知状态。常用命令如下命令含义典型使用场景0xFE软件复位上电后调用复位寄存器0xE3读温度无时钟拉伸常用主机等待延时0xF3读湿度无时钟拉伸常用主机等待延时0xF5读湿度有时钟拉伸传感器拉低SDA直到完成0xE0读用户寄存器检查分辨率、加热选项IIC通信协议里一次测量流程是主机发START - 写地址写位0x80- 发命令字节 - 等待测量时间- 发START - 写地址读位0x81- 读取数据高字节 - 读取数据低字节 - 读取CRC可选- STOP。如果你用的是HAL库HAL_I2C_Mem_Read这类函数并不直接适用因为SHT20的命令流程是“先写后读”且中间有延时更推荐用HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive分步做。2.1.1 为什么等待时间不能拍脑袋SHT20在分辨率默认RH 12位T 14位下典型转换时间温度是58ms湿度是22ms最差情况下温度85ms、湿度29ms。很多人用HAL_Delay(50)后读数据运气好能读到但温度在低温或电压波动时转换时间变长就会在读到的数据里出现0x...的无效值。我一般会延时100ms以上或者用传感器提供的测量状态位来判断。在无时钟拉伸模式下最安全的做法是延时到100ms既不影响显示刷新也不容易读到旧数据。2.2 从原始码到温湿度的换算公式读回来的数据是16位无符号整型高字节在前。温度和湿度的换算公式在SHT20数据手册里写得很清楚温度T -46.85 175.72 * (raw / 65535)湿度补偿后RH -6 125 * (raw / 65535)实际有效范围0~100%RH。若算出来小于0取0大于100取100。在C语言里注意raw是uint16_t做除法前先转成float否则整数除法的结果是0。不少人在这一步得到固定值比如温度总是-46.85就是因为忘了转浮点。2.3 一个最小可用的SHT20读取函数下面给出一个基于STM32 HAL库的读取函数适配CubeMX生成的IIC初始化代码。这里不依赖中断或DMA逻辑最简单。#define SHT20_ADDR_W 0x80 #define SHT20_ADDR_R 0x81 #define CMD_READ_TEMP_NO_STRETCH 0xE3 #define CMD_READ_HUM_NO_STRETCH 0xF3 uint8_t sht20_read_measure(uint8_t cmd, float *result) { uint8_t buf[3]; *result 0.0f; // 发送测量命令 HAL_StatusTypeDef status HAL_I2C_Master_Transmit(hi2c1, SHT20_ADDR_W, cmd, 1, 100); if (status ! HAL_OK) { return 0; // 发送失败检查地址与连线 } // 等待转换完成温度建议100ms湿度建议50ms以上 HAL_Delay(100); // 读取2字节数据1字节CRC第三个字节忽略 status HAL_I2C_Master_Receive(hi2c1, SHT20_ADDR_R, buf, 3, 100); if (status ! HAL_OK) { return 0; } uint16_t raw ((uint16_t)buf[0] 8) | buf[1]; raw 0xFFFC; // 清除低2位状态位 if (cmd CMD_READ_TEMP_NO_STRETCH) { *result -46.85f 175.72f * ((float)raw / 65535.0f); } else { *result -6.0f 125.0f * ((float)raw / 65535.0f); if (*result 100.0f) *result 100.0f; if (*result 0.0f) *result 0.0f; } return 1; }逻辑说明cmd决定是温度命令还是湿度命令测量期间用HAL_Delay(100)保守等待读回来的低两位是状态位00表示OK所以要 0xFFFC清掉。最后按手册公式换算。如果你用的不是HAL换成寄存器操作或LL库也一样IIC时序不变。提示如果你的IIC总线还挂载了其他设备比如OLED注意每次通信完成后要释放总线。在HAL库中HAL_I2C_Master_Transmit返回前会发送STOP所以只要没有中断嵌套问题总线是干净的。3. 0.96寸OLED的SSD1306驱动从初始化到显示温湿度0.96寸OLED绝大多数是SSD1306驱动芯片分辨率128x64IIC接口的7位地址通常为0x3C写地址0x78。这款屏的内存是一个1KB的GRAM每一bit对应一个像素。控制它需要的不是帧缓冲而是逐页每页8像素高写入数据。驱动核心就两件事初始化序列和显存刷新策略。3.1 初始化序列的取舍网上流传的SSD1306初始化代码很多多数是从u8g2或Adafruit移植过来的但有些指令是冗余的。我常用的最小初始化序列如下IIC模式下命令前面要加0x00控制字节数据前面加0x40static void ssd1306_write_cmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; HAL_I2C_Master_Transmit(hi2c1, 0x78, buf, 2, 100); } static void ssd1306_write_data(uint8_t data) { uint8_t buf[2] {0x40, data}; HAL_I2C_Master_Transmit(hi2c1, 0x78, buf, 2, 100); } void ssd1306_init(void) { HAL_Delay(10); ssd1306_write_cmd(0xAE); // 关闭显示 ssd1306_write_cmd(0x20); // 设置内存地址模式 ssd1306_write_cmd(0x00); // 水平寻址模式 ssd1306_write_cmd(0xB0); // 设置页地址为0 ssd1306_write_cmd(0xC0); // 扫描方向从左上角开始 ssd1306_write_cmd(0xA8); // 设置多路复用比 ssd1306_write_cmd(0x3F); // 64行 ssd1306_write_cmd(0xA1); // 列地址重映射左右翻转 ssd1306_write_cmd(0xDA); // 设置COM引脚硬件配置 ssd1306_write_cmd(0x12); // 常见值 ssd1306_write_cmd(0x81); // 对比度 ssd1306_write_cmd(0x7F); // 中等亮度 ssd1306_write_cmd(0xA4); // 显示RAM内容 ssd1306_write_cmd(0xA6); // 正常点亮非反色 ssd1306_write_cmd(0xAF); // 开启显示 HAL_Delay(10); }参数说明0xA1和0xC0控制显示方向如果你的屏出厂镜像或者是0.91寸版本可能需要改成0xA0和0xC8。0x81后续值越大屏幕越亮。0x20后的0x00是水平寻址模式适合连续刷一行的字模。3.1.1 为什么OLED要用页模式SSD1306的GRAM被分成8页每页8行像素。水平寻址模式下写数据时列地址自动加1从第0页第0列开始写写满128个字节后自动跳到下一页。这个特性让显示字符非常方便一个8x16像素的字符正好是2页内16行的内容。如果你的驱动代码里出现“字符上下两半错位”多半是页地址没有正确切换。3.2 显示温湿度的字模渲染一般做法是内置ASCII字库每个字符用const uint8_t数组表示8x16点阵。比如字符‘A’的字模有16个字节前8字节是上半部分后8字节是下半部分。显示一个字符串时遍历每个字符在页P写前8字节在页P1写后8字节。void ssd1306_set_cursor(uint8_t page, uint8_t x) { ssd1306_write_cmd(0xB0 page); // 设置页 ssd1306_write_cmd(0x00 (x 0x0F)); // 列低4位 ssd1306_write_cmd(0x10 ((x 4) 0x0F)); // 列高4位 } void ssd1306_show_char(uint8_t page, uint8_t x, char c) { const uint8_t *font ascii_8x16[(uint8_t)c - 0x20]; ssd1306_set_cursor(page, x); for (int i 0; i 8; i) { ssd1306_write_data(font[i]); // 上半部分 } ssd1306_set_cursor(page 1, x); for (int i 8; i 16; i) { ssd1306_write_data(font[i]); // 下半部分 } }这里要注意IIC每次传输只能发一个字节数据效率很低。想提速可以用HAL_I2C_Master_Transmit一次发送多个字节把连续的一段数据帧合并成一包。比如显示一个完整的8x16字符可以构造一个17字节的缓存0x40 16个数据一次发出去这样IC总线上少了很多START/STOP实测显示刷新率能提高一半以上。4. 把SHT20和OLED串成一个DEMOSTM32 HAL库完整流程前面两章分别讲了SHT20的读取和OLED的驱动现在把它们整合进一个实际可运行的DEMO里。这里以STM32F103和STM32CubeMX为例梳理整个流程其他芯片和开发环境思路一致。4.1 CubeMX的IIC配置与引脚分配在CubeMX中把I2C1勾选为I2C模式速率为Standard Mode100kHz或Fast Mode400kHz。SHT20和OLED都挂在这个总线上遵循IIC通信协议所以不需要额外片选。一个值得注意的参数是上拉电阻IIC的SCL和SDA是开漏结构必须接上拉。很多开发板上已经有4.7kΩ的外部上拉如果你的板子是飞线的建议在数据手册推荐的基础上选用2.2kΩ~4.7kΩ。上拉太大会导致边沿过缓太小题材功耗高且容易拉不动。引脚分配上I2C1默认在PB6SCL和PB7SDA。OLED的VCC接3.3VSHT20的VDD也接3.3V。注意SHT20如果工作电压高于3.6V会损坏所以别接到5V。生成代码后在main.c的while(1)里循环读取并显示while (1) { float temp, hum; if (sht20_read_measure(CMD_READ_TEMP_NO_STRETCH, temp)) { // 格式化温度字符串保留一位小数 char t_buf[16]; snprintf(t_buf, sizeof(t_buf), T:%.1f C, temp); ssd1306_clear(); ssd1306_show_string(0, 0, t_buf); } if (sht20_read_measure(CMD_READ_HUM_NO_STRETCH, hum)) { char h_buf[16]; snprintf(h_buf, sizeof(h_buf), H:%.1f %%, hum); ssd1306_show_string(2, 0, h_buf); } HAL_Delay(1000); // 每秒刷新一次 }逻辑说明每次刷新先清屏再分别显示温度和湿度。读取间隔1秒避免传感器加热效应导致湿度值漂移。ssd1306_show_string底层会调用前面写的字符渲染函数页参数为0和2让温湿度分别显示在第0页和第2页。4.1.1 格式化输出的坑snprintf重定向到显示时需要保证单片机固件里实现了_write或putc之类的字符输出函数否则编译能过但运行时无法显示。更推荐的做法是自己写一个整数转字符串函数避免依赖庞大的vsnprintf库。比如void float_to_str(char *buf, int value_1decimal) { buf[0] (value_1decimal / 10) % 10 0; // 整数位 buf[1] .; buf[2] value_1decimal % 10 0; // 小数位 buf[3] \0; }调用方式float_to_str(buf, (int)(temp * 10))这样可以省下几十KB的ROM占用尤其在使用标准库的GCC工具链时效果明显。4.2 软复位与上电顺序优化SHT20上电后最好先执行0xFE软复位等待50ms再开始测量。OLED也需要初始化时序稳定后再刷显存。建议在main()初始化外设后先调用ssd1306_init()再调用sht20_soft_reset()最后进入主循环。顺序错了会出现第一次测量异常或OLED花屏。DEMO里还必须考虑IIC总线死锁的情况。常见死锁发生在读取SHT20时传感器完成测量前主机抢先发起读操作此时总线可能被拉死。解决办法是读取失败后翻转一下SCL并发送9个时钟脉冲让总线恢复空闲。这在单片机里可以通过软件模拟IIC实现或者在HAL中调用HAL_I2C_DeInit再重新初始化。5. 验证DEMO的4个关键步骤与排错技巧最后分享几个让DEMO稳定运行的小技巧按下面的顺序验证能快速定位是接线问题、地址问题还是数据处理问题。先检查地址用IIC扫描例程扫出0x40和0x3C两个地址。如果只扫到0x3C说明SHT20没工作检查供电和SDA/SCL是否接反。如果地址全无很可能是上拉电阻缺失。飞线时我一般用4.7kΩ上拉到VCC不要用内部上拉因为内部上拉电流太小在100kHz模式下不够驱动总线。再看转换数据把读取的原始raw值通过串口打印出来。温度原始值在室温25℃下大约在21000左右湿度在50%RH时大约在23000左右。如果读到的raw是0xFF或0x0000说明传感器没有应答或IIC读时序不对。这里要检查时间参数主机发送命令后HAL_Delay(100)只适用于无时钟拉伸模式若你用了0xF5命令就必须等待传感器拉低SDA结束不能用固定延时。第三是显示层验证OLED上电后如果屏幕有背光但无字符先看是否有数据写入。可以用一个简单的测试函数直接给整屏写0xAA即全部像素交替点亮确认GRAM写入正常。如果能看到棋盘格说明OLED驱动没问题问题在字模或页地址计算。0.96寸OLED的IIC地址有的模块是0x3D需要跳线或改代码。在HAL_I2C_Master_Transmit里把地址从0x78改成0x7A尝试一下。最后一个技巧是如何提高显示刷新率。一次IIC写单字节数据需要约0.3ms显示一帧128x64的内容需要1024字节如果逐字节写就是307ms这显然不可用。我会把整个显存映射进一个uint8_t buffer[8][128]的数组所有文字写入buffer然后一次性刷到屏幕上。刷新函数如下void ssd1306_refresh(void) { for (uint8_t page 0; page 8; page) { ssd1306_write_cmd(0xB0 page); ssd1306_write_cmd(0x00); ssd1306_write_cmd(0x10); // 先发控制字节再连续发128个数据 uint8_t frame[129]; frame[0] 0x40; memcpy(frame[1], buffer[page][0], 128); HAL_I2C_Master_Transmit(hi2c1, 0x78, frame, 129, 200); } }这个函数把每页的128字节打包成一个大包一次IIC传输完成一页刷新。这样整屏刷新时间可以降到20ms以内对DEMO来说完全够用。以后想加动画或者动态曲线也不用担心总线瓶颈。到这里SHT20温湿度数据就能稳定、清晰地显示在0.96寸OLED上了。你可以把采样周期改短也可以增加一个按键翻页显示历史最大值和最小值这套IIC驱动框架足够支撑。本文还有配套的精品资源点击获取

相关新闻

MySQL锁等待超时排查指南:从事务到索引的完整突围

MySQL锁等待超时排查指南:从事务到索引的完整突围

凌晨两点半,手机告警群突然弹出消息,生产环境的订单接口开始陆续报错。打开日志一看,清一色的MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction,接口调用方那边已经开始积压重试。这个…

2026/9/20 7:03:58 阅读更多 →
限流算法核心取舍:令牌桶与漏桶该如何选型

限流算法核心取舍:令牌桶与漏桶该如何选型

限流算法的核心取舍:令牌桶和漏桶,到底该怎么选先聊一个大多数后端同学都经历过的场景。某个周三下午,运营那边突然上了一波活动,流量瞬间从平时的几百 QPS 冲到几千甚至上万,服务端监控开始飘红,数据库连接…

2026/9/21 0:14:19 阅读更多 →
线上服务器变慢?从负载飙升到揪出定时任务的实战排障指南

线上服务器变慢?从负载飙升到揪出定时任务的实战排障指南

线上服务器突然变慢,我花了2小时才定位到一个定时任务——聊聊Linux系统排障那些事这事发生在上周三,值班群里突然有人喊“线上页面打开好慢”。我赶紧上机器看了一眼,load average 直接飙到了 12 多,CPU 使用率看着也不算离谱&am…

2026/9/19 13:21:09 阅读更多 →

最新新闻

3步搞定美国人平均寿命数据校验,最佳实践避坑指南

3步搞定美国人平均寿命数据校验,最佳实践避坑指南

3步搞定美国人平均寿命数据校验,最佳实践避坑指南 配置环境就卡半天,是不是你也曾为了一个看似简单的数据校验逻辑,在本地和测试环境之间反复横跳?明明代码在本地跑得飞快,一到线上就报错,或者精度丢失导致业务逻辑错乱。别急,这不只是你一个人的问题…

2026/9/22 1:43:55 阅读更多 →
闫辉速查手册:3个底层优化让Java接口快10倍

闫辉速查手册:3个底层优化让Java接口快10倍

闫辉速查手册:3个底层优化让Java接口快10倍 复制来的代码跑不通,报错日志像天书一样刷屏,90%的应届生都卡在这一步。别急着删库重练,你需要一份能直接落地的 闫辉 性能优化 速查手册…

2026/9/22 1:43:55 阅读更多 →
:D是什么意思 视频吧手写实现

:D是什么意思 视频吧手写实现

3个底层逻辑搞懂:D是什么意思视频吧手写实现与性能优化 配置环境就卡半天,是不是经常遇到?刚把 Node.js 装好,npm install 转了十分钟还没动静,或者 Python…

2026/9/22 1:43:55 阅读更多 →
3道高频面试题拆解g674源码 告别教程依赖

3道高频面试题拆解g674源码 告别教程依赖

3道高频面试题拆解g674源码 告别教程依赖 看了一堆教程还是不会写项目?别急,这锅不该教程背,得背在“只抄不读”上。 很多后端开发卡在“能跑就行”的阶段,面试时遇到 高频面试题 问底层原理,脑子一片空白。比如今天我们要聊的 g674…

2026/9/22 1:43:55 阅读更多 →
图解原理搞懂ping检测:告别配置环境卡半天的3个实战技巧

图解原理搞懂ping检测:告别配置环境卡半天的3个实战技巧

图解原理搞懂ping检测:告别配置环境卡半天的3个实战技巧 你是不是也遇到过这种情况?为了验证服务器通不通,或者检查网络设备是否在线,结果在命令行里敲了半天 ping…

2026/9/22 1:43:55 阅读更多 →
pp助手ios7入门到精通:5个真实场景对比,面试官最想看这个

pp助手ios7入门到精通:5个真实场景对比,面试官最想看这个

pp助手ios7入门到精通:5个真实场景对比,面试官最想看这个 面试被问“为什么选这个方案”答不上来,或者只会背八股文,现场让你写个Demo却卡壳?这种尴尬我见得太多了。很多学员觉得pp助手ios7只是老掉牙的安卓工具,但在特定遗留系统或逆…

2026/9/22 1:42:55 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →