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

共3条 1/1 1 跳转至

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

助工
2025-12-15 02:04:55     打赏
【Let'sdo第3期-拾色播放器DIY】--成果帖 

一、项目简介

基于 ESP32-S3  系统通过 TCS3200 颜色传感器 读取物体颜色,并实现以下互动:

  • 板载 RGB LED(NeoPixel):同步显示识别到的颜色;

  • 蜂鸣器模块:发出该颜色对应的音符。


a27e6a29-5d71-4311-bffe-4fae45c7c065.png

二、硬件清单



模块功能
ESP32-S3开发板主控制器
TCS3200颜色传感器识别RGB颜色
板载RGB LED(NeoPixel)显示识别到的颜色
DFRobot蜂鸣器模块播放音符




 

三、核心功能

  1. 颜色识别(TCS3200)

  2. RGB视觉反馈(NeoPixel)

  3. 声音输出(蜂鸣器与音阶映射)

主要逻辑流程:

  • 按D0键 

  • 启动检测;

  • TCS3200 取 R,G、B 三通道频率;

  • 判断主色,点亮对应颜色 LED;

  • 根据蜂鸣器不同颜色发声。




 import board
import digitalio
import displayio
import terminalio
import neopixel
import time
import pwmio
from adafruit_display_text import label

pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixel.brightness = 0.3
pwm = pwmio.PWMOut(board.D13, duty_cycle=0, frequency=440, variable_frequency=True)
 
DEBOUNCE_TIME = 0.2
NOTE_DURATION = 0.5

# 简谱音名与频率映射
note_freq = {
    " ": 1,
    "do": 131,   # 低音do
    "re": 147,   # 低音re
    "mi": 165,    # 低音mi
    "fa": 175,    # 低音mi
    "so": 180,    # 低音mi
    "la": 220,    # 低音mi
    "1": 262,   # do
    "2": 294,   # re
    "3": 330,   # mi
    "4": 349,   # fa
    "5": 360,   # so
    "6": 440,   # la
    "7": 494,   # xi
    "1'": 523,  # 高音do
    "2'": 587,  # 高音re
    "3'": 659,  # 高音mi
    "4'": 698,  # 高音fa
    "5'": 784,  # 高音so
}

