Розпізнавання кольору і рівня освітленості за допомогою APDS-9960

 

Нещодавно промайнула стаття в якій, серед іншого, повідомлялося про датчик освітленості. Деякий час тому я знайшов і придбав цікаву річ — модуль виробництва фірми RobotDyn на основі датчика APDS-9960, який теж вміє вимірювати рівень освітленості. Пошукавши і не зумівши відшукати згадок цього приладу на даному ресурсі, я вирішив, що це відповідний привід для написання статті.

У статті мені б хотілося в загальних рисах познайомити читачів з можливостями, які надає цей датчик і більш детально розглянути, яким чином з його допомогою можна визначати колір і вимірювати рівень освітленості.

APDS-9960 — це датчик від компанії Avago, він являє собою комбінований цифровий датчик з цілим рядом різних цікавих і корисних функцій.
Він вміє розпізнавати жести, визначати наближення, а ще він вміє реєструвати інтенсивність навколишнього освітлення і визначати колір.
Ось саме про це і піде мова в цій статті — за допомогою старенької STM32VLDISCOVERY і APDS-9960 ми з Вами виміряємо освітленість і будемо визначати колір у всьому його багатстві відтінків Червоного, Зеленого і Блакитного.

Однак, перш ніж ми приступимо до практичної частини, дозвольте спершу кілька слів написати про загальні можливості APDS-9960.

Функціональна схема APDS-9960 представлена на малюнку нижче

 

Розпізнавання жестів

Уявлення про те як виглядає розпізнавання жестів на APDS-9960 дуже непогано показано на цьому відео.

В документації описаний принцип реєстрації жестів:
Для розпізнавання жесту використовується чотири спрямованих фотодіода які реєструють відбите світло (в ІЧ діапазоні) випромінюваний вбудованим світлодіодом.

 

 

Функція виявлення наближення

Судячи з опису з тієї ж документації, механізм виявлення (наближення) працює за таким ж принципом, що і розпізнавання жестів.

Розпізнавання кольору і рівень навколишнього освітлення (Color/ALS)

Згідно з функціональною схемою, датчик визначає колір/рівень освітленості за допомогою відповідних фотодіодів. Також заявлено, що APDS-9960 має вбудовані фільтри, що блокують ультрафіолетовий та інфрачервоний діапазони.
Спрощено це виглядає так: зареєстровані фотодиодами сигнали вимірюються за допомогою АЦП, заносяться в буфер і потім дані відправляються за i2c.

Читайте також  Композиція UIViewController-ів і навігація між ними (і не тільки)

 

 

Графіки на картинці вище взяті з документації на датчик, зліва зверху представлена спектральна характеристика 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 дуже сподобався — цікава річ, цікаво було дослідити, цікаво було писати статтю. Сподіваюся, кому-небудь даний матеріал виявиться корисним. Дякую за увагу.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *