前言
这是"拾颜播放器"项目学习记录的第四部分,用颜色传感器检测物体的颜色。
TCS3200 是 DFRobot 的一款经典颜色传感器,内部有红、绿、蓝三种滤光片和光敏二极管阵列。它的工作原理是:白色 LED 照亮物体 → 物体反射光线 → TCS3200 通过不同滤光片测量 RGB 强度 → 输出频率信号 → MCU 计数并换算成颜色值。
一、硬件准备
项目用的硬件:
组件说明
| ESP32-S3 Reverse TFT Feather | 主控板,240MHz 双核 |
| DFRobot SEN0101 | TCS3200 颜色传感器 |
| 彩色纸片 | 用于校准和测试(红、绿、蓝、白四色) |
TCS3200 引脚说明引脚功能说明
| VCC | 电源 | 3.3V 或 5V |
| GND | 地线 | 接地 |
| S0 | 频率缩放 | HIGH = 100%输出频率 |
| S1 | 频率缩放 | HIGH = 100%输出频率 |
| S2 | 滤波器选择 | 控制红/绿/蓝/无滤波 |
| S3 | 滤波器选择 | 控制红/绿/蓝/无滤波 |
| OUT | 频率输出 | 脉冲信号,频率与光强成正比 |
| LED | LED 控制 | 控制白光照明的开关 |
滤波器选择真值表S2S3选择通道
| LOW | LOW | 红色 |
| LOW | HIGH | 蓝色 |
| HIGH | HIGH | 绿色 |
| HIGH | LOW | 无滤波 (Clear) |
注意:这个真值表与某些教程相反,需要以实际测试为准。本代码使用的映射与 Arduino 参考代码一致。
二、ESP32-S3 引脚连接
# 引脚配置(与Arduino代码一致) S0_PIN = board.D9 # 频率缩放控制 S1_PIN = board.D10 # 频率缩放控制 S2_PIN = board.D11 # 滤波器选择 S3_PIN = board.D12 # 滤波器选择 OUT_PIN = board.D13 # 脉冲输出 (GPIO13) LED_PIN = board.D18 # LED控制引脚
连接示意:
TCS3200 ESP32-S3 -------- --------- VCC ──────► 3.3V 或 5V GND ──────► GND S0 ──────► GPIO9 (D9) S1 ──────► GPIO10 (D10) S2 ──────► GPIO11 (D11) S3 ──────► GPIO12 (D12) OUT ──────► GPIO13 (D13) LED ──────► GPIO18 (D18)

