znlgis 博客

GIS开发与技术分享

第9章:运动与碰撞检测

本章介绍人体红外热释传感器(PIR)的工作原理、信号检测方法与智能家居应用。


9.1 人体红外热释传感器

9.1.1 工作原理

PIR(Passive Infrared)传感器检测人体发出的红外辐射:

    菲涅尔透镜
        ↓
    ╔═══════════╗
    ║ / | | | \ ║  ← 分区检测
    ║           ║
    ╚═══════════╝
        ↓
    热释电元件
        ↓
    信号处理电路
        ↓
    数字输出

核心原理

  1. 人体恒温约 37°C,发出红外辐射
  2. 菲涅尔透镜将红外线聚焦到热释电元件
  3. 人体移动时,红外辐射变化
  4. 热释电元件产生微弱电信号
  5. 放大电路处理后输出数字信号

9.1.2 RE200B-P 元件

套件中的 PIR 模块采用 RE200B-P 热释电元件:

  • 双元件差分结构
  • 探测角度:约 100°
  • 探测距离:3-7 米

9.1.3 输出逻辑

状态 信号值 LED
检测到人移动 高电平 (1)
无人移动 低电平 (0)

注意:PIR 检测的是运动,静止的人无法检测到。

9.1.4 延时与灵敏度

模块通常有两个电位器:

  • 灵敏度调节:调节探测距离
  • 延时调节:触发后保持高电平的时间(通常 5 秒 ~ 5 分钟)

9.1.5 基础检测代码

'''
实验:人体红外检测
接线:S 端连接 GP19
'''
from machine import Pin
import time

pir = Pin(19, Pin.IN)

while True:
    if pir.value() == 1:
        print("检测到人体移动!")
    else:
        print("无人移动")
    time.sleep(0.5)

9.2 智能照明系统

9.2.1 基础版

'''
实验:人体感应灯
PIR → GP19, LED → GP0
'''
from machine import Pin
import time

pir = Pin(19, Pin.IN)
led = Pin(0, Pin.OUT)

while True:
    if pir.value() == 1:
        led.value(1)
        print("有人,灯亮")
    else:
        led.value(0)
        print("无人,灯灭")
    time.sleep(0.3)

9.2.2 带延时关灯

'''
实验:延时关灯
人离开后延时 30 秒关灯
'''
from machine import Pin
import time

pir = Pin(19, Pin.IN)
led = Pin(0, Pin.OUT)

DELAY_SECONDS = 30
last_motion_time = 0

while True:
    if pir.value() == 1:
        led.value(1)
        last_motion_time = time.time()
        print("检测到移动,灯亮")
    else:
        if led.value() == 1:
            if time.time() - last_motion_time > DELAY_SECONDS:
                led.value(0)
                print(f"{DELAY_SECONDS}秒无人移动,灯灭")
    
    time.sleep(0.5)

9.2.3 结合光敏传感器

'''
实验:智能感应灯
仅在夜间且有人时开灯
'''
from machine import Pin, ADC
import time

pir = Pin(19, Pin.IN)
led = Pin(0, Pin.OUT)
light_sensor = ADC(26)

LIGHT_THRESHOLD = 30000  # 光线阈值

def is_dark():
    """判断是否为暗环境"""
    return light_sensor.read_u16() < LIGHT_THRESHOLD

while True:
    motion = pir.value() == 1
    dark = is_dark()
    
    if motion and dark:
        led.value(1)
        print("暗环境+有人 → 开灯")
    elif not dark:
        led.value(0)
        print("环境明亮 → 关灯")
    else:
        # 暗环境无人:保持当前状态或延时关灯
        pass
    
    time.sleep(0.5)

9.3 安防报警系统

9.3.1 基础报警

'''
实验:PIR 入侵报警
'''
from machine import Pin
import time

pir = Pin(19, Pin.IN)
buzzer = Pin(20, Pin.OUT)
led = Pin(0, Pin.OUT)

ARMED = True  # 布防状态

def alarm_on():
    """触发报警"""
    for _ in range(5):
        buzzer.value(1)
        led.value(1)
        time.sleep(0.1)
        buzzer.value(0)
        led.value(0)
        time.sleep(0.1)

