所有关于电路
项目

设计一个颜色传感器,通过RGB LED模块显示测量结果,第2部分

2016年1月23日通过罗伯特Keim

学习如何收集和处理由BH1745NUC颜色传感器IC生成的RGB数据。

采集并处理由BH1745NUC颜色传感器IC生成的RGB数据。

支持信息

本系列的前一篇文章

传感器

在第1部分中,我们讨论了如何使用DAC和一些负反馈来精确控制红色、绿色和蓝色led的强度。我们现在可以使用我们的RGB LED模块作为单像素显示-即。在美国,通过控制红光、绿光和蓝光的混合,我们可以产生各种各样的颜色。

我们希望使用此LED模块重复照亮我们的RGB传感器的光的颜色。如第一篇文章所述,我们正在使用BH1745NUC颜色传感器IC由Rohm制造(以下缩写为BH1745)。这实际上是一个相当令人印象深刻的设备。The package is miniscule (about 2 mm × 2 mm), which is one reason why we are using a custom-designed PCB for this project (maybe you could solder jumper wires onto eight microscopic 0.5-mm-pitch lands, but I couldn’t). And despite this small size, the part incorporates extensive functionality and requires only a few external components. Here is the “typical application circuit” from the datasheet:

光学滤波器,四个光电二极管,四个单独的16位ADC,需要任何信号调节电路,I2C接口和某些中断逻辑,可用于在红色,绿色,蓝色或清除测量到上方时提醒微控制器or below customizable thresholds—I’d say that’s pretty good for something so tiny and inexpensive (在Digi-Key,每单元1.61美元)。

以下是示意图的相关部分:

收集数据

BH1745的数字部分包括一组21个8位寄存器。除了中断功能之外,微控制器和BH1745之间的所有交互都是通过标准I2C事务向这些寄存器写入或从这些寄存器读取来完成的。有关I2C协议的大量一般信息和实用指南,请参阅“支持信息”下列出的文章。这里我们将重点讨论BH1745特有的实现细节。

从BH1745控制和检索数据需要三种类型的I2C事务:写、读前写和读。

  • 写入:这些事务用于将数据加载到BH1745的寄存器库中。从地址 - 加上R / NW字节之后的第一个字节指定寄存器地址,然后以下字节是要加载到寄存器中的数据。

  • 写道阅读:如果您熟悉I2C协议,则知道主设备无法在一个事务中编写和读取数据。每个事务都被定义为读取或写入。因此,我们不能使用单个事务来指示寄存器地址,然后读取来自该寄存器的数据。解决方案是两个单独的交易 - 首先,我们将数据写入BH1745,告诉它我们要读取的寄存器,然后我们跟进读取事务以从指定的寄存器检索数据。此过程中的第一个事务是我称之为读取的交易。
  • 读:这些事务允许master从write-before-read事务中传输的寄存器地址中读取数据。

如您所见,一个读事务并不局限于一个指定的寄存器地址。如果您继续从BH1745读取字节,它将自动增加寄存器地址并从新的寄存器发送数据。实际上,你可以对写事务做同样的事情:

我通常会避免自动递增功能,因为我重视保持简单原则,这在这里特别相关,因为BH1745的寄存器不是连续排列的(即,无效的寄存器地址混合在有效的寄存器地址中)。但是,我在读取RGBC数据时确实使用了这个特性——所有8个字节都是连续的(从地址0x50开始),使用16个单独的单字节事务(8个写前读和8个读)频繁收集RGBC数据会非常低效。

还要注意,写前读和读事务可以通过重复的启动条件(如上图所示)来实现,而不是先停止条件后启动条件。如果我们在I2C总线上有多个主机,这将是更好的(有关更多信息,请参见中的“Start without a Stop”部分I2C总线:固件实现详细信息)。不过,在这个项目中,我们只有一个master,因此我们将再次调用keep-it-simple原则,并使用典型的“停止然后启动”方法。

处理数据

来自BH1745的RGBC数据是4个16位的单词,如下所示:

