TCS34725颜色传感器在TI C2000 TMS320F28P550开发板上的I2C驱动移植与RGB数据采集实战
TCS34725颜色传感器在TI C2000 TMS320F28P550开发板上的I2C驱动移植与RGB数据采集实战最近在做一个颜色识别的小项目用到了AMS的TCS34725颜色传感器。这块传感器精度不错能直接输出红、绿、蓝RGB和全光谱Clear的原始数据非常适合做颜色检测。我把它接到了TI的C2000系列TMS320F28P550开发板上整个过程踩了一些坑也积累了不少经验。今天我就把从硬件连接到软件驱动再到数据读取和色彩空间转换的完整过程手把手地分享给大家希望能帮到正在做类似项目的朋友。1. 准备工作认识你的传感器TCS34725是一款数字颜色传感器它内部集成了滤光片和光电二极管阵列能直接感知环境光中的RGB分量。简单来说它就像一个小型的“电子眼”能告诉你它“看到”的颜色成分。根据原始资料它有以下几个关键特性通信接口I2C这是我们和它对话的唯一方式。I2C地址固定为0x297位地址。这里有个细节要注意在8位地址模式下需要左移一位写地址变成0x52读地址变成0x53。工作电压3.3V - 5V和我们的开发板完美兼容。核心功能输出红(R)、绿(G)、蓝(B)和全光谱(C)四个通道的16位原始数据。我们的目标就是在TMS320F28P550开发板上通过I2C总线与它建立通信读取这些原始数据并最终转换成更直观的HSL色彩空间值。2. 硬件连接把传感器“插”到板子上硬件连接是第一步也是最简单的一步。TCS34725模块通常有7个引脚我们只需要连接其中4根线。传感器模块引脚开发板连接点说明VIN5V0或3V3供电引脚接3.3V或5V都可以我习惯接3.3V。GNDGND电源地必须接。SDAGPIO50I2C数据线需要配置为开漏或准双向模式。SCLGPIO51I2C时钟线同样需要配置。注意I2C总线通常需要上拉电阻一般4.7kΩ。如果你的传感器模块上没有集成需要在SDA和SCL线上各接一个上拉到3.3V的上拉电阻。连接好硬件后我们就要在软件里告诉开发板GPIO50和GPIO51这两个引脚是用来做I2C的。3. 软件配置用SysConfig图形化工具TI的C2000芯片现在推荐使用SysConfig图形化工具来配置外设和引脚这比直接写寄存器方便太多了。在你的CCS工程里找到并双击c2000.syscfg文件它会打开SysConfig配置界面。在界面中找到GPIO配置部分点击“ADD”添加一个新的GPIO配置集。为我们的I2C引脚进行配置找到GPIO50将其功能Pin Function设置为“GPIO50”。在属性栏里将方向Direction设置为“输出”Output初始输出值Initial Output Value可以设为高1。这就是我们的SDA线。同样地找到GPIO51也设置为GPIO输出模式作为SCL线。这里我们采用“软件模拟I2C”的方式所以把两个引脚都配置为普通的GPIO输出。如果使用芯片自带的硬件I2C外设配置方法会不同。按下Ctrl S保存配置。按下Ctrl B重新构建工程。这时SysConfig会自动根据你的图形化配置生成对应的代码。关键的引脚定义比如Module_SDA和Module_SCL对应的具体GPIO编号会出现在自动生成的board.h文件中。因为我们工程里已经包含了tjx_init.h这个头文件而它又包含了board.h所以我们后续在代码中直接包含tjx_init.h就可以了非常方便。4. 驱动编写手把手实现I2C通信硬件和基础配置搞定后核心就是编写驱动代码了。我们在工程里新建一个文件夹module_driver然后在里面创建两个文件bsp_tcs34725.c和bsp_tcs34725.h。别忘了把bsp_tcs34725.h的头文件路径添加到工程的编译包含路径里。4.1 头文件定义 (bsp_tcs34725.h)头文件里主要做三件事宏定义、结构体定义和函数声明。#ifndef _BSP_TCS34725_H_ #define _BSP_TCS34725_H_ #include tjx_init.h // 1. GPIO操作宏定义基于SysConfig生成的配置 #define SDA_OUT() GPIO_setDirectionMode(Module_SDA, GPIO_DIR_MODE_OUT) #define SDA_IN() GPIO_setDirectionMode(Module_SDA, GPIO_DIR_MODE_IN) #define SDA_GET() GPIO_readPin(Module_SDA) #define SDA(x) GPIO_writePin(Module_SDA, x) #define SCL(x) GPIO_writePin(Module_SCL, x) // 2. TCS34725寄存器地址和关键参数宏定义 #define TCS34725_ADDRESS (0x29) // 7位设备地址 #define TCS34725_COMMAND_BIT (0x80) // 寄存器命令位写寄存器时必须置1 // 使能寄存器 #define TCS34725_ENABLE (0x00) #define TCS34725_ENABLE_PON (0x01) // 上电 #define TCS34725_ENABLE_AEN (0x02) // ADC使能 // 积分时间寄存器 #define TCS34725_ATIME (0x01) #define TCS34725_INTEGRATIONTIME_24MS 0xF6 // 24ms积分时间 // 增益控制寄存器 #define TCS34725_CONTROL (0x0F) #define TCS34725_GAIN_1X 0x00 // 1倍增益 // 状态寄存器 #define TCS34725_STATUS (0x13) #define TCS34725_STATUS_AVALID (0x01) // 数据有效标志位 // 数据寄存器低字节和高字节 #define TCS34725_CDATAL (0x14) // Clear通道数据低字节 #define TCS34725_RDATAL (0x16) // Red通道数据低字节 #define TCS34725_GDATAL (0x18) // Green通道数据低字节 #define TCS34725_BDATAL (0x1A) // Blue通道数据低字节 // 3. 数据结构体定义 typedef struct{ unsigned short c; // 全光谱通道值 [0-65535] unsigned short r; // 红色通道值 unsigned short g; // 绿色通道值 unsigned short b; // 蓝色通道值 }COLOR_RGBC; typedef struct{ unsigned short h; // 色相 [0, 360] 度 unsigned char s; // 饱和度 [0, 100] % unsigned char l; // 亮度 [0, 100] % }COLOR_HSL; // 4. 函数声明 uint8_t TCS34725_Init(void); uint8_t TCS34725_GetRawData(COLOR_RGBC *rgbc); void RGBtoHSL(COLOR_RGBC *Rgb, COLOR_HSL *Hsl); #endif4.2 核心驱动实现 (bsp_tcs34725.c)C文件比较长我们拆解成几个关键部分来讲。首先是软件模拟I2C的底层时序函数。因为C2000的硬件I2C配置稍复杂用GPIO模拟对于初学者理解协议更有帮助。#include bsp_tcs34725.h #include stdio.h // 微秒级延时函数需要根据你的系统主频实现例如用空循环或定时器 extern void delay_us(int us); // I2C起始信号SCL高电平时SDA从高变低 void IIC_Start(void) { SDA_OUT(); SDA(1); delay_us(5); SCL(1); delay_us(5); SDA(0); delay_us(5); // SDA产生下降沿 SCL(0); delay_us(5); } // I2C停止信号SCL高电平时SDA从低变高 void IIC_Stop(void) { SDA_OUT(); SCL(0); SDA(0); SCL(1); delay_us(5); SDA(1); delay_us(5); // SDA产生上升沿 } // 发送一个字节8位数据 void Send_Byte(uint8_t dat) { int i 0; SDA_OUT(); SCL(0); // 拉低时钟线准备发送数据位 for(i 0; i 8; i) { // 先发送最高位(MSB) SDA((dat 0x80) 7); delay_us(1); SCL(1); delay_us(5); // 时钟上升沿从机采样数据位 SCL(0); delay_us(5); dat 1; // 左移准备发送下一位 } } // 读取一个字节 unsigned char Read_Byte(void) { unsigned char i, receive 0; SDA_IN(); // 将SDA设置为输入模式准备读取从机数据 for(i 0; i 8; i) { SCL(0); delay_us(5); SCL(1); delay_us(5); // 时钟上升沿主机读取数据位 receive 1; // 左移为接收新数据位腾出空间 if(SDA_GET()) // 读取SDA引脚电平 { receive | 1; // 如果为高电平则该位置1 } delay_us(5); } SCL(0); return receive; } // 等待从机应答ACK。返回0表示收到应答1表示超时无应答通信失败 unsigned char I2C_WaitAck(void) { char ack 0; unsigned char ack_flag 10; // 超时计数 SCL(0); SDA(1); // 主机先释放SDA线 SDA_IN(); // 将SDA设置为输入检测从机拉低 delay_us(5); SCL(1); delay_us(5); // 产生第9个时钟脉冲用于应答 // 循环检测SDA是否被从机拉低 while((SDA_GET() 1) (ack_flag)) { ack_flag--; delay_us(5); } if(ack_flag 0) // 超时未收到应答 { IIC_Stop(); // 发送停止信号 return 1; } else // 收到应答 { SCL(0); SDA_OUT(); // 将SDA重新设置为输出为后续操作做准备 } return ack; } // 主机发送应答(ACK)或非应答(NACK)信号 void IIC_Send_Ack(unsigned char ack) { SDA_OUT(); SCL(0); SDA(0); // 先拉低SDA delay_us(5); if(!ack) SDA(0); // 如果ack为0发送ACK保持低电平 else SDA(1); // 如果ack为1发送NACK拉高电平 SCL(1); delay_us(5); // 时钟上升沿从机接收应答信号 SCL(0); SDA(1); // 释放SDA线 }有了底层的I2C时序函数我们就可以封装针对TCS34725的读写函数了。// 向TCS34725写入多个字节数据 void TCS34725_I2C_Write(uint8_t slaveAddress, uint8_t* dataBuffer, uint8_t bytesNumber, uint8_t stopBit) { uint8_t i 0; IIC_Start(); // 发送7位设备地址 写标志位(0) Send_Byte((slaveAddress 1) | 0x00); I2C_WaitAck(); // 等待传感器应答 for(i 0; i bytesNumber; i) { Send_Byte(*(dataBuffer i)); // 发送数据字节 I2C_WaitAck(); } if(stopBit 1) IIC_Stop(); // 根据参数决定是否发送停止信号 } // 从TCS34725读取多个字节数据 void TCS34725_I2C_Read(uint8_t slaveAddress, uint8_t* dataBuffer, uint8_t bytesNumber, uint8_t stopBit) { uint8_t i 0; IIC_Start(); // 发送7位设备地址 读标志位(1) Send_Byte((slaveAddress 1) | 0x01); I2C_WaitAck(); for(i 0; i bytesNumber; i) { if(i bytesNumber - 1) // 如果是最后一个要读的字节 { *(dataBuffer i) Read_Byte(); IIC_Send_Ack(1); // 读取最后一个字节后发送NACK } else { *(dataBuffer i) Read_Byte(); IIC_Send_Ack(0); // 读取非最后一个字节后发送ACK } } if(stopBit 1) IIC_Stop(); }再往上我们封装更符合传感器寄存器操作的函数。// 向传感器的指定寄存器地址写入数据 void TCS34725_Write(uint8_t subAddr, uint8_t* dataBuffer, uint8_t bytesNumber) { uint8_t sendBuffer[10] {0, }; uint8_t byte 0; // 寄存器地址需要与命令位(0x80)进行或操作 sendBuffer[0] subAddr | TCS34725_COMMAND_BIT; for(byte 1; byte bytesNumber; byte) { sendBuffer[byte] dataBuffer[byte - 1]; } // 调用底层写函数地址为0x29最后发送停止位 TCS34725_I2C_Write(TCS34725_ADDRESS, sendBuffer, bytesNumber 1, 1); } // 从传感器的指定寄存器地址读取数据 void TCS34725_Read(uint8_t subAddr, uint8_t* dataBuffer, uint8_t bytesNumber) { subAddr | TCS34725_COMMAND_BIT; // 同样需要命令位 // 先发送要读取的寄存器地址不带停止位 TCS34725_I2C_Write(TCS34725_ADDRESS, (uint8_t*)subAddr, 1, 0); // 然后重新启动读取数据 TCS34725_I2C_Read(TCS34725_ADDRESS, dataBuffer, bytesNumber, 1); }现在我们可以用这两个函数来配置传感器和读取数据了。// 设置积分时间影响采样精度和速度 void TCS34725_SetIntegrationTime(uint8_t time) { TCS34725_Write(TCS34725_ATIME, time, 1); } // 设置增益放大信号适用于弱光环境 void TCS34725_SetGain(uint8_t gain) { TCS34725_Write(TCS34725_CONTROL, gain, 1); } // 使能传感器上电并启动ADC void TCS34725_Enable(void) { uint8_t cmd TCS34725_ENABLE_PON; // 上电 TCS34725_Write(TCS34725_ENABLE, cmd, 1); delay_ms(3); // 等待振荡器稳定这个延时很重要 cmd TCS34725_ENABLE_PON | TCS34725_ENABLE_AEN; // 上电 ADC使能 TCS34725_Write(TCS34725_ENABLE, cmd, 1); } // 初始化传感器 uint8_t TCS34725_Init(void) { uint8_t id 0; // 读取设备IDTCS34725的ID是0x44TCS34727是0x4D TCS34725_Read(TCS34725_ID, id, 1); if(id 0x4D || id 0x44) // 检查ID是否正确 { TCS34725_SetIntegrationTime(TCS34725_INTEGRATIONTIME_24MS); // 设置24ms积分时间 TCS34725_SetGain(TCS34725_GAIN_1X); // 设置1倍增益 TCS34725_Enable(); // 使能传感器 return 0; // 初始化成功 } else { return 1; // 初始化失败可能是I2C通信或设备连接问题 } } // 读取单个通道R/G/B/C的16位数据 uint16_t TCS34725_GetChannelData(uint8_t reg) { uint8_t tmp[2] {0, 0}; uint16_t data; TCS34725_Read(reg, tmp, 2); // 读取低字节和高字节 data (tmp[1] 8) | tmp[0]; // 合并成16位数据 return data; } // 读取所有四个通道的原始数据并检查数据是否有效 uint8_t TCS34725_GetRawData(COLOR_RGBC *rgbc) { uint8_t 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 0; // 数据有效 } return 1; // 数据无效ADC转换未完成 }最后我们实现一个将原始RGB数据转换为HSL色彩空间的函数这样颜色信息更符合人眼的感知。// RGB转HSL色彩空间 void RGBtoHSL(COLOR_RGBC *Rgb, COLOR_HSL *Hsl) { uint8_t maxVal, minVal, difVal; // 将RGB值归一化到0-100范围基于Clear通道近似于亮度归一化 uint8_t r Rgb-r * 100 / Rgb-c; uint8_t g Rgb-g * 100 / Rgb-c; uint8_t b Rgb-b * 100 / Rgb-c; // 找出RGB中的最大值和最小值 maxVal (r g) ? ((r b) ? r : b) : ((g b) ? g : b); minVal (r g) ? ((r b) ? r : b) : ((g b) ? g : b); difVal maxVal - minVal; // 计算亮度(L)最大值和最小值的平均值 Hsl-l (maxVal minVal) / 2; // [0-100] if(maxVal minVal) // 如果RGB则是灰色无色相和饱和度 { Hsl-h 0; Hsl-s 0; } else { // 计算色相(H) 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 // maxVal b { Hsl-h 60 * (r - g) / difVal 240; } // 计算饱和度(S) if(Hsl-l 50) Hsl-s difVal * 100 / (maxVal minVal); else Hsl-s difVal * 100 / (200 - (maxVal minVal)); } }5. 实战应用在主函数中读取并显示颜色驱动都写好了最后就是在主程序里调用它们了。下面是一个简单的示例在main.c中实现。#include driverlib.h #include device.h #include board.h #include tjx_init.h #include bsp_tcs34725.h void main(void) { // 系统初始化CCS自动生成 Device_init(); Device_initGPIO(); Interrupt_initModule(); Interrupt_initVectorTable(); Board_init(); EINT; ERTM; // 初始化串口用于打印假设lc_printf已实现 lc_printf(\r\n TCS34725 Color Sensor Demo \r\n); // 初始化TCS34725失败则重试 do{ lc_printf(Sensor Init Failed !! Retry ...\r\n); delay_ms(1000); } while(TCS34725_Init()); lc_printf(TCS34725 Init Successfull !!\r\n); COLOR_RGBC rgb; COLOR_HSL hsl; while(1) { // 尝试获取原始RGB数据 if(!TCS34725_GetRawData(rgb)) // 返回0表示成功 { // 将原始RGB转换为HSL RGBtoHSL(rgb, hsl); // 通过串口打印结果 lc_printf(\r\n--- Color Data ---\r\n); lc_printf(Raw - R:%d, G:%d, B:%d, C:%d\r\n, rgb.r, rgb.g, rgb.b, rgb.c); lc_printf(HSL - H:%d, S:%d%%, L:%d%%\r\n, hsl.h, hsl.s, hsl.l); lc_printf(------------------\r\n); } else { lc_printf(Data not ready yet.\r\n); } delay_ms(500); // 每500ms读取一次 } }把代码编译下载到TMS320F28P550开发板打开串口调试助手你就能看到传感器实时采集并转换的颜色数据了。把传感器对准不同颜色的物体观察RGB和HSL值的变化非常直观。6. 调试心得与常见问题I2C无应答这是最常见的问题。首先检查硬件连接VCC, GND, SDA, SCL是否正确且牢固。其次用示波器或逻辑分析仪抓一下I2C波形看起始信号、地址和数据位的时序是否符合规范。最后确认上拉电阻是否已接上通常4.7kΩ。读取的数据全为0或固定值检查传感器初始化是否成功TCS34725_Init函数是否返回0。确保在调用TCS34725_Enable()后有足够的延时代码中是3ms等待内部振荡器稳定。数据变化不灵敏或错误调整积分时间TCS34725_SetIntegrationTime和增益TCS34725_SetGain。在光线较暗的环境下可以尝试更长的积分时间如101ms或更高的增益如4x、16x。在强光下则使用较短的积分时间和低增益防止数据饱和。HSL转换结果异常注意我们提供的RGBtoHSL函数中先将RGB值用Clear通道进行了归一化。这是一种简化处理适用于很多场景。如果你的应用对颜色精度要求极高可能需要更复杂的颜色校准和白平衡算法。整个移植过程的核心就是理解I2C协议和TCS34725的寄存器操作。一旦底层通信打通了剩下的就是按照数据手册配置参数和读取数据。希望这篇详细的教程能让你在TMS320F28P550上顺利玩转TCS34725颜色传感器。