这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » Let'sdo-2025年第3期-DIY拾色播放器-成果贴

共2条 1/1 1 跳转至

Let'sdo-2025年第3期-DIY拾色播放器-成果贴

菜鸟
2025-12-08 11:12:13     打赏

 一、 项目概述

本项目旨在利用 Adafruit ESP32-S3 TFT Feather 开发板和 TCS3200 颜色传感器,构建一个便携式的颜色识别装置。该系统不仅能实时在板载TFT屏幕上显示目标物体的RGB数值和颜色块,还能通过 NeoPixel 指示灯同步显示颜色。更有趣的是,项目引入了“通感”的设计理念,通过蜂鸣器根据颜色的RGB强度发出不同频率的音调,实现了“听见颜色”的功能。

 二、 系统框图

系统以 ESP32-S3 为核心控制器,通过 GPIO 读取 TCS3200 传感器的频率信号,经过白平衡算法处理后,分别输出到显示屏、RGB LED 和 蜂鸣器。

screenshot_2025-12-08_10-26-54.png


 三、 电路原理图与硬件连接

本项目主要硬件连接如下(基于 CircuitPython 代码配置):

screenshot_2025-12-08_10-27-39.png


 四、 主要参数情况

   主控芯片:ESP32-S3 mini,主频 240MHz。

   开发环境:CircuitPython 8.x/9.x。

   颜色识别范围:RGB 0-255,支持白平衡校准。

   音频输出:PWM 驱动,频率范围 262Hz - 1660Hz (对应14个音阶)。

   显示刷新率:约 3-5 FPS (受限于传感器采样时间和音频播放时长)。

   传感器模式:设置频率比例为 100% (High Frequency)。


 五、 实现步骤与软件调试


 1. 硬件搭建

使用面包板连接 TCS3200 与 ESP32-S3,确保传感器供电稳定,并加装了遮光罩以减少环境光干扰。将蜂鸣器连接至 D13 引脚。


 2. 软件驱动开发

   频率测量:编写 `measure_frequency` 函数,通过计算引脚电平翻转的时间差来精确获取传感器输出的频率。

   TCS3200 逻辑:封装 `TCS3200` 类,通过控制 S2、S3 引脚切换红、绿、蓝滤波器,并实现 `frequency_to_rgb` 映射算法。


 3. 白平衡校准算法

由于传感器在不同光照下对RGB的敏感度不同,实现了 `calibrate_white` 功能。

   原理:在系统启动或按下 D1 键时,记录白色物体反射的 R/G/B 基准频率。后续测量时,将测量值与基准值对比归一化到 0-255 区间。


 4. 音效与显示同步调试 (关键点)

在调试过程中发现,若先播放声音(耗时操作)再刷新屏幕,会导致显示严重的滞后感。

   解决方案:调整代码逻辑,先执行 `display.refresh()` 更新视觉数据,紧接着执行 `play_tone()`。虽然整体循环时间不变,但用户体验上屏幕响应变得即时且准确。


 5. 音调映射逻辑

为了让声音反映颜色信息,设计了简单的映射算法:

`yf = int((rgb_sum / 765)  14)`

将 RGB 三通道总值映射到预设的 14 个音调频率数组中,颜色越亮/白,音调越高;颜色越暗/黑,音调越低。


 六、 如何开启运行

1.  将编写好的 `code.py` 和依赖库(`adafruit_display_text`, `neopixel`, `pwmio` 等)存入开发板的 `CIRCUITPY` 磁盘。

2.  上电初始化:系统启动时会播放一段音阶,屏幕显示 "Initializing..."。

3.  白平衡校准:

       屏幕提示 "White balance calibration..."。

       请立即将传感器对准白色纸张或白色表面,保持2秒不动。

       校准完成后,屏幕显示 "Calibration complete"。

4.  正常使用:将传感器对准任意颜色物体,屏幕将显示 RGB 值,左上角显示当前颜色块,板载 LED 同步亮起,蜂鸣器发出对应音调。

5.  按键功能:

       按下 D1:重新进行白平衡校准。

       按下 D2:调整颜色判定阈值(灵敏度)。


 七、 效果演示

   视觉:系统启动后,TFT 屏幕清晰显示 R、G、B 的频率原始值与转换后的 0-255 数值。屏幕右上角的色块实时跟随被测物体颜色变化。

   听觉:当识别到红色(较亮)时,发出中高音;识别到黑色(无光)时,声音低沉或静音;识别到白色时,发出高频音调。

   交互:通过 D1 按键成功校正了环境光变化带来的色偏问题。

八、源代码

import time
import board
import digitalio
import displayio
import terminalio
from adafruit_display_text import label
import neopixel
import pwmio  # 新增引用

