团队布置的任务内容是2019年中国机器人大赛的题目,小车运行过程中需要对不同物料进行颜色上的识别。因此我最近就学了TCS3200颜色传感器的基本原理和代码的具体实现。网上对该模块的使用基本是基于Arduino的,对于基于STM32的介绍较少。鉴于此,补一篇代码可以跑STM32的笔记是有必要的(
实际上为了凑一篇笔记赶紧交作业)
TCS3200是TAOS公司推出的可编程彩色光到频率的转换器,它把可配置的硅光电二极管与电流频率转换器集成在一个单一的CMOS电路上,同时集成了三种颜色(RGB)的滤光器
,是业界第一个具有数字兼容接口的RGB彩色传感器。
工作时,通过两个可编程的引脚来动态选择所需要的滤光器,该传感器的典型输出频率范围从2Hz~500kHz,用户还可以通过两个可编程引脚来选择100%、20%、2%的输出比例因子
,或是电源关断模式。选择不同的输出比例因子,可以对输出频率范围进行调整,适应不同的测量范围。
TCS3200适合于色度计测量应用领域。比如彩色打印、医疗诊断、计算机彩色监视器校准以及油漆、纺织品、化妆品和印刷材料的过程控制。
下图是其电路原理图(不看也行)
通常所看到的物体颜色,实际上是物体表面吸收了照射到它上面的白光中的一部分有色成分,而反射出的另一部分有色光在人眼中的反应。白色是由各种频率的可见光混合在一起构成的,也就是说白光中包含着各种颜色的色光(如红R、黄Y、绿G、青V、蓝B、紫P)。根据德国物理学家赫姆霍兹的三原色理论可知,各种颜色是由不同比例的三原色(红、绿、蓝)混合而成的。每一种颜色都一个标准值。由上面的三原色感应原理可知,特定颜色的值就是红绿蓝三种颜色标准值的线性组合。
对于TCS3200来说,当选定一个颜色滤波器时,它只允许某种特定的原色通过,阻止其它原色的通过。例如:当选择红色滤波器时,入射光中只有红色可以通过,蓝色和绿色都被阻止,这样就可以得到红色光的光强;同理,选择其它的滤波器,就可以得到蓝色光和绿色光的光强。通过这三个光强值,就可以分析出反射到TCS3200D传感器上的光的颜色。TCS3200传感器有红绿蓝和清除4种滤光器,可以通过其引脚 S2 和 S3 的高低电平来选择滤波器模式,如下图。
L表示低电平,H表示高电平
当被测物体反射光的红、绿、蓝三色光线分别透过相应滤波器到达TAOS TCS3200RGB感应芯片时,其内置的振荡器会输出方波,方波频率与所感应的光强成比例关系,光线越强,内置的振荡器方波频率越高。
TCS3200传感器有一个OUT引脚,它输出信号的频率与内置振荡器的频率也成比例关系,
它们的比率因子可以靠其引脚S0和S1的高低电平来选择,如下图。
现在我们对光强值做了处理,令其转换成了波,又在它的基础上乘上了一个因子来转换成OUT引脚输出给主控板的信号频率。现在就差要得到对应颜色的RGB标准值,这需要我们就需要做 白平衡校正。
白平衡就是告诉传感器什么是“白色”的。以白色为参照颜色,才能计算其他颜色的RGB标准值。从理论上讲,白色是由等量的红色、蓝色、绿色混合而成的。
Window画图功能中编辑颜色界面
而然实际上白色中的RGB并不是完全相等,并且TCS3200光传感器对三种颜色的敏感性并不相同,导致RGB输出并不相同。因此必须要做白平衡校正,使得TCS3200检测的`“白色”``输出的RGB值一样。
白平衡的方法:把一个白色物体放置在TCS3200颜色传感器之下,两者相距10mm左右,点亮传感器上的4个白光LED灯,然后选通三原色的滤波器,让被测物体反射光中红、绿、蓝三色光分别通过滤波器,得到OUT引脚输出信号的脉冲数。在用白色的RGB标准值分别处以对应颜色就得到了三种颜色的比例因子。有了白平衡校正得到的RGB比例因子,就可以讲测得其他颜色的信号脉冲换算成RGB标准值了。
S = 255 N 0 (1) S={255\over N_0}\tag{1} S=N0255(1)
E = S ∗ N 0 (1) E=S*N_0\tag{1} E=S∗N0(1)
N0是白平衡是测得的脉冲数,S是RGB比例因子,E是当前检测颜色的RGB值。
根据上文的介绍,对颜色信号的采集基本上是以下步骤
我们的检测思路是:开一个定时器,间隔1s进入一次中断服务,在中断服务函数中采集信号并进行计算处理。1s检测RGB中的一种颜色。另外再开一个定时器,用来统计OUT引脚输出的脉冲数。第一次的检测做白平衡检测,用255除以脉冲数得到RGB比例因子,就可以用于之后的识别。所以白平衡进行一次,之后的检测都写在while函数里即可。开启串口通信,用printf函数把RGB值每隔4s打印一次,检查TCS3200传感器的识别结果。
我使用STM32CubeMax进行STM32开发,开发方式使用HAL库。用的板子型号是正点原子MINI板,芯片型号为STM32F103RCT6。
巨量的初始化函数自动生成,函数直接集成不同功能,谁用谁爽~~
这是我使用的GPIO口:
GPIO输出模式
PA4 —————— S0
PA5 —————— S1
PA6 —————— S2
PA7 —————— S3
PC4 —————— LED
定时器2外部时钟模式
PA0 —————— OUT
然后开了串口1 定时器1
定时器1采用内部时钟模式,经过倍频后APB2总线上的频率为72kHz,设置预分频系数和计数值分别为(7200-1)和(10000-1),这样定时间隔即为1s
定时器2采用外部时钟模式,统计OUT引脚输出的脉冲个数,计数方式为向上计数,65535溢出
打开串口1,采用异步传输方式,波特率为115200,8位字节的数据长度。没有奇偶校验位,数据发送停止位有1位。
下图是对时钟树的配置,在这里不多说
下面上代码:
我把有用的代码都写在了主函数文件里面,那些初始化函数啥的就不放出来,直接看有用的部分吧。另外声明,这些代码参考了http://www.eefocus.com/zhang700309/blog/13-08/296390_6c438.html,我改成了能在STM32板子上跑的代码。
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h" //使用printf函数需要调用这个库
#define S0_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
#define S0_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
#define S1_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
#define S1_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
#define S2_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
#define S2_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);
#define S3_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
#define S3_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
#define LED_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET);
#define LED_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET);
float RGB_Scale[3]; //数组存储3个RGB比例因子
int count=0; //统计脉冲数
int cnt[3]; //数组存储RGB三种颜色的脉冲值
int flag=0; //滤波器选择模式标志
void SystemClock_Config(void);
void filter(int s2,int s3) //滤波器模式选择函数,根据S2和S3的电位来选择红、绿、蓝、三种颜色的滤波器
{
if(s2==0&&s3==0){
S2_L;S3_L;
}
if(s2==0&&s3==1){
S2_L;S3_H;
}
if(s2==1&&s3==0){
S2_H;S3_L;
}
if(s2==1&&s3==1){
S2_H;S3_H;
}
}
void TSC_WB(int s2, int s3) //结束上一种颜色识别,开始下一种颜色的识别
{
count = 0; //统计脉冲值清零
flag ++; //输出信号计数标志+1,进行下一个颜色的脉冲统计
filter(s2, s3); //选择滤波器模式
}
int fputc(int c, FILE *stream) //重定义printf函数,在川口打印数据
{
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
int main(void)
{
HAL_Init(); //初始化函数
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
HAL_TIM_Base_Start_IT(&htim1); //使能TIM1
HAL_TIM_Base_Start_IT(&htim2); //使能TIM2
S0_L;
S1_H; //选择2%的输出比例因子
LED_ON; //打开白光LED进行白平衡
HAL_Delay(4000); //延时四秒等待识别
//通过白平衡测试,计算得到白色物体RGB值255与1s内三色光脉冲数的RGB比例因子
RGB_Scale[0] = 255.0/ cnt[0]; //红色光比例因子
RGB_Scale[1] = 255.0/ cnt[1] ; //绿色光比例因子
RGB_Scale[2] = 255.0/ cnt[2] ; //蓝色光比例因子
//打印白平衡后的红、绿、蓝三色的RGB比例因子
printf("%5lf\r\n",RGB_Scale[0]);
printf("%5lf\r\n",RGB_Scale[1]);
printf("%5lf\r\n",RGB_Scale[2]);
//红、绿、蓝三色光分别对应的1s内TCS3200输出脉冲数乘以相应的比例因子就是RGB标准值
//打印被测物体的RGB值
for(int i=0; i<3; i++)
printf("%d\r\n",(int)(cnt[i]*RGB_Scale[i]));
LED_OFF; //关闭LED
printf("白平衡调试结束\r\n");
//白平衡结束后,就可以检测其他的颜色了,为了让它不断检测,我们让他循环起来
while (1)
{
flag=0; //标志位清0 开始下一轮的识别
count=0; //脉冲数置为0,开始统计下一个脉冲数
HAL_Delay(4000); //延时4s 等待识别结果
for(int i=0; i<3; i++)
printf("%d\r\n",(int)(cnt[i] * RGB_Scale[i]));
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {
0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {
0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
//定时器1的中断服务函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
if(htim->Instance==htim1.Instance){
count=__HAL_TIM_GET_COUNTER(&htim2);//将TIM2统计的脉冲数存入变量count
switch(flag){
case 0:
printf("->WB Start\r\n");
TSC_WB(0, 0); //选择让红色光线通过滤波器的模式
break;
case 1:
printf("->Frequency R=");
printf("%d\r\n",count); //打印1s内的红光通过滤波器时,TCS3200输出的脉冲个数
cnt[0] = count; //存储1s内的红光通过滤波器时,TCS3200输出的脉冲个数
TSC_WB(1, 1); //选择让绿色光线通过滤波器的模式
break;
case 2:
printf("->Frequency G=");
printf("%d\r\n",count); //打印1s内的绿光通过滤波器时,TCS3200输出的脉冲个数
cnt[1] = count; //存储1s内的绿光通过滤波器时,TCS3200输出的脉冲个数
TSC_WB(0, 1); //选择让蓝色光线通过滤波器的模式
break;
case 3:
printf("->Frequency B=");
printf("%d\r\n",count); //打印1s内的蓝光通过滤波器时,TCS3200输出的脉冲个数
printf("->WB End\r\n");
cnt[2] = count; //存储1s内的蓝光通过滤波器时,TCS3200输出的脉冲个数
TSC_WB(1, 0); //选择无滤波器的模式
break;
default:
count = 0; //计数值清零
break;
}
}
}
void Error_Handler(void)
{
}
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
下面是实物接线,emmm,不太会拍,具体怎么接线参考其他文章吧。
现在我们来测试一下,打开串口助手。
可以看到我们接收到了来自传感器的信号,然后根据传来的数值做进一步处理。比如在这个比赛里,可以利用传来数据控制舵机呀,电机这些的下一步动作。不过这个测试还是有一些问题的,白平衡阶段和识别颜色阶段获得的脉冲数有异常的变动。我认为可能是硬件方面的问题,需要做进一步排查。欢迎大家积极讨论,发现问题。