news 2026/9/9 11:02:50

STM32F103C8T6驱动HC-SR501人体感应,OLED实时显示状态与计数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103C8T6驱动HC-SR501人体感应,OLED实时显示状态与计数

把HC-SR501接到STM32F103C8T6上,再把检测结果实时刷到OLED屏幕,这几乎是嵌入式入门里最有“即时反馈感”的小项目之一。传感器感应到人,OLED立刻从“无人”变成“有人”,整套链路包含GPIO输入采集、信号防抖、OLED显示驱动,原理不复杂,代码量也不大,但真正动手做一遍你会发现,坑全藏在细节里:模块上电后要等接近一分钟输出才稳定、OLED的I2C地址偶尔不是0x3C、灵敏度旋钮拧反了感应距离只剩半米。这篇文章是我从接线到烧录再到反复调试的完整记录,适合刚入手STM32F103C8T6最小系统板、想做人体检测,或者单纯想搞定一块I2C接口OLED的读者参考。

1. 先聊原理:热释电效应、菲涅尔透镜和HC-SR501的输出机制

1.1 热释电效应:传感器为什么能“看见”人

HC-SR501的核心是一个热释电红外传感器(PIR)。人体温度通常在36~37℃左右,会持续向外辐射红外线,峰值波长大约在9.4μm附近。传感器内部有两片敏感元,都是能“感知温度变化”的材料:温度一变化,材料内部的极化状态就会改变,在表面产生电荷,进而输出一个微弱的电压变化。

但这里有一个关键点:热释电传感器对“温度变化”敏感,准确说是对“红外辐射变化”敏感,对静止不动的红外源反而不敏感。两块敏感元采用差分结构——把两个敏感元反相连接,当人体从一片敏感元对应的探测区域移动到另一片时,两片输出形成正负叠加,产生的信号最强;而环境温度慢慢变化时,两片输出相同,相互抵消,以此抑制温漂。这也是HC-SR501本质上只能“检测运动的人”的原因。后面调试部分我会专门讲这个坑:人一直在感应区域里坐着不动,输出会消失,不是传感器坏了,而是物理原理决定的。

1.2 菲涅尔透镜:把人体变成“移动的光斑”

传感器感应到的红外信号非常微弱,而且依赖于人“走过”探测区域。HC-SR501表面上那一片乳白色、有波纹的面板就是菲涅尔透镜,它做的事是把模块前方大范围的视野分割成很多个明暗相间的区域,相当于许多小透镜把红外线聚焦到敏感元上。

人一旦走动,就会依次穿过这些明暗区域,热释电敏感元上接收到的红外辐射就会周期性变化,形成一串脉冲。所以菲涅尔透镜的作用不仅是“聚光”,更关键的是“分光”——把人体的移动转化成传感器能识别的交变热信号。这也是为什么模块的探测角度能到120度左右、距离能到7米上下,全靠这片透镜。我在调试中发现,如果菲涅尔透镜表面被灰尘、油污或贴纸盖住,检测距离会明显变短,见过不少人贴了保护膜忘了撕,然后怎么调灵敏度都没用。

1.3 跳帽和电位器:决定输出行为的三个开关

HC-SR501板子上有三个可调部分:一个触发模式跳帽,两个电位器。

跳帽在靠近OUT引脚一侧,标着H和L:

  • H(可重复触发):人只要持续在感应范围内活动,输出就一直保持高电平,每次检测到运动会重新计时。
  • L(不可重复触发):模块检测到人后,输出高电平持续一段固定延时时间,期间不管人怎么动,延时结束就拉低。

两个电位器分别标着RT(延时)和RG(灵敏度):

  • RT调节输出高电平的持续时长,顺时针拧延时变长,常见可调范围从几秒到200秒左右。
  • RG调节检测距离,顺时针拧灵敏度变高、距离变远,可调范围大概从3米到7米。

不同批次模块的电位器方向可能略有差异,但丝印和手感基本一致。实测中最稳的办法是把模块固定好后,拿一个人从远处慢慢靠近,边测试边拧,用肉眼判断当前旋钮位置对应的实际距离。

注意:理解这三个配置很重要,很多“程序逻辑不对”的问题根本不是代码问题,而是跳帽模式和你设计的业务逻辑不匹配。比如你想做“人离开后延时10秒熄屏”的功能,跳帽却放在L模式,模块输出的高电平只会保持一个固定时间,程序收不到持续有效的触发信号,逻辑自然对不上。

