零知开源——基于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 系统初始化

cpp 复制代码
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 主循环逻辑

cpp 复制代码
void loop() {
  // 检查串口校准命令
  if (Serial.available()) {
    char c = Serial.read();
    if (c == 'c' || c == 'C') {
      calibrate(); // 执行校准
    }
  }
  
  // 检查按钮是否按下
  if (digitalRead(button) == LOW) {
    detectColor(); // 执行颜色检测
    delay(300);    // 简单消抖
  }
}

2.3 颜色检测算法

cpp 复制代码
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 系统校准

cpp 复制代码
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 颜色数据采集

cpp 复制代码
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 完整代码

cpp 复制代码
/*
  TCS230 Color Recognizer with ST7789 Display
  Designed 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