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

共1条 1/1 1 跳转至

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

助工
2025-12-12 14:24:05     打赏

感谢 EEPW 和 DigilKey 给予的这次宝贵学习机会!

1.     项目简介

 【项目名称】DIY拾色播放器:

【任务目标】构建一个能“看见”颜色并“唱出”音符的智能设备。

 【主线任务】

1)征服颜色传感器,按键取色,串口/屏幕打印数据;

2)点亮板载RGB,让它实时反映世界的色彩;

3)驱动蜂鸣器,精准还原Do-Re-Mi……

 

2.     硬件介绍

    1)     开发板 - ESP32-S3 Rev TFT FEATHER,带有彩色LCD屏幕

d653c57a-bbe7-4ab1-9540-d5c63ef53209.png

    2)     颜色传感器 - TCS3200 RGB COLOR SENSOR BOARD

bf9c15b1-fe1d-4b78-9c60-c297d1468140.png

    3)     音频 - DIGITAL BUZZER MODULE (自备,低电平触发)


 96a05187-aab1-4f42-b7e4-7fbb074ed23c.png

3.     硬件结构和接线

 aa440526-80ad-4c12-87ba-9e7203b36921.png

          1)开发板是基于 esp32-s3的 Adafruit Rev TFT Feather,是系统的控制核心。

2)颜色传感器四个引脚S0-S3接收mcu的控制信号,OUT引脚将感知到的颜色频率输出给mcu;同时颜色传感器板上有4个LED补光灯,板子LED引脚接收mcu的高电平,补光LED亮,反之则LED灭。

3)蜂鸣器接受mcu一个控制信号,低电平时蜂鸣器鸣叫。为了输出不同的音符,控制信号采用PWM输出,频率不同代表不同的音符。

4)板子上内置240x135分辨率TFT屏,以及三个按钮D0~D2。

5)三色灯NeoPixel接收两个GPIO控制信号:一个控制电源PWR,另一个控制输出灯的三元色 Pixel

 

4.     开发环境和用到的库

使用在线的 CircuitPython IDE:https://urfdvw.github.io/circuitpython-online-ide-2/


 d29157c2-006c-4f0a-bd3f-3d30af3e8633.png

用到的第三方库,放到 开发板所在盘符的lib下即可,如下图:

e3b0b8ac-fe4f-40e9-b4c4-f1c79d4899fb.png


然后在盘符根目录下的 code.py 中写入代码即可。

 

5.     软件设计

思路:

1)开机先初始化各个外设,完成后屏幕显示 Ready。

2)长按D0按钮,开始白平衡校准,完成后屏幕显示新的R、G、B校准值。

3) 单击D0按钮,开始获取TCS3200的频率值,并转化为RGB显示在屏幕上,并将屏幕的背景色设置为识别出的颜色,Neo三色灯输出对应颜色。

4)双击D0按钮,输出D5信号,切换颜色传感器板上的补光LED灯亮灭。

5)单击D2按钮,D13输出PWM信号,输出不同的频率依次循环播放Do-Re-Mi……8个音符。

 

完整代码:(参考学习 lllde大神的帖子 并修改适配,在此表示感谢!)

import time
import digitalio
import board
import displayio
import terminalio
from adafruit_display_text import label
import neopixel
import pwmio
# ==============================
# 初始化硬件
# ==============================
# Neo三色灯
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixel.brightness = 0.3
# 蜂鸣器PWM
pwm = pwmio.PWMOut(board.D13, duty_cycle=65535, frequency=440, variable_frequency=True)
note_names = ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5']
frequencies = [261, 293, 329, 349, 392, 440, 493, 523]
DEBOUNCE_TIME = 0.2
NOTE_DURATION = 0.5