# -----------------------
# TCS3200 传感器类
# -----------------------
class TCS3200:
    def __init__(self, s0, s1, s2, s3, out_pin):
        self.s0 = s0
        self.s1 = s1
        self.s2 = s2
        self.s3 = s3
        self.out = out_pin

        self.s0.direction = digitalio.Direction.OUTPUT
        self.s1.direction = digitalio.Direction.OUTPUT
        self.s2.direction = digitalio.Direction.OUTPUT
        self.s3.direction = digitalio.Direction.OUTPUT
        self.out.direction = digitalio.Direction.INPUT

        # 白平衡校准值
        self.white_r = 1.0
        self.white_g = 1.0
        self.white_b = 1.0
        
        # 颜色校正参数
        self.color_threshold = 0.15  # 颜色阈值,低于此值的颜色分量将被设为0
        self.calibration_mode = False  # 校准模式标志

    def set_frequency(self, freq_mode):
        if freq_mode == 0:  # 2%
            self.s0.value = False
            self.s1.value = False
        elif freq_mode == 1:  # 20%
            self.s0.value = False
            self.s1.value = True
        elif freq_mode == 2:  # 100%
            self.s0.value = True
            self.s1.value = False
        else:
            self.s0.value = True
            self.s1.value = True

    def set_color_channel(self, color):
        if color == 'R':
            self.s2.value = False
            self.s3.value = False
        elif color == 'G':
            self.s2.value = True
            self.s3.value = True
        elif color == 'B':
            self.s2.value = False
            self.s3.value = True
    
    def calibrate_white(self, out_pin):
        """执行白平衡校准,获取白色表面的RGB频率值"""
        text_label.text = "White balance calibration...\nPoint sensor at white surface"
        display.refresh()
        time.sleep(2)
        
        # 测量白色表面的RGB频率
        self.set_color_channel('R')
        time.sleep(0.2)
        self.white_r = measure_frequency(out_pin)
        
        self.set_color_channel('G')
        time.sleep(0.2)
        self.white_g = measure_frequency(out_pin)
        
        self.set_color_channel('B')
        time.sleep(0.2)
        self.white_b = measure_frequency(out_pin)
        
        print(f"White balance calibration complete: R={self.white_r}, G={self.white_g}, B={self.white_b}")
        
    def frequency_to_rgb(self, freq_r, freq_g, freq_b):
        """将频率值转换为RGB(0-255)值"""
        # 避免除以0
        if self.white_r <= 0: self.white_r = 1
        if self.white_g <= 0: self.white_g = 1
        if self.white_b <= 0: self.white_b = 1
        
        # 计算相对值并转换为0-255范围
        r = min(255, int(255 * freq_r / self.white_r))
        g = min(255, int(255 * freq_g / self.white_g))
        b = min(255, int(255 * freq_b / self.white_b))
        
        # 颜色校正:增强颜色饱和度,减少偏色
        # 找出最大值
        max_val = max(r, g, b)
        if max_val > 0:
            # 计算阈值,低于阈值的颜色分量设为0
            threshold = max_val * self.color_threshold
            if g < threshold: g = 0
            if b < threshold: b = 0
            if r < threshold: r = 0
        
        return (r, g, b)
    
    def adjust_color_threshold(self, delta):
        """调整颜色阈值参数"""
        self.color_threshold = max(0.05, min(0.5, self.color_threshold + delta))
        print(f"Color threshold adjusted to: {self.color_threshold:.2f}")
        return self.color_threshold


# -----------------------
# 频率测量 (保持原样,未修改)
# -----------------------
NUM_CYCLES = 10

def measure_frequency(out_pin):
    timestamps = []
    last = out_pin.value
    while len(timestamps) < NUM_CYCLES:
        current = out_pin.value
        if current != last:
            timestamps.append(time.monotonic_ns())
            last = current

    periods = []
    for i in range(2, len(timestamps), 2):
        period = timestamps[i] - timestamps[i-2]
        periods.append(period)

    avg_period = sum(periods) / len(periods)
    return 1_000_000_000 / avg_period


# -----------------------
# 蜂鸣器逻辑 (新增部分)
# -----------------------
# 初始化PWM输出到蜂鸣器引脚 (修改为 D13)
buzzer = pwmio.PWMOut(board.D13, duty_cycle=0, frequency=440, variable_frequency=True)

# 使用蜂鸣器播放声调
def play_tone(frequency, duration):
    """播放指定频率和持续时间的音调"""
    if frequency == 0:
        # 静音
        buzzer.duty_cycle = 0
    else:
        # 设置频率并启动声音
        buzzer.frequency = frequency
        buzzer.duty_cycle = 32768  # 50%占空比
    time.sleep(duration) # 注意:这里会暂停程序运行
    # 停止声音
    buzzer.duty_cycle = 0
    # 音符间短暂停顿
    time.sleep(0.05)