while True:
    if ARMED and pir.value() == 1:
        print("入侵警报!")
        alarm_on()
    time.sleep(0.3)

9.3.2 多区域监控

'''
实验:多区域 PIR 监控
'''
from machine import Pin
import time

zones = {
    '门厅': Pin(19, Pin.IN),
    '客厅': Pin(18, Pin.IN),
    '卧室': Pin(17, Pin.IN),
}

buzzer = Pin(20, Pin.OUT)

def check_zones():
    """检查所有区域"""
    alerts = []
    for name, sensor in zones.items():
        if sensor.value() == 1:
            alerts.append(name)
    return alerts

while True:
    alerts = check_zones()
    
    if alerts:
        print(f"警报区域: {', '.join(alerts)}")
        buzzer.value(1)
        time.sleep(0.5)
        buzzer.value(0)
    
    time.sleep(0.3)

9.4 人员计数系统

9.4.1 单向计数

'''
实验:进出人数统计
双 PIR 传感器判断方向
'''
from machine import Pin
import time

sensor_a = Pin(18, Pin.IN)  # 外侧
sensor_b = Pin(19, Pin.IN)  # 内侧

count = 0
state = 'idle'

while True:
    a = sensor_a.value()
    b = sensor_b.value()
    
    if state == 'idle':
        if a == 1 and b == 0:
            state = 'entering'
        elif a == 0 and b == 1:
            state = 'exiting'
    
    elif state == 'entering':
        if a == 1 and b == 1:
            state = 'enter_confirmed'
        elif a == 0 and b == 0:
            state = 'idle'
    
    elif state == 'enter_confirmed':
        if a == 0 and b == 1:
            count += 1
            print(f"有人进入,当前人数: {count}")
            state = 'idle'
    
    elif state == 'exiting':
        if a == 1 and b == 1:
            state = 'exit_confirmed'
        elif a == 0 and b == 0:
            state = 'idle'
    
    elif state == 'exit_confirmed':
        if a == 1 and b == 0:
            count = max(0, count - 1)
            print(f"有人离开,当前人数: {count}")
            state = 'idle'
    
    time.sleep(0.05)

9.5 ADXL345 加速度传感器

9.5.1 传感器介绍

ADXL345 是一款三轴数字加速度传感器:

  • 测量范围:±2g / ±4g / ±8g / ±16g
  • 分辨率:13 位
  • 接口:I2C / SPI
  • I2C 地址:0x53(ALT 接 GND)或 0x1D(ALT 接 VCC)

9.5.2 数据编码格式

ADXL345 输出为 16 位有符号整数:

每轴数据 = 2 字节 (低字节在前)
数据格式: 二进制补码

数据转换

# 读取原始数据
raw_x = (data[1] << 8) | data[0]

# 转换为有符号数
if raw_x > 32767:
    raw_x -= 65536

# 转换为 g 值(默认 ±2g 范围,灵敏度 256 LSB/g)
g_x = raw_x / 256

9.5.3 I2C 读取代码

'''
实验:ADXL345 加速度读取
接线:SDA→GP20, SCL→GP21
'''
from machine import I2C, Pin
import time

# I2C 初始化
i2c = I2C(0, sda=Pin(20), scl=Pin(21), freq=400000)

ADXL345_ADDR = 0x53

# 寄存器地址
REG_POWER_CTL = 0x2D
REG_DATA_FORMAT = 0x31
REG_DATAX0 = 0x32

def write_reg(reg, value):
    i2c.writeto_mem(ADXL345_ADDR, reg, bytes([value]))

def read_reg(reg, length=1):
    return i2c.readfrom_mem(ADXL345_ADDR, reg, length)

def init_adxl345():
    """初始化 ADXL345"""
    # 设置测量模式
    write_reg(REG_POWER_CTL, 0x08)
    # 设置数据格式: ±2g, 全分辨率
    write_reg(REG_DATA_FORMAT, 0x08)

