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

共2条 1/1 1 跳转至

【Let'sdo第3期-拾色播放器DIY】—成果分享贴

助工
2025-12-13 22:59:01     打赏

上期我们完成开发环境的搭建与测试,本次我们完成官方给布置的任务,任务如下:


任务一:

1、实现颜色传感器驱动,按键获取当前颜色数据并可以通过串口或屏幕进行打印;

2、实现板载RGB的全彩控制,实时显示当前获取的颜色;

3、实现蜂鸣器驱动,并播放八阶音符;


颜色传感器使用的是TCS3200我们需要把传感器获取到的颜色,经过处理后,显示在屏幕上。这一步很简单。

我们这次依然需要把屏幕的驱动库放入到lib文件夹中,这样我们将获取到的R,G,B的颜色值显示在屏幕上。

7.png


拷贝资料包关于TCS3200的驱动文件和程序,在程序上添加获取的RGB值并通过屏幕显示出来

硬件接线图如下

9.png

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


# ===============================
#     TCS3200 类
# ===============================
class TCS3200:
    NUM_CYCLES = 10  # 测试多少个周期

    def __init__(self, s0, s1, s2, s3, out):
        self.s0 = digitalio.DigitalInOut(s0)
        self.s1 = digitalio.DigitalInOut(s1)
        self.s2 = digitalio.DigitalInOut(s2)
        self.s3 = digitalio.DigitalInOut(s3)
        self.out = digitalio.DigitalInOut(out)

        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.set_frequency_scaling(100)

        # 白平衡系数(默认 1)
        self.r_scal = 1.0
        self.g_scal = 1.0
        self.b_scal = 1.0

        print("TCS3200 初始化完成")

    def set_frequency_scaling(self, scaling):
        if scaling == 2:
            self.s0.value = False
            self.s1.value = True
        elif scaling == 20:
            self.s0.value = True
            self.s1.value = False
        elif scaling == 100:
            self.s0.value = True
            self.s1.value = True
        else:
            self.s0.value = False
            self.s1.value = False
        time.sleep(0.01)

    def set_color_filter(self, filter_type):
        if filter_type == "Red":
            self.s2.value = False
            self.s3.value = False
        elif filter_type == "Green":
            self.s2.value = True
            self.s3.value = True
        elif filter_type == "Blue":
            self.s2.value = False
            self.s3.value = True
        else:  # Clear
            self.s2.value = True
            self.s3.value = False
        time.sleep(0.01)

    def measure_frequency(self):
        timestamps = []
        last_state = self.out.value

        while len(timestamps) < self.NUM_CYCLES:
            state = self.out.value
            if state != last_state:
                timestamps.append(time.monotonic_ns())
                last_state = state

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

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

    def read_rgb_freq(self):
        self.set_color_filter("Red")
        r = self.measure_frequency()

        self.set_color_filter("Green")
        g = self.measure_frequency()

        self.set_color_filter("Blue")
        b = self.measure_frequency()

        return r, g, b

    def read_rgb(self):
        r_f, g_f, b_f = self.read_rgb_freq()

        r = int(r_f / 70 * self.r_scal)
        g = int(g_f / 70 * self.g_scal)
        b = int(b_f / 70 * self.b_scal)

        r = max(0, min(255, r))
        g = max(0, min(255, g))
        b = max(0, min(255, b))

        return r, g, b, r_f, g_f, b_f


# ===============================
#   LED & 补光灯
# ===============================
led0 = digitalio.DigitalInOut(board.LED)
led0.direction = digitalio.Direction.OUTPUT

led1 = digitalio.DigitalInOut(board.D5)
led1.direction = digitalio.Direction.OUTPUT
led1.value = True   # 补光灯常亮


# ===============================
#   显示屏
# ===============================
display = board.DISPLAY
splash = displayio.Group()
display.root_group = splash

text_l = label.Label(terminalio.FONT, text="Val      Freq", color=0xFFFFFF, x=40, y=20)
text_r = label.Label(terminalio.FONT, text="R:", color=0xFF0000, x=40, y=40)
text_g = label.Label(terminalio.FONT, text="G:", color=0x00FF00, x=40, y=60)
text_b = label.Label(terminalio.FONT, text="B:", color=0x0000FF, x=40, y=80)