# 不同的颜色发出不同的音调 (频率表)
yifu_f = [
    262, 360, 460, 569, 760, 860, 960,
    1060, 1160, 1260, 1360, 1460, 1560, 1660
]


# -----------------------
# TFT 显示设置 (保持原样)
# -----------------------
display = board.DISPLAY
splash = displayio.Group()
display.root_group = splash

text_label = label.Label(
    font=terminalio.FONT,
    text="Initializing...",
    color=0xFFFFFF,
    scale=1,
    x=10,
    y=30
)
splash.append(text_label)

# 用于显示颜色的矩形块
color_bitmap = displayio.Bitmap(60, 60, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x000000  # 初始黑色

color_tilegrid = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=150, y=20)
splash.append(color_tilegrid)


# -----------------------
# 硬件引脚分配 (保持原样)
# -----------------------
s0 = digitalio.DigitalInOut(board.D3)
s1 = digitalio.DigitalInOut(board.D4)
s2 = digitalio.DigitalInOut(board.D5)
s3 = digitalio.DigitalInOut(board.D6)
out_pin = digitalio.DigitalInOut(board.D9)

# 校准按键 (D1)
calibrate_button = digitalio.DigitalInOut(board.D1)
calibrate_button.direction = digitalio.Direction.INPUT

# 调整颜色阈值按键 (D2)
adjust_button = digitalio.DigitalInOut(board.D2)
adjust_button.direction = digitalio.Direction.INPUT

sensor = TCS3200(s0, s1, s2, s3, out_pin)
sensor.set_frequency(2)  # 100% 模式

# 初始化NeoPixel
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixel.brightness = 0.3


# -----------------------
# 主循环
# -----------------------

# 开机启动音效
for f in yifu_f:
    play_tone(f, 0.1)

# 启动时执行白平衡校准
sensor.calibrate_white(out_pin)
text_label.text = "Calibration complete\nStarting color recognition...\nPress D1 (HIGH) to recalibrate\nPress D2 (HIGH) to adjust color"
display.refresh()
time.sleep(1)

# 主循环
while True:
    # 检查是否需要重新校准
    if calibrate_button.value:
        time.sleep(0.05)
        if calibrate_button.value:
            sensor.calibrate_white(out_pin)
            text_label.text = "Calibration complete\nStarting color recognition...\nPress D1 (HIGH) to recalibrate\nPress D2 (HIGH) to adjust color"
            display.refresh()
            while calibrate_button.value:
                time.sleep(0.01)
            time.sleep(0.5)
    
    # 检查是否需要调整颜色阈值
    if adjust_button.value:
        time.sleep(0.05)
        if adjust_button.value:
            sensor.adjust_color_threshold(0.05)
            while adjust_button.value:
                time.sleep(0.01)
            time.sleep(0.5)
    
    # --- 1. 颜色读取 (保持原样) ---
    sensor.set_color_channel('R')
    time.sleep(0.05)
    freq_r = measure_frequency(out_pin)

    sensor.set_color_channel('G')
    time.sleep(0.05)
    freq_g = measure_frequency(out_pin)

    sensor.set_color_channel('B')
    time.sleep(0.05)
    freq_b = measure_frequency(out_pin)

    # --- 2. 数据转换 (保持原样) ---
    r, g, b = sensor.frequency_to_rgb(freq_r, freq_g, freq_b)

    # --- 3. 更新显示数据 (保持原样) ---
    color_palette[0] = (r << 16) | (g << 8) | b
    pixel.fill((r, g, b))

    text_label.text = (
        f"Frequency:   RGB Value\n"
        f"R: {freq_r:6.1f}    {r:3}\n"
        f"G: {freq_g:6.1f}    {g:3}\n"
        f"B: {freq_b:6.1f}    {b:3}\n"
        f"Threshold: {sensor.color_threshold:.2f}\n"
        f"D1(HIGH):cal D2(HIGH):adj"
    )

    # --- 4. 立即刷新屏幕---
    display.refresh()

    # --- 5. 蜂鸣器发声---
    # 计算音调索引
    rgb_sum = r + g + b
    yf = int((rgb_sum / 765) * 14)
    if yf > 13: yf = 13
    if yf < 0: yf = 0
    
    play_tone(yifu_f[yf], 0.2) # 这个函数会阻塞程序0.25秒
    
    # --- 6. 循环末尾延时 ---
    # 原代码有0.1s延时,加上play_tone的0.25s,总循环变慢是正常的。
    time.sleep(0.1)



助工
2025-12-08 11:37:39     打赏
2楼

很系统 很全面,感谢~~


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]