STM Урок 90. Датчик освещённости VL6180X. Часть 4

 

 

 

 

Урок 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

 

index23

 

Данный регистр хранит в себе умножитель результата измеренной освещенности. Наряду с коэффициентом усиления, данный параметр также помогает более точно производить измерения в условиях низкой освещённости. Используются всего 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

 

index24

 

Также есть таблица актуальных значений коэффициентов усиления, из которой также видно, что необходимо ещё устанавливать бит 7 в единицу

 

index25

 

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

Теперь вызовем данную функцию в функции 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

 

index26

 

Вызовем данную функцию в функции 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

 

index27

 

Данный регистр восьмибитный и значение здесь хранится в десятках милисекунд.

Вызовем данную функцию в функции 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

 

index28

 

index29

 

Вызовем данную функцию в функции 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

 

index30

 

На этом функция подготовки значений завершена.

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

 

index32

 

В файле 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, в котором мы и узнаём состояние

 

index33

 

Вызовем данную функцию в функции 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

и здесь Nucleo STM32F401RE

Оценочную плату можно приобрести здесь X-NUCLEO-6180XA1

и здесь X-NUCLEO-6180XA1

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM Name