class TCS3200(object):
    """
    This class reads RGB values from a TCS3200 colour sensor.

    GND   Ground.
    VDD   Supply Voltage (2.7-5.5V)
    LED   1: LEDs on, 0: LEDs off
    /OE   Output enable, active low. When OE is high OUT is disabled
         allowing multiple sensors to share the same OUT line.
    OUT   Output frequency square wave.
    S0/S1 Output frequency scale selection.
    S2/S3 Colour filter selection.

    OUT is a square wave whose frequency is proprtional to the
    intensity of the selected filter colour.

    S2/S3 selects between red, green, blue, and no filter.

    S0/S1 scales the frequency at 100%, 20%, 2% or off.

    To take a reading the colour filters are selected in turn for a
    fraction of a second and the frequency is read and converted to
    Hz.

    Default connections:
    TCS3200 WeMos GPIO
      S0     D3   17
      S1     D4   16
      S2     D8   05
      S3     D5   18
      OUT    D6   19
      LED    D7   23
      OE     GND
    """
    # class variables

    ON  = True  # on for debugging and the leds
    OFF = False # off

    RED   = (0,0) # S2 and S3 low
    BLUE  = (0,1) # S2 low, S3 high
    GREEN = (1,1) # S2 and S3 high
    CLEAR = (1,0) # S2 high and S3 low

    POWER_OFF       = (0,0) # S0 and S1 low
    TWO_PERCENT     = (0,1) # S0 low, S1 high
    TWENTY_PERCENT  = (1,0) # S0 high, S1 low
    HUNDRED_PERCENT = (1,1) # S0 and S1 high

    def __init__(self, OUT=16, S2=5, S3=18, S0=None, S1=None, LED=None,OE=None,timeout=1.0):
        """
        The gpios connected to the sensor OUT, S2, and S3 pins must
        be specified.  The S0, S1 (frequency) and LED and OE (output enable)
        gpios are optional.
        The OE pin is missing on some TCS3200 boards


        self._OUT = Pin(OUT,Pin.IN,Pin.PULL_UP)

        self._S2 = Pin(S2,Pin.OUT)
        self._S3 = Pin(S3,Pin.OUT)

        self._S0  = S0
        self._S1  = S1
        self._OE  = OE
        self._LED = LED


        if S0 and S1 :
            self._S0 = Pin(S0,Pin.OUT)
            self._S1 = Pin(S1,Pin.OUT)

        if LED :
            self._LED = Pin(LED,Pin.OUT)
            self._LED.on()

        if OE :
            self._OE =  Pin(OE,Pin.OUT)

        # define the timer for measurement timeout
        self._tim = Timer(0)
   """
        pinmap = {
            5: board.D5,
            6: board.D6,
            8: board.D8,
            9: board.D9,
            10: board.D10,
            11: board.D11,
            12: board.D12,
            13: board.D13,
            14: board.D14,
            15: board.D15,
            16: board.D16,
            17: board.D17,
            18: board.D18
        }

        self._OUT = digitalio.DigitalInOut(pinmap[OUT])
        self._OUT.direction = digitalio.Direction.INPUT
        self._OUT.pull = digitalio.Pull.UP

        self._S2 = digitalio.DigitalInOut(pinmap[S2])
        self._S2.direction = digitalio.Direction.OUTPUT

        self._S3 = digitalio.DigitalInOut(pinmap[S3])
        self._S3.direction = digitalio.Direction.OUTPUT

        self._S0  = S0
        self._S1  = S1
        self._OE  = OE
        self._LED = LED

        self._timeout = timeout  # 超时时长(秒)
        self._tim_start = None   # 记录定时器启动时间

        if S0 and S1 :
            self._S0 = digitalio.DigitalInOut(pinmap[S0])
            self._S0.direction = digitalio.Direction.OUTPUT
            self._S1 = digitalio.DigitalInOut(pinmap[S1])
            self._S1.direction = digitalio.Direction.OUTPUT

        if LED :
            self._LED = digitalio.DigitalInOut(pinmap[LED])
            self._LED.direction = digitalio.Direction.OUTPUT
            self._LED.value = True  # 点亮LED

        if OE :
            self._OE = digitalio.DigitalInOut(pinmap[OE])
            self._OE.direction = digitalio.Direction.OUTPUT
        self._debug = self.OFF
        self._cycles = 10 # the number of cycles of the out signal for which the time is measured
        self._cycle = 0
        self._freq_div = self.POWER_OFF
        self._start_tick = 0
        self._end_tick = 0
        meas_finished = False

        self._timeout = 2000 # timeout in ms
        
        @property
    def debugging(self) :
        return self._debug

    @debugging.setter
    def debugging(self,onOff) :
        if onOff:
            print("Debugging switched on")
        else :
            print("Debugging switched off")
        self._debug = onOff

    # controls the illumination LEDs
    @property
    def led(self):
        # get the current state of the illumination leds
        return self._LED.value()

    def on(self):
        if self._LED:
            self._LED.value = True

    def off(self):
        if self._LED:
            self._LED.value = False

    @led.setter
    def led(self,onOff):
        if onOff:
            self.on()
        else:
            self.off()

    # sets the filters
    @property
    def filter(self):
        current_setting = (self._S2.value(),self._S3.value())
        if self._debug:
            if current_setting == self.RED:
                print("Red filter is set")
            elif current_setting == self.GREEN:
                print("Green filter is set")
            elif current_setting == self.BLUE :
                print("Blue filter is set")
            else:
                print("No filters are set. The filter setting is clear")
        return current_setting

    @filter.setter
    ###
    def filter(self,filter_setting):
        if self._debug:
            print("Setting S2 to {:d} and S3 to {:d}".format(filter_setting[0],filter_setting[1]))
        self._S2.value(filter_setting[0])
        self._S3.value(filter_setting[1])
    ###
    def filter(self, filter_setting):
        if self._debug:
            print("Setting S2 to {:d} and S3 to {:d}".format(filter_setting[0], filter_setting[1]))
        self._S2.value = filter_setting[0]
        self._S3.value = filter_setting[1]

    # Sets the frequency divider
    @property
    def freq_divider(self):
        """
        if not self._S0 or not self._S1:
            print("S0 or S1 signal is not connected. The frequency divider is therefore fixed")
            return
        current_freq_div = (self._S0.value(),self._S1.value())
        """
        if self._S0 is None or self._S1 is None:
            print("S0 or S1 signal is not connected. The frequency divider is therefore fixed")
            return
        current_freq_div = (self._S0.value, self._S1.value)
        if self._debug:
            if current_freq_div == self.POWER_OFF:
                print("Device set to sleep mode")
            elif current_freq_div == self.TWO_PERCENT:
                print("Frequency divided by a factor 50")
            elif current_freq_div == self.TWENTY_PERCENT:
                print("Frequency divided by a factor 5")
            else:
                print("Frequency at 100%")

        return current_freq_div

    @freq_divider.setter
    def freq_divider(self,freq_div):
        if not self._S0 or not self._S1:
            print("S0 or S1 signal is not connected. The frequency divider is therefore fixed and cannot be set")
            return

        if self._debug:
            print("Setting S0 to {:d} and S1 to {:d}".format(freq_div[0],freq_div[1]))
        #self._S0.value(freq_div[0])
        #self._S1.value(freq_div[1])
        self._S0.value = freq_div[0]
        self._S1.value = freq_div[1]

    # Puts the TCS3200 into sleep mode
    def power_off(self):
        self.freq_divider = self.POWER_OFF

    # defines the number of pulses to wait for in the measurement IRQ handler
    @property
    def cycles(self):
        return self._cycles

    @cycles.setter
    def cycles(self,no_of_cycles):
        if no_of_cycles < 1:
            print("The number of cycles must be at least 1")
            return
        self._cycles = no_of_cycles
        if self._debug:
            print("No of cycles to be measured was set to {:d}".format(self._cycles))

    # Starts the measurement
    @property
    def meas(self):
        if self._debug:
            if self._meas:
                print("Measurement is started")
            else:
                print("Measurement is stopped")
        return self._meas

    def wait_for_rising(pin, callback):
        last_state = pin.value
        while True:
            current_state = pin.value
            if not last_state and current_state:  # 检测上升沿
                callback()
            last_state = current_state
            time.sleep(0.001)  # 1ms轮询
            # 使用方法
            # wait_for_rising(self._OUT, self._cbf)

    def start_timer(self):
        self._tim_start = time.monotonic()

    def timer_expired(self):
        if self._tim_start is None:
            return False
        return (time.monotonic() - self._tim_start) > self._timeout

    def stop_timer(self):
        self._tim_start = None

    def example_usage(self):
        self.start_timer()
        while not self.timer_expired():
            # 执行你的任务
            pass
        print("Timeout reached!")
    @meas.setter
    def meas(self,startStop):
        if startStop:
            self._meas = True
            self._cycle = 0
            self._start_tick = 0
            self._end_tick = 0
            if self._debug:
                print("Measurement handler started")