# ==============================
# 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
        for pin in (s0, s1, s2, s3):
            pin.direction = digitalio.Direction.OUTPUT
        out_pin.direction = digitalio.Direction.INPUT
    def set_frequency(self, mode):
        self.s0.value = (mode >> 1) & 1
        self.s1.value = mode & 1
    def set_color_channel(self, color):
        if color == 'R':
            self.s2.value, self.s3.value = False, False
        elif color == 'G':
            self.s2.value, self.s3.value = True, True
        elif color == 'B':
            self.s2.value, self.s3.value = False, True
    def measure_frequency(self, duration=0.08):
        start = time.monotonic()
        count = 0
        last = self.out.value
        while (time.monotonic() - start) < duration:
            cur = self.out.value
            if cur != last:
                count += 1
                last = cur
        return (count / 2) / duration if duration > 0 else 0
tcs = TCS3200(
    s0=digitalio.DigitalInOut(board.D12),
    s1=digitalio.DigitalInOut(board.D11),
    s2=digitalio.DigitalInOut(board.D10),
    s3=digitalio.DigitalInOut(board.D9),
    out_pin=digitalio.DigitalInOut(board.D6)
)
tcs.set_frequency(1)

# TCS3200 板载 补光LED (D5) —— 上电默认关闭
led = digitalio.DigitalInOut(board.D5)
led.direction = digitalio.Direction.OUTPUT
led.value = False  #  上电即关闭补光LED