三、初始化传感器3.1 基础初始化
from digitalio import DigitalInOut, Direction, Pullclass TCS3200Test:
def __init__(self):
self.s0 = None
self.s1 = None
self.s2 = None
self.s3 = None
self.out = None
self.led = None
self.enabled = False
self.calibration_data = {}
def setup(self):
"""初始化传感器引脚"""
# S0 - 频率缩放控制
self.s0 = DigitalInOut(S0_PIN)
self.s0.direction = Direction.OUTPUT
self.s0.value = True # HIGH = 100%输出频率
# S1 - 频率缩放控制
self.s1 = DigitalInOut(S1_PIN)
self.s1.direction = Direction.OUTPUT
self.s1.value = True # HIGH = 100%输出频率
# S2, S3 - 滤波器选择
self.s2 = DigitalInOut(S2_PIN)
self.s2.direction = Direction.OUTPUT
self.s3 = DigitalInOut(S3_PIN)
self.s3.direction = Direction.OUTPUT # OUT - 频率输出(输入引脚)
self.out = DigitalInOut(OUT_PIN)
self.out.direction = Direction.INPUT
self.out.pull = Pull.UP # LED控制
self.led = DigitalInOut(LED_PIN)
self.led.direction = Direction.OUTPUT
self.led.value = True # LED亮
self.enabled = True
return True3.2 初始化流程说明
S0 和 S1:设置为 HIGH,让传感器以 100% 频率输出。如果信号太弱,可以尝试降低频率(LOW, LOW = 2%)。
S2 和 S3:初始化为 LOW(红色滤波器),之后会根据需要切换。
OUT:设置为输入上拉,读取脉冲信号。
LED:初始化为高电平,点亮白色 LED 照明。
四、读取颜色值4.1 选择颜色通道
def select_color(self, color): """选择颜色通道 (与Arduino代码一致)""" if color == 'red': self.s2.value = False self.s3.value = False elif color == 'green': self.s2.value = True self.s3.value = True elif color == 'blue': self.s2.value = False self.s3.value = True elif color == 'clear': self.s2.value = True self.s3.value = False
4.2 读取单通道原始值
def read_value(self, sample_time=0.15): """读取当前通道的原始值""" count = 0 start = time.monotonic() while time.monotonic() - start < sample_time: if not self.out.value: # 低电平计数 count += 1 return count
工作原理:
在 sample_time 时间内,统计 OUT 引脚为低电平的次数
反射光越强,输出频率越高,低电平占比约 50%
返回值范围通常是几百到几万
4.3 读取所有通道
def read_all_channels(self):
"""读取所有通道的原始值"""
raw = {}
for color_name in ['red', 'green', 'blue', 'clear']:
self.select_color(color_name)
time.sleep(0.005) # 等待稳定
raw[color_name] = self.read_value(0.1)
return raw返回值示例:
{
'red': 5234, # 红色滤波器下的计数值
'green': 2156, # 绿色滤波器下的计数值
'blue': 1876, # 蓝色滤波器下的计数值
'clear': 6421 # 无滤波下的计数值
}五、校准功能5.1 为什么需要校准
TCS3200 的原始读数受以下因素影响:
光照条件:环境光强度变化
距离:物体与传感器的距离
物体材质:不同纸张的反射率不同
传感器个体差异:每个传感器略有不同
因此,需要在正式使用前进行校准,建立"标准颜色 → 原始读数"的映射关系。
5.2 校准流程
def calibration(self):
"""校准流程"""
cal_colors = [
('white', '白色', (255, 255, 255)),
('red', '红色', (255, 0, 0)),
('green', '绿色', (0, 255, 0)),
('blue', '蓝色', (0, 0, 255)),
]
for color_key, color_name, _ in cal_colors:
print(f"请将 【{color_name}】 纸放在传感器上方(紧贴)")
print("放置好后,按 【回车键】 开始读取...")
input() # 等待用户确认
# 读取5次取平均,减少随机误差
samples = []
for i in range(5):
raw = self.read_all_channels()
samples.append(raw)
time.sleep(0.1)
# 计算平均值
avg_raw = {}
for channel in ['red', 'green', 'blue', 'clear']:
avg_raw[channel] = sum(s[channel] for s in samples) / len(samples)
self.calibration_data[color_key] = avg_raw校准输出示例:
校准数据摘要: 白色: R=6421, G=6156, B=5890 红色: R=8234, G=2156, B=1876 绿色: R=2156, G=7890, B=2034 蓝色: R=1876, G=2034, B=7456
5.3 校准注意事项
使用标准颜色:最好用标准色卡或打印的纯色块
保持距离一致:校准和正式使用时,物体距离传感器应相同
光线稳定:避免强光直射或光线突变
多测几次:可以多次校准取平均
六、颜色识别算法6.1 欧几里得距离匹配
校准后,识别颜色的方法是:计算当前读数与各校准颜色的距离,找出最近的:
def calculate_color_from_calibration(self, raw):
"""根据校准数据计算实际颜色"""
best_match = None
best_score = float('inf')
for color_key in ['white', 'red', 'green', 'blue']:
cal_data = self.calibration_data.get(color_key)
if not cal_data:
continue
# 计算欧几里得距离
diff = (
(raw['red'] - cal_data['red']) ** 2 +
(raw['green'] - cal_data['green']) ** 2 +
(raw['blue'] - cal_data['blue']) ** 2
)
score = diff ** 0.5
if score < best_score:
best_score = score
best_match = color_key
return best_match, best_score距离越小,说明当前颜色与校准颜色越接近。
6.2 RGB 归一化显示
除了颜色识别,还可以输出标准的 RGB 值:
def get_normalized_rgb(self, raw): """计算归一化的RGB值""" max_val = max(raw['red'], raw['green'], raw['blue']) if max_val > 0: r = min(255, int(raw['red'] * 255 / max_val)) g = min(255, int(raw['green'] * 255 / max_val)) b = min(255, int(raw['blue'] * 255 / max_val)) else: r = g = b = 0 return r, g, b
归一化原理:将最大值通道设为 255,其他通道按比例缩放,这样可以消除光照强度的影响。
七、完整使用流程7.1 主程序
def main():
test = TCS3200Test()
if test.setup():
test.run()def run(self):
"""运行测试(校准后监测)"""
# 1. 校准
if not self.calibration():
print("校准失败")
return
# 2. 实时监测
while True:
raw = self.read_all_channels()
r, g, b = self.get_normalized_rgb(raw)
hex_color = (r << 16) | (g << 8) | b # 识别颜色
matched_color, score = self.calculate_color_from_calibration(raw)
print("R=0x%02X G=0x%02X B=0x%02X #%06X %s" %
(r, g, b, hex_color, matched_color))
time.sleep(0.3)7.2 运行结果