for t in (text_l, text_r, text_g, text_b):
    splash.append(t)


# ===============================
#   初始化 TCS3200
# ===============================
color_sensor = TCS3200(
    s0=board.D12,
    s1=board.D11,
    s2=board.D10,
    s3=board.D9,
    out=board.D6,
)


# ===============================
#   主循环:上电即测量
# ===============================
while True:
    led0.value = True

    try:
        R, G, B, Rf, Gf, Bf = color_sensor.read_rgb()

        text_r.text = "R: {:3d}  {:.2f}".format(R, Rf)
        text_g.text = "G: {:3d}  {:.2f}".format(G, Gf)
        text_b.text = "B: {:3d}  {:.2f}".format(B, Bf)

        print(f"RGB = {R},{G},{B}")
        print(f"Freq = {Rf:.2f},{Gf:.2f},{Bf:.2f}")

    except Exception as e:
        print("读取失败:", e)

    led0.value = False
    time.sleep(0.5)

程序的效果是运行之后不断的将TCS3200获取到RGB数值的数值显示在屏幕上

1765603084927700.jpg

此时任务1.1完成


接下来结合我们测试的程序和TCS3200的驱动程序合并,实现任务1.2:实现板载RGB的全彩控制,实时显示当前获取的颜色;

这是我们了解TCS3200需要在初始化的时候做一个白平衡校准,通过校准后在测量我们目标的颜色,这里我是使用3D打印材料的色卡实现颜色的校准和颜色的识别。

多种颜色的色卡

1765603485142942.jpg

另外我在看DFROBOT官方对于TCS3200的传感器使用建议上,有一段话值得我们去注意:

8.png

1765603485319578.jpg

因此我设计了一个黑色的空心盒,这样既可以稳定获取颜色的数值,还可以减少外部光源的干扰,从而提升获取颜色的稳定性。


在开发之前我们先了解以下什么是三原色和白平衡

三原色原理:物体颜色实际上是物体表面吸收部分有色成分,反射另一部分有色光在人眼中的反应。白色是由各种频率的可见光混合在一起构成的。根据三原色理论可知,各种颜色是由不同比例的三原色(红、绿、蓝)混合而成的。对于TCS3200来说,当选定一个颜色滤波器时, 允许某种特定的原色通过,阻止其它原色的通过,从而得到单一原色的值。

白平衡:从理论上讲,白色是由等量的红色、绿色和蓝色混合而成的;但实际上,白色中的三原色并不完全相等,并且对于TCS3200的光传感器来说,其对这三种基本色的敏感性是不相同的,导致RGB输出并不相等,测试前必须进行白平衡调整,使得TCS3200获取检测的“白色”中的三原色的值。

因此我们程序需要先对传感器进行白平衡的校准,然后在对目标颜色进行有效的识别。

具体程序如下:

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

# ==============================
# 初始化硬件
# ==============================
# Neo三色灯
try:
    pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
    pixel.brightness = 0.3
except Exception as e:
    print(f"NeoPixel initialization failed: {e}")
    pixel = None

# 蜂鸣器PWM
try:
    pwm = pwmio.PWMOut(board.D13, duty_cycle=65535, frequency=440, variable_frequency=True)
except Exception as e:
    print(f"PWM initialization failed: {e}")
    pwm = None

# ==============================
# 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

# 初始化TCS3200传感器
try:
    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)
except Exception as e:
    print(f"TCS3200 initialization failed: {e}")
    raise

# TCS3200板载补光LED (D5) —— 上电默认关闭
try:
    led = digitalio.DigitalInOut(board.D5)
    led.direction = digitalio.Direction.OUTPUT
    led.value = False  # 上电即关闭补光LED
except Exception as e:
    print(f"LED initialization failed: {e}")
    led = None

# ==============================
# 显示初始化
# ==============================
try:
    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)
except Exception as e:
    print(f"Display initialization failed: {e}")
    raise

# 按键初始化
try:
    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_button = digitalio.DigitalInOut(board.D1)
    d1_button.switch_to_input(pull=digitalio.Pull.DOWN)

    sda_button = digitalio.DigitalInOut(board.SDA)
    sda_button.switch_to_input(pull=digitalio.Pull.DOWN)
