Создайте цветной датчик с измерениями, отображаемыми через светодиодный модуль RGB, часть 2
Собирайте и обрабатывайте данные RGB, полученные с помощью ИК-датчика цвета BH1745NUC.
Вспомогательная информация
- Пользовательский дизайн печатной платы с микроконтроллером EFM8
- Введение в шину I2C
- Шина I2C: сведения о реализации оборудования
- Шина I2C: информация о внедрении встроенного ПО
- Внедрение I2C с микроконтроллером EFM8, часть 1
- Внедрение I2C с микроконтроллером EFM8, часть 2
Предыдущая статья в этой серии
Создайте цветной датчик с измерениями, отображаемыми через светодиодный модуль RGB, часть 1
Датчик
В первой части мы обсудили, как использовать ЦАП и некоторую отрицательную обратную связь, чтобы точно контролировать интенсивность красных, зеленых и синих светодиодов. Теперь мы можем использовать наш светодиодный модуль RGB в виде однопиксельного дисплея, т. Е. Манипулируя смесью красного, зеленого и синего света, мы можем производить широкий спектр цветов.
Мы хотим использовать этот светодиодный модуль для дублирования цвета света, освещающего наш датчик RGB. Как упоминалось в первой статье, мы используем ИС датчика цвета BH1745NUC производства Rohm (далее сокращенно BH1745). На самом деле это довольно впечатляющее устройство. Пакет минимально (около 2 мм × 2 мм), что является одной из причин, почему мы используем специально разработанную плату для этого проекта (возможно, вы могли бы припаять провода перемычки на восемь микроскопических земель размером 0, 5 мм, но я не мог " т). И, несмотря на этот небольшой размер, эта деталь включает в себя широкие функциональные возможности и требует лишь нескольких внешних компонентов. Вот «типовая схема приложения» из таблицы данных:

Оптические фильтры, четыре фотодиода, четыре отдельных 16-разрядных АЦП с любой схемой формирования сигнала, интерфейс I2C и некоторая логика прерываний, которые могут использоваться для оповещения микроконтроллера всякий раз, когда красное, зеленое, синее или четкое измерения превышают или ниже настраиваемых порогов - я бы сказал, что это очень хорошо для чего-то такого крошечного и недорогого (1, 61 доллара за единицу в Digi-Key).
Вот соответствующая часть схемы:

Сбор данных
Цифровой раздел BH1745 включает банк из 21 8-битного регистра. Все взаимодействия, кроме функций прерывания, которые мы не используем в этом проекте, - между микроконтроллером и BH1745, выполняются путем записи или чтения из этих регистров посредством стандартных транзакций I2C. Для изобилия общей информации и практических рекомендаций, связанных с протоколом I2C, обратитесь к статьям, перечисленным в разделе «Поддержка информации». Здесь мы остановимся на деталях реализации, специфичных для BH1745.
Для управления и получения данных из BH1745 требуются три типа транзакций I2C: запись, запись перед чтением и чтение.
Запись: эти транзакции используются для загрузки данных в банк регистров BH1745. Первый байт после байта slave-address-plus-R / nW указывает адрес регистра, тогда следующий байт - это данные, которые должны быть загружены в регистр.

- Запись перед чтением: если вы знакомы с протоколом I2C, вы знаете, что мастер не может писать и читать данные в одной транзакции. Каждая транзакция определяется как чтение или запись. Таким образом, мы не можем использовать одну транзакцию для указания адреса регистра, а затем считывать данные из этого регистра. Решение состоит из двух отдельных транзакций: сначала мы записываем данные в BH1745, чтобы сообщить, какой регистр мы хотим прочитать, затем мы отслеживаем транзакцию чтения для извлечения данных из указанного регистра. Первая транзакция в этом процессе - это то, что я называю транзакцией write-before-read.
- Чтение: эти транзакции позволяют мастеру считывать данные из любого адреса регистра, который был передан в транзакции с записью перед чтением.

Как вы можете видеть, транзакция чтения не ограничивается одним указанным адресом регистра. Если вы продолжите чтение байтов из BH1745, он автоматически увеличит адрес регистра и отправит данные из нового регистра. Фактически, вы можете сделать то же самое с транзакциями записи:

Обычно я избегаю функции автоматического увеличения, потому что я ценю принцип keep-it-simple, что особенно важно здесь, потому что регистры BH1745 не упорядочены (т. Е. Неверные адреса регистров смешаны между допустимыми адресами регистра). Тем не менее, я использую эту функцию при чтении данных RGBC: все 8 байтов смежны (начиная с адреса 0x50), и было бы серьезно неэффективно часто собирать данные RGBC с использованием 16 отдельных однобайтовых транзакций (8 записей, читает и 8 просмотров).
Также обратите внимание, что транзакции write-before-read и read могут быть реализованы с повторным началом (как показано на диаграмме выше) вместо условия остановки, за которым следует условие начала. Это было бы предпочтительнее, если бы у нас было несколько мастеров на шине I2C (для получения дополнительной информации см. Раздел «Начать без остановки» в разделе «Шина I2C: сведения о встроенном ПО»). В этом проекте, тем не менее, у нас есть только один мастер, поэтому мы снова будем использовать принцип keep-it-simple и использовать типичный подход stop-then-start.
Обработка данных
Данные RGBC поступают из BH1745 в виде четырех 16-битных слов, как показано ниже:

Мы можем игнорировать четкие данные для этого проекта; все, что нам нужно сделать, это преобразовать слова R, G и B в 8-битные значения, которые мы можем использовать для управления интенсивностью светодиодов R, G и B. Первое, что нужно понять, это то, что три цветовых детектора в BH1745 не одинаково чувствительны:

Из этого графика видно, что R составляет около 0, 72 и B составляет около 0, 56, когда G равно 1. Таким образом, нам нужно умножить значения R и B на соответствующий поправочный коэффициент:
(CF_R = \ frac {1} {0.72} = 1.39, \ \ \ CF_B = \ frac {1} {0.56} = 1.79 )
Теперь нам нужно изменить данные таким образом, чтобы подчеркивать цветовые характеристики падающего света. Наша цель здесь - «измерить» цвет, независимо от общей интенсивности света, освещающего фотоприемники. Таким образом, нам необходимо масштабировать значения RGB таким образом, чтобы стандартизировать абсолютное значение измерений при сохранении относительного значения, другими словами, мы максимизируем общую интенсивность, сохраняя при этом долю красного, зеленого и синего в падающем свете, Чтобы достичь этого, мы умножаем наивысшее из трех измерений любым фактором, который увеличивает это самое высокое измерение до максимального значения, затем мы умножаем два других измерения на один и тот же коэффициент. Этот прокомментированный отрывок кода проясняет весь процесс:
//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) <= 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;
Обратите внимание, что конечные значения масштабируются таким образом, что максимум равен 100. На предыдущей странице вы можете вспомнить, что ЦАП имеет 8-битное разрешение, и, следовательно, мы можем достичь 255. Так почему я ограничиваю интенсивность светодиодов до 100 вместо того, чтобы использовать полный файл «hwconf» с 8-битным диапазоном для доступа к деталям конфигурации для портов и периферийных устройств. Также обратите внимание, что эти исходные файлы содержат некоторый код, который не понадобится до тех пор, пока мы не включим USB-соединение.
Файлы источника и проекта
Вот некоторые важные разделы кода. Во-первых, основная () процедура:
int main(void) { unsigned char R_scaled, G_scaled, B_scaled; unsigned int R_word, G_word, B_word; float R_intensity, G_intensity, B_intensity, MaxIntensity; // Call hardware initialization routine enter_DefaultMode_from_RESET(); //enable global interrupts IE_EA = 1; //tell the RGBC sensor to start performing measurements I2C_MasterWrite(RGBC_Tx_EnableConv); while (1) { Delay_10ms(100); //the LED is updated once per second //load the proper register address (for reading) into the RGBC sensor I2C_MasterWrite(RGBC_Tx_SetReadRGBC); //read the RGBC data I2C_MasterRead(RGBC_Rx_RGBC); //wait until the read transaction is complete while(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) <= 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:
unsigned char I2C_SlaveAddr; //global variable for current slave address unsigned char I2C_NumReadBytes; //number of bytes to be read unsigned char idata *I2C_WriteBufferPtr; //pointer to bytes to be transmitted unsigned char I2C_FinalWriteAddress; //the ISR uses this to determine which byte is the final byte /*These "transaction arrays" contain all the information needed for a particular I2C transaction*/ //write to register address 0x42 to enable RGBC conversions and keep the gain at default (1x) unsigned char idata RGBC_Tx_EnableConv(4) = {RGB_SENS_ADDR, 2, 0x42, 0x10}; //write to register address 0x42 to enable RGBC conversions and set the gain to 16x //(so far it appears that it is best to leave the gain at 1x) unsigned char idata RGBC_Tx_EnableConv_16x(4) = {RGB_SENS_ADDR, 2, 0x42, 0x12}; //set the read address to 0x50, which is the beginning of the registers that hold RGBC data unsigned char idata RGBC_Tx_SetReadRGBC(3) = {RGB_SENS_ADDR, 1, 0x50}; //read RGBC data (after setting the read address using RGBC_Tx_SetReadRGBC) unsigned char idata RGBC_Rx_RGBC(3) = {RGB_SENS_ADDR, RGBC_DATA_LEN}; void I2C_MasterWrite(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 = 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; switch(I2C_State) { //Master Read=================================================== //start condition transmitted case MstR_STA_SENT: SMB0CN0_STA = 0; //clear start-condition bit SMB0CN0_STO = 0; //make sure that stop-condition bit is cleared SMB0DAT = (I2C_SlaveAddr<<1)|BIT0; //combine slave address with R/nW = 1 I2C_State = MstR_ADDR_SENT; //set state variable to next state SMB0CN0_SI = 0; //clear interrupt flag break; //master transmitted "address + R/W" byte case MstR_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 { 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; }
Вывод
Функциональность этого проекта демонстрируется в следующих двух видео.
Во-первых, светодиод сначала освещен ярким (хотя и косвенным) солнечным светом, сияющим через большое окно, и, таким образом, он беловатый с синим оттенком. Затем светодиод меняется на цвет полупрозрачной пластиковой крышки, расположенной над датчиком.
Во втором светодиод воспроизводит цвет пяти разных рождественских огней. Я сделал это в темноте, потому что маленькие огни более или менее одолевают окружающее освещение. Порядок цветов - пурпурный, желтовато-оранжевый, зеленый, синий, красный. Все цвета хорошо сочетаются, хотя на видео светодиод выглядит более голубоватым, когда он должен быть зеленым (в реальной жизни он выглядел более зеленым).