# ==============================
# 显示初始化
# ==============================
display = board.DISPLAY
main_group = displayio.Group()
display.root_group = main_group
bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
bg_palette = displayio.Palette(1)
bg_palette[0] = 0x000000
bg_sprite = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)
main_group.append(bg_sprite)
text_label = label.Label(
    terminalio.FONT,
    text="Ready",
    color=0xFFFFFF,
    scale=2
)
text_label.anchor_point = (0.5, 0.5)
text_label.anchored_position = (display.width // 2, display.height // 2)
main_group.append(text_label)
#接近开关D14
reed_switch = digitalio.DigitalInOut(board.D14)
reed_switch.switch_to_input(pull=digitalio.Pull.UP)
#按钮D0、D2
d0_button = digitalio.DigitalInOut(board.D0)
d0_button.switch_to_input(pull=digitalio.Pull.UP)
d2_button = digitalio.DigitalInOut(board.D2)
d2_button.switch_to_input(pull=digitalio.Pull.DOWN)

# ==============================
# 白卡参考值
# ==============================
white_r = 620.0
white_g = 420.0
white_b = 300.0
def measure_avg(channel, n=3):
    vals = []
    for _ in range(n):
        tcs.set_color_channel(channel)
        f = tcs.measure_frequency(duration=0.08)
        if f > 5:
            vals.append(f)
        time.sleep(0.02)
    return sum(vals) / len(vals) if vals else 0
# ==============================
# 颜色转换
# ==============================
def freq_to_rgb(fr, fg, fb):
    r_wb = fr / white_r if white_r > 10 else 0
    g_wb = fg / white_g if white_g > 10 else 0
    b_wb = fb / white_b if white_b > 10 else 0
    max_val = max(r_wb, g_wb, b_wb, 0.001)
    r = min(255, int(255 * r_wb / max_val))
    g = min(255, int(255 * g_wb / max_val))
    b = min(255, int(255 * b_wb / max_val))
    return r, g, b
# ==============================
# 白卡校准
# ==============================
def calibrate_white():
    global white_r, white_g, white_b
    print("Place WHITE card over sensor. Calibrating in 2s...")
    text_label.text = "Put WHITE card\nCalibrating..."
    pixel.fill((255, 255, 255))
    time.sleep(2)
    fr = measure_avg('R')
    fg = measure_avg('G')
    fb = measure_avg('B')
    white_r = fr if fr > 10 else white_r
    white_g = fg if fg > 10 else white_g
    white_b = fb if fb > 10 else white_b
    print(f" White calibrated:\n   R={white_r:.1f}, G={white_g:.1f}, B={white_b:.1f}")
    text_label.text = f"White OK!\nR{int(white_r)} G{int(white_g)} B{int(white_b)}"
    pixel.fill((255, 255, 0))
    time.sleep(2)
# ==============================
# 音符控制
# ==============================
def play_note(note_index):
    if 0 <= note_index < len(note_names):
        freq = frequencies[note_index]
        pwm.frequency = int(freq)
        pwm.duty_cycle = 2 ** 15
def stop_playing():
    pwm.duty_cycle = 2**16-1   #蜂鸣器为:低电平触发
    
# ==============================
# 电源管理
# ==============================
def enter_sleep_mode():
    global is_awake
    if is_awake:
        display.brightness = 0.0
        bg_palette[0] = 0x000000
        pixel.fill((0, 0, 0))
        text_label.text = ""
        is_awake = False
        # 注意:不操作 led.value,保持用户设置
def enter_wake_mode():
    global is_awake
    if not is_awake:
        display.brightness = 1.0
        bg_palette[0] = 0x000000
        pixel.fill((0, 0, 0))
        text_label.text = "D0: Scan\nLong(3s): Calib\nDouble: LED"
        is_awake = True
        
# ==============================
# 主循环
# ==============================
is_awake = True
note_index = 0
last_d2_state = False
note_start_time = time.monotonic()
last_d0_press = 0
pending_d0_click = False
d0_press_start = None
D0_DOUBLE_DELAY = 0.3
D0_LONG_PRESS_TIME = 3.0
print("System started.")
print(f"Initial white ref: R={white_r}, G={white_g}, B={white_b}")
# 初始进入唤醒状态(但 LED 保持关闭)
enter_wake_mode()
while True:
    reed_open = reed_switch.value
    if reed_open:
        enter_wake_mode()
    else:
        enter_sleep_mode()
        
    if is_awake:
        d0_current = d0_button.value
        if not d0_current:  
            d0_press_start = time.monotonic()
            while not d0_button.value:
                if time.monotonic() - d0_press_start > D0_LONG_PRESS_TIME:
                    break
            press_duration = time.monotonic() - d0_press_start
            d0_press_start = None
            if press_duration > D0_LONG_PRESS_TIME:
                calibrate_white()
                text_label.text = "D0: Scan\nLong(3s): Calib\nDouble: LED"
            else:
                now = time.monotonic()
                if pending_d0_click:  #D0双击
                    pending_d0_click = False
                    led.value = not led.value  # 切换 TCS3200 LED
                    status = "ON" if led.value else "OFF"
                    text_label.text = f"TCS LED {status}"
                    #  No NeoPixel feedback for LED toggle
                    time.sleep(0.3)
                else:
                    last_d0_press = now
                    pending_d0_click = True
        
        #单击D0,识别颜色            
        if pending_d0_click and (time.monotonic() - last_d0_press > D0_DOUBLE_DELAY):
            pending_d0_click = False
            text_label.text = "Scanning..."
            fr = measure_avg('R')
            fg = measure_avg('G')
            fb = measure_avg('B')
            if fr < 10 and fg < 10 and fb < 10:
                r, g, b = 0, 0, 0
                text_label.text = "Too dark!"
                bg_palette[0] = 0x000000
                pixel.fill((0, 0, 0))
                print("Color: Too dark! (R=0, G=0, B=0)")
            else:
                r, g, b = freq_to_rgb(fr, fg, fb)
                text_label.text = f"R:{r}\nG:{g}\nB:{b}"
                bg_palette[0] = (r << 16) | (g << 8) | b
                pixel.fill((r, g, b))
                print(f"Color: R={r}, G={g}, B={b}")
                
        # --- D2 音阶测试 ---
        d2_current = d2_button.value
        if d2_current and not last_d2_state:
            stop_playing()
            play_note(note_index)
            note_index = (note_index + 1) % len(note_names)
            note_start_time = time.monotonic()
            time.sleep(DEBOUNCE_TIME)
        if time.monotonic() - note_start_time > NOTE_DURATION:
            stop_playing()
            note_start_time = time.monotonic()
        last_d2_state = d2_current
    else:
        time.sleep(0.1)
    time.sleep(0.01)

 

6.     实际效果演示

上视频:(b站视频待审核)





关键词: 拾色     播放器     TCS3200     蜂鸣器     NeoPix    

共1条 1/1 1 跳转至

回复

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