在智能硬件项目中,光电传感器常用于判断"物体是否经过某个位置"。例如计数器、限位检测、物料经过检测、小车测速、闸机识别和自动分拣设备,背后的基础逻辑都离不开传感器状态读取。对于 Python 硬件编程而言,这类实验非常适合作为输入检测入门案例,因为程序不只是控制 LED 发光,还会根据外部环境变化做出判断。
本实验使用 CanMV K210 开发板读取 U 型光电传感器的信号状态。当传感器光路无遮挡时,绿色 LED 点亮;当光路被物体遮挡时,红色 LED 点亮,并统计遮挡发生次数。程序还加入了稳定采样和状态变化判断,只在遮挡状态发生变化时打印信息,避免控制台不断刷屏。
| 学习目标 | 说明 |
|---|---|
| 理解光电检测 | 认识 U 型光电传感器如何通过光路遮挡判断物体经过 |
| 掌握 GPIO 输入 | 使用 GPIOHS 输入读取传感器输出电平 |
| 掌握 GPIO 输出 | 使用普通 GPIO 控制红色 LED 和绿色 LED 显示状态 |
| 理解状态变化判断 | 只在遮挡和恢复之间发生变化时更新输出,避免重复打印 |
| 建立检测类项目思路 | 为计数器、限位检测、小车测速和自动分拣实验打基础 |
U 型光电传感器的特点是结构直观,一端发射光,一端接收光。当中间光路被物体挡住,输出电平会发生变化;开发板通过 GPIO 输入读取这个电平,再用代码控制不同颜色的 LED 显示状态。这个过程完整体现了"传感器采集状态,程序判断逻辑,硬件输出反馈"的基本控制链路。
文章目录
理论基础
U 型光电传感器可以理解成一个"光路开关"。无遮挡时,发射端的光能够被接收端检测到,传感器输出一种电平状态;有物体插入 U 型槽中时,光路被挡住,传感器输出另一种电平状态。开发板不需要知道物体的颜色、形状或材质,只需要读取传感器信号端的高低电平,就能判断是否发生遮挡。
本实验中,U 型光电传感器的信号端接入 IO6,红色 LED 接入 IO7,绿色 LED 接入 IO8。代码中约定 BLOCKED_LEVEL = 0 表示光路被遮挡,CLEAR_LEVEL = 1 表示光路无遮挡。红色 LED 和绿色 LED 采用低电平点亮方式,因此 LED_ON = 0,LED_OFF = 1。如果实际模块的电平逻辑与代码不同,只需要调整这些常量,主循环和函数结构不需要重写。
传感器输入实验和普通 LED 输出实验的区别在于,程序需要先读取外部环境,再根据读取结果决定输出状态。LED 实验通常是程序主动控制硬件;传感器实验则是外部环境先变化,程序再响应。这个思路更接近真实自动化项目,例如物体经过检测、设备到位判断、通道计数、机械限位和异常停留识别。
#mermaid-svg-IFr1GPU2Ve8wizz6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IFr1GPU2Ve8wizz6 .error-icon{fill:#552222;}#mermaid-svg-IFr1GPU2Ve8wizz6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IFr1GPU2Ve8wizz6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .marker.cross{stroke:#333333;}#mermaid-svg-IFr1GPU2Ve8wizz6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IFr1GPU2Ve8wizz6 p{margin:0;}#mermaid-svg-IFr1GPU2Ve8wizz6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .cluster-label text{fill:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .cluster-label span{color:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .cluster-label span p{background-color:transparent;}#mermaid-svg-IFr1GPU2Ve8wizz6 .label text,#mermaid-svg-IFr1GPU2Ve8wizz6 span{fill:#333;color:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .node rect,#mermaid-svg-IFr1GPU2Ve8wizz6 .node circle,#mermaid-svg-IFr1GPU2Ve8wizz6 .node ellipse,#mermaid-svg-IFr1GPU2Ve8wizz6 .node polygon,#mermaid-svg-IFr1GPU2Ve8wizz6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .rough-node .label text,#mermaid-svg-IFr1GPU2Ve8wizz6 .node .label text,#mermaid-svg-IFr1GPU2Ve8wizz6 .image-shape .label,#mermaid-svg-IFr1GPU2Ve8wizz6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-IFr1GPU2Ve8wizz6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .rough-node .label,#mermaid-svg-IFr1GPU2Ve8wizz6 .node .label,#mermaid-svg-IFr1GPU2Ve8wizz6 .image-shape .label,#mermaid-svg-IFr1GPU2Ve8wizz6 .icon-shape .label{text-align:center;}#mermaid-svg-IFr1GPU2Ve8wizz6 .node.clickable{cursor:pointer;}#mermaid-svg-IFr1GPU2Ve8wizz6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .arrowheadPath{fill:#333333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IFr1GPU2Ve8wizz6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IFr1GPU2Ve8wizz6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IFr1GPU2Ve8wizz6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IFr1GPU2Ve8wizz6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .cluster text{fill:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 .cluster span{color:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IFr1GPU2Ve8wizz6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IFr1GPU2Ve8wizz6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-IFr1GPU2Ve8wizz6 .icon-shape,#mermaid-svg-IFr1GPU2Ve8wizz6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IFr1GPU2Ve8wizz6 .icon-shape p,#mermaid-svg-IFr1GPU2Ve8wizz6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IFr1GPU2Ve8wizz6 .icon-shape .label rect,#mermaid-svg-IFr1GPU2Ve8wizz6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IFr1GPU2Ve8wizz6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IFr1GPU2Ve8wizz6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IFr1GPU2Ve8wizz6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-IFr1GPU2Ve8wizz6 .sensor>*{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .sensor span{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .sensor tspan{fill:#1F3B6D!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .input>*{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .input span{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .input tspan{fill:#1F5C3A!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .logic>*{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .logic span{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .logic tspan{fill:#7A4A00!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .output>*{fill:#F4EEFF!important;stroke:#8E6AD8!important;stroke-width:2px!important;color:#3D2B68!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .output span{fill:#F4EEFF!important;stroke:#8E6AD8!important;stroke-width:2px!important;color:#3D2B68!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .output tspan{fill:#3D2B68!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .power>*{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .power span{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-IFr1GPU2Ve8wizz6 .power tspan{fill:#7A2740!important;} U 型光电传感器
发射端与接收端
光路状态
无遮挡 / 被遮挡
信号端 IO6
输出高低电平
GPIOHS0 输入
photo_sensor.value()
稳定采样
多次读取减少抖动
状态判断
BLOCKED / CLEAR
红色 LED IO7
遮挡时点亮
绿色 LED IO8
无遮挡时点亮
计数统计
遮挡次数累加
模块供电
VCC
公共地线
GND
这条链路可以从电路和程序两个角度理解。电路侧,U 型光电传感器把光路变化转换成电平变化;程序侧,CanMV K210 读取 IO6 的电平,根据当前状态控制 IO7 和 IO8 的 LED 输出,并在遮挡发生时更新计数。整个实验不是单纯读取一个 GPIO,而是完成了"输入采集、状态判断、输出反馈、事件统计"的完整过程。
稳定采样是本实验中的一个重要设计。物体经过光路边缘时,传感器输出可能会在很短时间内抖动。如果程序每次读取到变化都立即响应,就可能出现一次遮挡被统计多次的问题。代码中的 read_photo_sensor_stable() 会连续读取多次,并用多数结果作为最终状态,从而降低瞬时抖动造成的误判。
硬件设施
本实验围绕 U 型光电传感器、红色 LED、绿色 LED 和 CanMV K210 的 GPIO 输入输出能力展开。代码没有使用 LCD、摄像头、蜂鸣器、电机或其他传感器,因此硬件重点集中在 IO6、IO7、IO8 三个引脚,以及传感器供电和公共地线。
接线关系可以先通过下面这张图建立整体印象。IO6 用于读取 U 型光电传感器信号,IO7 用于控制红色 LED,IO8 用于控制绿色 LED。传感器模块需要正常供电,并与 CanMV K210 保持公共地线。

| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,并读取传感器状态、控制 LED 输出 |
| U 型光电传感器 | 遮挡检测模块 | 信号端接入 IO6,光路被遮挡时输出低电平,无遮挡时输出高电平 |
| 红色 LED | 遮挡状态提示 | 接入 IO7,传感器检测到遮挡时点亮 |
| 绿色 LED | 无遮挡状态提示 | 接入 IO8,传感器无遮挡时点亮 |
| 限流电阻 | LED 保护 | 如果使用独立 LED,需要串联限流电阻,避免 LED 或 GPIO 过流 |
maix.GPIO |
GPIO 控制模块 | 用于创建输入和输出对象,完成 LED 控制与传感器读取 |
fpioa_manager.fm |
引脚映射模块 | 用于把物理引脚绑定到指定 GPIO 或 GPIOHS 功能 |
time |
延时模块 | 用于传感器稳定采样和主循环节奏控制 |
实验中用到的主要零件如下。U 型光电传感器负责检测光路是否被遮挡,红色 LED 和绿色 LED 负责显示当前状态,CanMV K210 负责读取输入并输出控制信号。实物接线时重点检查信号线和 LED 极性,不要只看模块是否插上。

接线关系可以直接从代码中的引脚常量和 fm.register() 推导出来。红灯连接到 IO7,绿灯连接到 IO8,U 型光电传感器信号端连接到 IO6。红灯和绿灯使用普通 GPIO 输出,光电传感器使用 GPIOHS0 输入,并配置为上拉输入模式。
| 物理引脚 | GPIO 功能 | 代码变量 | 对应硬件 | 说明 |
|---|---|---|---|---|
| IO7 | GPIO0 | red_led |
红色 LED | 低电平点亮,用于表示光路被遮挡 |
| IO8 | GPIO1 | green_led |
绿色 LED | 低电平点亮,用于表示光路无遮挡 |
| IO6 | GPIOHS0 | photo_sensor |
U 型光电传感器信号端 | 使用上拉输入读取传感器输出电平 |
| VCC | 模块供电 | 无 | U 型光电传感器 VCC | 根据模块标识连接对应供电端 |
| GND | 公共地线 | 无 | U 型光电传感器 GND | 与 CanMV K210 共地,保证输入电平稳定 |
完成接线后的整体效果如下。检查时重点关注 IO6、IO7、IO8 三个位置:IO6 是否接到传感器信号端,IO7 是否接到红色 LED,IO8 是否接到绿色 LED。程序运行后,无遮挡时绿色 LED 应该点亮;用物体遮挡 U 型槽后,绿色 LED 熄灭,红色 LED 点亮。

| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | 默认进入无遮挡状态,绿色 LED 点亮 | 两个 LED 都不亮时,检查 LED 极性、限流电阻和 GPIO 映射 |
| 光路无遮挡 | 传感器输出高电平,绿灯亮,红灯灭 | 若红灯亮,可能是传感器电平逻辑与代码设定相反 |
| 光路被遮挡 | 传感器输出低电平,红灯亮,绿灯灭 | 遮挡无反应时,检查 IO6 是否接到传感器信号端 |
| 物体快速经过 | 控制台只在状态变化时打印 | 如果计数偏多,需要增加采样次数或延时 |
| 光路恢复 | 程序打印恢复信息,绿色 LED 重新点亮 | 状态不恢复时,检查传感器是否被持续遮挡或安装偏移 |
| 多次遮挡 | 每次从无遮挡进入遮挡时累计次数增加 | 计数异常时,检查传感器抖动和消抖参数 |
软件代码
本实验代码围绕一个传感器输入和两个 LED 输出展开。程序先完成引脚注册和 GPIO 对象创建,再封装红绿灯控制函数,接着通过稳定采样读取传感器状态。主循环只在状态发生变化时更新 LED 和打印信息,这样既能及时响应遮挡变化,也能避免控制台被重复信息占满。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能运行基础 print() 测试 |
| CanMV 固件 | 提供 maix.GPIO、fpioa_manager 等模块 |
固件环境需要支持当前 GPIO 与 FPIOA 写法 |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口工具中能看到对应端口 |
| 串口终端 | 查看遮挡事件和累计次数 | 遮挡或恢复时能看到状态打印 |
maix.GPIO |
创建 GPIO 输入输出对象 | 能控制 LED,并读取传感器信号端电平 |
fpioa_manager.fm |
完成 IO 与 GPIO 功能映射 | IO6、IO7、IO8 能映射到对应 GPIO 功能 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CanMV K210 U 型光电传感器实验 Demo
实验目标:
1. 读取 U 型光电传感器的遮挡状态
2. 遮挡时点亮红灯,无遮挡时点亮绿灯
3. 统计被遮挡次数
4. 只在状态变化时打印信息,避免控制台刷屏
"""
from maix import GPIO
from fpioa_manager import fm
import time
# =========================
# 硬件引脚配置区
# =========================
RED_LED_PIN = 7 # 红色 LED 接 IO7
GREEN_LED_PIN = 8 # 绿色 LED 接 IO8
PHOTO_PIN = 6 # U 型光电传感器信号端接 IO6
RED_LED_GPIO = fm.fpioa.GPIO0
GREEN_LED_GPIO = fm.fpioa.GPIO1
PHOTO_GPIO = fm.fpioa.GPIOHS0
# =========================
# 电平配置区
# =========================
LED_ON = 0 # LED 低电平点亮
LED_OFF = 1 # LED 高电平熄灭
BLOCKED_LEVEL = 0 # 光路被遮挡时,传感器输出低电平
CLEAR_LEVEL = 1 # 光路无遮挡时,传感器输出高电平
# =========================
# 初始化
# =========================
def setup():
"""初始化 LED 和 U 型光电传感器"""
global red_led
global green_led
global photo_sensor
fm.register(RED_LED_PIN, RED_LED_GPIO, force=True)
fm.register(GREEN_LED_PIN, GREEN_LED_GPIO, force=True)
fm.register(PHOTO_PIN, PHOTO_GPIO, force=True)
red_led = GPIO(GPIO.GPIO0, GPIO.OUT)
green_led = GPIO(GPIO.GPIO1, GPIO.OUT)
photo_sensor = GPIO(GPIO.GPIOHS0, GPIO.IN, GPIO.PULL_UP)
set_clear_state()
print("U 型光电传感器初始化完成")
print("无遮挡:绿灯亮")
print("被遮挡:红灯亮")
# =========================
# LED 状态控制
# =========================
def red_on():
red_led.value(LED_ON)
def red_off():
red_led.value(LED_OFF)
def green_on():
green_led.value(LED_ON)
def green_off():
green_led.value(LED_OFF)
def set_blocked_state():
"""遮挡状态:红灯亮,绿灯灭"""
red_on()
green_off()
def set_clear_state():
"""无遮挡状态:绿灯亮,红灯灭"""
red_off()
green_on()
# =========================
# 传感器读取
# =========================
def read_photo_sensor():
"""
读取 U 型光电传感器状态
返回值:
0 表示光路被遮挡
1 表示光路无遮挡
"""
return photo_sensor.value()
def read_photo_sensor_stable(samples=5, delay_ms=2):
"""
多次采样,减少抖动误判
samples : 采样次数
delay_ms : 每次采样间隔
"""
high_count = 0
for _ in range(samples):
if read_photo_sensor() == 1:
high_count += 1
time.sleep_ms(delay_ms)
if high_count > samples // 2:
return CLEAR_LEVEL
else:
return BLOCKED_LEVEL
# =========================
# 状态处理
# =========================
def handle_status_change(status, block_count):
"""根据传感器状态切换 LED,并打印事件信息"""
if status == BLOCKED_LEVEL:
block_count += 1
set_blocked_state()
print("检测到物体遮挡,累计次数:{}".format(block_count))
else:
set_clear_state()
print("光路恢复无遮挡")
return block_count
# =========================
# 主循环
# =========================
def loop():
"""循环检测 U 型光电传感器状态"""
last_status = None
block_count = 0
while True:
current_status = read_photo_sensor_stable()
if current_status != last_status:
block_count = handle_status_change(current_status, block_count)
last_status = current_status
time.sleep_ms(30)
# =========================
# 程序入口
# =========================
if __name__ == '__main__':
setup()
try:
loop()
except KeyboardInterrupt:
red_off()
green_off()
print("程序已停止,LED 已关闭")
这段程序可以分成输入、判断和输出三个层级。输入层由 photo_sensor 和 read_photo_sensor_stable() 完成,负责读取 U 型光电传感器的稳定状态;判断层由 BLOCKED_LEVEL、CLEAR_LEVEL、last_status 和 handle_status_change() 完成,负责判断遮挡是否发生变化;输出层由红色 LED、绿色 LED 和串口打印完成,负责把检测结果反馈出来。
引脚配置区把红灯、绿灯和光电传感器的物理连接写成常量,后续修改接线时只需要调整顶部配置。电平配置区把 LED 点亮逻辑和传感器输出逻辑独立出来,如果实际硬件电平相反,优先调整 LED_ON、LED_OFF、BLOCKED_LEVEL 和 CLEAR_LEVEL,不要直接改主循环。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
setup() |
初始化 LED、光电传感器和引脚映射 | 程序启动后绿灯亮起,并打印初始化提示 |
red_on() |
点亮红色 LED | 遮挡状态下红灯亮 |
red_off() |
熄灭红色 LED | 无遮挡状态下红灯灭 |
green_on() |
点亮绿色 LED | 无遮挡状态下绿灯亮 |
green_off() |
熄灭绿色 LED | 遮挡状态下绿灯灭 |
set_blocked_state() |
设置遮挡显示状态 | 红灯亮,绿灯灭 |
set_clear_state() |
设置无遮挡显示状态 | 绿灯亮,红灯灭 |
read_photo_sensor() |
直接读取传感器电平 | 获取当前光路是否被遮挡 |
read_photo_sensor_stable() |
多次采样并判断稳定状态 | 减少传感器抖动带来的误判 |
handle_status_change() |
根据状态切换 LED 并更新计数 | 遮挡时累计次数,恢复时切换绿灯 |
loop() |
持续检测传感器状态 | 程序持续响应遮挡和恢复事件 |
主循环中的 last_status 是避免刷屏和重复计数的关键。程序每次读取当前状态后,会和上一次状态比较。只有遮挡状态发生变化时,才调用 handle_status_change() 切换 LED 并打印信息。遮挡发生时,block_count 自增一次;光路恢复时,程序切换回绿灯状态并输出恢复提示。这种写法更接近真实项目中的事件触发逻辑,而不是简单地反复打印传感器当前值。
扩展应用
U 型光电传感器实验的调试重点在于电平逻辑、接线关系和状态变化判断。遇到问题时,不适合直接大范围修改代码,应围绕传感器输出、LED 电平和引脚映射逐步确认。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| 程序运行后 LED 都不亮 | LED 接线错误、供电异常、低电平点亮逻辑与实际硬件不一致 | 检查 IO7、IO8 连接关系,尝试切换 LED_ON 和 LED_OFF 的取值 |
| 遮挡时绿灯亮、无遮挡时红灯亮 | 传感器输出电平与代码设定相反 | 对调 BLOCKED_LEVEL 和 CLEAR_LEVEL,或单独打印传感器电平确认 |
| 控制台没有遮挡信息 | 传感器信号线未接到 IO6,或 GPIOHS0 映射不正确 | 核对 PHOTO_PIN = 6 和实际信号端接线,单独打印 photo_sensor.value() 测试 |
| 控制台信息频繁变化 | 遮挡边缘抖动、采样次数太少、传感器安装不稳定 | 增大 samples 或 delay_ms,固定传感器位置,避免物体停在光路边缘 |
| 遮挡次数统计偏多 | 同一次遮挡过程中状态反复跳变 | 提高稳定采样次数,或在状态切换后增加更长的消抖等待 |
| 按 Ctrl+C 后 LED 仍亮 | 程序异常退出前没有执行关闭逻辑,或中断没有被捕获 | 保留 try...except KeyboardInterrupt,停止时调用 red_off() 和 green_off() |
| 传感器一直显示遮挡 | U 型槽内有异物、传感器安装偏移、输出逻辑与代码相反 | 清理光路,重新固定传感器,并检查 BLOCKED_LEVEL 设置 |
| 传感器一直显示无遮挡 | 信号线接错、传感器未供电、遮挡物没有进入有效光路 | 检查 VCC、GND、信号线和遮挡位置 |
U 型光电传感器实验的核心价值在于把"是否有物体经过"转换成程序可以处理的数字信号。当前代码已经具备输入读取、稳定采样、状态变化判断、LED 状态反馈和次数统计能力,因此可以自然扩展到很多检测类项目中。只要将遮挡事件看作一次输入触发,就可以继续连接计数、报警、限位、测速或自动化控制逻辑。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 物体通过计数 | 每次检测到遮挡状态时累加计数 | 可扩展为生产线计数器、通道计数器或课堂演示计数器 |
| 小车测速实验 | 在固定距离放置两个 U 型光电传感器,记录通过时间差 | 后续可扩展双传感器测速和速度计算 |
| 限位检测 | 将遮挡状态作为设备到位信号 | 可扩展到滑轨、小车、机械结构的边界检测 |
| 闸机通行检测 | 遮挡光路表示有物体或卡片经过 | 可扩展蜂鸣器提示、LCD 显示和通行次数记录 |
| 自动分拣触发 | 物体经过传感器时触发后续动作 | 后续可扩展电机、舵机或继电器控制 |
| 教学演示实验 | 通过红绿灯直观看到输入状态变化 | 可用于讲解 GPIO 输入、上拉模式、状态机和事件触发 |
| 异常状态提示 | 长时间遮挡时判断为异常停留 | 可扩展超时判断和声光报警逻辑 |
从工程结构看,当前程序已经把读取、判断、显示和计数拆成独立函数。新增功能时,可以继续保留 read_photo_sensor_stable() 作为输入入口,把 handle_status_change() 扩展成更完整的事件处理函数。后续接入蜂鸣器、LCD、串口上传或网络通信时,也可以在状态变化发生时触发,而不是在主循环中无意义地重复执行。
总结
本实验通过 CanMV K210 开发板完成了 U 型光电传感器遮挡检测,核心能力包括 FPIOA 引脚映射、GPIO 输出控制、GPIOHS 输入读取、上拉输入配置、传感器稳定采样、状态变化判断、LED 状态提示和遮挡次数统计。代码把外部光路变化转换成程序中的高低电平,再通过红绿灯和控制台信息展示检测结果,完整体现了传感器输入与硬件输出联动的基本思路。
这类实验是智能硬件课程中非常重要的输入检测案例。屏幕中的 if 判断不再只是比较数字,而是对应真实世界中"有物体经过"或"光路恢复"的状态变化。后续课程可以在这个基础上继续扩展按键输入、蜂鸣器反馈、LCD 显示、双传感器测速、数据上传、自动分拣和 AI 摄像头识别等内容。只要理解传感器信号、GPIO 输入和状态处理之间的关系,更多自动化检测项目都可以沿着同一套思路搭建。