2. 接线方案与电源边界:5V给传感器、3.3V给OLED,别混在一起

2.1 最小系统板的引脚分配与完整接线表

我用的是最常见的STM32F103C8T6最小系统板,OLED是0.96寸、SSD1306控制芯片、I2C接口的四针版本。这种OLED接线最省事,只需四根线。

整个项目只需要用到三个外设引脚:一个GPIO输入(接HC-SR501的OUT),两个GPIO接OLED的I2C。我的选择是:

模块引脚接到STM32F103C8T6
HC-SR501VCC5V引脚
HC-SR501GNDGND
HC-SR501OUTPB0
OLEDVCC3.3V
OLEDGNDGND
OLEDSCLPB6
OLEDSDAPB7

PB6和PB7是STM32F103的I2C1_SCL和I2C1_SDA复用引脚,即使我用软件模拟I2C也选这两个脚,方便以后想切成硬件I2C时不用改板子。如果你用的是SPI接口的OLED,接线会多几根,但这个项目用I2C版足够,画面更新频率不高,I2C完全扛得住。

2.2 供电和电平兼容:5V、3.3V、共地分别怎么处理

供电是新手最容易出错的地方,用一句话概括:HC-SR501必须用5V供电,OLED必须用3.3V供电,两者地线要共地。

HC-SR501的工作电压范围是4.5~20V,给3.3V虽然也能跑,但内部运放动态范围不够,实测感应距离会缩短很多,甚至只有三五十厘米。所以直接接最小系统板的5V引脚。

OLED的SSD1306控制器额定电压是3.3V,虽然网上有人说接5V也能亮,但那是因为模块上带了AMS1117-3.3稳压芯片。并不是所有卖家都焊了稳压,而且即使有稳压,一路5V供电也容易发热、影响寿命。按手册来,OLED接3.3V最稳妥。

关于HC-SR501输出电平,很多资料标注是“高电平3.3V、低电平0V”。实测下来,模块在5V供电时输出高电平确实在3.3V左右,并没有到5V,所以直接接STM32的GPIO是安全的,不用加分压电阻。但如果你买的模块是其他厂牌、输出逻辑高电平接近电源电压,那建议串一个1k电阻或者用两个电阻分压再进GPIO。这一点可以通过万用表实测OUT引脚电平确认,花十秒钟就能避免烧引脚。

共地问题:所有模块的GND必须连到STM32F103C8T6的GND。如果没有共地,传感器输出的高低电平对比的是它自己的地,STM32读到的电平可能是错的。这是最常见的一类“时好时坏”问题,大多是杜邦线松了或者地线没接。

2.3 为什么我用软件I2C驱动OLED而不是硬件I2C

关于OLED驱动,我最终选择的是软件模拟I2C,而不是STM32F103的硬件I2C外设。

原因很现实:第一,SSD1306对I2C时序要求不高,100kHz或400kHz都能跑,软件模拟I2C完全足够,OLED刷一屏才几十毫秒,根本不需要高速。第二,STM32F1系列的硬件I2C在标准库时代被很多人踩过坑,卡BUSY、死锁的问题不少,虽然有争议说HAL库已经好很多,但对一个入门项目来说,没必要把时间花在和I2C硬件状态机较劲上。软件I2C只要两根GPIO推挽输出,代码逻辑透明,出问题还容易定位。

这个选择也影响了接线方案:软件I2C对引脚没有复用要求,理论上任意两个GPIO都行,我选PB6/PB7纯粹是为了保留以后使用硬件I2C的可能性。

3. 代码逻辑:GPIO采样、防抖状态机与OLED渲染

3.1 CubeMX配置:工程搭建的五个关键点

我用STM32CubeMX + HAL库这套流程,整体效率比标准库高,代码结构也清晰。打开CubeMX新建工程,选择STM32F103C8T6,关键配置如下:

RCC的HSE选择Crystal/Ceramic Resonator。最小系统板上的晶振一般是8MHz,如果不配置外部晶振,HAL的时钟树没有正确时钟源,后面延时和串口全都不准。

SYS的Debug选择Serial Wire。不选的话,烧录一次之后SWD接口容易被关闭,第二次下载会失败。

GPIO配置:PB0设为Input。HC-SR501输出高电平是明确的,可以用NOPULL,但如果你线比较长、环境干扰大,建议打开下拉电阻,防止浮空误触发。

