第一次接触SR501人体红外模块时,我把它误当成了普通光电传感器。直到深夜调试时发现它能隔着窗帘检测到我的存在,才真正理解它的独特价值。这个火柴盒大小的模块内部其实藏着精妙的设计——它通过菲涅尔透镜聚焦人体发出的10μm左右红外线,配合BISS0001信号处理芯片实现生物移动识别。
模块背面三个镀金引脚清晰标注着VCC(3.3-5V供电)、OUT(数字信号输出)和GND。实测中发现个有趣现象:用杜邦线直接连接树莓派时偶尔会出现误触发,后来改用带屏蔽的导线问题立即消失。这提醒我们,虽然模块本身抗干扰能力不错,但在工业环境中建议使用双绞线连接。
两个蓝色电位器分别控制着模块的延时调节和灵敏度。顺时针旋转左边电位器,模块触发后的保持时间可从3秒延长到300秒;右边电位器则影响探测距离(实测可调范围约3-7米)。有个实用技巧:调试时先用螺丝刀将灵敏度调到最小,然后慢慢增大直到能稳定检测目标,这样可以有效避免环境干扰。
硬件连接示意图:
code复制树莓派GPIO14 —— SR501 OUT
树莓派3.3V —— SR501 VCC
树莓派GND —— SR501 GND
特别注意:不同开发板的GPIO电压可能不同。我曾不小心将5V的RK3568开发板直接连接模块,导致输出信号异常。后来用万用表测量才发现问题——虽然模块标称支持5V,但实际3.3V供电时稳定性更好。
现代Linux嵌入式开发离不开设备树这个"硬件描述语言"。为SR501添加节点时,需要先确认GPIO控制器的寄存器布局。以瑞芯微RK3568为例,通过查阅芯片手册发现其GPIO控制器分为4组(GPIO0-GPIO3),每组32个引脚。
在设备树源文件(.dts)中添加如下节点:
c复制sr501: sr501@0 {
compatible = "my,sr501";
gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio4>;
interrupts = <19 IRQ_TYPE_EDGE_BOTH>;
};
这段配置有几个关键点:
compatible属性必须与驱动中的定义严格匹配gpios参数的第二部分"19"表示GPIO4组的第19号引脚IRQ_TYPE_EDGE_BOTH表示同时捕获上升沿和下降沿中断编译设备树时容易遇到的一个坑是引脚冲突问题。有次我的系统突然无法启动,排查半天发现是SD卡和SR501复用了同一个GPIO。后来学会先用gpioinfo命令查看引脚分配情况再配置。
验证设备树是否生效的小技巧:
bash复制# 查看解析后的设备树
cat /proc/device-tree/sr501/gpios
# 检查GPIO是否注册成功
ls /sys/class/gpio/gpio138 # 计算公式:4组*32+19=147(注意不同平台编号方式可能不同)
写Linux驱动最让人头疼的就是中断处理。SR501的驱动核心在于如何高效处理GPIO中断,这里我分享几个实战中总结的要点:
首先是等待队列的使用。在read函数中,我们通过wait_event_interruptible让进程休眠,直到中断到来:
c复制static DECLARE_WAIT_QUEUE_HEAD(sr501_wq);
static atomic_t sr501_data = ATOMIC_INIT(0);
static ssize_t sr501_read(struct file *file, char __user *buf,
size_t size, loff_t *offset)
{
wait_event_interruptible(sr501_wq, atomic_read(&sr501_data));
if (copy_to_user(buf, &sr501_data, sizeof(int)))
return -EFAULT;
atomic_set(&sr501_data, 0);
return sizeof(int);
}
中断服务程序(ISR)要尽可能简短。我的经验法则是:ISR执行时间不应超过100μs。对于SR501这种可能频繁触发的传感器,更要注意:
c复制static irqreturn_t sr501_isr(int irq, void *dev_id)
{
atomic_set(&sr501_data, 1);
wake_up_interruptible(&sr501_wq);
return IRQ_HANDLED;
}
在probe函数中注册中断时有个重要细节:IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING表示同时捕获上升沿和下降沿。这能确保不会漏掉任何人体移动事件:
c复制ret = request_irq(irq, sr501_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"sr501", NULL);
调试中断驱动时,这几个命令特别有用:
bash复制# 查看中断触发统计
cat /proc/interrupts | grep sr501
# 手动触发GPIO(调试用)
echo 1 > /sys/class/gpio/gpio138/value
写完驱动后,一个健壮的测试程序能帮我们发现很多潜在问题。下面这个增强版测试程序增加了去抖动处理和信号统计功能:
c复制#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define SAMPLE_INTERVAL 100000 // 100ms
int main(int argc, char **argv)
{
int fd, ret, val;
struct timeval last_trig = {0};
if(argc != 2) {
printf("Usage: %s /dev/sr501\n", argv[0]);
return -1;
}
if((fd = open(argv[1], O_RDONLY)) < 0) {
perror("open device failed");
return -1;
}
while(1) {
ret = read(fd, &val, sizeof(val));
if(ret == sizeof(val) && val == 1) {
struct timeval now;
gettimeofday(&now, NULL);
// 简单去抖动:500ms内不重复触发
if(last_trig.tv_sec == 0 ||
(now.tv_sec - last_trig.tv_sec) * 1000 +
(now.tv_usec - last_trig.tv_usec) / 1000 > 500) {
printf("[%.2ld:%.2ld] Motion detected!\n",
now.tv_sec % 3600 / 60, now.tv_sec % 60);
last_trig = now;
}
}
usleep(SAMPLE_INTERVAL);
}
close(fd);
return 0;
}
这个程序有三个改进点:
实际部署时,建议配合syslog记录事件:
c复制#include <syslog.h>
// ...
syslog(LOG_NOTICE, "Human detected at GPIO%d", gpio_num);
将SR501集成到实际项目中时,我发现几个影响稳定性的关键因素。首先是电源噪声问题——当系统中有电机等大功率设备时,模块容易误触发。解决方法是在VCC和GND之间加装100μF电解电容和0.1μF陶瓷电容。
其次是环境适应性调整。在不同季节使用时,发现温度变化会影响检测灵敏度。后来我在驱动中增加了自动校准功能:
c复制// 在probe函数中添加
gpiod_set_debounce(sr501_gpio, 20000); // 20ms消抖
对于需要多个SR501组网的场景,可以通过以下方式优化中断处理:
c复制// 在设备树中为每个传感器分配独立中断
interrupts-extended = <&gpio4 19 IRQ_TYPE_EDGE_BOTH>,
<&gpio4 20 IRQ_TYPE_EDGE_BOTH>;
最后分享一个监控脚本,用于长期运行时的状态检查:
bash复制#!/bin/bash
while true; do
cnt=$(dmesg | grep "sr501" | wc -l)
echo "$(date) - Interrupt count: $cnt"
gpio=$(cat /sys/kernel/debug/gpio | grep "gpio-138")
echo "GPIO state: $gpio"
sleep 60
done
记得给驱动添加proc文件接口方便调试:
c复制static int sr501_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Interrupt count: %d\n", irq_count);
return 0;
}