上期我们完成开发环境的搭建与测试,本次我们完成官方给布置的任务,任务如下:
任务一:
1、实现颜色传感器驱动,按键获取当前颜色数据并可以通过串口或屏幕进行打印;
2、实现板载RGB的全彩控制,实时显示当前获取的颜色;
3、实现蜂鸣器驱动,并播放八阶音符;
颜色传感器使用的是TCS3200我们需要把传感器获取到的颜色,经过处理后,显示在屏幕上。这一步很简单。
我们这次依然需要把屏幕的驱动库放入到lib文件夹中,这样我们将获取到的R,G,B的颜色值显示在屏幕上。

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

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数值的数值显示在屏幕上

此时任务1.1完成
接下来结合我们测试的程序和TCS3200的驱动程序合并,实现任务1.2:实现板载RGB的全彩控制,实时显示当前获取的颜色;
这是我们了解TCS3200需要在初始化的时候做一个白平衡校准,通过校准后在测量我们目标的颜色,这里我是使用3D打印材料的色卡实现颜色的校准和颜色的识别。
多种颜色的色卡

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


因此我设计了一个黑色的空心盒,这样既可以稳定获取颜色的数值,还可以减少外部光源的干扰,从而提升获取颜色的稳定性。
在开发之前我们先了解以下什么是三原色和白平衡
三原色原理:物体颜色实际上是物体表面吸收部分有色成分,反射另一部分有色光在人眼中的反应。白色是由各种频率的可见光混合在一起构成的。根据三原色理论可知,各种颜色是由不同比例的三原色(红、绿、蓝)混合而成的。对于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越办越给力!
视频演示:https://www.bilibili.com/video/BV1vHmZBgEhH/?vd_source=446f9a5257a45be18ced68a9c54da1ad

我要赚赏金
