Недавно на Habr.com промелькнула статья в которой, среди прочего, сообщалось о датчике освещенности. Некоторое время назад я нашел и приобрел интересную вещь — модуль производства фирмы 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.
Усиление регулируется в диапазоне от 1x до 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;
И вот теперь дошло наше дело до долгожданных цветов
//_________________________________________________________________________
// 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;
А теперь весь код целиком:
#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 очень понравился — интересная вещь, интересно было исследовать, интересно было писать статью. Надеюсь, кому-нибудь данный материал окажется полезен. Спасибо за внимание.
Автор: StroboNights