硬件介绍:
12指神探是一款基于树莓派基金会推出的微控制器RP2040制作的多功能硬件调试助手,它有12根引脚,提供了5V和3.3V的电压,其中有9根GPIO,功能灵活,通过搭配不同程序可以做成各种调试器。RP2040芯片具有双Arm Cortex M0+内核,默认运行的125MHz时钟超频到200MHz也可稳定运行,搭载的PIO功能使其可以生成各种常用或者自定义的协议,开发语言可以选择MicroPython或C/C++并且官方文档例程丰富,适用于初学者快速入门应用的同时也可开发出芯片极致性能。
12指神探的传感器扩展板,接口完全适配,正确方向插入后即可使用。扩展板搭载了几款常见传感器和功能模块,麦克风、蜂鸣器、、红外收发、霍尔效应开关、加热电阻,为进阶操作准备的温湿度传感器、六轴传感器、接近/环境光/IR传感器、颜色传感器。其中温湿度传感器、六轴传感器、接近传感器、颜色传感器可拆卸为单个模块,通过杜邦线等连接线延伸其使用的空间范围。
任务选择:
我选择的是任务3:颜色识别及校准功能。任务要求:可识别至少五种颜色;可在LCD上显示基本信息如当前识别颜色;可在LCD上用色块显示识别的颜色并大致接近。
编程语言使用Arduino。开发工具使用 Vscode+platformio。
任务实现:
任务整体不复杂,就是从颜色传感器中获得环境中的颜色,然后在屏幕上进行还原即可。
第一步:读取传感器。这次颜色传感器是LTR-381RGB-WA。也许是因为太新的缘故吧,在网上没有找到这个传感器的驱动库。于是只能手搓驱动文件了。在网上找到个类似的产品LTR390的光线传感器,以此传感器驱动文件作为基础,进行修改。IIC初始化部分大体相同,留意启动时写入命令字有所区别。读取数据部分区别在于LTR381多了几个数据。
由手册可以得知,LTR381的数据有4组,分别是IR、green、red、blue,存放在寄存器0X0A~0X15中。IR猜测是红外数据,这里没有使用。只需要读取R/G/B三色数据即可。
#define LTR381_ADDR 0x53 // default address
// LTR381 register addresses
#define LTR381_CONTR 0x00
#define LTR381_MEAS_RATE 0x04
#define LTR381_ALS_GAIN 0x05
#define LTR381_PART_ID 0x06
#define LTR381_STATUS 0x07
#define LTR381_DATA_IR_0 0x0A
#define LTR381_DATA_IR_1 0x0B
#define LTR381_DATA_IR_2 0x0C
#define LTR381_DATA_GREEN_0 0x0D
#define LTR381_DATA_GREEN_1 0x0E
#define LTR381_DATA_GREEN_2 0x0F
#define LTR381_DATA_RED_0 0x10
#define LTR381_DATA_RED_1 0x11
#define LTR381_DATA_RED_2 0x12
#define LTR381_DATA_BLUE_0 0x13
#define LTR381_DATA_BLUE_1 0x14
#define LTR381_DATA_BLUE_2 0x15
#define LTR381_INTERRUPT 0x19
#define LTR381_INTR_PERS 0x1A
#define LTR381_THRES_UP_0 0x21
#define LTR381_THRES_UP_1 0x22
#define LTR381_THRES_UP_2 0x23
#define LTR381_THRES_LOW_0 0x24
#define LTR381_THRES_LOW_1 0x25
#define LTR381_THRES_LOW_2 0x26
enum COLOR
{
IR = 0,
GREEN,
RED,
BLUE
};
boolean LTR381::getColorData(enum COLOR color, unsigned long &data)
{
switch (color)
{
case IR:
return (readLongInt(LTR381_DATA_IR_0, data));
break;
case RED:
return (readLongInt(LTR381_DATA_RED_0, data));
break;
case GREEN:
return (readLongInt(LTR381_DATA_GREEN_0, data));
break;
case BLUE:
return (readLongInt(LTR381_DATA_BLUE_0, data));
break;
}
return false;
}
boolean LTR381::readLongInt(uint8_t address, unsigned long &value)
{
// Reads an unsigned integer (16 bits) from a LTR381 address (low byte first)
// Address: LTR381 address (0 to 15), low byte first
// Value will be set to stored unsigned integer
// Returns true (1) if successful, false (0) if there was an I2C error
// (Also see getError() above)
uint8_t high, med, low;
// Check if sensor present for read
Wire.beginTransmission(_i2c_address);
Wire.write(address);
_error = Wire.endTransmission();
// Read two bytes (low and high)
if (_error == 0)
{
Wire.requestFrom(_i2c_address, 3);
if (Wire.available() == 3)
{
low = Wire.read();
med = Wire.read();
high = Wire.read();
// Combine bytes into unsigned int
value = ((long)(high & 0x0F)) << 16;
value |= ((long)med) << 8;
value |= (long)low;
return (true);
}
}
return (false);
}
另外还需要修改I2C默认的管脚,这里12指神探使用的管脚为SDA(20),SCL(21)。
第二步:合成颜色。驱动起LTR381后,可以读取到4个长整形数据。其中第一个数据应该是红外数据,使用空调遥控器对着传感器按,数据会有较大的变化,但是这个数据对此项目无效,故此舍弃。
读取LTR381官方文档,文档中有使用传感器数据合成亮度数据的方法。这里看的不是很明白,为啥亮度和红外数据和绿光相关,为啥和红光、蓝光无关呢?感觉还是自己英文不好,没能理解文档的意思。
如果简单地按照读取到传感器R、G、B三色数据的比例,混合出颜色来,经过测试颜色严重失真,混合后的颜色与实际颜色风马牛不相及。
在合成颜色这块卡了很久,找了蛮多文章,都没能很好地解决这个问题。使用手机屏幕给出纯色(红、绿、蓝),从传感器读取到的数据,在(R,G,B)三个分量上依然是都有值,这样一合成,颜色就偏离纯色了。然后我就改变思路,既然无法准确模拟出环境的颜色,那么就预设几种颜色,去猜测环境中的颜色是这预设的那个颜色即可。这样至少预设的几种颜色能够完全模拟出来。就像一块停了的手表,一天能有两次时间是准确的。
先采集各类纯色时,传感器收集到的R、G、B的值,然后用统计方法求均值。将(R,G,B)作为全集,计算在纯色时,每个分量占比全集的比例值。这样获得了7种颜色在三原色中的坐标信息。
白色 0.2622 0.4890 0.2488
红色 0.5926 0.3118 0.0955
绿色 0.1300 0.6446 0.2255
蓝色 0.1244 0.2746 0.6010
黄色 0.3734 0.5131 0.1135
pink 0.3949 0.2279 0.3772
绿蓝 0.0983 0.5042 0.3975
然后当传感器获得数据后,通过获得的(R,G,B)数据计算距离以上7种颜色的几何距离,取距离最近的颜色作为模拟颜色。
//计算距离指定颜色的距离
float distinctColor(float *in_c, float *targetedcolor)
{
return sqrt((in_c[0] - targetedcolor[0]) * (in_c[0] - targetedcolor[0]) + (in_c[1] - targetedcolor[1]) * (in_c[1] - targetedcolor[1]) + (in_c[2] - targetedcolor[2]) * (in_c[2] - targetedcolor[2]));
}
//寻找最小值
int findMinVal(float *inval)
{
int offset = 0;
float diffval = inval[0];
for (int i = 1; i < 7; i++)
{
if (inval[i] < diffval)
{
diffval = inval[i];
offset = i;
}
}
return offset;
}
// 用传感器计算获得的颜色,简化了程序,仅仅还原 RGB 饱和色的混合
// 方法是 计算到已知颜色的距离,取最小距离作为结果
// 白色 0.2622 0.4890 0.2488
// 红色 0.5926 0.3118 0.0955
// 绿色 0.1300 0.6446 0.2255
// 蓝色 0.1244 0.2746 0.6010
// 黄色 0.3734 0.5131 0.1135
// pink 0.3949 0.2279 0.3772
// 绿蓝 0.0983 0.5042 0.3975
long countColor(long r, long g, long b)
{
float sum = r + g + b;
float in_c[3] = {0};
float countcolor[7] = {0};
diapcolorinfo(r, g, b);
in_c[0] = float(r) / sum;
in_c[1] = float(g) / sum;
in_c[2] = float(b) / sum;
for (int i = 0; i < 7; i++)
{
countcolor[i] = distinctColor(in_c, staticcolor + i * 3);
Serial.print(countcolor[i]);
Serial.print(" ");
}
Serial.println();
//从7种颜色里选出最小的值,作为识别到的颜色。
// Serial.println(findMinVal(countcolor));
return findMinVal(countcolor);
}
//从识别到的颜色(0~7) 转换成颜色的数值
int changeColorRGB(int color)
{
switch (color)
{
case 0:
return tft.color565(255, 255, 255); //白光
break;
case 1:
return tft.color565(255, 0, 0); // red
break;
case 2:
return tft.color565(0, 255, 0); // green
break;
case 3:
return tft.color565(0, 0, 255); // blue
break;
case 4:
return tft.color565(255, 255, 0); // yellow
break;
case 5:
return tft.color565(255, 0, 255); // pink
break;
case 6:
return tft.color565(0, 255, 255); // cyan
break;
}
return 0;
}
通过以上方法,可以保障在遇到以上7中颜色的光线时,能够准确地模拟出对应的颜色,但是更多的颜色也终将落到这7色中去,无法完美地模拟出环境颜色。
心得体会:
感谢电子森林举办的寒假一起练活动。知易行难,觉着很简单的任务,当自己动手去做时才会明白各种困难。也正是克服了各种困难才会有成就感。还要感谢群里的各位老师,提供了很多解决问题的思路。