准备的测试颜色卡:





八、常见问题Q1: 读数一直不变
可能原因:
LED 没有亮起(检查 GPIO18 连接)
S2/S3 引脚接错
物体距离太远
Q2: 所有颜色都显示白色
可能原因:
校准数据有问题
传感器灵敏度太高,需要增加物体距离
环境光干扰
Q3: 红色显示蓝色,蓝色显示红色
这是滤波器选择映射错误。本代码使用与 Arduino 参考代码一致的映射,如果实际相反,修改 select_color() 函数中的真值表。
Q4: 读数波动大
解决方法:
增加采样时间 read_value(0.2) 而不是 0.1
多次读数取平均
确保物体稳定不移动
Q5: 如何识别更多颜色
在 cal_colors 列表中添加更多颜色,然后更新颜色识别逻辑:
cal_colors = [
('white', '白色', (255, 255, 255)),
('red', '红色', (255, 0, 0)),
('green', '绿色', (0, 255, 0)),
('blue', '蓝色', (0, 0, 255)),
('yellow', '黄色', (255, 255, 0)), # 新增
('purple', '紫色', (128, 0, 128)), # 新增]九、进阶用法9.1 颜色相似度阈值
可以设置一个阈值,拒绝识别置信度太低的颜色:
def get_color_with_confidence(self, raw, threshold=1000): """获取颜色及其置信度""" matched_color, score = self.calculate_color_from_calibration(raw) if score > threshold: return 'unknown', score return matched_color, score
9.2 连续监测模式
在实时监测时,可以添加变化检测:
last_color = Nonewhile True:
raw = self.read_all_channels()
color, _ = self.calculate_color_from_calibration(raw)
if color != last_color:
print(f"颜色变化: {last_color} -> {color}")
last_color = color
time.sleep(0.3)9.3 数据记录
将读取的值保存到文件:
def log_color_data(self, filename='color_log.txt'):
"""记录颜色数据"""
with open(filename, 'a') as f:
while True:
raw = self.read_all_channels()
r, g, b = self.get_normalized_rgb(raw)
f.write(f"{time.time()}, {raw['red']}, {raw['green']}, {raw['blue']}, {r}, {g}, {b}\n")
time.sleep(1)十、总结
通过 TCS3200 颜色传感器,可以实现:
颜色检测:读取红、绿、蓝三通道的原始值
颜色校准:建立标准颜色的基准数据
颜色识别:根据校准数据识别未知颜色
RGB输出:输出标准格式的 RGB 值
关键要点:
校准是准确识别的关键
保持测试条件与校准条件一致
可以用欧几里得距离或曼哈顿距离进行匹配
采样时间越长,读数越稳定
在"拾颜播放器"项目中,颜色传感器负责检测用户放置的物体颜色,然后系统根据颜色播放对应风格的音乐,实现了"检测 → 识别 → 反馈"的完整交互。