拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32与PIR传感器、OLED组合:一套入门到实战的人体检测显示项目

做嵌入式这些年我给新手推荐过不少练手项目如果只让我留一个既简单又有用的组合那肯定是STM32F103C8T6最小系统板加HC-SR501人体红外传感器再配一块0.96寸OLED显示屏。这套组合成本低、接线少、逻辑清晰从GPIO读取到I2C显示把单片机最常用的几个功能全串起来了而且做出来的东西确确实实能用在实际场景里——人来灯亮、人员检测、安防提醒都能往上靠。这篇文章我从HC-SR501的检测原理开始讲把接线电平、CubeMX配置、HAL库代码逻辑、OLED显示驱动以及我这些年踩过的各种坑全部拆开揉碎。适合刚学会点灯、想往实际应用走一步的初学者也适合给课程设计或者小项目找方案的朋友参考。看完之后你可以直接照着搭一套传感器触发结果实时显示在OLED上整个过程半天左右就能跑通。1. 项目整体设计与思路拆解1.1 为什么是这三件套STM32F103C8T6这颗芯片在嵌入式圈子里有多普及不用我多说。最小系统板几十块钱主频72MHz20KB RAM、64KB Flash跑一个传感器采集加OLED刷屏绰绰有余。而且它的资料密度极高不管是用标准库还是HAL库遇到问题随便一搜就有答案对新手极其友好。HC-SR501则是目前市面上最经典的热释电红外PIR传感器模块十几块钱一块集成了菲涅尔透镜、热释电探头、信号放大电路和比较器输出直接输出TTL电平的高/低信号。它对人体移动敏感不需要像超声波那样主动发射信号也不像摄像头那样有隐私问题做“有人/没人”检测是最省事的选择。OLED选0.96寸I2C接口版本主要是因为接线少。整块屏幕只用SDA和SCL两根信号线加上VCC和GND一共四根线驱动芯片是SSD1306内部自带显存单片机只管往显存里写数据就行。相比1602液晶屏需要8根数据线SSD1306的接线和驱动都要友好太多而且还能显示中文、图形、小动画。这三样东西组合在一起刚好覆盖了嵌入式开发的几个核心技能点GPIO输入检测、外部信号判读、I2C通信协议、显示驱动、状态机逻辑。做完这个小项目再往其他方向扩展就有底子了。1.2 HC-SR501的检测原理到底是怎么回事很多人拿到HC-SR501就直接接线用从没想过它内部是怎么工作的。结果遇到“静止的人检测不到”“对着窗户乱触发”这类问题就懵了。所以我先把原理讲透。人体体温大约37摄氏度会持续向外辐射波长9到10微米左右的红外线。HC-SR501的核心是一个热释电红外探头内部有两片有些型号是四片热释电元件它们串联反向连接。当人体静止不动时两片元件接收到的红外辐射基本相同差分信号为零当人体在探测范围内移动时两片元件感受到的红外辐射出现差异就会产生一个微弱的交流电信号这个信号经过模块内部场效应管和高增益比较器处理后输出端就会输出高电平。这就是PIR传感器的根本特点只对移动的人体敏感对静止的人不敏感。你坐在椅子上不动传感器不一定能一直保持高电平输出但只要稍微动一下立刻就能检测到。明白了这一点就不会再拿“为什么人坐着不动灯却灭了”这种问题来困惑自己了。模块前面那个白色半透明的“小圆罩”叫菲涅尔透镜作用是把探测区域分割成若干个明暗相间的条纹。人体在透镜前移动时红外辐射会周期性变化相当于给传感器输入了一个交变信号这对提高检测可靠性至关重要。如果拆掉这个透镜探测距离会急剧缩短到一米以内而且方向性极差。所以使用时要保持透镜干净、不要遮挡新模块透镜上的保护膜一定要撕掉否则灵敏度会大打折扣。1.3 HAL库还是标准库我为什么推荐HAL库路线STM32F103C8T6的开发方式目前有三条路寄存器、标准外设库、HAL库。寄存器最底层、代码最啰嗦但你能看到每一个位操作标准库曾经是主流但ST已经停止更新HAL库配合STM32CubeMX图形化配置工具是目前官方主推的方案。我给新手的建议很明确直接用HAL库加CubeMX。理由有两个。第一CubeMX把时钟树、引脚复用、外设初始化这些繁琐的配置全部图形化鼠标点几下就能生成初始化代码大大降低入门门槛。第二HAL库的函数封装层次高比如I2C通信你只需要调用HAL_I2C_Master_Transmit内部时序细节全由库处理出了问题也容易排查。这篇文章里的所有代码都是基于HAL库加CubeMX生成的工程标准库的读者也不要紧逻辑是相通的核心就是GPIO读电平加I2C写数据。2. 硬件接线与关键调节别让电平问题毁掉整个项目2.1 完整接线表和引脚分配STM32F103C8T6最小系统板的引脚排列比较规整周边一圈排针3.3V、5V、GND都有引出。HC-SR501模块有三个引脚VCC、GND、OUTOLED四根线VCC、GND、SCL、SDA。下面是我用的接线表直接照着接就行。HC-SR501引脚接STM32F103C8T6说明VCC5V或3.3V见2.2节分析传感器供电GNDGND共地OUTPA0GPIO输入检测输出电平OLED引脚接STM32F103C8T6说明VCC3.3VSSD1306工作电压GNDGND共地SCLPB6I2C1_SCLSDAPB7I2C1_SDAPA0选它是因为这脚默认支持外部中断后面想改成“有人触发中断”模式可以直接用不用换引脚。PB6和PB7是F103的I2C1默认引脚在CubeMX里直接选择I2C1之后这两个引脚会自动被分配到对应功能不用额外手动映射。2.2 电平匹配这可能是你最该注意的坑很多新手在这里翻车。HC-SR501的典型工作电压是4.5V到20V官方推荐5V供电。如果直接用5V给它供电那么OUT引脚输出高电平的时候电压也接近5V。而STM32F103C8T6的GPIO耐压要看引脚类型标记为FTFive-volt tolerant的引脚可以承受5V但PA0不是FT引脚它是普通的TTL引脚手册上标注最大输入电压就是VDD0.3也就是3.6V左右。长期让5V直接灌进PA0轻则读到的电平不稳定重则烧坏引脚甚至整个芯片。解决这个问题有两条路。第一条是传感器用3.3V供电这样OUT高电平约3.3V直接接PA0安全。但缺点是HC-SR501在3.3V下工作点有点微妙有些模块能正常工作有些模块会出现灵敏度下降、触发不稳定、甚至上电后乱输出高电平的情况个体差异很大。第二条是传感器保持5V供电OUT输出先经过电阻分压再接PA0。用两个电阻比如10K串联加20K并联到地5V经过分压后大约3.33V安全且稳定。我实测下来第二条路最省心推荐直接采用。如果你不想加分压电阻又必须用5V给传感器供电至少也要在OUT和PA0之间串联一个1K的限流电阻虽然不能把高电平降到理想值但能降低灌入引脚的风险。2.3 传感器上的两个旋钮和跳线怎么调HC-SR501模块正面有两个小的可调电位器一个是灵敏度调节一个是延时调节。很多人不知道这俩能调也不清楚调节方向。灵敏度旋钮控制的是检测距离顺时针旋到底检测距离最大标称能到7米左右逆时针旋到底检测距离可能只有1到2米。实际使用中不建议直接旋到底因为灵敏度过高会把空调吹出的热风、窗外移动的汽车红外辐射、甚至夏天隔着玻璃的日光都误判成人导致频繁误触发。我一般会先把灵敏度调到中等偏小的位置然后在现场实测调整。延时旋钮控制的是输出高电平的持续时间顺时针延长最长可达300秒左右逆时针缩短最短约5秒。调的时候注意延时太长会让“有人”状态一直锁住看起来就像传感器坏了延时太短又可能在人还在移动范围内时输出就变低了。调试阶段建议先把延时调到最短这样响应快方便观察。模块背面通常还有一个跳线帽用于选择触发模式。跳到H位置是可重复触发模式只要人在探测区内持续活动输出就一直保持高电平人离开后才开始计时并恢复低电平跳到L位置是不可重复触发模式检测到人后输出一个固定宽度的高电平脉冲时间由延时旋钮决定即使人继续在探测区内也不会重新触发。做“人来灯亮、人走灯灭”就选H模式做“检测到一次人活动就报警一下”就选L模式。3. 代码逻辑与核心实现从CubeMX配置到OLED刷屏3.1 CubeMX工程配置的完整步骤打开STM32CubeMX选择芯片型号STM32F103C8T6然后按下面步骤配置。SYS选项里把Debug设为Serial Wire这个很关键。不设置的话烧录器下载完程序后SWD引脚功能可能被禁用下次就没法下载程序了只能按复位键加下载非常麻烦。RCC选项里把HSE设为Crystal/Ceramic Resonator如果你用的最小系统板上有8MHz晶振的话这样可以让系统时钟跑满72MHz。CubeMX会自动根据HSE值计算PLL倍频系数。I2C1选项里把I2C设为I2C标准模式速度选100KHz即可。SSD1306虽然支持400KHz快速模式但100KHz更稳定尤其飞线比较长或者模块质量一般的时候降低I2C时钟能减少很多诡异的花屏问题。GPIO里把PA0设为GPIO_Input下拉电阻方式。这里设置为下拉是为了保证传感器空闲输出低电平时PA0能读到确定性的低电平。如果PA0悬空I2C和OLED初始化时序可能会引起引脚电平抖动导致上电瞬间误报一次“有人”。时钟树页面基本不用动CubeMX会自动配置好。生成工程时选择Toolchain为MDK-ARM代码生成选项里勾选“生成外设初始化函数”的独立.c/.h文件方便阅读。生成后用Keil打开工程就可以开始写逻辑了。3.2 OLED显示的I2C驱动核心SSD1306驱动芯片的I2C通信其实不复杂。每个传输都先从设备地址开始0.96寸OLED模块的I2C地址大多数是0x787位地址0x3C左移一位如果模块SA0引脚被拉高则是0x7A。HAL库的HAL_I2C_Master_Transmit函数里传的DevAddress参数需要的是8位地址所以填0x78或者0x7A别弄混了。发送地址之后还要跟一个控制字节。控制字节0x00表示后面跟的是命令0x40表示后面跟的是数据。比如写初始化命令、设置显示坐标这些都属于命令往显存里填充显示内容则属于数据。下面是一段精简的SSD1306初始化代码基于HAL库的I2C1#define OLED_ADDR 0x78 void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 100); } void OLED_WriteData(uint8_t data) { uint8_t buf[2] {0x40, data}; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 100); } void OLED_Init(void) { HAL_Delay(100); OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0xD5); OLED_WriteCmd(0x80); // 设置显示时钟 OLED_WriteCmd(0xA8); OLED_WriteCmd(0x3F); // 设置复用率 1/64 OLED_WriteCmd(0xD3); OLED_WriteCmd(0x00); // 显示偏移 0 OLED_WriteCmd(0x40); // 起始行 0 OLED_WriteCmd(0x8D); OLED_WriteCmd(0x14); // 开启电荷泵必须开 OLED_WriteCmd(0x20); OLED_WriteCmd(0x00); // 水平寻址模式 OLED_WriteCmd(0xA1); // 段重映射 OLED_WriteCmd(0xC8); // COM扫描方向 OLED_WriteCmd(0xDA); OLED_WriteCmd(0x12); // COM引脚配置 OLED_WriteCmd(0x81); OLED_WriteCmd(0xCF); // 对比度 OLED_WriteCmd(0xD9); OLED_WriteCmd(0xF1); // 预充电周期 OLED_WriteCmd(0xDB); OLED_WriteCmd(0x40); // VCOMH电平 OLED_WriteCmd(0xA4); // 显示内容跟随RAM OLED_WriteCmd(0xA6); // 正常显示非反显 OLED_WriteCmd(0xAF); // 打开显示 OLED_Clear(); }这段初始化序列是SSD1306的标准流程大部分现成库都是这个套路。重点是那个开启电荷泵的命令0x8D 0x14如果漏掉屏幕会一直黑着什么都不显示这是新手最容易踩的坑。清屏函数要把显存全部写成0。SSD1306的显存组织方式是页寻址模式128列分成8页每页对应8行像素一页128字节。清屏就是循环操作8页每页128列都写0x00void OLED_Clear(void) { for (uint8_t page 0; page 8; page) { OLED_WriteCmd(0xB0 page); // 设置页地址 OLED_WriteCmd(0x00); // 列地址低4位 OLED_WriteCmd(0x10); // 列地址高4位 for (uint8_t col 0; col 128; col) OLED_WriteData(0x00); } }显示字符串需要字库。ASCII字符可以用8x16点阵或者6x12点阵网上大量ST7920或SSD1306字库源码可以直接移植。汉字需要16x16点阵用取模软件如PCtoLCD2002生成取模方式选择“逐行式、阴码”生成后的数组就是按页排列的显示时先写上半部分到当前页再写下半部分到下一页。这个方法我用过很多次稳定可靠。3.3 主程序逻辑PIR状态读取与显示切换主逻辑的本质就是读GPIO然后决定OLED显示什么。但为了让代码更合理我建议不要在循环里每次都刷OLED而是只在传感器输出状态变化时更新一次显示这样既能减少I2C通信负担也能让屏幕显示更干净。主循环的核心代码如下uint8_t last_state GPIO_PIN_RESET; uint8_t cur_state; while (1) { cur_state HAL_GPIO_ReadPin(PIR_GPIO_Port, PIR_Pin); if (cur_state ! last_state) { if (cur_state GPIO_PIN_SET) { OLED_ShowString(0, 2, Human: YES); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); } else { OLED_ShowString(0, 2, Human: NO ); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); } last_state cur_state; } HAL_Delay(50); }OLED_ShowString的第一个参数是起始列第二个参数是起始页。0.96寸OLED共8页每页8像素如果使用8x16字体一个字符占2页高那么第一行从第0页开始第二行从第2页开始所以我这里传的是2。这个参数如果不理解显示位置就会错乱字会出现在屏幕中间甚至乱码位置。加了LED指示就是因为OLED刷屏有I2C通信时间如果传感器状态变化非常快纯粹靠OLED判断会有延迟感。LED点灯是GPIO直驱动毫秒级响应更适合做实时状态观察。我实际调试时也是先看LED再看OLED效率高很多。如果你想用串口辅助调试重定义fputc后加printf即可逻辑更直观printf(PIR ON\r\n);串口输出对排查传感器输出抖动特别有帮助OLED上可能只看到状态闪了一下串口里能看出几次跳变。4. 调试排坑实测这些问题我基本都踩过4.1 上电乱触发和一直触发怎么办HC-SR501上电后会有几十秒的“预热”阶段内部电路正在建立稳定的工作点这时候输出可能会随机跳变几次也可能会输出一个高电平脉冲。这不是坏了是正常现象。做产品的话程序里可以加一个开机延时让系统上电后30秒内忽略传感器信号或者延时初始化后再开始读取。如果预热结束后仍然一直稳定输出高电平大概率是灵敏度过高环境里有干扰源。最常见的干扰源是空调出风口、暖气片、白炽灯/日光灯附近、阳光直射区域、以及窗外移动的车辆。解决方法是逆时针调小灵敏度旋钮同时调整传感器朝向不要正对干扰源。还有一个容易被忽略的情况夏天房间内开了空调室温低于体温人一旦进入探测区红外差分信号特别强误触发反而比冬天更容易。如果传感器输出一直低电平、怎么走动都不触发首先检查有没有撕掉菲涅尔透镜上的保护膜其次检查供电电压。用3.3V供电的时候部分模块会出现灵敏度很低甚至完全不触发的情况这时候换成5V供电加分压电阻就能解决。4.2 OLED白屏、花屏、乱码的排查思路OLED白屏是最常见的故障。先确认模块有电看背面有没有电压标注VCC接3.3V一般没问题。然后确认SCL和SDA有没有接反这两个接反了通信完全失败。接着确认地址对不对0x78和0x7A只差一位错了就白屏。如果地址正确、接线没问题还是白屏八成是初始化时序里少了开启电荷泵的命令。我在3.2节里特别强调过0x8D 0x14这一条必须要有。还有一种情况是CubeMX生成的I2C初始化在OLED_Init之前没有执行或者执行顺序不对导致OLED初始化时I2C根本没有准备好。在main函数里MX_I2C1_Init必须在OLED_Init之前调用。花屏或者乱码则和通信稳定性有关。I2C速度太高、飞线太长、供电纹波偏大会引起画面内容错乱。解决办法是先把I2C时钟降到100KHz再用杜邦线把OLED供电引脚就近对地加一个100nF去耦电容。显示乱码还有可能是字库取模方式不对导致字符上下颠倒、左右反转这个属于取模参数问题重新生成字库即可。老版本HAL库的I2C还有一个著名的卡死问题就是调用HAL_I2C_Master_Transmit后程序卡在I2C等待超时标志里。更新到较新的HAL库版本能解决不想升级库的话在MX_I2C1_Init函数末尾加上两行__HAL_I2C_DISABLE(hi2c1); __HAL_I2C_ENABLE(hi2c1);强制复位一下I2C外设很多卡死问题就消失了。4.3 常见问题速查表现象可能原因解决方法上电LED闪一下但功能正常传感器预热自检正常现象程序里延时30秒再检测即可一直触发没人却显示有人灵敏度过高或环境干扰源逆时针调灵敏度调整朝向避开空调/阳光/暖气人走过不触发灵敏度过低、透镜保护膜未撕、3.3V供电调高灵敏度、撕膜、改5V供电加分压静止的人检测不到PIR原理限制只能检测移动人体这是正常的OLED白屏接线错误、地址错误、电荷泵命令缺失检查SCL/SDA、换0x7A试、检查初始化序列OLED花屏I2C速率过高、供电不稳、字库取模错误降速到100KHz、加去耦电容、重新取模代码卡死在I2C函数旧版HAL库I2C状态机bug更新HAL库或用__HAL_I2C_DISABLE/ENABLE复位检测距离变很短供电电压低、透镜脏污、灵敏度过低换5V供电、清洁透镜、适当调高灵敏度4.4 一个容易忽略的安装细节HC-SR501的安装高度和角度对检测效果影响很大。装在桌面上平放探测范围是一个很扁的扇形人从侧边走过容易漏检。合理做法是安装在离地面2米左右的高度透镜朝下倾斜15到30度这样探测区域能覆盖到人的正常活动范围而且不容易被宠物触发。另外传感器不要正对门口安装人推门进来时红外辐射方向变化太快反而会短暂触发后迅速消失。稍微侧装一点让检测区域交叉覆盖门口效果会好很多。这些小细节在实验室静态测试时看不出来真正装到实际环境里差别非常大。5. 扩展方向与个人经验这套系统跑稳之后扩展空间非常大。加一个继电器模块就能把“有人”状态直接转成灯控信号做成走廊和卫生间的感应灯加一个有源蜂鸣器就成了简易入侵报警器加一个ESP8266或者4G模块就能把“有人/没人”状态上报到物联网平台手机远程查看。传感器换成一氧化碳或者火焰传感器代码逻辑几乎不用改动就变成一个环境监测节点这也是这套架构最大的价值所在。这么多年做下来我给新手朋友的建议就一条先把GPIO读电平加OLED显示这一步彻底跑通再考虑加中断、加通信、加云平台。很多人一上来就想把所有功能全加上结果出了问题根本不知道是传感器的问题、OLED的问题还是网络的问题排查起来特别痛苦。这个组合最大的优点就是每一层链路都简单独立分工明确方便你一步步把整个系统吃透。模块接线的时候最好用不同颜色的杜邦线红色接电源黑色接地黄色或蓝色接信号这样排查起来一眼就能分清。PCB板做原型测试时传感器和单片机之间尽量拉开一点距离别把散热片或者电源模块紧贴着传感器放置热辐射会影响PIR的判断。最后再说一句HC-SR501这东西个体差异确实存在同一批模块灵敏度都可能不一样项目里用到多套的时候每一套都要单独标定一下旋钮位置不要想着用一个参数通吃所有模块。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门