我们可以忽略这个项目的清晰数据;我们需要做的就是将R、G和B转换成8位值,我们可以使用这些值来控制R、G和B led的强度。首先要意识到的是,BH1745中的三种彩色探测器并非同样敏感:

根据此图,我们可以看到R约为0.72,而B是当G是1.因此,我们需要通过适当的校正因子乘以R和B值:

\ [CF_R = \压裂{1}{0.72}= 1.39,\ \ \ CF_B = \压裂{1}{0.56}= 1.79 \]

现在我们需要以这样的方式修改数据强调色彩特征入射光。我们的目标是“测量”颜色,而不考虑照亮光电探测器的光的整体强度。因此,我们需要以一种标准化的方式缩放RGB值绝对在保留时测量的值相对价值——换句话说,我们使整体最大化强度同时保持部分在入射光中的红色,绿色和蓝色。为了实现这一点,我们将三种测量中的最高测量值乘以最大值的任何因素,然后我们将其他两个测量值乘以相同的因素。此注释的代码摘录阐明了整个过程:

//从接收到的数据中提取16位的R, G和B值R_word = (I2C_RcvData[1] << 8) | I2C_RcvData[0];G_word = (I2C_RcvData[3] << 8) | I2C_RcvData[2];B_word = (I2C_RcvData[5] << 8) | I2C_RcvData[4];//根据三种光电探测器的相对灵敏度应用校正因子R_intensity = R_word * 1.39;G_intensity = G_word * 1;B_intensity = B_word * 1.79;if(R_intensity >= G_intensity && R_intensity >= B_intensity) MaxIntensity = R_intensity;if(G_intensity >= R_intensity && G_intensity >= B_intensity) MaxIntensity = G_intensity;else MaxIntensity = B_intensity;/*现在我们将每个测量值缩放到0到100的范围。 * This preserves the relative ratios of the three * intensities but standardizes the absolute intensities. * Thus, the appearance of the LED module is * determined by the proportion of red, green, * and blue light, not by the overall intensity of * the incident light.*/ R_scaled = (R_intensity/MaxIntensity)*100; G_scaled = (G_intensity/MaxIntensity)*100; B_scaled = (B_intensity/MaxIntensity)*100;

注意,最终的值被缩放,使最大值为100。您可能还记得,在上一篇文章中,DAC具有8位分辨率,因此我们可以达到255。那么为什么我要限制LED强度到100而不是使用完整的8位范围呢?因为盯着这个高达20毫安的LED模块看,我的视力就会变差!光线是如此的集中和纯粹,它似乎混淆了眼睛在一个恼人的方式。

一步一步

以下是配置BH1745,收集数据,处理数据和更新DAC的整体过程;可以在源代码中找到进一步的详细信息。

  1. 写入寄存器0x42以启用RGBC转换。
  2. 延迟1秒(或测量之间的其他适当间隔)。
  3. 写入寄存器地址0x50,准备读取RGBC数据。
  4. 读取8个字节的RGBC数据(对于这个项目,我们只需要前6个字节)。
  5. 等到I2C交易完成。
  6. 将16位RGB数据读入三个无符号16位变量。
  7. 将这三个变量转换为浮点值,同时将每个变量乘以适当的修正因子。
  8. 如上所述,找到三个并缩放每个值;将结果存储在未签名的8位变量中。
  9. 通过加载8位值到适当的DAC通道来更新LED的颜色。

固件

这里有一个链接,可以下载包含所有源文件和项目文件的zip文件。您可以打开“hwconf”文件来访问端口引脚和外设的配置细节。另外,请注意,这些源文件包括一些代码,直到我们合并USB连接时才需要这些代码。

RGBSensorwithLEDFeedback_Part2.zip

以下是一些显著的代码部分。首先,main()例程:

