Урок 90
Часть 4
Датчик освещённости VL6180X
В предыдущей части нашего занятия мы написали ряд функций и начали уже непосредственно работать с датчиком освещённости.
Добавим функцию подготовки регистров датчика выше функции чтения данных
//-------------------------------------------------
int vl6180_Prepare(void)
{
int status;
return status;
}
//-------------------------------------------------
Вызовем данную функцию в функции чтения данных датчика в теле условия изменения состояния переключателя
switch_state=new_switch_state;
status = vl6180_Prepare();
if(status) Error_Handler();
А теперь, чтобы вам стало понятнее, я расскажу секрет данного мероприятия.
Наш переключатель в одном положения будет переключать датчик на измерение освещённости в люксах, а в другом в десятых долях люкса. Это нужно при слабом освещении, чтобы точнее оценить освещённость. Но так как это будет происходить не с помощью простого умножения или деления значения, а также и изменением значений некоторых регистров, то данную подготовку в этом случае и надо будет проводить. А если состояние переключателя не изменится, мы будем только читать показания и обрабатывать их для дальнейшего отображения на индикаторе.
Сначала нам нужно будет сохранить некоторые значения из регистров датчика. Для этого мы в файле vl6180.h создадим структуру
//-------------------------------------------------
typedef struct VL6180xDevData_t {
uint16_t IntegrationPeriod; /*!< cached als Integration period avoid slow read from device at each measure */
uint16_t AlsGainCode; /*!< cached Als gain avoid slow read from device at each measure */
uint16_t AlsScaler; /*!< cached Als scaler avoid slow read from device at each measure */
}VL6180xDevData_ptr;
//-------------------------------------------------
Вернемся в файл vl6180.c и добавим локальную переменную типа этой структуры
extern volatile int8_t new_switch_state;
VL6180xDevData_ptr VL6180_DevData;
Добавим ещё одну функцию выше функции vl6180_Prepare, в которой мы также проинициализируем кое-что
//-------------------------------------------------
int vl6180_StaticInit(void)
{
int status;
uint8_t data;
do {
status = vl6180_ReadByte(FW_ALS_RESULT_SCALER, &data);
if (status)
break;
VL6180_DevData.AlsScaler = data;
} while (0);
return status;
}
//-------------------------------------------------
Здесь мы пока наоборот считаем данные из регистра с адресом 0x120
Данный регистр хранит в себе умножитель результата измеренной освещенности. Наряду с коэффициентом усиления, данный параметр также помогает более точно производить измерения в условиях низкой освещённости. Используются всего 4 бита, поэтому умножитель можно установить от 1 до 16. Как правило по умолчанию там хранится 1, то есть результат выдается без умножения. Пока мы только считываем данный результат и записываем его в соответствующее поле структуры.
Теперь коэффициент усиления (Gain). Для его установки мы добавим даже отдельную функцию выше только что добавленной
//-------------------------------------------------
int vl6180_AlsSetAnalogueGain(uint8_t gain)
{
int status;
uint8_t GainTotal;
gain &= ~0x40;
if (gain > 7) {
gain = 7;
}
GainTotal = gain | 0x40;
status = vl6180_WriteByte(SYSALS_ANALOGUE_GAIN, GainTotal);
if (!status) {
VL6180_DevData.AlsGainCode = gain;
}
return status;
}
//-------------------------------------------------
Само собой для данного параметра существует регистр с адресом 0x3F
Также есть таблица актуальных значений коэффициентов усиления, из которой также видно, что необходимо ещё устанавливать бит 7 в единицу
В вышенаписанной функции мы устанавливаем значение коэффициента усиления, переданное во входящем параметре, записав его в соответствующий регистр датчика и также сохранив в соответствующее поле структуры на будущее.
Теперь вызовем данную функцию в функции vl6180_StaticInit
VL6180_DevData.AlsScaler = data;
status = vl6180_ReadByte(SYSALS_ANALOGUE_GAIN, &data);
if (status)
break;
vl6180_AlsSetAnalogueGain(data);
} while (0);
Мы сначала считываем значение коэффициента и затем передаём его в функцию. Получается, что мы лишний раз записываем то же самое в регистр. Но ничего страшного. Это разовая запись. Нам функция здесь нужна только для того, чтобы сохранить значение в поле структуры.
На этом функция vl6180_StaticInit закончена. Вызовем её в функции vl6180_Prepare
int status;
do {
status = vl6180_StaticInit();
if (status)
break;
} while (0);
return status;
Кроме функции записи байта в регистр датчика нам потребуется ещё функция записи слова (двухбайтной величины). Добавим её сразу после функции записи байта
//-------------------------------------------------
int vl6180_WriteWord(uint16_t index, uint16_t data)
{
int status;
uint8_t buffer[4];
buffer[0]=index>>8;
buffer[1]=index&0xFF;
buffer[2]=data>>8;
buffer[3]=data&0xFF;
status = vl6180_I2C_Write(buffer, (uint8_t)4);
return status;
}
//-------------------------------------------------
Теперь над функцией vl6180_Prepare добавим функцию установки времени измерения освещённости
//-------------------------------------------------
int vl6180_AlsSetIntegrationPeriod(uint16_t period_ms)
{
int status;
uint16_t SetIntegrationPeriod;
if (period_ms >= 1)
SetIntegrationPeriod = period_ms - 1;
else
SetIntegrationPeriod = period_ms;
if (SetIntegrationPeriod > 464) {
SetIntegrationPeriod = 464;
} else if (SetIntegrationPeriod == 255) {
SetIntegrationPeriod++; /* can't write 255 since this causes the device to lock out.*/
}
status = vl6180_WriteWord(SYSALS_INTEGRATION_PERIOD, SetIntegrationPeriod);
if (!status) {
VL6180_DevData.IntegrationPeriod = SetIntegrationPeriod;
}
return status;
}
//-------------------------------------------------
В случае поступления во входящем параметре завышенных и заниженных величин мы их корректируем. Затем заносим величину в милисекундах в соответствующий регистр, а также в соответствующее поле структуры.
Регистр для этого располагается по адресу 0x40
Вызовем данную функцию в функции vl6180_Prepare
break;
status = vl6180_AlsSetIntegrationPeriod(100);
if (status)
break;
} while (0);
Как и рекомендуется, мы установили этот период 100 милисекунд.
Также выше функции vl6180_Prepare добавим функцию установки периода между измерениями
//-------------------------------------------------
int vl6180_AlsSetInterMeasurementPeriod(uint16_t intermeasurement_period_ms)
{
int status;
if (intermeasurement_period_ms >= 255 * 10)
intermeasurement_period_ms = 255 * 10;
status = vl6180_WriteByte(SYSALS_INTERMEASUREMENT_PERIOD, (uint8_t)(intermeasurement_period_ms / 10));
return status;
}
//-------------------------------------------------
Данную величину мы уже нигде не храним, только записываем её в регистр, обработав предварительно от превышений и принижений.
Используется для этого регистр с адресом 0x3E
Данный регистр восьмибитный и значение здесь хранится в десятках милисекунд.
Вызовем данную функцию в функции vl6180_Prepare, установив время 200 милисекунд
break;
status = vl6180_AlsSetInterMeasurementPeriod(200);
if (status)
break;
} while (0);
Теперь в зависимости от положения переключателя для обеспечения большей точности в измеряемых диапазонах установим соответствующий коэффициент усиления
break;
//выставим усиление для обеспечения точности в заданном диапазоне
if(new_switch_state) status=vl6180_AlsSetAnalogueGain(0);
else status=vl6180_AlsSetAnalogueGain(3);
if (status)
break;
} while (0);
Выше функции vl6180_Prepare напишем функцию установки порогов измерения, передав во входящих параметрах значения нижнего и верхнего порогов
//-------------------------------------------------
int vl6180_AlsSetThresholds(uint8_t low, uint8_t high)
{
int status;
status = vl6180_WriteByte(SYSALS_THRESH_LOW, low);
if (!status) {
status = vl6180_WriteByte(SYSALS_THRESH_HIGH, high);
}
return status;
}
//-------------------------------------------------
Используем мы в данной функции два регистра, для нижнего порога находящийся по адресу 0x3C, а для верхнего — 0x3A
Вызовем данную функцию в функции vl6180_Prepare, задав нужные значения для порогов
break;
status = vl6180_AlsSetThresholds(0, 0xFF);
if (status)
break;
} while (0);
Хоть и значения пороговых величин хранятся в виде двухбайтных значений, вполне достаточно установить в регисрах только старшие байты. По крайней мере так сделано в примере с официального сайта. Я это также не стал нарушать и оставил как есть.
После функции записи слова в регистр добавим функцию обновления байта в регистре
//-------------------------------------------------
int vl6180_UpdateByte(uint16_t index, uint8_t AndData, uint8_t OrData){
int status;
uint8_t buffer[3];
buffer[0]=index>>8;
buffer[1]=index&0xFF;
status=vl6180_I2C_Write((uint8_t *)buffer,(uint8_t)2);
if( !status ){
status=vl6180_I2C_Read(&buffer[2],1);
if( !status ){
buffer[2]=(buffer[2]&AndData)|OrData;
status=vl6180_I2C_Write(buffer, (uint8_t)3);
}
}
return status;
}
//-------------------------------------------------
Вот такая интересная функция.
В файле vl6180.h добавим макросы для различных типов прерываний и ещё два макроса
#define FW_ALS_RESULT_SCALER 0x120
//-------------------------------------------------
#define INTERRUPT_CLEAR_RANGING 0x01
#define INTERRUPT_CLEAR_ALS 0x02
#define INTERRUPT_CLEAR_ERROR 0x04
//-------------------------------------------------
#define CONFIG_GPIO_INTERRUPT_NEW_SAMPLE_READY 0x04
//-------------------------------------------------
#define INVALID_PARAMS -2 /*!< parameter passed is invalid or out of range */
//-------------------------------------------------
Вернёмся в файл vl6180.c и над функцией vl6180_Prepare добавим функцию установки флагов прерываний и функцию сброса флагов прерываний
//-------------------------------------------------
int vl6180_AlsConfigInterrupt(uint8_t ConfigGpioInt)
{
int status;
if (ConfigGpioInt <= CONFIG_GPIO_INTERRUPT_NEW_SAMPLE_READY) {
status = vl6180_UpdateByte(SYSTEM_INTERRUPT_CONFIG_GPIO, (uint8_t)(~CONFIG_GPIO_ALS_MASK), (ConfigGpioInt << 3));
} else {
status = INVALID_PARAMS;
}
return status;
}
//-------------------------------------------------
int vl6180_ClearInterrupt(uint8_t IntClear)
{
int status;
if (IntClear <= 7) {
status = vl6180_WriteByte(SYSTEM_INTERRUPT_CLEAR, IntClear);
} else {
status = INVALID_PARAMS;
}
return status;
}
//-------------------------------------------------
В файле vl6180.h добавим ещё два макроса для того, чтобы сбрасывать сразу флаги всех типов прерываний и флаг прерывания датчика освещённости
#define INVALID_PARAMS -2 /*!< parameter passed is invalid or out of range */
//-------------------------------------------------
#define vl6180_ClearAllInterrupt() vl6180_ClearInterrupt(INTERRUPT_CLEAR_ERROR|INTERRUPT_CLEAR_RANGING|INTERRUPT_CLEAR_ALS)
#define vl6180_AlsClearInterrupt() vl6180_ClearInterrupt(INTERRUPT_CLEAR_ALS)
//-------------------------------------------------
Вернёмся в файл vl6180.c и вызовем наши функции в функции vl6180_Prepare
break;
status = vl6180_AlsConfigInterrupt(CONFIG_GPIO_INTERRUPT_NEW_SAMPLE_READY);
if (status)
break;
status = vl6180_ClearAllInterrupt();
} while (0);
Прерываниями мы управляли в вышенаписанных функциях с помощью регистров, находящихся по адресам 0x14 и 0x15
На этом функция подготовки значений завершена.
Продолжаем работать с функией чтения и обработки значений vl6180_ReadData.
Для этого нам необходима ещё одна функция, которую мы добавим выше
//-------------------------------------------------
void InitAlsMode(void)
{
//anything after prepare and prior to go into AlsState
int time = 100;
vl6180_AlsSetIntegrationPeriod(time);
}
//-------------------------------------------------
Данная функция просто устанавливает период измерения. Мы это уже делали выше, но не помешает это проделать и здесь, так как вдруг мы будем вызывать этот фрагмент кода только при определённых условиях.
В функции vl6180_ReadData установим статус и вызовем нашу только что написанную функцию
if(status) Error_Handler();
VL6180_State.mode = RunAlsPoll;
InitAlsMode();
}
}
Выше добавим ещё одну функцию
//-------------------------------------------------
void vl6180_AlsState(void)
{
int status;
}
//-------------------------------------------------
Вызовем эту функцию в функции vl6180_ReadData
InitAlsMode();
}
switch (VL6180_State.mode)
{
case RunAlsPoll:
vl6180_AlsState();
break;
}
}
То есть эта функция будет уже вызваться в зависимости от статуса, но независимо от положения переключателя. Если мы не переключаем его, то подготовка уже не происходит.
В файле vl6180.h добавим структуру для хранения считанных значений с датчика
}VL6180xDevData_ptr;
//-------------------------------------------------
typedef struct VL6180x_AlsData_st {
uint32_t lux; /**< Light measurement (Lux) */
uint32_t errorStatus; /**< Error status of the current measurement. n
* No Error := 0. n
* Refer to product sheets for other error codes. */
} VL6180x_AlsData_t;
//-------------------------------------------------
Вернёмся в файл vl6180.c и добавим глобальную переменную типа данной структуры
VL6180xDevData_ptr VL6180_DevData;
VL6180x_AlsData_t Als; /* ALS measurement */
Выше функции vl6180_AlsState добавим функцию опроса датчика
//-------------------------------------------------
int vl6180_AlsPollMeasurement(VL6180x_AlsData_t *pAlsData)
{
int status;
int ClrStatus;
uint8_t IntStatus;
over:
return status;
}
//-------------------------------------------------
Выше данной функции добавим функцию запуска измерения освещённости
//-------------------------------------------------
int vl6180_AlsSetSystemMode(uint8_t mode)
{
int status;
if (mode <= 3) {
status = vl6180_WriteByte(SYSALS_START, mode);
} else {
status = INVALID_PARAMS;
}
return status;
}
//-------------------------------------------------
Здесь используется регистр, расположенный по адресу 0x38, который и отвечает за данный запуск. В данный регистр мы заносим биты, отвечающие за различные режимы работы в состоянии измерения
В файле vl6180.h добавим макросы для некоторых режимов работы датчика
#define INVALID_PARAMS -2 /*!< parameter passed is invalid or out of range */
//-------------------------------------------------
#define MODE_START_STOP 0x01
/** bit 1 write 1 in #SYSRANGE_START set continuous operation mode */
#define MODE_CONTINUOUS 0x02
/** bit 1 write 0 in #SYSRANGE_START set single shot mode */
#define MODE_SINGLESHOT 0x00
//-------------------------------------------------
Вернёмся в файл vl6180.c и вызовем функцию, которую мы только что напиисали в функции vl6180_AlsPollMeasurement
uint8_t IntStatus;
status = vl6180_AlsSetSystemMode(MODE_START_STOP | MODE_SINGLESHOT);
if (status) {
goto over;
}
over:
В файле vl6180.h добавим макрос для бита флага нужного нам прерывания, который устанавливается, когда процесс считывания значения закончен
#define MODE_SINGLESHOT 0x00
//-------------------------------------------------
#define RES_INT_STAT_GPIO_NEW_SAMPLE_READY 0x04
//-------------------------------------------------
Вернёмся в файл vl6180.c и над функцией vl6180_AlsPollMeasurement добавим ещё функцию, которая будет узнавать информацию о флагах прерываний, вернее только одного флага, состояние которого знать нам будет очень необходимо
//-------------------------------------------------
int vl6180_AlsGetInterruptStatus(uint8_t *pIntStatus)
{
int status;
uint8_t IntStatus;
status = vl6180_ReadByte(RESULT_INTERRUPT_STATUS_GPIO, &IntStatus);
*pIntStatus = (IntStatus >> 3) & 0x07;
return status;
}
//-------------------------------------------------
Для того, чтобы узнать состояние флагов прерываний, есть определённый регистр с адресом 0x4F, в котором мы и узнаём состояние
Вызовем данную функцию в функции vl6180_AlsPollMeasurement
goto over;
}
while(1)
{
status = vl6180_AlsGetInterruptStatus(&IntStatus);
if (status) {
break;
}
if (IntStatus == RES_INT_STAT_GPIO_NEW_SAMPLE_READY) {
break; /* break on new data (status is 0) */
}
};
over:
Также над функцией vl6180_AlsPollMeasurement добавим функцию задержки между опросом состояния флага прерывания
//-------------------------------------------------
void vl6180_PollDelay(void)
{
Delay_MS_Tim(5);
}
//-------------------------------------------------
Думаю, что 5 милисекунд между опросами достаточно. Не слижком часто и не слишком редко.
Вызовем данную функцию в функции vl6180_AlsPollMeasurement
break; /* break on new data (status is 0) */
}
vl6180_PollDelay();
};
over:
То есть задержку мы применяем, если мы пока ещё не считали необходимый флаг, затем считываем его заново и выходим из цикла while, если необходимый флаг будет считан, что будет свидетельствовать об окончании измерения освещенности.
Над данной функцией добавим функцию получения данных с датчика, так как мы узнали, что мы уже готовы к считыванию
//-------------------------------------------------
int vl6180_AlsGetMeasurement(VL6180x_AlsData_t *pAlsData)
{
int status;
uint8_t ErrStatus;
return status;
}
//-------------------------------------------------
В следующей части занятия мы закончим работу с кодом и проверим, как работает наш датчик, на практике.
Предыдущая часть Программирование МК STM32 Следующая часть
Техническая документация:
Документация на датчик VL6180X
Документация на микросхему STMPE1600
Руководство пользователя на оценочную плату X-NUCLEO-6180XA1
Отладочную плату можно приобрести здесь Nucleo STM32F401RE
Оценочную плату можно приобрести здесь X-NUCLEO-6180XA1
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)