except Exception as e:
    print(f"Button initialization failed: {e}")
    raise

# ==============================
# 白卡参考值
# ==============================
white_r = 620.0
white_g = 420.0
white_b = 300.0

def measure_avg(channel, n=5):
    """测量平均频率"""
    vals = []
    for _ in range(n):
        try:
            tcs.set_color_channel(channel)
            f = tcs.measure_frequency(duration=0.08)
            if f > 5:
                vals.append(f)
            time.sleep(0.02)
        except Exception as e:
            print(f"Measurement error: {e}")
            continue
    return sum(vals) / len(vals) if vals else 0

# ==============================
# 颜色转换
# ==============================
def freq_to_rgb(fr, fg, fb):
    """将频率转换为RGB值"""
    # 白平衡校正
    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
    
    # 应用伽马校正以改善颜色感知
    gamma = 1.8  # 伽马校正值
    r_gamma = pow(r_wb, gamma)
    g_gamma = pow(g_wb, gamma)
    b_gamma = pow(b_wb, gamma)
    
    # 归一化到0-255范围
    max_val = max(r_gamma, g_gamma, b_gamma, 0.001)
    r = min(255, int(255 * r_gamma / max_val))
    g = min(255, int(255 * g_gamma / max_val))
    b = min(255, int(255 * b_gamma / max_val))

    return r, g, b

# ==============================
# 白卡校准(D0长按)
# ==============================
def calibrate_white():
    """白卡校准函数 - 长按D0执行"""
    global white_r, white_g, white_b
    
    # 打开LED灯
    if led:
        led.value = True
    text_label.text = "LED ON\nPlace WHITE card\nCalibrating..."
    if pixel:
        pixel.fill((255, 255, 255))
    print("LED turned ON for calibration")
    
    time.sleep(2)  # 等待用户放置白卡
    
    # 测量白卡RGB频率
    fr = measure_avg('R')
    fg = measure_avg('G')
    fb = measure_avg('B')
    
    # 更新全局白卡参考值
    if fr > 10:
        white_r = fr
    if fg > 10:
        white_g = fg
    if fb > 10:
        white_b = fb
    
    # 关闭LED灯
    if led:
        led.value = False
    text_label.text = f"Calibrated!\nR{int(white_r)} G{int(white_g)} B{int(white_b)}\nLED OFF"
    if pixel:
        pixel.fill((0, 0, 0))
    print(f"White calibrated: R={white_r:.1f}, G={white_g:.1f}, B={white_b:.1f}")
    print("LED turned OFF after calibration")
    
    time.sleep(2)  # 显示结果
    # 打开LED灯
    if led:
        led.value = True

# ==============================
# 音符控制(更新为8个精确音符)
# ==============================
def play_note(note_index):
    """播放音符(C4 到 C5,共8个音)"""
    # 精确频率(单位:Hz)
    note_frequencies = [
        261.63,  # C4 - 哆
        293.66,  # D4 - 瑞
        329.63,  # E4 - 米
        349.23,  # F4 - 发
        392.00,  # G4 - 嗦
        440.00,  # A4 - 啦
        493.88,  # B4 - 西
        523.25   # C5 - 哆(高八度)
    ]
    
    if pwm and 0 <= note_index < len(note_frequencies):
        try:
            freq = int(note_frequencies[note_index])  # PWM frequency 必须是整数
            pwm.frequency = freq
            pwm.duty_cycle = 2 ** 15  # 约50% 占空比(32768 / 65535)
        except Exception as e:
            print(f"PWM error: {e}")

def stop_playing():
    """停止播放"""
    if pwm:
        try:
            pwm.duty_cycle = 0  # 关闭蜂鸣器
        except Exception as e:
            print(f"Stop playing error: {e}")
# ==============================
# 主循环变量
# ==============================
last_d0_state = True
last_d1_state = True
last_sda_state = False
is_playing = False  # 是否正在播放音符
note_index = 0
note_start_time = 0

# 初始化系统
stop_playing()
print("System started.")
print(f"Initial white ref: R={white_r}, G={white_g}, B={white_b}")