def read_xyz():
    """读取三轴加速度"""
    data = read_reg(REG_DATAX0, 6)
    
    # 解析数据(小端序,有符号)
    x = (data[1] << 8) | data[0]
    y = (data[3] << 8) | data[2]
    z = (data[5] << 8) | data[4]
    
    # 转换为有符号数
    if x > 32767: x -= 65536
    if y > 32767: y -= 65536
    if z > 32767: z -= 65536
    
    # 转换为 mg
    return x * 3.9, y * 3.9, z * 3.9

# 初始化
init_adxl345()

while True:
    x, y, z = read_xyz()
    print(f"X: {x:+.0f}mg  Y: {y:+.0f}mg  Z: {z:+.0f}mg")
    time.sleep(0.1)

9.5.4 运动检测

'''
实验:运动/静止检测
'''
from machine import I2C, Pin
import time
import math

# ... (初始化代码同上)

MOTION_THRESHOLD = 100  # mg

def calculate_magnitude(x, y, z):
    """计算加速度幅值"""
    return math.sqrt(x*x + y*y + z*z)

last_magnitude = 1000  # 静止时约 1000mg (1g)

while True:
    x, y, z = read_xyz()
    magnitude = calculate_magnitude(x, y, z)
    
    delta = abs(magnitude - last_magnitude)
    
    if delta > MOTION_THRESHOLD:
        print(f"检测到运动! 变化量: {delta:.0f}mg")
    
    last_magnitude = magnitude
    time.sleep(0.1)

9.5.5 倾斜角度计算

'''
实验:计算倾斜角度
'''
import math

def calculate_angles(x, y, z):
    """计算相对于水平面的倾斜角度"""
    # 转换为 g
    gx = x / 1000
    gy = y / 1000
    gz = z / 1000
    
    # 计算角度(弧度转角度)
    pitch = math.atan2(gx, math.sqrt(gy*gy + gz*gz)) * 180 / math.pi
    roll = math.atan2(gy, math.sqrt(gx*gx + gz*gz)) * 180 / math.pi
    
    return pitch, roll

while True:
    x, y, z = read_xyz()
    pitch, roll = calculate_angles(x, y, z)
    print(f"俯仰: {pitch:+.1f}°  横滚: {roll:+.1f}°")
    time.sleep(0.2)

9.6 跌倒检测

9.6.1 检测原理

跌倒过程中的加速度变化:

  1. 自由落体:总加速度接近 0g
  2. 撞击:加速度峰值 > 3g
  3. 静止:恢复约 1g

9.6.2 检测代码

'''
实验:跌倒检测
'''
import math

FREEFALL_THRESHOLD = 300   # 自由落体阈值 (mg)
IMPACT_THRESHOLD = 3000    # 撞击阈值 (mg)

state = 'normal'
freefall_start = 0

while True:
    x, y, z = read_xyz()
    magnitude = math.sqrt(x*x + y*y + z*z)
    
    if state == 'normal':
        if magnitude < FREEFALL_THRESHOLD:
            state = 'freefall'
            freefall_start = time.ticks_ms()
            print("检测到自由落体!")
    
    elif state == 'freefall':
        if magnitude > IMPACT_THRESHOLD:
            duration = time.ticks_diff(time.ticks_ms(), freefall_start)
            if duration > 50:  # 自由落体持续超过 50ms
                print(f"⚠️ 跌倒警报! 下落时间: {duration}ms")
            state = 'normal'
        elif time.ticks_diff(time.ticks_ms(), freefall_start) > 500:
            # 超时,可能是误判
            state = 'normal'
    
    time.sleep(0.01)

9.7 本章小结

本章介绍了运动与碰撞检测传感器:

  1. PIR 传感器
    • 检测人体红外辐射变化
    • 只能检测移动,无法检测静止目标
    • 适用于智能照明、安防报警
  2. ADXL345 加速度传感器
    • 三轴数字加速度检测
    • I2C 通信,16 位有符号数据
    • 可计算倾斜角度、检测运动/跌倒
  3. 数据编码
    • PIR:简单数字信号(高/低)
    • ADXL345:I2C + 二进制补码

下一章将学习蜂鸣器与声音输出。