关于TCS34725颜色识别传感器的彩球分拣装置
前言
新的进阶练习小制作来啦!这次带来的是关于TCS34725颜色识别传感器的彩球分拣装置。和往常一样,此次依然作为51的进阶练习,基础薄弱的可以看verimake的51相关教程《以 CH549 为例的 51 教程》。
这次的小制作尝试了新的器件:颜色识别传感器TCS34725,正如之前的上面提到的教程中所说的CH549并没有硬件I2C通信,而该传感器为I2C通信,所以此次软件中根据商家给的STM32驱动TCS34725的例程自己完成了关于I2C和TCS34725的.h和.c文件的撰写。
TCS34725简介
本模块是基于AMS的TCS3472XFN彩色光数字转换器为核心的颜色传感器,传感器提供红色,绿色,蓝色(RGB)和清晰光感应值的数字输出。集成红外阻挡滤光片可最大限度地减少入射光的红外光谱成分,并可精确地进行颜色测量。具有高灵敏度,宽动态范围和红外阻隔滤波器。最小化IR和UV光谱分量效应,以产生准确的颜色测量。并且带有环境光强检测和可屏蔽中断。通过I2C接口通信。本设计使用的是双孔版本,如下图所示布局了2个LED灯对于物体进行补光。
TCS34725颜色识别传感器
所测彩球
暂且不论要自己定义模拟I2C,定义TCS34725的各项参数,这个传感器受外部光线影响非常大,他本身布局的两个LED灯并不能帮助补光,可能是是因为要检测的物品是玻璃球,有反光等问题的影响,导致所测的数据会发生较大的波动,并且很难测量到准确的数据,衡量再三之后选择直接关闭选择在转盘下方加装一个LED灯从下方透过玻璃球来补光。至此我和TCS34725的爱恨情仇才刚刚开始。
初版彩球分拣器
从图中可以看出初版的彩球分拣装置并没有在TCS周围遮光,仅由下方的LED灯进行补光,周围光线对传感器的数据影响太大,数据波动严重,且坏数据占大部分,甚至能只用清晰光感应值分辨球该去哪一个盒子(球的不透明度不一样,下方的LED灯透过球反馈给传感器的数据),不提他本身偏离我们的目的——通过RGB数值判断球该去哪里,透光度受外部光线影响严重(调整好数据,吃个晚饭,数据普遍变化了20%),不能满足我们此次制作的要求,于是就来到了第二版。
第二版彩球分拣器
第二版其实修改了很多,一方面是加了TCS34725附近的遮光,另一方面改变了漏斗下方的搅拌轮的轮齿,先前的轮齿在搅拌的时候会有两颗小球因为尺寸的原因配合着他的旋转不掉下去。我们这个彩球分拣器原版是由白色的材料3D打印而成的,该材料有一定的透光性,会一定程度上干扰颜色识别,在第二版选择黑色的材料,打印的时候产生了一定的加工偏差,导致本该水平放于彩球上方的传感器产生倾斜,并且阻碍了下方圆盘的旋转。于是便决定重新制作各个部件,这便到了终版。
终版彩球分拣器
如图所示终版的彩球分拣装置排除了大部分的环境光线的影响,并且在运行期间的正确数据基本可控,到此外形设计部分基本完成。
接线
接线图
CH549 | 功能引脚 | 功能 |
P0.5 | TCS34725 SCL | 传感器I2C时钟输入 |
P0.6 | TCS34725 SDA | 传感器I2C数据输入 |
P1.4 | SSD1036 CS | OLED屏CS片选 |
P1.5 | SSD1036 D1 | OLED屏MOSI数据 |
P1.7 | SSD1036 D0 | OLED屏SCLK时钟信号 |
P2.2 | MG90S 黄线 | MG90S舵机PWM控制 |
P2.4 | MX1508 电机1+ | 转盘PWM控制 |
P2.7 | SSD1036 DC | OLED屏数据/命令控制 |
P3.5 | SSD1036 RST | OLED屏LED复位 |
PS:传感器下方补光的LED灯记得连电阻;其余VCC和GND不再给出;搅拌轮电机的PWM控制正极直接连VCC。
模拟I2C
I2C通信协议简介
I2C(Inter Integrated Circuit)总线是PHILIPS公司开发的一种半双工、双向二线制同步串行总线。I2C总线传输数据时只需两根信号线,一根是双向数据线SDA(serial data),另一根是双向时钟线SCL(serial clock)。SPI总线有两根线分别用于主从设备之间接收数据和发送数据,而I2C总线只使用一根线进行数据收发。
I2C和SPI一样以主从的方式工作,不同于SPI一主多从的结构,它允许同时有多个主设备存在,每个连接到总线上的器件都有唯一的地址,主设备启动数据传输并产生时钟信号,从设备被主设备寻址,同一时刻只允许有一个主设备。
I2C总线在传送数据过程中共有三种类型信号:开始信号、结束信号和应答信号。
时序
此处为本次小制作中模拟I2C的头文件,定义P0.5为SCL,定义P0.6为SDA。
CH549_IIC.c
#include "CH549_IIC.h"
//初始化I2C
void IIC_Init()
{
GPIO_Init(PORT0,PIN5,MODE2);
GPIO_Init(PORT0,PIN6,MODE2);//CH549电压为5V,而非3.3V
}
//开始
void IIC_Start()
{
SDA_UP;
SCL_UP;
mDelayuS(4);
SDA_DOWN;
mDelayuS(4);
SCL_DOWN;
}
//结束
void IIC_Stop()
{
SCL_DOWN;
SDA_DOWN;
mDelayuS(4);
SCL_UP;
SDA_UP;
mDelayuS(4);
}
//返回值:1.接收应答失败;0.接收应答成功。
UINT8 IIC_Wait_Ack()
{
UINT32 t=0;
SDA_UP;
mDelayuS(1);
SCL_UP;
mDelayuS(1);
while(SDA_READ)
{
t++;
if(t > 250)
{
IIC_Stop();
return 1;
}
}
SCL_DOWN;
return 0;
}
//不产生ACK应答
void IIC_Nack()
{
SCL_DOWN;
SDA_UP;
mDelayuS(2);
SCL_UP;
mDelayuS(2);
SCL_DOWN;
}
//产生ACK应答
void IIC_Ack()
{
SCL_DOWN;
SDA_DOWN;
mDelayuS(2);
SCL_UP;
mDelayuS(2);
SCL_DOWN;
}
//I2C发送一个字节
void IIC_Send_Byte(UINT8 byte)
{
UINT8 i;
SCL_DOWN;
for( i = 0 ; i < 8 ; i++ )
{
if(byte&0x80)
{
SDA_UP;
}else
{
SDA_DOWN;
}
byte <<= 1;
mDelayuS(2);
SCL_UP;
mDelayuS(2);
SCL_DOWN;
mDelayuS(2);
}
}
//读1个字节,ack=1时,发送ack;ack=0时发送nack
UINT8 IIC_Read_Byte(UINT8 ack)
{
UINT8 i = 0;
UINT8 receive = 0;
for( i = 0 ; i < 8 ; i++ )
{
SCL_DOWN;
mDelayuS(2);
SCL_UP;
receive <<= 1;
if(SDA_READ)
{
receive++;
}
mDelayuS(1);
}
if (!ack)
{
IIC_Nack();
}else
{
IIC_Ack();
}
return receive;
}
TCS34725相关
传感器原理框图
颜色传感器吸收物体反射回来的光由红光、绿光、蓝光和清晰(未滤光)光电二极管吸收,产生光电效应,并且产生光电流,四个积分ADC同时将放大的光电二极管电流转换为16位数字值。转换周期完成后,结果被传输到数据寄存器,数据寄存器采用双缓冲方式,以确保数据的完整性。
积分时间设置
RGBC定时寄存器以2.4ms的增量获得一组RGBC的数据,例如积分时间取24ms,即每次取10组数据,整个通道最大值就变为10倍,而实际取RGB(0-255)的算法为R*255/C,在整个算法下理论上积分时间越长数据越精确,但是转盘的旋转速度做不到太慢,太慢的PWM值下驱动不了转盘,即采集数据的速度较快,积分时间不宜过高。(本次使用24ms)
传感器增益相关
在上面提到的算法中若结果数值较小或者误差较大请调高增益,若数值过大请调低增益。(本次使用4x)
CH549_TCS34725.c
#include "CH549_TCS34725.h"
#define max3v(v1, v2, v3) ((v1)<(v2)? ((v2)<(v3)?(v3):(v2)):((v1)<(v3)?(v3):(v1)))
#define min3v(v1, v2, v3) ((v1)>(v2)? ((v2)>(v3)?(v3):(v2)):((v1)>(v3)?(v3):(v1)))
/*******************************************************************************
* @brief Writes data to a slave device.
*
* @param slaveAddress - Adress of the slave device.
* @param dataBuffer - Pointer to a buffer storing the transmission data.
* @param bytesNumber - Number of bytes to write.
* @param stopBit - Stop condition control.
* Example: 0 - A stop condition will not be sent;
* 1 - A stop condition will be sent.
*******************************************************************************/
void TCS34725_I2C_Write(UINT8 slaveAddress, UINT8 * dataBuffer,UINT8 bytesNumber, UINT8 stopBit)
{
UINT8 i = 0;
IIC_Start();
IIC_Send_Byte((slaveAddress << 1) | 0x00); //发送从机地址写命令
IIC_Wait_Ack();
for(i = 0; i < bytesNumber; i++)
{
IIC_Send_Byte(*(dataBuffer + i));
IIC_Wait_Ack();
}
if(stopBit == 1) IIC_Stop();
}
/*******************************************************************************
* @brief Reads data from a slave device.
*
* @param slaveAddress - Adress of the slave device.
* @param dataBuffer - Pointer to a buffer that will store the received data.
* @param bytesNumber - Number of bytes to read.
* @param stopBit - Stop condition control.
* Example: 0 - A stop condition will not be sent;
* 1 - A stop condition will be sent.
*******************************************************************************/
void TCS34725_I2C_Read(UINT8 slaveAddress, UINT8 * dataBuffer, UINT8 bytesNumber, UINT8 stopBit)
{
UINT8 i = 0;
IIC_Start();
IIC_Send_Byte((slaveAddress << 1) | 0x01); //发送从机地址读命令
IIC_Wait_Ack();
for(i = 0; i < bytesNumber; i++)
{
if(i == bytesNumber - 1)
{
dataBuffer[i] = IIC_Read_Byte(0);//读取的最后一个字节发送NACK
}
else
{
dataBuffer[i] = IIC_Read_Byte(1);
}
}
if(stopBit == 1) IIC_Stop();
}
/*******************************************************************************
* @brief Writes data into TCS34725 registers, starting from the selected
* register address pointer.
*
* @param subAddr - The selected register address pointer.
* @param dataBuffer - Pointer to a buffer storing the transmission data.
* @param bytesNumber - Number of bytes that will be sent.
*
* @return None.
*******************************************************************************/
void TCS34725_Write(UINT8 subAddr, UINT8 * dataBuffer, UINT8 bytesNumber)
{
UINT8 sendBuffer[10] = {0, };
UINT8 byte = 0;
sendBuffer[0] = subAddr | TCS34725_COMMAND_BIT;
for(byte = 1; byte <= bytesNumber; byte++)
{
sendBuffer[byte] = dataBuffer[byte - 1];
}
TCS34725_I2C_Write(TCS34725_ADDRESS, sendBuffer, bytesNumber + 1, 1);
}
/*******************************************************************************
* @brief Reads data from TCS34725 registers, starting from the selected
* register address pointer.
*
* @param subAddr - The selected register address pointer.
* @param dataBuffer - Pointer to a buffer that will store the received data.
* @param bytesNumber - Number of bytes that will be read.
*
* @return None.
*******************************************************************************/
void TCS34725_Read(UINT8 subAddr, UINT8 * dataBuffer, UINT8 bytesNumber)
{
subAddr |= TCS34725_COMMAND_BIT;
TCS34725_I2C_Write(TCS34725_ADDRESS, (UINT8 *)&subAddr, 1, 0);
TCS34725_I2C_Read(TCS34725_ADDRESS, dataBuffer, bytesNumber, 1);
}
/*******************************************************************************
* @brief TCS34725设置积分时间
*
* @return None
*******************************************************************************/
void TCS34725_SetIntegrationTime(UINT8 time)
{
TCS34725_Write(TCS34725_ATIME, &time, 1);
}
/*******************************************************************************
* @brief TCS34725设置增益
*
* @return None
*******************************************************************************/
void TCS34725_SetGain(UINT8 gain)
{
TCS34725_Write(TCS34725_CONTROL, &gain, 1);
}
/*******************************************************************************
* @brief TCS34725使能
*
* @return None
*******************************************************************************/
void TCS34725_Enable(void)
{
UINT8 cmd = TCS34725_ENABLE_PON;
TCS34725_Write(TCS34725_ENABLE, &cmd, 1);
cmd = TCS34725_ENABLE_PON | TCS34725_ENABLE_AEN;
TCS34725_Write(TCS34725_ENABLE, &cmd, 1);
//delay_s(600000);//delay_ms(3);//延时应该放在设置AEN之后
}
/*******************************************************************************
* @brief TCS34725失能
*
* @return None
*******************************************************************************/
void TCS34725_Disable(void)
{
UINT8 cmd = 0;
TCS34725_Read(TCS34725_ENABLE, &cmd, 1);
cmd = cmd & ~(TCS34725_ENABLE_PON | TCS34725_ENABLE_AEN);
TCS34725_Write(TCS34725_ENABLE, &cmd, 1);
}
/*******************************************************************************
* @brief TCS34725初始化
*
* @return ID - ID寄存器中的值
*******************************************************************************/
UINT8 TCS34725_Init(void)
{
UINT8 id=0;
IIC_Init();
TCS34725_Read(TCS34725_ID, &id, 1); //TCS34725 的 ID 是 0x44 可以根据这个来判断是否成功连接,0x4D是TCS34727;
if(id==0x4D | id==0x44)
{
TCS34725_SetIntegrationTime(TCS34725_INTEGRATIONTIME_50MS);
TCS34725_SetGain(TCS34725_GAIN_1X);
TCS34725_Enable();
return 1;
}
return 0;
}
/*******************************************************************************
* @brief TCS34725获取单个通道数据
*
* @return data - 该通道的转换值
*******************************************************************************/
UINT16 TCS34725_GetChannelData(UINT8 reg)
{
UINT8 tmp[2] = {0,0};
UINT16 data;
TCS34725_Read(reg, tmp, 2);
data = (tmp[0] << 8) | tmp[1];
return data;
}
/*******************************************************************************
* @brief TCS34725获取各个通道数据
*
* @return 1 - 转换完成,数据可用
* 0 - 转换未完成,数据不可用
*******************************************************************************/
UINT8 TCS34725_GetRawData(COLOR_RGBC *rgbc)
{
UINT8 status = TCS34725_STATUS_AVALID;
TCS34725_Read(TCS34725_STATUS, &status, 1);
if(status & TCS34725_STATUS_AVALID)
{
rgbc->c = TCS34725_GetChannelData(TCS34725_CDATAL);
rgbc->r = TCS34725_GetChannelData(TCS34725_RDATAL);
rgbc->g = TCS34725_GetChannelData(TCS34725_GDATAL);
rgbc->b = TCS34725_GetChannelData(TCS34725_BDATAL);
return 1;
}
return 0;
}
/******************************************************************************/
//RGB转HSL
void RGBtoHSL(COLOR_RGBC *Rgb, COLOR_HSL *Hsl)
{
UINT8 maxVal,minVal,difVal;
UINT8 r = Rgb->r*100/Rgb->c; //[0-100]
UINT8 g = Rgb->g*100/Rgb->c;
UINT8 b = Rgb->b*100/Rgb->c;
maxVal = max3v(r,g,b);
minVal = min3v(r,g,b);
difVal = maxVal-minVal;
//计算亮度
Hsl->l = (maxVal+minVal)/2; //[0-100]
if(maxVal == minVal)//若r=g=b,灰度
{
Hsl->h = 0;
Hsl->s = 0;
}
else
{
//计算色调
if(maxVal==r)
{
if(g>=b)
Hsl->h = 60*(g-b)/difVal;
else
Hsl->h = 60*(g-b)/difVal+360;
}
else
{
if(maxVal==g)Hsl->h = 60*(b-r)/difVal+120;
else
if(maxVal==b)Hsl->h = 60*(r-g)/difVal+240;
}
//计算饱和度
if(Hsl->l<=50)Hsl->s=difVal*100/(maxVal+minVal); //[0-100]
else
Hsl->s=difVal*100/(200-(maxVal+minVal));
}
}
该段程序基本上沿用STM32中关于TCS34725的设置,将其中STM32与CH549不同的地方做了适应CH549修改。
具体程序main.c
#include <CH549_TCS34725.h>//TCS34725颜色识别相关设定
#include <CH549_IIC.h> //I2C相关设定
#include <CH549_PWM.h> //CH549的PWM相关设定
#include <CH549_GPIO.h> //GPIO相关设定
#include <CH549_BMP.h> //用于显示图片的头文件
#include <CH549_OLED.h> //其中有驱动屏幕使用的函数
#include <CH549_SPI.h> //CH549官方提供库的头文件,定义了一些关于SPI初始化,传输数据等函数
#include <CH549_DEBUG.h> //CH549官方提供库的头文件,定义了一些关于主频,延时,串口设置,看门口,赋值设置等基础函数
#include <CH549_sdcc.H> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
//#define numLEDs 10 //灯的个数
#define max3v(v1, v2, v3) ((v1)<(v2)? ((v2)<(v3)?(v3):(v2)):((v1)<(v3)?(v3):(v1))) //三个数中最大值
#define min3v(v1, v2, v3) ((v1)>(v2)? ((v2)>(v3)?(v3):(v2)):((v1)>(v3)?(v3):(v1))) //三个数中最小值
UINT8 box[4] = {40,60,78,98}; //控制分向舵机的PWM参数三组,例如第一个数值即为第一个盒子
COLOR_RGBC rgb; //RGB相关结构体
COLOR_HSL hsl; //HSL相关结构体
int oled_colum; //oled列
int oled_row; //oled行
void setCursor(int column,int row);//屏幕输出函数
/********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
void main(void)
{
UINT8 r ,g ,b;
UINT32 M = 0;
UINT16 m = 0,n = 0,l = 0,i = 0;
CfgFsys(); //CH549时钟选择配置
mDelaymS(20);
SPIMasterModeSet(3); //SPI主机模式设置,模式3
SPI_CK_SET(12); //设置spi sclk 时钟信号为12分频
OLED_Init(); //初始化OLED
OLED_Clear(); //将oled屏幕上内容清除
setFontSize(8); //设置文字大小
TCS34725_Init(); //初始化TCS34725
TCS34725_SetGain(TCS34725_GAIN_4X); //设置TCS34725的增益
TCS34725_SetIntegrationTime(TCS34725_INTEGRATIONTIME_24MS); //设置TCS34725的积分时间,即通道最大值
SetPWMClkDiv(255); //PWM时钟配置,Fsys/255,Fsys为12Mhz
SetPWMCycle256Clk(); //PWM周期 Fsys/255/256
PWM_SEL_CHANNEL(PWM_CH1,Enable); //使能CH1,即P2.4
PWM_SEL_CHANNEL(PWM_CH3,Enable); //使能CH3,即P2.2
SetPWM1Dat(18);
/* 主循环 */
while (1)
{
TCS34725_GetRawData(&rgb); //读取当前颜色数据,即RGBC
//RGBtoHSL(&rgb, &hsl); //转RGB为HSL
r = rgb.r*255.0/rgb.c; //将实际的RGB值读取出来,范围为0-255
g = rgb.g*255.0/rgb.c;
b = rgb.b*255.0/rgb.c;
mDelaymS(20);
setCursor(0,0);//设置printf到屏幕上的字符串起始位置
printf_fast_f("R : %u ",m);
setCursor(0,2);//设置printf到屏幕上的字符串起始位置
printf_fast_f("G : %u ",n);
setCursor(0,4);//设置printf到屏幕上的字符串起始位置
printf_fast_f("B : %u ",l);
setCursor(0,6);//设置printf到屏幕上的字符串起始位置
printf_fast_f("RGB.C : %u ",rgb.c);
if (rgb.c > 100) //能检测到亮度时开始读取颜色值
{
if (i == 3) //2次循环后再读取防抖动
{
if ((max3v(r,g,b) > 240) || (max3v(r,g,b) < 60)) //经过防抖动之后可能还是有数据异常的情况,再检测一次
{
i--;
}else
{
m = r; //读取当前RGB值
n = g;
l = b;
}
}
i++;
}
if (rgb.c < 100) //亮度归零意味着小球经过监测点,开始根据读取到的RGB值进行判断
{
if (i != 0) //仅判断一次
{
if (m > n && m > l)
{
M = M << 8 | box[0]; //红球
}
else if ((l - n > 10) && l > m && l > n)
{
M = M << 8 | box[1]; //蓝球
}
else if (n > m && n > l)
{
M = M << 8 | box[2]; //绿球
}
//else if ((l - n < 10) && l > m && l > m)
//{
// M = M << 8 | box[3];
//}
else
{
M = M << 8 | box[3]; //白球
}
}
i = 0;
}
SetPWM3Dat((unsigned char)(M >> 8*2)); //将第三个数据发送给舵机
}
}
/********************************************************************
* 函 数 名 : putchar
* 函数功能 : 将printf映射到OLED屏幕输出上
* 输 入 : 字符串
* 输 出 : 字符串
********************************************************************/
int putchar( int a)
{
//在光标处显示文字 a
OLED_ShowChar(oled_colum,oled_row,a);
//将光标右移一个字的宽度,以显示下一个字
oled_colum+=6;
/*当此行不足以再显示一个字时,换行.
同时光标回到最边(列坐标=0).
*/
if (oled_colum>122){oled_colum=0;oled_row+=1;}
return(a);
}
/********************************************************************
* 函 数 名 : setCursor
* 函数功能 : 设置光标(printf到屏幕上的字符串起始位置)
* 输 入 : 行坐标 列坐标(此处一行为8个像素,一列为1个像素,所以屏幕上共有8行128列)
* 输 出 : 无
********************************************************************/
void setCursor(int column,int row)
{
oled_colum = column;
oled_row = row;
}
其中SPI相关还有main函数后面设定的input函数均为OLED屏幕相关,仅为方便观察实时RGB数据,可以省去。
PWM相关
本次主函数中N20电机和MG90S舵机使用到了PWM控制。
UINT8 box[4] = {40,60,78,98}; //控制分向舵机的PWM参数三组,例如第一个数值即为第一个盒子
SetPWMClkDiv(255); //PWM时钟配置,Fsys/255,Fsys为12Mhz
SetPWMCycle256Clk(); //PWM周期 Fsys/255/256
PWM_SEL_CHANNEL(PWM_CH1,Enable); //使能CH1,即P2.4
PWM_SEL_CHANNEL(PWM_CH3,Enable); //使能CH3,即P2.2
SetPWM1Dat(18);
- MG90S舵机
舵机的控制信号为周期是20ms的脉宽调制(PWM)信号,其中脉冲宽度从0.5ms-2.5ms,相对应舵盘的位置为0-180度,呈线性变化。也就是说,给它提供一定的脉宽,它的输出轴就会保持在一个相对应的角度上,无论外界转矩怎样改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应的位置上。一般而言,舵机的基准信号都是周期为20ms,宽度为1.5ms。这个基准信号定义的位置为中间位置。其中间位置的脉冲宽度是一定的,那就是1.5ms。
角度是由来自控制线的持续的脉冲所产生。这种控制方法叫做脉冲调制。脉冲的长短决定舵机转动多大角度。当舵机接收到一个小于1.5ms的脉冲,输出轴会以中间位置为标准,逆时针旋转一定角度。接收到的脉冲大于1.5ms情况相反。不同品牌,甚至同一品牌的不同舵机,都会有不同的最大值和最小值。一般而言,最小脉冲为1ms,最大脉冲为2ms。
本次设计中采用等等PWM时钟配置的Fsys为12MHz,为了达到目标的20ms的周期,将值均取最大,勉强达到周期5.4ms,可以进行操作。
舵机示意图
安装时选取如图中90°的线作为中心线。
- N20电机
用MX1508控制两台N20电机,电机1是旋转盘,经测试18/255的占空比设置刚好满足要求,搅拌轮的正极直接接VCC全速旋转,防止玻璃珠卡在漏斗上。
TCS34725颜色识别相关
TCS34725_GetRawData(&rgb); //读取当前颜色数据,即RGBC
//RGBtoHSL(&rgb, &hsl); //转RGB为HSL
r = rgb.r*255.0/rgb.c; //将实际的RGB值读取出来,范围为0-255
g = rgb.g*255.0/rgb.c;
b = rgb.b*255.0/rgb.c;
if (rgb.c > 100) //能检测到亮度时开始读取颜色值
{
if (i == 3) //2次循环后再读取防抖动
{
if ((max3v(r,g,b) > 240) || (max3v(r,g,b) < 60)) //经过防抖动之后可能还是有数据异常的情况,再检测一次
{
i--;
}else
{
m = r; //读取当前RGB值
n = g;
l = b;
}
}
i++;
}
if (rgb.c < 100) //亮度归零意味着小球经过监测点,开始根据读取到的RGB值进行判断
{
if (i != 0) //仅判断一次
{
if (m > n && m > l)
{
M = M << 8 | box[0]; //红球
}
else if ((l - n > 10) && l > m && l > n)
{
M = M << 8 | box[1]; //蓝球
}
else if (n > m && n > l)
{
M = M << 8 | box[2]; //绿球
}
//else if ((l - n < 10) && l > m && l > m)
//{
// M = M << 8 | box[3];
//}
else
{
M = M << 8 | box[3]; //白球
}
}
i = 0;
}
SetPWM3Dat((unsigned char)(M >> 8*2)); //将第三个数据发送给舵机
其中:
TCS34725_GetRawData(&rgb); //读取当前颜色数据,即RGBC
//RGBtoHSL(&rgb, &hsl); //转RGB为HSL
r = rgb.r*255.0/rgb.c; //将实际的RGB值读取出来,范围为0-255
g = rgb.g*255.0/rgb.c;
b = rgb.b*255.0/rgb.c;
这里为RGBC的数据获取和转RGB的算法部分。
if (rgb.c > 100) //能检测到亮度时开始读取颜色值
{
if (i == 3) //2次循环后再读取防抖动
{
if ((max3v(r,g,b) > 240) || (max3v(r,g,b) < 60)) //经过防抖动之后可能还是有数据异常的情况,再检测一次
{
i--;
}else
{
m = r; //读取当前RGB值
n = g;
l = b;
}
}
i++;
}
- 完全遮光后Clear的数据在非检测区为0,只有经过1处检测区时,通过下方的LED灯的照射才会有数值,以此作为开始检测的标志;
- 在转盘旋转时,因为球需要在4处落下经过滑道进入盒子,所以球在运输过程中并非卡死在检测区有缝隙,即随着他的运动会产生LED灯直接照射传感器或者是反光影响颜色识别的现象,尤其是在刚开始检测和快结束检测的时候;
- 一次检测大概过6次循环左右,所以选定第三次循环时的数据为准,以防数据仍有问题,加入防抖判断,即RGB三个数值有一个大于240或者都小于60时即判断为坏数据,取下一次的数据为准。
颜色检测示意图
if (rgb.c < 100) //亮度归零意味着小球经过监测点,开始根据读取到的RGB值进行判断
{
if (i != 0) //仅判断一次
{
if (m > n && m > l)
{
M = M << 8 | box[0]; //红球
}
else if ((l - n > 10) && l > m && l > n)
{
M = M << 8 | box[1]; //蓝球
}
else if (n > m && n > l)
{
M = M << 8 | box[2]; //绿球
}
//else if ((l - n < 10) && l > m && l > m)
//{
// M = M << 8 | box[3];
//}
else
{
M = M << 8 | box[3]; //白球
}
}
i = 0;
}
SetPWM3Dat((unsigned char)(M >> 8*2)); //将第三个数据发送给舵机
由图中可以看出1准备检测时刚好是4落下时,所以设定32位的数,将每一次的数据左移进去,而最后读取的时候将第三个数据发给舵机,完美实现彩球分拣。
成品展示图
成品
运行展示
后话
这次的小制作作为传感器相关学习还是很有参考价值的。就我个人而言我感觉这次的实践让我加深对传感器控制的理解。这次的小制作的程序里面也留了一个小思考,即程序中有而本篇中未介绍部分,使用那种算法也许会使结果更加精确,这里就留给大家思考了。
当然这次的文件我也放到了gitee仓库上,文件名区别于上一篇的采用:SDCC-TCS34725
欢迎大家参考!!