# 显示初始菜单
text_label.text = "D0: Long Calib\nD1: Scan Color\nD2: Play Notes"

while True:
    try:
        # 检查接近开关
        reed_open = reed_switch.value
        if not reed_open:  # 接近物体时进入睡眠模式
            display.brightness = 0.0
            bg_palette[0] = 0x000000
            if pixel:
                pixel.fill((0, 0, 0))
            text_label.text = ""
            time.sleep(0.1)
            continue
        else:  # 没有接近时保持唤醒
            display.brightness = 1.0

        # D0按键处理:长按校准(打开LED -> 校准 -> 关闭LED)
        d0_current = d0_button.value
        if not d0_current and last_d0_state:  # 按下事件
            # 记录按下时间以判断是否长按
            press_start = time.monotonic()
            while not d0_button.value:  # 等待释放
                if time.monotonic() - press_start > 1.0:  # 长按3秒
                    break
                time.sleep(0.01)
            
            press_duration = time.monotonic() - press_start
            if press_duration > 1.0:
                # 长按:执行校准流程
                calibrate_white()
                text_label.text = "D0: Long Calib\nD1: Scan Color\nD2: Play Notes"
        last_d0_state = d0_current

        # D1按键处理:扫描颜色并显示
        d1_current = d1_button.value
        if not d1_current and last_d1_state:  # 按下事件
            text_label.text = "Scanning..."
            
            # 测量RGB频率
            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
                if pixel:
                    pixel.fill((0, 0, 0))
                print("Color: Too dark! (R=0, G=0, B=0)")
            else:
                # 转换为RGB并显示
                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
                # 设置NeoPixel颜色
                if pixel:
                    pixel.fill((r, g, b))
                print(f"Color: R={r}, G={g}, B={b}")
            
            time.sleep(0.5)  # 显示一段时间后恢复菜单
            text_label.text = "D0: Long Calib\nD1: Scan Color\nD2: Play Notes"
        last_d1_state = d1_current

        # SDA 按键处理:循环播放音符(原 D2 功能)
        sda_current = sda_button.value
        if not sda_current and last_sda_state:  # 按下事件
            if not is_playing:
                # 开始播放
                is_playing = True
                note_start_time = time.monotonic()
                note_index = 0
                play_note(note_index)
                note_names = ["C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5"]
                text_label.text = f"Playing: {note_names[note_index]}"
            else:
                # 停止播放
                is_playing = False
                stop_playing()
                text_label.text = "D0: Long Calib\nD1: Scan Color\nSDA: Play Notes"
            time.sleep(0.2)  # 防抖
        last_sda_state = sda_current

        # 如果正在播放音符,处理循环播放逻辑
        if is_playing:
            # 检查当前音符是否播放完毕(0.5秒)
            if time.monotonic() - note_start_time > 0.5:
                # 播放下一个音符
                note_index = (note_index + 1) % 8
                play_note(note_index)
                note_names = ["C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5"]
                text_label.text = f"Playing: {note_names[note_index]}"
                note_start_time = time.monotonic()

        time.sleep(0.01)
    except KeyboardInterrupt:
        print("Program interrupted by user")
        break
    except Exception as e:
        print(f"Error in main loop: {e}")
        time.sleep(0.1)

程序中D0按键长按实现白平衡的校准,校准完毕后会在屏幕上显示校准成功的提示并打开颜色传感器上的照明LED灯

D1按键短按实现目标的颜色扫描,将扫描到的颜色传输到屏幕背景和背部的RGB灯上。

另外接近传感器主要实现音节的播放,当磁铁靠近的时候,不断循环播放7个音节的声音。


以上就是我在EEPW参加的第三期DIY拾色播放器的成功展示,感谢你的耐心阅读与观看,在本次活动学到了传感器的配置与校准,并使用Python语言开发单片机。希望EEPW越办越给力!

DIY拾色播放器-yanqisui.zip

视频演示:https://www.bilibili.com/video/BV1vHmZBgEhH/?vd_source=446f9a5257a45be18ced68a9c54da1ad





关键词: 拾色     播放器     成果分享     Python    

菜鸟
2025-12-15 09:46:00     打赏
2楼

黑盒很棒


共2条 1/1 1 跳转至

回复

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