Розпізнавання кольору і рівня освітленості за допомогою APDS-9960
Нещодавно промайнула стаття в якій, серед іншого, повідомлялося про датчик освітленості. Деякий час тому я знайшов і придбав цікаву річ — модуль виробництва фірми RobotDyn на основі датчика APDS-9960, який теж вміє вимірювати рівень освітленості. Пошукавши і не зумівши відшукати згадок цього приладу на даному ресурсі, я вирішив, що це відповідний привід для написання статті.
У статті мені б хотілося в загальних рисах познайомити читачів з можливостями, які надає цей датчик і більш детально розглянути, яким чином з його допомогою можна визначати колір і вимірювати рівень освітленості.
APDS-9960 — це датчик від компанії Avago, він являє собою комбінований цифровий датчик з цілим рядом різних цікавих і корисних функцій.
Він вміє розпізнавати жести, визначати наближення, а ще він вміє реєструвати інтенсивність навколишнього освітлення і визначати колір.
Ось саме про це і піде мова в цій статті — за допомогою старенької STM32VLDISCOVERY і APDS-9960 ми з Вами виміряємо освітленість і будемо визначати колір у всьому його багатстві відтінків Червоного, Зеленого і Блакитного.
Однак, перш ніж ми приступимо до практичної частини, дозвольте спершу кілька слів написати про загальні можливості APDS-9960.
Функціональна схема APDS-9960 представлена на малюнку нижче
Розпізнавання жестів
Уявлення про те як виглядає розпізнавання жестів на APDS-9960 дуже непогано показано на цьому відео.
В документації описаний принцип реєстрації жестів:
Для розпізнавання жесту використовується чотири спрямованих фотодіода які реєструють відбите світло (в ІЧ діапазоні) випромінюваний вбудованим світлодіодом.
Функція виявлення наближення
Судячи з опису з тієї ж документації, механізм виявлення (наближення) працює за таким ж принципом, що і розпізнавання жестів.
Розпізнавання кольору і рівень навколишнього освітлення (Color/ALS)
Згідно з функціональною схемою, датчик визначає колір/рівень освітленості за допомогою відповідних фотодіодів. Також заявлено, що APDS-9960 має вбудовані фільтри, що блокують ультрафіолетовий та інфрачервоний діапазони.
Спрощено це виглядає так: зареєстровані фотодиодами сигнали вимірюються за допомогою АЦП, заносяться в буфер і потім дані відправляються за i2c.
Графіки на картинці вище взяті з документації на датчик, зліва зверху представлена спектральна характеристика Color Sense (RGBC).
Сигнал RGBC фотодіодів накопичується протягом періоду часу, встановленого значенням регістра ATIME. У SparkFun (в їх “apds9960.h”) це значення визначено константою DEFAULT_ATIME і одно 219 що відповідає 103 ms.
Посилення регулюється в діапазоні від 1х до 64x і визначається настроюванням параметра CONTROL AGAIN. Константа DEFAULT_AGAIN, рівна, в свою чергу значенням 1, що відповідає посиленню в 4 рази.
Практична частина
Особисто мене в APDS-9960 цікавила тільки функція Color/ALS, тому я вирішив розглянути її найбільш докладно і написав невеликий код демонструє її роботу.
Код я навмисно намагався зробити максимально компактним, лаконічним і гранично простим для розуміння; весь код цілком буде представлений в кінці статті.
Отже, вся документація (креслення, терморегулятори і принципова електрична схема) на модуль доступна на сайті виробника.
Підключимо наш модуль APDS-9960 до STM32VLDISCOVERY
APDS9960 для зв’язку із зовнішнім світом використовує інтерфейс i2c, тому у STM32VLDISCOVERY задіємо шину I2C1 підключивши висновок модуля SCL до ніжці PB6, а висновок SDA, відповідно до ніжці PB7. Не забуваємо підключити живлення і загальний провід. Переривання в даному випадку використовуватися не будуть, тому висновок Int можна не підключати. У мене на фото він підключений, але не використовується.
А тепер трошки коду. Оскільки все спілкування з модулем відбувається з допомогою i2c, створимо необхідну конфігурацію і визначимо функції читання/запису для i2c.
Ініціалізація I2C.
Ініціалізація
void I2C1_init(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB| RCC_APB2Periph_AFIO , ENABLE);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_StructInit(&I2C_InitStructure);
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_InitStructure.I2C_OwnAddress1 = 0x01;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
Читання регістра.
Функція читання значення з регістра
uint8_t i2c1_read(uint8_t addr)
{
uint8_t data;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED));
data = I2C_ReceiveData(I2C1);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
return data;
}
Запис значення в регістр
Функція запису значення в регістр
void i2c1_write(uint8_t addr, uint8_t data)
{
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)) {};
}
Щоб модуль належним чином заробив, його спершу треба належним чином налаштувати. Конкретно для розпізнавання кольору і освітлення необхідно виконати наступне:
1) Визначити регістр ATIME. За замовчуванням, при старті модуля регістр ATIME має значення 0xFF і якщо нічого не змінювати, це позначиться на чутливості датчика — чутливість буде невисокою.
i2c1_write(APDS9960_ATIME, DEFAULT_ATIME);
2) наступним етапом встановлюємо поле параметра AGAIN (ALS and Color Gain Control) регістра Control Register One (0x8F) значення відповідне посилення рівному x4 (DEFAULT_AGAIN дорівнює AGAIN_4X).
i2c1_write(APDS9960_CONTROL, DEFAULT_AGAIN);
3) включити опцію ALS установкою біта AEN регістра Enable Register (0x80)
4) включити живлення модуля установкою біта PON цього ж регістра
ось так:
i2c1_write(APDS9960_ENABLE, (APDS9960_PON | APDS9960_AEN));
Ось і вся настройка. Наш датчик готовий до праці і оборони, можна починати вимірювати всі кольори.
Але спершу виміряємо рівень освітленості
Colour_tmpL = i2c1_read(APDS9960_CDATAL);
Colour_tmpH = i2c1_read(APDS9960_CDATAH);
Colour_Clear = (Colour_tmpH << 8) + Colour_tmpL;
І ось тепер дійшла наша справа до довгоочікуваних кольорів
Отримаємо дані RGB
//_________________________________________________________________________
// RED color Recognize:
Colour_tmpL = i2c1_read(APDS9960_RDATAL);
Colour_tmpH = i2c1_read(APDS9960_RDATAH);
Colour_Red = (Colour_tmpH << 8) + Colour_tmpL;
//_________________________________________________________________________
// GREEN color Recognize:
Colour_tmpL = i2c1_read(APDS9960_GDATAL);
Colour_tmpH = i2c1_read(APDS9960_GDATAH);
Colour_Green = (Colour_tmpH << 8) + Colour_tmpL;
//_________________________________________________________________________
// BLUE color Recognize:
Colour_tmpL = i2c1_read(APDS9960_BDATAL);
Colour_tmpH = i2c1_read(APDS9960_BDATAH);
Colour_Blue = (Colour_tmpH << 8) + Colour_tmpL;
А тепер весь код цілком:
main.c
#include "stm32f10x.h"
#define APDS9960_I2C_ADDR 0x39
#define APDS9960_ATIME 0x81
#define APDS9960_CONTROL 0x8F
#define APDS9960_ENABLE 0x80
#define APDS9960_CDATAL 0x94
#define APDS9960_CDATAH 0x95
#define APDS9960_RDATAL 0x96
#define APDS9960_RDATAH 0x97
#define APDS9960_GDATAL 0x98
#define APDS9960_GDATAH 0x99
#define APDS9960_BDATAL 0x9A
#define APDS9960_BDATAH 0x9B
/* Bit fields */
#define APDS9960_PON 0x01
#define APDS9960_AEN 0x02
#define APDS9960_PEN 0x04
#define APDS9960_WEN 0x08
#define APSD9960_AIEN 0x10
#define APDS9960_PIEN 0x20
#define APDS9960_GEN 0x40
#define APDS9960_GVALID 0x01
/* ALS Gain (AGAIN) values */
#define AGAIN_1X 0
#define AGAIN_4X 1
#define AGAIN_16X 2
#define AGAIN_64X 3
#define DEFAULT_ATIME 219 // 103ms
#define DEFAULT_AGAIN AGAIN_4X
uint8_t Colour_tmpL = 0;
uint8_t Colour_tmpH = 0;
uint16_t Colour_Clear = 0;
uint16_t Colour_Red = 0;
uint16_t Colour_Green = 0;
uint16_t Colour_Blue = 0;
//-----------------------------------------------------------------------
void I2C1_init(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB| RCC_APB2Periph_AFIO , ENABLE);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_StructInit(&I2C_InitStructure);
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_InitStructure.I2C_OwnAddress1 = 0x01;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
//-----------------------------------------------------------------------
uint8_t i2c1_read(uint8_t addr)
{
uint8_t data;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED));
data = I2C_ReceiveData(I2C1);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
return data;
}
//-----------------------------------------------------------------------
void i2c1_write(uint8_t addr, uint8_t data)
{
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)) {};
}
//-----------------------------------------------------------------------
int main()
{
I2C1_init();
i2c1_write(APDS9960_ATIME, DEFAULT_ATIME);
i2c1_write(APDS9960_CONTROL, DEFAULT_AGAIN);
i2c1_write(APDS9960_ENABLE, (APDS9960_PON | APDS9960_AEN));
while (1)
{
Colour_Clear = 0;
Colour_Red = 0;
Colour_Green = 0;
Colour_Blue = 0;
//_________________________________________________________________________
// Ambient Light Recognize:
Colour_tmpL = i2c1_read(APDS9960_CDATAL);
Colour_tmpH = i2c1_read(APDS9960_CDATAH);
Colour_Clear = (Colour_tmpH << 8) + Colour_tmpL;
//_________________________________________________________________________
// RED color Recognize:
Colour_tmpL = i2c1_read(APDS9960_RDATAL);
Colour_tmpH = i2c1_read(APDS9960_RDATAH);
Colour_Red = (Colour_tmpH << 8) + Colour_tmpL;
//_________________________________________________________________________
// GREEN color Recognize:
Colour_tmpL = i2c1_read(APDS9960_GDATAL);
Colour_tmpH = i2c1_read(APDS9960_GDATAH);
Colour_Green = (Colour_tmpH << 8) + Colour_tmpL;
//_________________________________________________________________________
// BLUE color Recognize:
Colour_tmpL = i2c1_read(APDS9960_BDATAL);
Colour_tmpH = i2c1_read(APDS9960_BDATAH);
Colour_Blue = (Colour_tmpH << 8) + Colour_tmpL;
}
}
Я навмисно не став виносити визначення констант в окремий хэдер для зручності.
Константи, до речі, запозичив з офіційного репозиторію SparkFun Electronics.
Мені APDS-9960 дуже сподобався — цікава річ, цікаво було дослідити, цікаво було писати статтю. Сподіваюся, кому-небудь даний матеріал виявиться корисним. Дякую за увагу.