Распознавание цвета и уровня освещенности с помощью APDS-9960 +12


image


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


В статье мне бы хотелось в общих чертах познакомить читателей с возможностями которые предоставляет этот датчик и более подробно рассмотреть каким образом с его помощью можно определять цвет и измерять уровень освещенности.


APDS-9960 — это датчик от компании Avago, он представляет собой комбинированный цифровой датчик с целым рядом разных интересных и полезных функций.
Он умеет распознавать жесты, определять приближение, а еще он умеет регистрировать интенсивность окружающего освещения и определять цвет.
Вот именно об этом и пойдет речь в данной статье — с помощью старенькой STM32VLDISCOVERY и APDS-9960 мы с Вами измерим освещенность и будем определять цвет во всем его богатстве оттенков Красного, Зеленого и Синего.


Однако, прежде чем мы приступим к практической части, позвольте сперва несколько слов написать об общих возможностях APDS-9960.


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


Распознавание жестов


Представление о том как выглядит распознавания жестов на APDS-9960 очень неплохо показано на этом видео.


В документации описан принцип регистрации жестов:
Для распознавания жеста используется четыре направленных фотодиода которые регистрируют отраженный свет (в ИК диапазоне) излучаемый встроенным светодиодом.


image


Функция обнаружения приближения


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


Распознавание цвета и уровень окружающего освещения (Color/ALS)


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


image


Графики на картинке выше взяты из документации на датчик, слева сверху представлена спектральная характеристика Color Sense (RGBC).


Сигнал RGBC фотодиодов накапливается в течение периода времени, установленного значением регистра ATIME. У SparkFun (в их "apds9960.h") это значение определено константой DEFAULT_ATIME и равно 219 что соответствует 103 ms.


Усиление регулируется в диапазоне от 1x до 64x и определяется настройкой параметра CONTROL AGAIN. Константа DEFAULT_AGAIN, равная, в свою очередь значению 1, что соответствует усилению в 4 раза.


Практическая часть


Лично меня в APDS-9960 интересовала только функция Color/ALS, поэтому я решил рассмотреть ее наиболее подробно и написал небольшой код демонстрирующий ее работу.
Код я намеренно старался сделать максимально компактным, лаконичным и предельно простым для понимания; весь код целиком будет представлен в конце статьи.


Итак, вся документация (чертеж, распиновка и принципиальная электрическая схема) на модуль доступна на сайте производителя.


image


Подключим наш модуль APDS-9960 к STM32VLDISCOVERY


APDS9960 для связи с внешним миром использует интерфейс i2c, поэтому у STM32VLDISCOVERY задействуем шину I2C1 подключив вывод модуля SCL к ножке PB6, а вывод SDA, соответственно к ножке PB7. Не забываем подключить питание и общий провод. Прерывания в данном случае использоваться не будут, поэтому вывод Int можно не подключать. У меня на фото он подключен, но не используется.


image


А теперь немножко кода. Поскольку все общение с модулем происходит с помощью 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 очень понравился — интересная вещь, интересно было исследовать, интересно было писать статью. Надеюсь, кому-нибудь данный материал окажется полезен. Спасибо за внимание.




К сожалению, не доступен сервер mySQL