时钟树配置为72MHz主频。很多人省略这一步直接默认8MHz,程序能跑,但所有HAL_Delay、串口波特率都不准。

PB6、PB7配置为GPIO_Output。软件I2C通常用推挽输出,因为要主动拉高拉低;OLED模块一般自带I2C上拉电阻,开漏输出同样可用,但没有上拉的手工DIY板子则推挽更稳。

3.2 防抖状态机:为什么不能直接读一次引脚就下结论

HC-SR501虽然在输出端集成了一定整形逻辑,但它归根到底是模拟信号处理后得到的电平,在触发瞬间、电源波动、附近有电磁干扰时,输出可能会短暂抖动。如果代码直接读一次引脚就切换状态,OLED上的显示可能会在“有人/无人”之间快速闪动,面板计数器也会乱跳。

我采用的方案是连续采样确认:每10毫秒读一次引脚,连续读到5次相同的电平,才认为状态稳定。10ms×5次=50ms,这个时间既不会漏掉人的正常走动(人走过传感器前至少持续几十毫秒),又能滤掉大部分抖动。

#define PIR_PORT GPIOB #define PIR_PIN GPIO_PIN_0 #define SAMPLE_COUNT 5 #define SAMPLE_PERIOD 10 // ms uint8_t pir_read_stable(uint8_t expect_level) { uint8_t match = 0; for (uint8_t i = 0; i < SAMPLE_COUNT; i++) { if (HAL_GPIO_ReadPin(PIR_PORT, PIR_PIN) == expect_level) { match++; } HAL_Delay(SAMPLE_PERIOD); } return (match == SAMPLE_COUNT); }

主程序里用状态变量切换:

