零知开源——基于STM32F407VET6的TCS230颜色识别器设计与实现
✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com
目录
一、硬件系统部分
二、软件架构设计
三、操作过程及数据展示
四、TCS230技术原理
五、常见问题指引
六、结论
(1)项目概述
本项目基于STM32F407VET6主控芯片的零知增强板,结合TCS230高精度颜色传感器和ST7789显示屏,开发了一套专业的颜色识别系统。系统通过创新的独立通道校准算法,实现了对RGB颜色的高精度识别,并能将检测结果以直观的UI界面展示,同时提供标准的HEX颜色代码输出。项目优化了颜色识别过程中的通道干扰问题和校准流程。
(2)项目亮点
>采用TCS230传感器,支持1600万色识别
>RGB三通道独立校准,解决通道干扰问题
>分区显示传感器数据和检测结果
>带错误检测的三步校准流程
>多次采样平均算法,抗干扰能力强
(3)项目难点及解决方案
问题描述:在识别纯色时,其他通道值偏高(如识别绿色时红蓝值偏高)
解决方案:
>独立通道校准算法
>多次采样平均值滤波
>通道干扰补偿机制
一、硬件系统部分
1.1 硬件清单
组件 | 型号 | 数量 |
---|---|---|
主控板 | 零知增强板(STM32F407VET6) | 1 |
颜色传感器 | TCS230 | 1 |
显示屏 | ST7789 (240x320) | 1 |
按钮 | 轻触开关 | 1 |
杜邦线 | 20cm | 若干 |
1.2 接线方案
TCS230传感器 | ST7789显示屏(SPI) | 零知增强板 |
---|---|---|
VCC | VCC | 5V / 3.3V |
GND | GND | GND |
/ | SCL | 52 (SPI1 SCL) |
/ | SDA | 51 (SPI1 MOSI) |
/ | RES | 47 |
/ | DC | 49 |
/ | CS | 53 |
S2 | / | 10 |
S3 | / | 11 |
OUT | / | 12 |
按钮 | / | 13 |
ps:TCS230传感器S1、S0接5V电源,OE接GND
1.3 硬件连接图
1.4 实物连接图
二、软件架构设计
2.1 系统初始化
void setup() {Serial.begin(9600);tft.init(240, 320); // 初始化240x320显示屏tft.setRotation(3); // 横向显示tft.fillScreen(BACKGROUND); // 设置背景色drawUI(); // 绘制UI界面// 初始化传感器引脚pinMode(s2, OUTPUT);pinMode(s3, OUTPUT);pinMode(out, INPUT);pinMode(button, INPUT_PULLUP);showMessage("Please Calibrate!", WARNING_COLOR);
}
2.2 主循环逻辑
void loop() {// 检查串口校准命令if (Serial.available()) {char c = Serial.read();if (c == 'c' || c == 'C') {calibrate(); // 执行校准}}// 检查按钮是否按下if (digitalRead(button) == LOW) {detectColor(); // 执行颜色检测delay(300); // 简单消抖}
}
2.3 颜色检测算法
void detectColor() {// 多次采样取平均值int r_sum = 0, g_sum = 0, b_sum = 0;const int samples = 3;for (int i = 0; i < samples; i++) {color();r_sum += red;g_sum += green;b_sum += blue;delay(50); // 采样间隔}red = r_sum / samples;green = g_sum / samples;blue = b_sum / samples;// 映射到0-255范围(使用独立通道校准)red = constrain(map(red, cal_min_r, cal_max_r, 255, 0), 0, 255);green = constrain(map(green, cal_min_g, cal_max_g, 255, 0), 0, 255);blue = constrain(map(blue, cal_min_b, cal_max_b, 255, 0), 0, 255);// 更新显示updateColorDisplay();// 串口输出Serial.print("R: "); Serial.print(red);Serial.print(" G: "); Serial.print(green);Serial.print(" B: "); Serial.println(blue);
}
2.4 系统校准
void calibrate() {// 重置校准值cal_min_r = 10000; cal_min_g = 10000; cal_min_b = 10000;cal_max_r = 0; cal_max_g = 0; cal_max_b = 0;// 第一步:黑色校准(取最大值)showMessage("BLACK surface", HIGHLIGHT);waitForButton(); // 等待按钮按下for (int i = 0; i < 5; i++) {color();cal_max_r = max(cal_max_r, red);// ... 其他通道类似Serial.print("Black Sample R="); Serial.println(red);}// 第二步:白色校准(取最小值)showMessage("WHITE surface", HIGHLIGHT);waitForButton(); // 等待按钮按下for (int i = 0; i < 5; i++) {color();cal_min_r = min(cal_min_r, red);// ... 其他通道类似Serial.print("White Sample R="); Serial.println(red);}// 校准值验证if (cal_min_r >= cal_max_r) {showMessage("Invalid Calibration!", WARNING_COLOR);} else {showMessage("Cal Success!", SUCCESS_COLOR);}
}
2.5 颜色数据采集
void color() {// 读取红色分量digitalWrite(s2, LOW);digitalWrite(s3, LOW);red = pulseIn(out, digitalRead(out) == HIGH ? LOW : HIGH);// 读取蓝色分量digitalWrite(s3, HIGH);blue = pulseIn(out, digitalRead(out) == HIGH ? LOW : HIGH);// 读取绿色分量digitalWrite(s2, HIGH);green = pulseIn(out, digitalRead(out) == HIGH ? LOW : HIGH);
}
2.6 完整代码
/*TCS230 Color Recognizer with ST7789 DisplayDesigned for 零知IDE 零知增强板Display Rotation: 3 (Landscape)Filename: ColorSensor_TCS230_ST7789_Optimized.ino
*/#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#include <Fonts/FreeSans18pt7b.h> // 大字体用于标题
#include <Fonts/FreeSans12pt7b.h> // 中字体用于按钮
#include <Fonts/FreeSans9pt7b.h> // 小字体用于数值// ST7789 显示屏引脚定义
#define TFT_CS 53
#define TFT_DC 49
#define TFT_RST 47 Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);// 颜色定义
#define BACKGROUND 0x2104 // 深灰色背景
#define PANEL_COLOR 0x39C7 // 面板蓝灰色
#define TEXT_COLOR 0xFFFF // 白色文字
#define HIGHLIGHT 0x07FF // 青色高亮
#define WARNING_COLOR 0xF800 // 红色警告
#define SUCCESS_COLOR 0x07E0 // 绿色成功提示// UI 尺寸定义
#define PANEL_WIDTH 140
#define COLOR_BOX_SIZE 100
#define MARGIN 10// 颜色传感器引脚
const int s2 = 10;
const int s3 = 11;
const int out = 12;
const int button = 13;// 颜色变量
int red = 0;
int green = 0;
int blue = 0;// 独立通道校准值
int cal_min_r = 10000; // 初始化为较大值
int cal_min_g = 10000;
int cal_min_b = 10000;
int cal_max_r = 0; // 初始化为较小值
int cal_max_g = 0;
int cal_max_b = 0;// 校准状态
unsigned long lastCalibration = 0;
bool calibrated = false;void setup() {Serial.begin(9600);// 初始化显示屏tft.init(240, 320); // 初始化240x320显示屏tft.setRotation(3); // 横向显示tft.invertDisplay(false);tft.fillScreen(BACKGROUND); // 设置背景色// 绘制UI界面drawUI();// 初始化颜色传感器引脚pinMode(s2, OUTPUT);pinMode(s3, OUTPUT);pinMode(out, INPUT);pinMode(button, INPUT_PULLUP);// 初始校准提示showMessage("Please Calibrate!", WARNING_COLOR);
}void loop() {// 检查串口校准命令if (Serial.available()) {char c = Serial.read();if (c == 'c' || c == 'C') {calibrate();}}// 检查按钮是否按下if (digitalRead(button) == LOW) {detectColor();delay(300); // 简单消抖}
}void drawUI() {// 清屏tft.fillScreen(BACKGROUND);// 绘制标题tft.setFont(&FreeSans18pt7b);tft.setTextColor(HIGHLIGHT);tft.setCursor(20, 40);tft.print("COLOR SENSOR");// 左侧信息面板tft.fillRoundRect(MARGIN, 60, PANEL_WIDTH, 240, 10, PANEL_COLOR);// 传感器标签tft.setFont(&FreeSans9pt7b);tft.setTextColor(TEXT_COLOR);// 调整间距防止重叠tft.setCursor(MARGIN + 10, 90);tft.print("RED:");tft.setCursor(MARGIN + 10, 130);tft.print("GREEN:");tft.setCursor(MARGIN + 10, 170);tft.print("BLUE:");tft.setCursor(MARGIN + 10, 210);tft.print("STATUS:");// 校准按钮tft.fillRoundRect(MARGIN + 10, 250, PANEL_WIDTH - 20, 40, 5, HIGHLIGHT);tft.setFont(&FreeSans12pt7b);tft.setCursor(MARGIN + 25, 280);tft.print("CALIBRATE");// 右侧颜色显示区域tft.fillRoundRect(PANEL_WIDTH + MARGIN*2, 60, 320 - PANEL_WIDTH - MARGIN*3, 240, 10, PANEL_COLOR);uint16_t detectedColor = tft.color565(204, 0, 255);// 颜色显示框tft.fillRoundRect(PANEL_WIDTH + MARGIN*3 + 15, 75, COLOR_BOX_SIZE, COLOR_BOX_SIZE, 10, detectedColor);// 颜色框标签tft.setFont(&FreeSans9pt7b);tft.setCursor(PANEL_WIDTH + MARGIN*2 - 5, 200);tft.print("DETECTED COLOR");// RGB值显示区域tft.setCursor(PANEL_WIDTH + MARGIN*2 - 5, 235);tft.print("HEX: #");
}void detectColor() {// 多次采样取平均值int r_sum = 0, g_sum = 0, b_sum = 0;const int samples = 3;for (int i = 0; i < samples; i++) {color();r_sum += red;g_sum += green;b_sum += blue;delay(50); // 采样间隔}red = r_sum / samples;green = g_sum / samples;blue = b_sum / samples;// 映射到0-255范围(使用独立通道校准)red = constrain(map(red, cal_min_r, cal_max_r, 255, 0), 0, 255);green = constrain(map(green, cal_min_g, cal_max_g, 255, 0), 0, 255);blue = constrain(map(blue, cal_min_b, cal_max_b, 255, 0), 0, 255);// 更新显示updateColorDisplay();// 串口输出Serial.print("R: "); Serial.print(red);Serial.print(" G: "); Serial.print(green);Serial.print(" B: "); Serial.println(blue);
}void updateColorDisplay() {// 转换为16位颜色值uint16_t detectedColor = tft.color565(red, green, blue);// 更新颜色框tft.fillRoundRect(PANEL_WIDTH + MARGIN*3 + 15, 75, COLOR_BOX_SIZE, COLOR_BOX_SIZE, 10, detectedColor);// 转换为十六进制格式char hexColor[7];sprintf(hexColor, "%02X%02X%02X", red, green, blue);// 更新HEX值显示tft.setFont(&FreeSans9pt7b);tft.fillRect(PANEL_WIDTH + MARGIN*3 + 40, 220, 80, 30, PANEL_COLOR); // 清除旧值tft.setCursor(PANEL_WIDTH + MARGIN*3 + 45, 235);tft.setTextColor(TEXT_COLOR);tft.print(hexColor);// 更新传感器数值显示tft.fillRect(MARGIN + 70, 70, 60, 30, PANEL_COLOR); // 清除旧红色值tft.fillRect(MARGIN + 91, 110, 30, 30, PANEL_COLOR); // 清除旧绿色值tft.fillRect(MARGIN + 90, 150, 30, 30, PANEL_COLOR); // 清除旧蓝色值tft.setCursor(MARGIN + 90, 90);tft.print(red);tft.setCursor(MARGIN + 91, 130);tft.print(green);tft.setCursor(MARGIN + 90, 170);tft.print(blue);// 更新校准状态if (calibrated) {tft.fillRect(MARGIN , 190, 130, 30, PANEL_COLOR);tft.setCursor(MARGIN + 25, 210);tft.setTextColor(SUCCESS_COLOR);tft.print("Calibrated!");}
}void calibrate() {// 重置校准值cal_min_r = 10000; cal_min_g = 10000; cal_min_b = 10000;cal_max_r = 0; cal_max_g = 0; cal_max_b = 0;// 第一步:黑色校准showMessage("BLACK surface", HIGHLIGHT);Serial.println("Calibration Step 1: Place BLACK surface and press button");// 等待按钮按下while (digitalRead(button)) {if (Serial.available() && Serial.read() == 'x') {showMessage("Cal Canceled", WARNING_COLOR);return;}}// 读取黑色值(多次采样取最大)for (int i = 0; i < 5; i++) {color();cal_max_r = max(cal_max_r, red);cal_max_g = max(cal_max_g, green);cal_max_b = max(cal_max_b, blue);delay(100);Serial.print("Black Sample ");Serial.print(i+1);Serial.print(": R="); Serial.print(red);Serial.print(" G="); Serial.print(green);Serial.print(" B="); Serial.println(blue);}// 第二步:白色校准showMessage("WHITE surface", HIGHLIGHT);Serial.println("Calibration Step 2: Place WHITE surface and press button");// 等待按钮按下while (digitalRead(button)) {if (Serial.available() && Serial.read() == 'x') {showMessage("Cal Canceled", WARNING_COLOR);return;}}// 读取白色值(多次采样取最小)for (int i = 0; i < 5; i++) {color();cal_min_r = min(cal_min_r, red);cal_min_g = min(cal_min_g, green);cal_min_b = min(cal_min_b, blue);delay(100);Serial.print("White Sample ");Serial.print(i+1);Serial.print(": R="); Serial.print(red);Serial.print(" G="); Serial.print(green);Serial.print(" B="); Serial.println(blue);}calibrated = true;lastCalibration = millis();// 显示成功信息showMessage("Cal Success!", SUCCESS_COLOR);Serial.println("Calibration complete!");// 输出详细校准信息Serial.println("Calibration Values:");Serial.print("R min/max: "); Serial.print(cal_min_r); Serial.print("/"); Serial.println(cal_max_r);Serial.print("G min/max: "); Serial.print(cal_min_g); Serial.print("/"); Serial.println(cal_max_g);Serial.print("B min/max: "); Serial.print(cal_min_b); Serial.print("/"); Serial.println(cal_max_b);// 验证校准值if (cal_min_r >= cal_max_r || cal_min_g >= cal_max_g || cal_min_b >= cal_max_b) {showMessage("Invalid Calibration!", WARNING_COLOR);Serial.println("ERROR: Invalid calibration values! White min should be LESS than black max.");delay(3000);}// 3秒后恢复状态显示delay(3000);updateColorDisplay();
}void color() {// 读取红色分量digitalWrite(s2, LOW);digitalWrite(s3, LOW);red = pulseIn(out, digitalRead(out) == HIGH ? LOW : HIGH);// 读取蓝色分量digitalWrite(s3, HIGH);blue = pulseIn(out, digitalRead(out) == HIGH ? LOW : HIGH);// 读取绿色分量digitalWrite(s2, HIGH);green = pulseIn(out, digitalRead(out) == HIGH ? LOW : HIGH);
}void showMessage(const char* msg, uint16_t color) {tft.fillRect(MARGIN, 190, PANEL_WIDTH, 40, PANEL_COLOR);tft.setFont(&FreeSans9pt7b);tft.setTextColor(color);tft.setCursor(MARGIN + 2, 210);tft.print(msg);
}
三、操作过程及数据展示
3.1 操作步骤
(1)确保Adafruit_GFX、Adafruit_ST7789库安装,将程序上传到零知IDE,驱动屏幕初始化,屏幕提示"Please Calibrate!"说明传感器需要进行校准操作。
(2)打开串口监视器设置为9600波特率,发送 'c' 开始校准,按照提示放置黑色参考物和白色参考物并按下按钮校准,屏幕提示"Calibrated"的时候说明校准成功:
(3)按下按钮识别当前颜色,查看显示屏上的颜色和HEX值,当前识别到的绿色物体HEX为"#005D1D":
3.2 校准过程演示
按照串口打印的提示内容观察校准过程和校准数据:
3.3 识别效果视频展示
TCS230颜色识别器进行校准和颜色数据读取
分别放置黑色、白色参考物进行校准,接着进行颜色识别
四、TCS230技术原理
4.1 工作原理
TCS230是可编程颜色光频率转换器,由光电二极管阵列和电流-频率转换器组成:
>64个光电二极管(16个红,16个绿,16个蓝,16个白)
>可配置的输出频率比例(S0、S1引脚)
>可选择的滤波器(S2、S3引脚)
4.2 工作模式配置
每16个光电二极管并联连接,因此使用两个控制引脚S2和S3,我们可以选择读取哪个。如果我们想要检测红色,我们可以通过根据表格将两个引脚设置为低逻辑电平来使用16个红色滤波光电二极管,根据以下配置可以选择对应的滤波器:
S2 | S3 | 选择的滤波器 |
---|---|---|
0 | 0 | 红色 |
0 | 1 | 蓝色 |
1 | 0 | 无滤波器 |
1 | 1 | 绿色 |
五、常见问题指引
Q1: 为什么校准后颜色识别不准确?
A: 可能原因:
参考物不标准(使用专业黑/白参考卡)
环境光变化(保持稳定光照)
传感器距离不当(保持2-5cm距离)
Q2: 如何判断校准是否成功?
A: 检查串口输出的校准值:
白色值(min)应小于黑色值(max)
各通道值比例应接近
观察黑色校准值是否合理
Q3: HEX值显示不正确怎么办?
A: 检查步骤:
确认校准成功
检查颜色映射范围(0-255)
验证HEX转换函数
确保显示屏初始化正确
Q4: 如何提高识别精度?
A: 优化建议:
增加采样次数(修改samples值)
使用遮光罩减少环境光影响
固定传感器与物体的距离
六、结论
项目成功实现了基于STM32F407VET6的高精度颜色识别系统,通过以下创新点解决了颜色识别中的关键问题:
>独立通道校准算法:显著提高了颜色识别准确性
>三步校准流程:简化操作并确保校准质量
>实时数据显示:提供详细的校准和识别反馈
>直观UI设计:展示颜色信息和传感器数据
项目资源:
TCS230用户手册:TCS230数据手册
显示屏库文件:ST7789驱动库
本项目已在零知增强板上全面测试通过,欢迎在评论区分享您的实现经验和改进建议!点击了解更多零知开发教程:
https://www.lingzhilab.com/freesources.html