PIR 人体红外控制板载 LED --- 保姆级笔记
配套硬件 :DshanMCU-F407(STM32F407ZGT6)+ 人体红外感应模块(PIR)
前置知识 :已完成 LED 闪烁实验(见 HAL快速入门笔记 第二章)
学习理念:每个知识点都亲手敲一遍,不只看不练

文章目录
- [PIR 人体红外控制板载 LED --- 保姆级笔记](#PIR 人体红外控制板载 LED — 保姆级笔记)
-
- [1. 实验概述](#1. 实验概述)
-
- [1.1 我们在做什么?](#1.1 我们在做什么?)
- [1.2 涉及的知识点](#1.2 涉及的知识点)
- [1.3 和 LCD 实验的关系](#1.3 和 LCD 实验的关系)
- [2. 认识 PIR 人体红外传感器](#2. 认识 PIR 人体红外传感器)
-
- [2.1 什么是 PIR?](#2.1 什么是 PIR?)
- [2.2 PIR 模块引脚](#2.2 PIR 模块引脚)
- [2.3 PIR 和按键的本质区别](#2.3 PIR 和按键的本质区别)
- [3. 新知识点:GPIO 输入](#3. 新知识点:GPIO 输入)
-
- [3.1 输出和输入的区别](#3.1 输出和输入的区别)
- [3.2 HAL_GPIO_ReadPin --- 读电平](#3.2 HAL_GPIO_ReadPin — 读电平)
- [4. 新知识点:上拉与下拉电阻](#4. 新知识点:上拉与下拉电阻)
-
- [4.1 为什么要上拉/下拉?](#4.1 为什么要上拉/下拉?)
- [4.2 上拉电阻(Pull-up)](#4.2 上拉电阻(Pull-up))
- [4.3 下拉电阻(Pull-down)](#4.3 下拉电阻(Pull-down))
- [4.4 我们的 PIR 用哪种?](#4.4 我们的 PIR 用哪种?)
- [5. 硬件接线](#5. 硬件接线)
-
- [5.1 接线表](#5.1 接线表)
- [5.2 板载 LED](#5.2 板载 LED)
- [5.3 接线实物对应](#5.3 接线实物对应)
- [6. CubeMX 配置](#6. CubeMX 配置)
-
- [6.1 新建工程](#6.1 新建工程)
- [6.2 引脚配置](#6.2 引脚配置)
- [6.3 PE0 配置为下拉](#6.3 PE0 配置为下拉)
- [6.4 时钟配置(时钟树)](#6.4 时钟配置(时钟树))
- [6.5 生成工程](#6.5 生成工程)
- [7. 代码逐行解读](#7. 代码逐行解读)
-
- [7.1 完整代码](#7.1 完整代码)
- [7.2 HAL_GPIO_ReadPin 详解](#7.2 HAL_GPIO_ReadPin 详解)
- [7.3 if-else 控制逻辑](#7.3 if-else 控制逻辑)
- [7.4 LED 亮灭控制(复习)](#7.4 LED 亮灭控制(复习))
- [7.5 HAL_Delay 的作用](#7.5 HAL_Delay 的作用)
- [8. 实验现象](#8. 实验现象)
- [9. 扩展思考](#9. 扩展思考)
-
- [9.1 如果接反了上下拉会怎样?](#9.1 如果接反了上下拉会怎样?)
- [9.2 如何让两个 LED 同时受控?](#9.2 如何让两个 LED 同时受控?)
- [9.3 如果换成按键,代码几乎一样](#9.3 如果换成按键,代码几乎一样)
- [9.4 下一步](#9.4 下一步)
1. 实验概述
1.1 我们在做什么?
用 PIR 人体红外传感器检测是否有人,有人时点亮板载 LED1,无人时熄灭。
1.2 涉及的知识点
| 知识点 | 类型 | 说明 |
|---|---|---|
| GPIO 输出 | 复习 | 控制板载 LED(PF9 低电平亮) |
| GPIO 输入 | 新学 | 读取 PIR 模块的电平信号 |
| 上拉/下拉电阻 | 新学 | 让输入引脚有确定的默认电平 |
| HAL_GPIO_ReadPin | 新函数 | 读取 GPIO 引脚电平 |
1.3 和 LCD 实验的关系
LCD 实验你学会了 SPI 发送 (往屏幕写数据)。
这个实验让你学会 GPIO 读取(从传感器读信号)。
一个是"写",一个是"读"。以后绝大多数传感器都是这两种操作的组合。
2. 认识 PIR 人体红外传感器
2.1 什么是 PIR?
PIR = Passive InfraRed(被动式红外),检测人体发出的红外线。
只要有人进入感应范围,OUT 脚输出高电平(3.3V) ;人离开后,OUT 脚恢复低电平(0V)。
人进入范围 ───────┬────── 人离开范围
│
OUT ──────┐ │ ┌───────── 低电平(无人)
│ │ │
└────┴────┘
高电平(有人)
2.2 PIR 模块引脚
你的 PIR 模块引出 3 根线:
| 线色 | 信号 | 接 F407 |
|---|---|---|
| 🔴 红 | VCC | 3.3V |
| ⚫ 黑 | GND | GND |
| 🟢 绿 | OUT | PE0(GPIO 输入) |
⭐ 记住这个配色:红线=电源、黑线=地、绿线=信号。这是传感器模块最常见的线色。
2.3 PIR 和按键的本质区别
按键: 你用手按 → 接通 → 高电平
PIR 传感器: 有人进入 → 内部感应 → 高电平
对 STM32 来说,两者都是"GPIO 读到高电平",
区别只在于:按键需要人主动去按,PIR 被动检测。
所以你用 PIR 代替按键,代码几乎一模一样------这就是"传感器替换"的思想。
3. 新知识点:GPIO 输入
⭐⭐⭐ MUST MASTER
3.1 输出和输入的区别
之前学 LED 时用的是 GPIO 输出------F407 主动控制引脚高低电平去驱动 LED。
GPIO 输入反过来------F407 读取外部设备送到引脚上的电平(高 or 低),做出判断。
输出:F407 ──→ 外设(F407 发出信号)
输入:F407 ←── 外设(F407 接收信号)
3.2 HAL_GPIO_ReadPin --- 读电平
HAL 库读取 GPIO 输入电平的函数:
c
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
参数:
| 参数 | 含义 | 例子 |
|---|---|---|
| GPIOx | 哪一组 GPIO | GPIOE(E 组) |
| GPIO_Pin | 哪一个引脚 | GPIO_PIN_0(第 0 号引脚) |
返回值:
| 返回值 | 含义 |
|---|---|
| GPIO_PIN_SET | 引脚当前为高电平(= 1) |
| GPIO_PIN_RESET | 引脚当前为低电平(= 0) |
用法示例:
c
// 读取 PE0 引脚电平
GPIO_PinState state = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_0);
if (state == GPIO_PIN_SET) {
// PE0 是高电平
} else {
// PE0 是低电平
}
⭐ 对比记忆:
操作 函数 方向 写高电平 HAL_GPIO_WritePin(GPIOx, Pin, GPIO_PIN_SET)输出 写低电平 HAL_GPIO_WritePin(GPIOx, Pin, GPIO_PIN_RESET)输出 读取电平 HAL_GPIO_ReadPin(GPIOx, Pin)输入
4. 新知识点:上拉与下拉电阻
⭐⭐⭐ MUST MASTER
4.1 为什么要上拉/下拉?
GPIO 配置为输入时,引脚处于"高阻态"------它只是在"听"外部信号,自己不主动输出电平。
问题来了:如果外部什么信号都没有(模块没接、导线断开),引脚读到的是什么?
答案是不确定 ------可能是 0,可能是 1,随机的,这就是"浮空"状态。
浮空引脚的问题:
┌────────────┐
│ 引脚浮空 │──→ 读到的值随机跳变:0 1 0 1 1 0 ...
│ (高阻态) │
└────────────┘
STM32 内部 → 不确定
你的程序用这个随机值做判断 → 就会出现"没人但 LED 偶尔自己亮"的诡异现象。
4.2 上拉电阻(Pull-up)
在引脚和 VCC(3.3V)之间接一个电阻,让引脚默认拉到高电平。
3.3V
│
┌┴┐
│ │ 上拉电阻(~40KΩ 内部)
└┬┘
│
└── GPIO 引脚 → PIR 没信号时默认读到 1
适用场景:外设默认高电平有效,或者外设用"拉低"来表示触发。
4.3 下拉电阻(Pull-down)
在引脚和 GND 之间接一个电阻,让引脚默认拉到低电平。
GPIO 引脚 → PIR 没信号时默认读到 0
│
┌┴┐
│ │ 下拉电阻(~40KΩ 内部)
└┬┘
│
GND
适用场景:外设默认低电平,或者外设用"拉高"来表示触发。
4.4 我们的 PIR 用哪种?
PIR 模块:无人=低电平,有人=高电平。
所以应该用 下拉(Pull-down)------没人时引脚稳定读到 0,有人时读到 1。
没人时:PIR 输出 0V + 下拉电阻 → 引脚保持 0 ✓
有人时:PIR 输出 3.3V > 下拉 → 引脚读到 1 ✓
在 CubeMX 里配成 Pull-down,就是让 STM32 内部自动接上下拉电阻,不需要外接。
⭐ 经验法则:
- 外设空闲时低电平、触发时高电平 → 用 Pull-down
- 外设空闲时高电平、触发时低电平 → 用 Pull-up
- 不确定的时候优先用 Pull-down,大多数传感器是"高电平触发"
5. 硬件接线
5.1 接线表
| PIR 模块 | F407 引脚 | 说明 |
|---|---|---|
| VCC(红) | 3.3V | 供电 |
| GND(黑) | GND | 共地 |
| OUT(绿) | PE0 | 信号输入 |
5.2 板载 LED
| LED | F407 引脚 | 控制逻辑 |
|---|---|---|
| LED1 | PF9 | 输出低电平亮,高电平灭 |
无需外接,LED1 在开发板上已经和 PF9 连好了。
5.3 接线实物对应
┌──────────────────────────────────────┐
│ PIR 模块 │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │ VCC│ │ GND│ │ OUT│ │
│ └──┬─┘ └──┬─┘ └──┬─┘ │
│ │ │ │ │
└─────┼───────┼───────┼─────────────────┘
│ │ │
│ 红线 │ 黑线 │ 绿线
│ │ │
▼ ▼ ▼
F407 F407 F407
3.3V GND PE0
6. CubeMX 配置
6.1 新建工程
- 打开 STM32CubeMX → New Project
- 搜索
STM32F407ZGTx→ 双击选中
6.2 引脚配置
找到以下引脚,左键点击设置功能:
| 引脚 | 设置 | 说明 |
|---|---|---|
| PF9 | GPIO_Output | 控制板载 LED1 |
| PE0 | GPIO_Input | 读取 PIR 信号 |
6.3 PE0 配置为下拉
- 点菜单栏 Pinout & Configuration
- 左边选 GPIO
- 在引脚列表中找到 PE0
- 将 GPIO Pull-up/Pull-down 改为 Pull-down
6.4 时钟配置(时钟树)
- 进入 Clock Configuration 标签页
- 在 HCLK 输入框输入 168,回车
- 确认时钟树无红色警告
6.5 生成工程
进入 Project Manager:
| 配置项 | 设置 |
|---|---|
| Project Name | pir_led(或其他名字) |
| Toolchain/IDE | MDK-ARM |
| Minimum Firmware Version | 选高版本 |
点击 GENERATE CODE → 弹窗选 Yes。
7. 代码逐行解读
7.1 完整代码
c
int main(void)
{
/* HAL 库初始化 */
HAL_Init();
/* 系统时钟配置 */
SystemClock_Config();
/* GPIO 初始化 */
MX_GPIO_Init();
/* USER CODE BEGIN WHILE */
while (1)
{
// 读取 PE0(PIR 输出脚)的电平
if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_0) == GPIO_PIN_SET)
{
// PIR 检测到人 → LED1 亮
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
}
else
{
// 无人 → LED1 灭
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
}
HAL_Delay(100); // 每 100ms 检测一次
}
}
7.2 HAL_GPIO_ReadPin 详解
c
if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_0) == GPIO_PIN_SET)
这行干了什么:
① STM32 内部读取 PE0 引脚的输入数据寄存器(IDR)
② 判断该位的值是 1(高电平)还是 0(低电平)
③ 如果是 1 → 返回 GPIO_PIN_SET(=1)
④ 如果是 0 → 返回 GPIO_PIN_RESET(=0)
对应硬件原理:
PIR_OUT=3.3V → PE0 读到 1 → GPIO_PIN_SET → 有人
PIR_OUT=0V → PE0 读到 0 → GPIO_PIN_RESET → 无人
7.3 if-else 控制逻辑
c
if (条件为真) {
// 执行这里
} else {
// 否则执行这里
}
这是 C 语言最基础的分支结构。配合 GPIO 读引脚,就是"传感器判断 + 动作执行"的基本模式。
几乎所有传感器驱动都可以归结为:
if (传感器触发条件满足) {
执行响应动作
} else {
不动作 / 恢复默认
}
7.4 LED 亮灭控制(复习)
c
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET); // PF9 低 → LED1 亮
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET); // PF9 高 → LED1 灭
DshanMCU-F407 的板载 LED 是低电平点亮(LED 正极接 3.3V,负极通过 PF9 接地):
LED1
↑(电流)
3.3V ───────┬─<LED>───┬─── PF9
│ │
R(限流) └── PF9=0 → 电流流过 → LED 亮
PF9=1 → 无压差 → LED 灭
7.5 HAL_Delay 的作用
c
HAL_Delay(100); // 延时 100 毫秒
不加延时会怎样?
while (1) 每纳秒执行一次
→ 每次都在读 PE0
→ 读到啥就立刻写 PF9
→ CPU 跑满,白白发热
加了延时 100ms 后:
→ 每 100ms 才检查一次 PIR
→ CPU 利用率大幅降低
→ 对人类的反应来说,100ms 和"即时"没有区别
延时 vs 实时性:
- 延时太长(如 1000ms)→ 手晃过去了但 LED 还没亮,体验差
- 延时太短(如 1ms)→ CPU 占用率高,但没必要
- 100ms 是个经验值,对"人检测"场景足够
后续会学到,延时可以用定时器中断替代,让 CPU 在等待时做别的事------那是 06-5 的内容。
8. 实验现象


正常现象:
手在 PIR 前晃动 → LED1 立刻亮
手离开 → LED1 亮几秒后熄灭
(PIR 模块内部有保持时间)
可能遇到的问题
| 现象 | 原因 | 解决方法 |
|---|---|---|
| LED 一直不亮 | 接线不对 | 检查 VCC/GND/OUT 接线 |
| LED 一直不亮 | PE0 没配 Pull-down | 检查 CubeMX 配置 |
| LED 一直亮从不灭 | PIR 模块检测范围有人 | 人离开范围测试 |
| LED 闪烁不停 | PIR 受到干扰 | 远离空调/暖气风口 |
编译报错 undefined identifier |
代码写在 USER CODE 外面 | 确保代码在 BEGIN 3 / END 3 之间 |
9. 扩展思考
9.1 如果接反了上下拉会怎样?
如果 PE0 配了 Pull-up 而不是 Pull-down:
没人时:PIR_OUT=0V,但内部上拉电阻把 PE0 拉到 3.3V → 读到 1 → 认为"有人"
有人时:PIR_OUT=3.3V,上拉也是 3.3V → 还是读到 1
→ 结果:LED 一直亮,永远检测不到"无人"
9.2 如何让两个 LED 同时受控?
PF9 和 PF10 各有一个板载 LED:
c
// 有人 → 两个都亮
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9 | GPIO_PIN_10, GPIO_PIN_RESET);
// 无人 → 两个都灭
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9 | GPIO_PIN_10, GPIO_PIN_SET);
9.3 如果换成按键,代码几乎一样
c
// 按键(假设接在 PE0,按键按下为高电平)
if (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_0) == GPIO_PIN_SET) {
// 按键按下 → 亮灯
} else {
// 按键松开 → 灭灯
}
PIR 和按键,对 STM32 来说都是 GPIO 输入,区别只在于"触发源"不同。这就是硬件抽象的思想------不管你用按键、PIR、还是其他传感器,只要输出数字信号,代码框架都是一样的。
9.4 下一步
现在你已经学会了:
- ✅ GPIO 输出(控制 LED)
- ✅ GPIO 输入(读 PIR 信号)
- ✅ 上拉/下拉电阻的概念
下一节 05-12 光敏传感器控制蜂鸣器 ,你会学到 GPIO 模拟输入(ADC)------不仅能读"有没有"(0 或 1),还能读"有多少"(0~4095 的数值)。
编写日期 :2026年5月25日
适用硬件 :DshanMCU-F407(STM32F407ZGT6)+ PIR 人体红外模块
配套视频:05-11 按键控制LED(用 PIR 替代按键实现)