在智能硬件项目中,光照检测是非常常见的基础能力。自动调节屏幕亮度、夜间模式判断、智能补光灯、环境监测设备、户外强光提醒,本质上都需要程序能够感知外部环境的明暗变化。光敏电阻实验的价值不只是读取一个数字,而是让 Python 程序通过传感器把真实世界的光线变化转成可处理的数据。
本实验使用 CanMV K210 开发板通过软件 I2C 连接 PCF8591 模数转换模块,再由 PCF8591 的 AIN0 通道读取光敏电阻的模拟量。程序会把 0~255 的 ADC 原始值转换成 0%~100% 的光照百分比,并通过多次采样平均降低数据抖动。运行后,串口终端会持续输出 ADC 原始值、光照强度百分比和环境状态,当光照等级发生变化时,程序才输出状态变化提醒,避免终端信息过于杂乱。
| 学习目标 | 说明 |
|---|---|
| 理解模拟量采集 | 认识光敏电阻输出电压、PCF8591 ADC 转换和数字采样值之间的关系 |
| 掌握 I2C 读取方式 | 使用 CanMV K210 的软件 I2C 与 PCF8591 通信 |
| 完成光照数据读取 | 通过 PCF8591 的 AIN0 通道读取光敏电阻模拟量 |
| 理解数据处理流程 | 将 ADC 原始值转换成光照百分比,并使用多次采样平均减少抖动 |
| 建立状态判断思路 | 根据光照百分比判断黑暗、偏暗、正常、明亮和强光状态 |
文章目录
理论基础
光敏电阻是一种会随光照强度变化而改变电阻值的传感器元件。光线变化会影响分压电路输出电压,但这个电压是连续变化的模拟量,不能直接被普通 GPIO 当作明确的 0 或 1 读取。CanMV K210 如果要获得这种连续变化的数据,就需要借助 ADC 模数转换模块。
PCF8591 在本实验中承担"模拟量转数字量"的角色。光敏电阻的输出电压接入 PCF8591 的 AIN0 通道,PCF8591 将该模拟电压转换成 0~255 范围内的数字值,再通过 I2C 总线传给 CanMV K210。程序读取到数字值后,可以继续换算成百分比,并根据阈值判断环境亮度等级。
这个实验的控制链路可以理解为:环境光线改变光敏电阻输出,PCF8591 完成 ADC 转换,CanMV K210 通过 I2C 读取数据,Python 程序对数据进行平均滤波、百分比换算和等级判断,最后通过串口输出当前光照状态。
环境光照变化
遮挡 / 照射 / 强光
光敏电阻模块
输出模拟电压
PCF8591 AIN0
采集模拟输入
ADC 转换
0~255 数字量
I2C 通信
SCL: IO6 / SDA: IO7
Python 程序
读取 raw_value
数据处理
平均采样 / 百分比转换
状态判断
黑暗 / 偏暗 / 正常 / 明亮 / 强光
串口输出
数值 + 状态 + 提醒
模块供电
VCC
公共地线
GND
这张流程图展示的是从环境光变化到程序状态判断的完整链路。程序读取的并不是光敏电阻本身,而是 PCF8591 转换后的数字值。这个数字值再经过百分比转换和阈值判断后,才变成"黑暗、偏暗、正常、明亮、强光"这些更适合项目使用的状态信息。
多次采样平均是本实验中的一个重要处理方式。模拟量容易受到电源波动、环境光变化、手部遮挡和连线干扰影响,单次读取可能出现轻微跳动。代码中每次读取会连续采样 5 次,再取平均值作为本轮结果,可以让串口输出更加稳定,也更接近真实项目中的数据处理方式。
硬件设施
本实验围绕光敏电阻模拟量采集展开,核心硬件是 CanMV K210 开发板、PCF8591 ADC 模块和光敏电阻传感器。由于 K210 程序无法直接读取光敏电阻产生的模拟电压,因此需要借助 PCF8591 把模拟量转换成数字量。代码中没有使用 LCD、按键、蜂鸣器、摄像头、电机等模块,这些内容不作为本节实验重点。
接线关系可以先通过下面这张图建立整体印象。CanMV K210 使用物理引脚 6 作为 SCL,物理引脚 7 作为 SDA,与 PCF8591 建立 I2C 通信;光敏电阻模块的模拟输出接入 PCF8591 的 AIN0 通道。

| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 执行 MicroPython 程序,通过 I2C 与 PCF8591 通信 |
| 光敏电阻传感器 | 环境光检测元件 | 光照变化会影响传感器输出的模拟电压 |
| PCF8591 模数转换模块 | ADC 采集模块 | 将光敏电阻的模拟量转换成 0~255 范围内的数字量 |
| 物理引脚 6 | I2C 时钟线 SCL | 代码中由 I2C_SCL_PIN = 6 指定 |
| 物理引脚 7 | I2C 数据线 SDA | 代码中由 I2C_SDA_PIN = 7 指定 |
machine.I2C |
I2C 通信模块 | 创建软件 I2C 对象,与 PCF8591 进行数据通信 |
fpioa_manager.fm |
引脚功能映射模块 | 为软件 I2C 分配 GPIOHS 功能 |
pcf8591 |
ADC 驱动模块 | 封装 PCF8591 的读取操作 |
time |
延时控制模块 | 控制采样间隔和检测周期 |
实验中使用的 PCF8591 和光敏电阻模块如下。光敏电阻负责感知环境亮度,PCF8591 负责完成模拟量转换,CanMV K210 负责读取和判断数据。这个组合是后续很多模拟传感器实验的基础结构。

接线关系可以从 I2C_SCL_PIN、I2C_SDA_PIN、ADC_CHANNEL 和 I2C() 初始化参数中推导。当前程序使用物理引脚 6 作为 SCL,物理引脚 7 作为 SDA,光敏电阻接在 PCF8591 的 AIN0 通道。电源和 GND 连接属于模块运行基础,具体电压需要以实际 PCF8591 模块规格为准。
| 物理引脚 / 接口 | 功能 | 代码变量 | 对应硬件 | 说明 |
|---|---|---|---|---|
| 6 | I2C SCL | I2C_SCL_PIN |
PCF8591 SCL | 作为软件 I2C 时钟线 |
| 7 | I2C SDA | I2C_SDA_PIN |
PCF8591 SDA | 作为软件 I2C 数据线 |
| PCF8591 AIN0 | ADC 输入通道 | ADC_CHANNEL = 0 |
光敏电阻模拟输出 | 读取光敏电阻电压变化 |
| PCF8591 VCC | 模块供电 | 代码未直接配置 | 电源正极 | 按模块规格接入合适电源 |
| PCF8591 GND | 公共地 | 代码未直接配置 | 电源地 | 需要与开发板 GND 共地 |
物理引脚 6 和 7 是开发板与 PCF8591 通信的通道,AIN0 是 PCF8591 接收光敏电阻模拟信号的入口。程序并不是直接读取光敏电阻,而是通过 I2C 向 PCF8591 请求 AIN0 的转换结果。这样一来,开发板只需要处理数字通信,模拟电压采样由 PCF8591 完成,代码结构也更清晰。
完成接线后的整体状态如下。运行程序后,可以通过遮挡光敏电阻或用光源照射传感器,观察串口中的 ADC 原始值、光照百分比和状态文字是否随环境变化而改变。

| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | 串口打印"光敏电阻检测 Demo 启动" | 没有输出时检查程序是否运行、串口是否连接 |
| 正常采样 | 串口持续输出 ADC 原始值、百分比和当前状态 | 如果没有变化,检查 AIN0 和传感器输出 |
| 遮挡光敏电阻 | 光照百分比发生明显变化 | 如果变化方向相反,调整 INVERT_VALUE |
| 强光照射 | 状态可能切换到"明亮"或"强光" | 若状态不变,检查阈值和原始值范围 |
| 状态变化 | 串口额外输出状态变化提醒 | 如果没有提醒,说明等级没有跨过阈值 |
| 数值轻微跳动 | 平均采样后仍可能有小幅波动 | 模拟量轻微波动属于正常现象,可增加采样次数 |
软件代码
本实验代码围绕 PCF8591 的 ADC 数据读取展开。程序先配置 I2C 引脚和 ADC 通道,再初始化 PCF8591 对象。主循环中持续读取光敏电阻数据,通过多次采样平均减少瞬间波动,把原始值转换为光照百分比,并根据阈值判断黑暗、偏暗、正常、明亮和强光等状态。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能正常运行基础 print() 测试 |
| CanMV / MaixPy 固件 | 提供 machine.I2C、fpioa_manager 等模块 |
固件环境需要支持当前 I2C 与 FPIOA 写法 |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口工具或 IDE 中能看到对应端口 |
pcf8591.py |
PCF8591 驱动文件 | 需要能正常 import pcf8591 |
| 串口终端 | 查看光照数据 | 能看到 ADC 原始值、光照百分比和状态变化 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CanMV K210 光敏电阻传感器实验 Demo
功能说明:
1. 通过 PCF8591 读取光敏电阻模拟量
2. 将 0~255 的 ADC 原始值转换成光照百分比
3. 使用多次采样平均,减少数值抖动
4. 根据光照强度判断环境状态
5. 只有状态变化时输出提醒,避免刷屏太乱
"""
from machine import I2C
from fpioa_manager import fm
import pcf8591
import time
# =========================
# 硬件配置区
# =========================
I2C_SCL_PIN = 6
I2C_SDA_PIN = 7
ADC_CHANNEL = 0 # 光敏电阻接在 PCF8591 的 AIN0
SAMPLE_COUNT = 5 # 每次读取采样次数
READ_INTERVAL_MS = 300 # 每轮检测间隔
# 不同模块的光敏电阻接法可能相反
# 如果光越亮数值越大,设置为 False
# 如果光越亮数值越小,设置为 True
INVERT_VALUE = False
# =========================
# 初始化
# =========================
def setup():
"""
初始化 I2C 和 PCF8591
"""
i2c = I2C(
I2C.I2C_SOFT,
freq=400000,
scl=I2C_SCL_PIN,
sda=I2C_SDA_PIN,
gscl=fm.fpioa.GPIOHS1,
gsda=fm.fpioa.GPIOHS2
)
adc = pcf8591.PCF8591(i2c)
return adc
# =========================
# 数据读取与处理
# =========================
def read_light_raw(adc):
"""
读取光敏电阻原始 ADC 数值
PCF8591 为 8 位 ADC,数值范围通常是 0~255
"""
return adc.read(ADC_CHANNEL)
def read_light_average(adc, count=SAMPLE_COUNT):
"""
多次采样取平均值,降低瞬间波动影响
"""
total = 0
for _ in range(count):
total += read_light_raw(adc)
time.sleep_ms(20)
return total // count
def convert_to_percent(raw_value):
"""
将 ADC 原始值转换成百分比
"""
if INVERT_VALUE:
raw_value = 255 - raw_value
percent = int(raw_value / 255 * 100)
if percent < 0:
percent = 0
elif percent > 100:
percent = 100
return percent
def get_light_level(percent):
"""
根据光照百分比判断环境亮度等级
"""
if percent < 15:
return "黑暗"
elif percent < 35:
return "偏暗"
elif percent < 65:
return "正常"
elif percent < 85:
return "明亮"
else:
return "强光"
def get_light_advice(level):
"""
根据光照等级给出设备状态说明
"""
if level == "黑暗":
return "环境光线很弱,可触发夜间模式或补光灯"
elif level == "偏暗":
return "环境略暗,可降低屏幕亮度或开启辅助照明"
elif level == "正常":
return "当前光照适合正常工作"
elif level == "明亮":
return "环境较亮,可用于白天模式判断"
else:
return "光照较强,可触发遮光、降亮度或强光提醒"
# =========================
# 主循环
# =========================
def loop(adc):
"""
循环读取光敏电阻数据
"""
last_level = None
while True:
raw_value = read_light_average(adc)
percent = convert_to_percent(raw_value)
level = get_light_level(percent)
advice = get_light_advice(level)
print("ADC原始值: {} | 光照强度: {}% | 当前状态: {}".format(
raw_value,
percent,
level
))
if level != last_level:
print("状态变化提醒: {}".format(advice))
last_level = level
time.sleep_ms(READ_INTERVAL_MS)
# =========================
# 程序入口
# =========================
if __name__ == '__main__':
try:
photo_adc = setup()
print("光敏电阻检测 Demo 启动")
loop(photo_adc)
except KeyboardInterrupt:
print("程序已停止")
这段代码可以分成硬件配置、初始化、数据读取、数据转换、状态判断和主循环输出几个部分。配置区集中保存 I2C 引脚、ADC 通道、采样次数、读取间隔和数值反转开关,这种写法便于后期根据不同模块快速调整参数。
setup() 负责创建软件 I2C 对象,并将它传入 pcf8591.PCF8591(i2c) 得到 ADC 读取对象。这里使用 I2C.I2C_SOFT,说明 I2C 通信由软件方式模拟,scl=6 和 sda=7 分别对应开发板物理引脚。read_light_average() 会连续采样 5 次,每次间隔 20 毫秒,再使用整数平均值作为本轮检测结果。
convert_to_percent() 负责把 0~255 的原始值转换成 0%~100% 的光照百分比。INVERT_VALUE 是一个很实用的兼容开关,因为不同光敏电阻模块的分压接法可能相反。有的模块光越亮数值越大,有的模块光越亮数值越小。通过这个开关,代码不需要改动整体逻辑,只需要调整取值方向。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
setup() |
初始化软件 I2C 和 PCF8591 | 程序建立与 ADC 模块的通信 |
read_light_raw(adc) |
读取 AIN0 原始 ADC 数值 | 串口中可获得 0~255 范围内的光照数据 |
read_light_average(adc) |
多次采样取平均值 | 光照数值变化更平稳,减少瞬间抖动 |
convert_to_percent(raw_value) |
将原始值转换成百分比 | 输出更直观的光照强度百分比 |
get_light_level(percent) |
根据百分比判断亮度等级 | 将数值转换为黑暗、偏暗、正常、明亮、强光 |
get_light_advice(level) |
根据等级生成状态说明 | 光照状态变化时输出对应提醒 |
loop(adc) |
循环检测光照变化 | 串口持续显示光照数据,并在状态变化时提醒 |
主程序入口使用 try...except KeyboardInterrupt 包裹运行逻辑。程序启动后会打印"光敏电阻检测 Demo 启动",接着进入 loop(photo_adc) 持续检测。last_level 用来记录上一次的光照等级,只有当前等级发生变化时才输出提醒信息。这个设计很接近真实项目中的事件触发思路,避免传感器每次读取都重复打印相同提醒。
扩展应用
光敏电阻实验常见问题集中在 I2C 接线、PCF8591 通信、ADC 通道、数值方向和采样稳定性上。排查时可以先确认模块是否能正常通信,再观察原始 ADC 数值是否随光线变化。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| 程序启动后报 I2C 或 PCF8591 错误 | SCL、SDA 接线错误,模块未供电,I2C 初始化失败 | 检查物理引脚 6 是否连接 SCL,物理引脚 7 是否连接 SDA,并确认模块电源和 GND 正常 |
无法导入 pcf8591 |
驱动文件缺失或没有放到运行目录 | 检查 pcf8591.py 是否已经上传到开发板或 SD 卡对应目录 |
| ADC 数值始终不变化 | 光敏电阻未接到 AIN0,通道选择不对,传感器输出异常 | 核对 ADC_CHANNEL = 0 是否对应 PCF8591 的 AIN0,遮挡或照射光敏电阻观察数值变化 |
| 光越亮百分比越低 | 光敏电阻模块分压接法相反 | 将 INVERT_VALUE = False 改为 INVERT_VALUE = True |
| 串口输出变化太频繁 | READ_INTERVAL_MS 较小,采样周期较短 |
适当增大 READ_INTERVAL_MS,例如改为 500 或 1000 |
| 数值抖动明显 | 环境光不稳定,采样次数偏少,传感器连接松动 | 增大 SAMPLE_COUNT,检查连接线,避免强烈闪烁光源干扰 |
| 状态提醒不输出 | 光照等级没有跨过阈值,last_level 阻止重复提醒 |
观察当前百分比是否进入新的等级区间,改变遮挡或照射强度进行测试 |
| 数值长期接近 0 或 255 | 输入端接线错误、传感器输出饱和或光照条件过极端 | 检查传感器输出端是否接入 AIN0,调整照射距离或遮挡程度 |
光敏电阻实验的扩展价值在于把环境光变化转成程序可以判断的状态。当前代码已经完成了数据采集、平均滤波、百分比转换、等级判断和状态变化提醒,这些逻辑可以自然迁移到智能灯光、环境监测、设备节能和自动化控制场景中。传感器采集到的不是单纯数字,而是设备做出决策的依据。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 自动夜间模式 | 根据光照百分比判断环境是否偏暗 | 后续可扩展 LCD 或屏幕亮度调节 |
| 智能补光灯 | 黑暗或偏暗状态下触发补光逻辑 | 后续可接入 LED 灯组或继电器控制补光设备 |
| 强光提醒设备 | 当状态进入强光等级时输出提醒 | 可扩展蜂鸣器、提示灯或屏幕提示 |
| 教学数据采集 | 通过遮挡和照射观察 ADC 数值变化 | 适合讲解模拟量、ADC、采样平均和阈值判断 |
| 环境监测终端 | 周期性读取光照强度并输出状态 | 后续可扩展温湿度、声音、人体感应等传感器 |
| 设备节能控制 | 根据环境亮度切换不同工作模式 | 可把亮度等级绑定到设备功耗策略 |
| 数据上传项目 | 当前串口输出可改造成网络上报数据 | Wi-Fi、MQTT、Web 控制属于后续课程扩展方向 |
从工程角度看,当前程序已经具备传感器项目的基本骨架。read_light_raw() 负责底层读取,read_light_average() 负责数据稳定,convert_to_percent() 负责数据标准化,get_light_level() 负责业务判断,loop() 负责持续调度。后续换成其他模拟传感器时,也可以沿用这套结构,只需要调整读取通道、换算公式和状态阈值。
总结
本实验通过 CanMV K210、PCF8591 和光敏电阻完成了环境光照检测,核心能力包括软件 I2C 通信、ADC 模拟量读取、多次采样平均、百分比转换、阈值判断和状态变化提醒。代码从硬件初始化开始,把光敏电阻的模拟电压转成数字量,再进一步转成更容易理解的亮度百分比和环境等级,完整展示了 Python 程序读取真实环境数据的过程。
这类实验非常适合作为传感器课程的入门案例。串口中变化的 ADC 数值对应外部光线变化,百分比转换让数据更直观,等级判断让传感器结果具备业务含义。后续课程可以在此基础上继续扩展 LED 指示、蜂鸣器提醒、LCD 显示、数据记录、网络上传、自动补光和多传感器融合等内容。理解了"采集数据、处理数据、判断状态、触发动作"这条主线,更多智能硬件项目都可以沿着同样的思路继续搭建。