int main(void) {unsigned char R_scaled, G_scaled, B_scaled;unsigned int R_word, G_word, B_word;R_intensity, G_intensity, B_intensity, MaxIntensity;//调用硬件初始化程序enter_DefaultMode_from_RESET();//启用全局中断IE_EA = 1;//告诉RGBC传感器开始执行测量I2C_MasterWrite(RGBC_Tx_EnableConv);while (1) {Delay_10ms(100);// LED每秒更新一次//加载正确的寄存器地址(用于读取)到RGBC传感器I2C_MasterWrite(RGBC_Tx_SetReadRGBC);//读取RGBC数据I2C_MasterRead(RGBC_Rx_RGBC);//等待直到读取事务完成,而(I2C_State != MstR_DATA_READY); I2C_State = IDLE; //extract the 16-bit R, G, and B values from the received data R_word = (I2C_RcvData[1] << 8) | I2C_RcvData[0]; G_word = (I2C_RcvData[3] << 8) | I2C_RcvData[2]; B_word = (I2C_RcvData[5] << 8) | I2C_RcvData[4]; //apply correction factors based on the relative sensitivity of the three photodetectors R_intensity = R_word * 1.39; G_intensity = G_word * 1; B_intensity = B_word * 1.79; //determine which intensity is the highest of the three if(R_intensity >= G_intensity && R_intensity >= B_intensity) MaxIntensity = R_intensity; else if(G_intensity >= R_intensity && G_intensity >= B_intensity) MaxIntensity = G_intensity; else MaxIntensity = B_intensity; /*Now we scale each measurement into the range 0 to 100. * This preserves the relative ratios of the three * intensities but standardizes the absolute intensities. * Thus, the appearance of the LED module is * determined by the proportion of red, green, * and blue light, not by the overall intensity of * the incident light.*/ R_scaled = (R_intensity/MaxIntensity)*100; G_scaled = (G_intensity/MaxIntensity)*100; B_scaled = (B_intensity/MaxIntensity)*100; /* This can be used to turn off the LED if the measured * light intensity is too low. This helps to avoid distracting * color changes related to irrelevant RGB variations detected * during low-intensity lighting conditions. This functionality * was used with the "Christmas lights" demonstration to make * the LED turn off when the sensor was not illuminated by one * of the lights.*/ if(MaxIntensity < 100) { R_scaled = 0; G_scaled = 0; B_scaled = 0; } //update each DAC channel with the scaled values UpdateDAC(DAC_RGB_R, R_scaled); UpdateDAC(DAC_RGB_G, G_scaled); UpdateDAC(DAC_RGB_B, B_scaled); } }

这段代码配置并启动I2C事务:

无符号字符I2C_SlaveAddr;//当前从地址的全局变量unsigned char I2C_NumReadBytes;//读取的字节数unsigned char idata *I2C_WriteBufferPtr;//要传输的字节的指针unsigned char I2C_FinalWriteAddress;/ /字节的ISR使用这个来确定最后一个字节/ *这些“事务数组包含所需的所有信息为特定I2C事务* / / /写寄存器地址0×启用RGBC转换和保持增益在违约(1 x)无符号字符idata RGBC_Tx_EnableConv [4] = {RGB_SENS_ADDR 2 0×,0 x10};//写入寄存器地址0x42来启用RGBC转换,并设置增益为16x //(到目前为止,似乎最好把增益保持在1x) unsigned char idata RGBC_Tx_EnableConv_16x[4] = {RGB_SENS_ADDR, 2, 0x42, 0x12};//设置读地址为0x50,这是存放RGBC数据的寄存器的开始位置unsigned char idata RGBC_Tx_SetReadRGBC[3] = {RGB_SENS_ADDR, 1, 0x50};//读取RGBC数据(使用RGBC_Tx_SetReadRGBC设置读地址后)unsigned char idata RGBC_Rx_RGBC[3] = {RGB_SENS_ADDR, RGBC_DATA_LEN};void I2C_MasterWrite(unsigned char* PtrtoCmdBuffer) //函数参数只是事务数组的名称{//确保我们不中断正在进行的事务而(I2C_State != IDLE);I2C_State = MstW_STA_SENT; //first state is "start condition generated" I2C_SlaveAddr = PtrtoCmdBuffer[0]; //copy the slave address from the transaction array to the global variable I2C_WriteBufferPtr = PtrtoCmdBuffer + 2; //set the address of the first data byte in the transaction array I2C_FinalWriteAddress = I2C_WriteBufferPtr + (PtrtoCmdBuffer[1] - 1); //set the final address based on the number of bytes to be transmitted SFRPAGE = SMB0_PAGE; SMB0CN0_STA = 1; //initiate the transaction by setting the start-condition bit } void I2C_MasterRead(unsigned char* PtrtoCmdBuffer) //function argument is simply the name of the transaction array { //ensure that we are not interrupting an ongoing transaction while(I2C_State != IDLE); I2C_State = MstR_STA_SENT; //first state is "start condition generated" I2C_SlaveAddr = PtrtoCmdBuffer[0]; //copy the slave address from the transaction array to the global variable I2C_NumReadBytes = PtrtoCmdBuffer[1]; //copy the number of bytes to be read from the transaction array to the global variable SFRPAGE = SMB0_PAGE; SMB0CN0_STA = 1; //initiate the transaction by setting the start-condition bit }

I2C事务在I2C状态机中继续,它被合并到系统管理总线(SMBus)外围设备的中断服务程序中(“多个名称,一个总线”部分)I2C总线简介说明SMBus与I2C的关系。

si_interrupt(smbus0_isr,smbus0_irqn){sfrpage_save = sfrpage;sfrpage = smb0_page;开关(I2C_STATE){//主读数=================================================== // start条件传输案例mstr_sta_sent:smb0cn0_sta = 0;//清除启动条件位smb0cn0_sto = 0;//确保停止条件位清除SMB0DAT =(I2C_SLAVEADDR << 1)| BIT0;//与R / NW = 1 i2c_state = mstr_addr_sent组合从地址;//将状态变量设置为下一个状态smb0cn0_si = 0;//清除中断旗帜中断;//主发送“地址+ R / W”字节案例mstr_addr_sent:ifstr_addr_sent:如果(smb0cn0_ack == i2c_nack)//,如果从站没有ACK {//取消传输和发行总线,如下所示:SMB0CN0_STO = 1; //transmit stop condition I2C_State = IDLE; //set current state as IDLE } else //if slave ACKed { if(I2C_NumReadBytes == 1) //if only one byte will be read { //master NACKs next byte to say "stop transmitting" SMB0CN0_ACK = I2C_NACK; } else //if more than one byte will be read { //master ACKs next byte to say "continue transmitting" SMB0CN0_ACK = I2C_ACK; } RcvdByteCount = 0; //this variable will be an index for storing received bytes in an array I2C_State = MstR_READ_BYTE; //set next state } SMB0CN0_SI = 0; //clear interrupt flag break; //master received a byte case MstR_READ_BYTE: I2C_RcvData[RcvdByteCount] = SMB0DAT; //store received byte RcvdByteCount++; //increment byte counter (which is also the array index) SMB0CN0_SI = 0; //clear interrupt flag if(RcvdByteCount == I2C_NumReadBytes) //if this was the final byte { //release bus, as follows: SMB0CN0_STO = 1; //transmit stop condition SMB0CN0_SI = 0; //clear interrupt flag I2C_State = MstR_DATA_READY; //this state tells the while loop in main() that the received data is ready } else if(RcvdByteCount == (I2C_NumReadBytes-1)) //if the next byte is the final byte { SMB0CN0_ACK = I2C_NACK; //master NACKs next byte to say "stop transmitting" } else { SMB0CN0_ACK = I2C_ACK; //master ACKs next byte to say "continue transmitting" } break; //Master Write=================================================== //start condition transmitted case MstW_STA_SENT: SMB0CN0_STA = 0; //clear start-condition bit SMB0CN0_STO = 0; //make sure that stop-condition bit is cleared SMB0DAT = (I2C_SlaveAddr<<1); //combine slave address with R/nW = 0 I2C_State = MstW_ADDR_SENT; //set state variable to next state SMB0CN0_SI = 0; //clear interrupt flag break; //master transmitted "address + R/W" byte case MstW_ADDR_SENT: if(SMB0CN0_ACK == I2C_NACK) //if slave did not ACK { //cancel transmission and release bus, as follows: SMB0CN0_STO = 1; //transmit stop condition I2C_State = IDLE; //set current state as IDLE } else //if slave ACKed { SMB0DAT = *I2C_WriteBufferPtr; //write first byte to SMBus data register I2C_State = MstW_BYTE_SENT; //set next state } SMB0CN0_SI = 0; //clear interrupt flag break; //master transmitted a byte case MstW_BYTE_SENT: if(SMB0CN0_ACK == I2C_NACK) //if slave NACKed { //stop transmission and release bus, as follows: SMB0CN0_STO = 1; //transmit stop condition I2C_State = IDLE; //set current state as IDLE } //if slave ACKed and this was the final byte else if(I2C_WriteBufferPtr == I2C_FinalWriteAddress) { SMB0CN0_STO = 1; //transmit stop condition I2C_State = IDLE; //set current state as IDLE } //if slave ACKed and this was not the final byte else { I2C_WriteBufferPtr++; //increment pointer that points at data to be transmitted SMB0DAT = *I2C_WriteBufferPtr; //write next byte to SMBus data register } SMB0CN0_SI = 0; //clear interrupt flag break; } SFRPAGE = SFRPAGE_SAVE; }

结论

下面两个视频演示了这个项目的功能。

在第一个场景中,LED最初是被透过一扇大窗户的明亮阳光(虽然是间接的)照亮的,因此它是白色的,带一点蓝色。然后LED就会变成传感器上半透明塑料盖的颜色。

在第二组实验中,LED复制了五种不同圣诞灯的颜色。我是在黑暗中做的,因为小灯或多或少会被周围的照明压倒。颜色的顺序是品红、黄橙、绿、蓝、红。所有的颜色都很匹配,虽然在视频中LED看起来更蓝,但它应该是绿色的(它在现实生活中看起来更绿)。

2的评论
  • 马修冲着 2018年8月15日

    这看起来像是一个有趣的项目,但我认为有一些细节缺失,以完全复制它。你提到的定制PCB的图表/文件在哪里?你如何安装BH1745NUC颜色传感器IC到PCB如果你没有焊接它?你是如何让PCB生产/订购的?我检查了“使用EFM8微控制器的定制PCB设计”的文章,同样的细节在那里缺失了。(这很可能是我忽略了显而易见的问题,如果我是,请原谅。)

    喜欢。 回复
    • RK37 2018年8月16日
      嗨Matthew,这个项目基于多功能PCB;换句话说,我没有为这篇文章设计和制造PCB。信息有些碎片,因为我不想呈现与这篇特定文章无关的PCB细节。此外,如果您的目标是设计一个颜色传感器,则您不想复制此PCB,因为您将包括不必要的电路。因此,如果要复制此功能,则应根据本文中给出的原理图信息和上一篇文章(本系列中的“上一篇文章中的链接”)设计您自己的PCB。如果要使用USB接口(而不是其他通信方法和提供电源),可以引用“使用EFM8微控制器”自定义PCB设计中的信息“。另外,请注意,除非您使用相同的CAD软件或可以将我的文件转换为与CAD软件兼容的格式,否则PCB的原理图和布局文件将不会有帮助。(我使用Diptrace。)关于颜色传感器,文章说:“也许您可以将跳线电线焊接到八个显微镜0.5 mm - 间距陆地上,但我不能。”重点是我将IC直接焊接到PCB而不是使用跳线和面包板。最后,订购和组装PCB是一个主题。 AAC has an abundance of helpful information in this regard, but now that I think about it, we may not have an article that presents the most essential information in a concise, comprehensive way. If this is the case I would consider it an oversight on our part, and I'll remedy the situation by writing the article today.
      喜欢。 回复