#            self._OUT.irq(trigger=Pin.IRQ_RISING,handler=self._cbf)
#             wait_for_rising(self._OUT, self._cbf)

            # start the timeout counter
            self._tim.init(period=self._timeout, mode=Timer.ONE_SHOT, callback=self._timeout_handler)
            self._tim_start = time.monotonic()
            """
            用 self._tim_start = time.monotonic() 记录定时器启动时间,替代硬件定时器初始化。
            后续可用 if time.monotonic() - self._tim_start > self._timeout: 判断是否超时。
            如需回调,可在主循环中检测超时并手动调用 self._timeout_handler()。
            """
        else:
            self._meas=False
            self._OUT.irq(trigger=Pin.IRQ_RISING,handler=None)
            if self._debug:
                print("Measurement handler stopped")
            # disarm the timeout
            self._tim.deinit()

    # returns the frequency measured
    @property
    def measured_freq(self):
        duration = self._end_tick - self._start_tick  # measurement duration
        frequency = 1000000 * self._cycles/duration   # duration is measured in us
        return frequency

    @property
    def timeout(self):
        return self-_timeout

    @timeout.setter
    def timeout(self,timeout_ms):
        self._timeout = timeout_ms

    # This is the callback function that measures the time taken by a predefined no of cycles of the out signal
    def _cbf(self,src):
        t = time.ticks_us()
        if self._cycle == 0:
            self._start_tick = t
        if self._cycle >= self._cycles: # the number of cycles has been reached
            self._end_tick = t
            self.meas=self.OFF
            return
        self._cycle += 1

    # The timeout handler raises a timeout exception
    def _timeout_handler(self,src):
        # stop the measurement
        self._OUT.irq(trigger=Pin.IRQ_RISING,handler=None)
        # raise the timeour exception
        raise Exception("Measurement Timeout!")
        
    display = board.DISPLAY
main_group = displayio.Group()
BORDER = 20
FONTSCALE = 2
BACKGROUND_COLOR = 0x000000  # Bright Green
FOREGROUND_COLOR = 0xAA0088  # Purple
TEXT_COLOR = 0xFFFFFF
display = board.DISPLAY
# 整个背景
splash = displayio.Group()