typedef enum { STATE_EMPTY, STATE_OCCUPIED } PIR_STATE; PIR_STATE state = STATE_EMPTY; uint32_t enter_time = 0; uint32_t total_count = 0; uint8_t last_show_state = 0xFF; while (1) { if (state == STATE_EMPTY) { if (pir_read_stable(1)) { state = STATE_OCCUPIED; enter_time = HAL_GetTick(); total_count++; } } else { if (pir_read_stable(0)) { state = STATE_EMPTY; } } // 渲染OLED,仅在状态变化或定时刷新时更新 if (state != last_show_state) { OLED_Clear(); if (state == STATE_OCCUPIED) { OLED_ShowString(0, 0, "Sta:IN "); } else { OLED_ShowString(0, 0, "Sta:OUT"); } last_show_state = state; } // 每秒刷新计数和持续时间 if (HAL_GetTick() - last_refresh >= 1000) { char line2[16]; char line3[16]; snprintf(line2, sizeof(line2), "Cnt:%lu", total_count); snprintf(line3, sizeof(line3), "Time:%lus", (HAL_GetTick() - enter_time) / 1000); OLED_ShowString(0, 2, line2); OLED_ShowString(0, 4, line3); last_refresh = HAL_GetTick(); } HAL_Delay(20); }

这里把“状态切换”和“界面更新”拆开,状态变了才刷屏,避免OLED每轮循环都全屏刷新导致闪烁。OLED的刷新写入I2C本身就不快,频繁全屏刷会让显示区域闪得厉害,这种“按需刷新”的思路在单片机项目里很常见。

3.3 OLED显示设计:状态、计数与停留时长

简单的几行字不满足完整教程的演示效果,我把显示分成三块:第一行显示当前状态,第二行显示累计触发次数,第三行显示本次有人状态的持续时间(每秒更新一次,用HAL_GetTick()减去enter_time换算成秒)。

OLED驱动用的是常见的SSD1306软件I2C驱动,初始化、清屏、字符显示函数可以从开源的ssd1306_i2c驱动基础上整理。关键点是初始化顺序:SSD1306要先发初始化序列、开启内部DC-DC电荷泵、设置对比度,最后清屏,否则屏幕可能不亮。另外I2C地址默认0x3C,如果你买的OLED是0x3D地址版本,显示函数里所有地址都要改。

这里不贴完整驱动了,它接近300行,网上开源版本很多,核心接入逻辑就是把SSD1306_I2C_WriteByte封装成自己的软件I2C读写函数,每次操作前拉低起始信号、发送设备地址+写位、再发送控制字节和数据字节。只要确认这几步对,任何来源的驱动都能跑起来。

注意:OLED驱动里的数字显示函数如果传入一个很大的uint32_t,转字符串时要处理好转字符串的长度,避免显示乱码。可以用snprintf把数字格式化到缓冲区,再调用显示字符串函数,比直接传数字给显示函数更安全。

4. 实测排坑日志:预热期伪触发、OLED花屏、距离缩水的完整排查

4.1 上电后乱报有人:传感器有“预热期”

第一次上电时,OLED显示“有人”的频率高得离谱,大概每几秒就触发一次,旁边明明没有人。查了一圈,程序没问题、接线没问题,最后发现HC-SR501刚上电的30~60秒内,内部放大器处在建立阶段,输出电平不稳定,模块说明书里管这个叫初始化时间,也就是预热期。

排查方法很简单:上电后不要立刻进入正常检测流程,先用一个变量记录启动时间,在预热期内屏蔽所有PIR状态切换,或者等预热期结束后再清屏显示。

#define WARMUP_MS 60000 while (HAL_GetTick() < WARMUP_MS) { HAL_Delay(10); } OLED_Clear();

实际中预热时间受环境温度影响,夏天可能30多秒就稳定,冬天可能要一分钟以上。建议预留60秒到90秒,或者看模块板载指示灯稳定后再开始检测。

4.2 OLED不亮或花屏:一套排查链路

OLED不亮是最容易卡死人的问题。按下面的链路排查,基本五分钟定位:

先用万用表量OLED的VCC对GND电压,确认是不是3.3V。很多时候问题根本不是代码,是杜邦线接触不良或面包板电源排没插紧。然后看屏幕背面丝印,确认是I2C版本还是SPI版本,I2C只有四个引脚,SPI通常有七个以上,接口版本不同,接线和驱动完全不同。接着确认I2C地址,用逻辑分析仪或写一段I2C扫描代码,把0x3C和0x3D都测一遍。再检查SCL和SDA有没有接反,这个失误率极高,因为有些模块丝印SDA/SCL在背面,插上杜邦线后字符方向是反的。最后检查软件I2C的GPIO时钟是否开启,CubeMX勾选了GPIO_OUTPUT之后HAL库的MX_GPIO_Init里会帮开时钟,手写初始化代码时漏掉__HAL_RCC_GPIOB_CLK_ENABLE()就会一直不亮。

如果屏幕能亮但花屏,大概率是I2C速率不匹配,或者驱动初始化序列没完整执行。软件I2C的延时建议设置在半周期5~10微秒,太快的GPIO翻转在OLED侧可能采样失败,太慢则显示刷新会有可感知的慢。

4.3 检测距离变短:从灵敏度旋钮到环境温差

我把板子放在桌面上测试,传感器正前方一米处挥手,输出正常;再走远一点,信号就没了。第一反应是灵敏度电位器没调,用小螺丝刀顺时针拧了两圈,距离确实变远了,但还是达不到标称的7米。

后来排查发现两个问题:一是HC-SR501的感应距离本来就是以“人在探测区域内移动”为前提,标称7米是在开阔环境、人横向走动的条件下测出来的,实际在室内、正对方向缓慢移动,距离打折到3~5米很正常;二是环境温度高的时候,人体辐射和环境的温差变小,传感器灵敏度会显著下降,夏天中午测和晚上测距离差很多。

如果一定要追求更远的感应距离,可以把灵敏度电位器顺时针拧到底,然后把传感器固定在支架上、避免透镜前方有遮挡物。另外探头不要对着强红外源方向,比如暖气片、热水管道,这些会让传感器背景红外强度升高,相当于“压低了”人体辐射的信号。

4.4 反复误触发:环境干扰与电源纹波

另一种更烦人的问题是没人经过,OLED却时不时报“有人”。这种反复误触发我遇到过两种典型原因。

第一种是安装位置问题。传感器正对空调出风口或者门外走廊,空调吹出的冷风会导致探测区域边缘出现快速温度变化,正好被热释电传感器当成“有人移动”;门口的余光也会让传感器“看到”路过的人。解决方法是调整安装位置或角度,让传感器不要直接面对温度剧烈变化的气流。

第二种是电源纹波。我的最小系统板早期直接用USB口供电,又把一个ESP8266模块接到了同一路5V上,ESP8266发射瞬间电流波动很大,导致HC-SR501的电源出现几毫伏级的跌落,输出就跟着抖动。排查方法是把传感器单独接到稳压后的5V排针,或者用电容在模块电源两端滤波。我最后在HC-SR501的VCC和GND之间加了一个100μF的电解电容和一个0.1μF的陶瓷电容,误触发频率基本归零。

4.5 有人静静坐着就不亮:热释电器件的物理边界

测试到一半发现,人坐在传感器面前一动不动时,OLED从“有人”变回“无人”。第一次遇到还以为传感器坏了,其实这是热释电原理的必然结果:传感器只对红外辐射变化敏感,人是恒温动物,保持静止时身体表面温度分布几乎不变,传感器输出的差分信号会衰减到零,模块自然认为人离开了。

如果项目需求只是“有没有人经过”,HC-SR501完全够用;如果要做“有没有人存在”,就得换方案,比如毫米波雷达模块可以检测微动甚至呼吸带来的胸腔起伏,能力完全不一样。选型阶段想清楚这一点,能避免后续返工。

4.6 延时与封锁时间配合不良:输出节奏不对

最后一个坑是模块本身的时序参数,主要涉及延时电位器(RT)和封锁时间。当我把RT调到几十秒长延时时,发现人走过去之后,输出高电平维持几十秒,期间再有人经过,模块可能不响应。

原因在于HC-SR501在每次输出结束后会有一个封锁时间,大约2.5秒,在这段时间里模块会忽略新的触发信号,目的是避免上电瞬间的干扰和输出抖动引起重复触发。正常使用这个时间很短,感知不明显;但如果延时设得很长,人一直在触发区域内,会看起来像“模块不理人”。

处理思路是:根据业务需要选择触发模式。如果希望检测区域内有人就持续输出,把跳帽拨到H模式,只要人在活动,模块自动重新计时,输出保持高电平;如果只是做“人经过一次报一次”的门禁触发,把延时调到最短,后续计数由STM32程序自己判断。模块的延时只能调节输出持续时间,不代表程序里不用再做防抖,两者配合才能得到稳定的检测结果。

整个项目调试下来,我最大的体会是:HC-SR501的可调元件看起来简单,但它的每一个配置都直接影响程序逻辑。不要把模块当成一个“自动给出干净高低电平”的黑盒,它输出的原始信号里带着热释电器件的物理特性——需要预热、需要运动、受环境温度影响。如果先花十分钟把传感器原理和旋钮含义搞明白,后面接线、写代码、调错都会顺很多。这次我把调试过程完整记录下来,希望对正在折腾STM32F103C8T6、HC-SR501和OLED的你有帮助,至少少走一次我之前走过的弯路。

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

基于Java springboot车队管理系统(源码+lw+部署文档+讲解等)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/9 10:59:08

服务器内存报错排查:从uncorrectable ECC到故障点定位

1. 从一次“uncorr. ecc 显示2”的告警说起 1.1 那个让我半夜爬起来查日志的数字 我有过一段做机房运维的经历&#xff0c;最怕的不是磁盘IO跑满&#xff0c;也不是CPU负载飙高&#xff0c;而是深夜监控平台突然弹出一条 Uncorrected Memory Error(s): 2 的告警。如果只是 …

作者头像 李华
网站建设 2026/9/9 10:58:26

技能资产化管理:从盘点、组合到复利增值的完整方法论

"skills"这个词&#xff0c;翻译过来是"技能"&#xff0c;但在招聘软件里&#xff0c;它往往就是简历上那一栏关键词的堆砌。我见过太多人填&#xff1a;Excel、PS、Python、英语四级、驾驶证……看起来满满当当&#xff0c;真到面试官追问"你做过什么…

作者头像 李华
网站建设 2026/9/9 10:56:39

【计算机毕业设计单片机案例】基于 STM32 的多车位红外检测智能停车引导系统设计 基于 STM32 的小型模拟停车场刷卡闸控系统设计(016507)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/9 10:56:19

基于Simulink的光储系统自适应MPPT仿真方案详解

1. 项目概览与设计背景1.1 这个课题到底在做什么光储系统&#xff0c;说白了就是把光伏发电和储能电池放在同一个直流母线上协同工作。光伏板输出随光照和温度剧烈波动&#xff0c;储能负责把多余的能量存起来、在光照不足时补上&#xff0c;二者配合才能让整个系统输出稳定。而…

作者头像 李华
网站建设 2026/9/9 10:55:52

【单片机课设毕设项目】基于 STM32 的温度补偿超声波探测声光语音报警系统设计 基于 STM32 的 WiFi 数据回传超声波测距视频监控装置开发(014207)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华