display.root_group = main_group
bg_bitmap = displayio.Bitmap(120, 120, 1)
bg_palette = displayio.Palette(1)
bg_palette[0] = 0x000000
FONTSCALE = 2
bg_sprite = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)
main_group.append(bg_sprite)
text_label = label.Label(
    terminalio.FONT,
    text="EEPW-DigiKey ColorTest",
    color=0xFFFFFF,
    scale=1
)
text_label.anchor_point = (0.5, 0.5)  
text_label.anchored_position = (display.width // 2, display.height // 2)  
main_group.append(text_label)  
reed_switch = digitalio.DigitalInOut(board.D14)  
reed_switch.switch_to_input(pull=digitalio.Pull.UP)  
d0_button = digitalio.DigitalInOut(board.D0)  
d0_button.switch_to_input(pull=digitalio.Pull.UP) 
# 初始化板载按键D1
button1 = digitalio.DigitalInOut(board.D1)
button1.switch_to_input(pull=digitalio.Pull.DOWN)
     
# ==============================
# 白平衡
# ==============================
white_r = 700.0
white_g = 600.0
white_b = 580.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 > 5 else white_r
    white_g = fg if fg > 5 else white_g
    white_b = fb if fb > 5 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_note1(note_index):
    if 0 <= note_index < len(note_names):
        freq = frequencies[note_index]
        pwm.frequency = int(freq)
        pwm.duty_cycle = 2 ** 15
def play_note(note, duration):
    frequency = note_freq.get(note, 440)
    pwm.frequency = frequency
   #  if note.strip() == " ":
#         空音名,不发声,休止
#         time.sleep(duration)
#     else:
    pwm.duty_cycle  = 2 ** 15 # = 32768  # 50% 占空比
    time.sleep(duration*2)
    pwm.duty_cycle = 0
    time.sleep(0.05)
def stop_playing():
    pwm.duty_cycle = 0
    
def color_to_note_name(r, g, b):
    # 先判断是否为休止(太暗)
    if max(r, g, b) < 10:
        return " "
    # 细化分量法,优先级:高音 > 中音 > 低音
    # 你可以根据实际需要调整阈值
    if r > 220:
        return "1'"   # 高音do
    elif r > 180:
        return "7"    # xi
    elif r > 150:
        return "6"    # la
    elif r > 120:
        return "5"    # so
    elif r > 90:
        return "4"    # fa
    elif r > 60:
        return "3"    # mi
    elif r > 30:
        return "2"    # re
    elif r > 10:
        return "1"    # do

    if g > 220:
        return "2'"   # 高音re
    elif g > 180:
        return "7"    # xi
    elif g > 150:
        return "6"    # la
    elif g > 120:
        return "5"    # so
    elif g > 90:
        return "4"    # fa
    elif g > 60:
        return "3"    # mi
    elif g > 30:
        return "2"    # re

    if b > 220:
        return "3'"   # 高音mi
    elif b > 180:
        return "7"    # xi
    elif b > 150:
        return "6"    # la
    elif b > 120:
        return "5"    # so
    elif b > 90:
        return "4"    # fa
    elif b > 60:
        return "3"    # mi
    elif b > 30:
        return "2"    # re

    return " "  # 其它情况休止

def rgb_to_note_name(r, g, b):
    # 归一化
    rf, gf, bf = r/255, g/255, b/255
    h, s, v = colorsys.rgb_to_hsv(rf, gf, bf)
    # h范围0~1,分为N段
    note_names = ["1", "2", "3", "4", "5", "6", "7", "1'"]
    idx = int(h * len(note_names)) % len(note_names)
    return note_names[idx]    
    
    print("TCS3200 上电 EEPW-DigiKey ColorTest.")

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 button1.value:
           print("D1") 
           time.sleep(0.02)
           led.value = not led.value
           time.sleep(0.02)

        if not d0_current:  # 按下 D0
            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:
                    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
        if pending_d0_click and (time.monotonic() - last_d0_press > D0_DOUBLE_DELAY):
            pending_d0_click = False
            text_label.text = "watting..."
            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 = "repeat Test or open Led"
                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}")
                
                  r, g, b = freq_to_rgb(fr, fg, fb)
                  text_label.text = f"R:{r}\nG:{g}\nB:{b}"
                  text_label.anchor_point = (0.5, 1.0)
                  text_label.anchored_position = (display.width - 70, display.height //2)
                  bg_palette[0] = (r << 16) | (g << 8) | b
                  pixel.fill((r, g, b))
                  print(f"Color: R={r}, G={g}, B={b}")
                  note_name = color_to_note_name(r, g, b)
                  freq = note_freq[note_name]
                  print(f"检测到颜色(R={r},G={g},B={b}),对应音名:{note_name},频率:{freq}Hz")
                  play_note1(note_index) #(note_index,0.5)
                  time.sleep(DEBOUNCE_TIME)
                  stop_playing()
         
    else:
        time.sleep(0.1)
    time.sleep(0.01)

 拾色播放器DIY






关键词: TCS3200     颜色    

菜鸟
2025-12-15 09:01:21     打赏
2楼

找到拖延症伙伴了,共勉


专家
2025-12-15 09:09:28     打赏
3楼

谢谢分享


共3条 1/1 1 跳转至

回复

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