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

 

 

 

 

Урок 90

 

Часть 3

 

Датчик освещённости VL6180X

 

 

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

 

Добавим глобальную переменную, а также заодно массив для строк

 

/* Private variables ---------------------------------------------------------*/

volatile uint32_t Tim2Cnt=0;

char str1[60]={0};

/* USER CODE END PV */

 

 

Теперь в обработке таймера  вызовем нашу обработку

 

if(htim==&htim2)

{

  Tim2Cnt++;

  if(Tim2Cnt>3)

  {

    LED_TIM_Callback();

    Tim2Cnt=0;

  }

  else if(Tim2Cnt>2)

  {

    led_off();

  }

}

 

Здесь мы ждём два тика, гасим индикатор, ждём ещё один тик, вызываем обрабочик, где рисуем следующий сегмент и обнуляем счётчик тиков. Получается что у нас сегмент горит 2 тика и не горит 1 тик. Если бы не было этого тика, то в погашенных разрядах также были бы артефакты.

Давайте проверим работу индикатора и в функции майн, нарисуем какую-нибудь цифру

 

if(!status)

{

  led_off();

  ledprint(1234);

}

 

Соберём код, прошьём контроллер и посмотрим на результат

 

index03

 

Ну, наконец то мы дошли до датчика освещённости.

Для него также мы сделаем отдельную библиотеку, добавив два файла следующего содержимого

 

vl6180.h:

#ifndef VL6180_H_

#define VL6180_H_

//-------------------------------------------------

#include "stm32f4xx_hal.h"

#include "stdint.h"

#include "stdio.h"

#include "string.h"

#include "stmpe1600.h"

//-------------------------------------------------

int vl6180_ini(void);

//-------------------------------------------------

#endif /* VL6180_H_ */

 

vl6180.c:

#include "vl6180.h"

//-------------------------------------------------

extern UART_HandleTypeDef huart2;

extern I2C_HandleTypeDef hi2c1;

//-------------------------------------------------

extern uint16_t gpio_data;

extern char str1[60];

//-------------------------------------------------

int vl6180_ini(void)

{

  int status;

  return status;

}

//-------------------------------------------------

 

Подключим библиотеку в файле main.h

 

#include "led.h"

#include "vl6180.h"

 

И вызовем функцию инициализации датчика в main(), а рисование тестового числа на индикаторе удалим

 

ledprint(1234);

status = vl6180_ini();

if(status) Error_Handler();

 

Добавим в файле main.c ещё одну глобальную переменную для счёта милисекунд

 

volatile uint32_t Tim2Cnt=0;

volatile uint32_t TickCnt;

 

Проинкрементируем её в обработчике таймера

 

  led_off();

}

TickCnt++;

 

Перейдём в файл vl6180.c и подключим данную переменную

 

extern uint16_t gpio_data;

extern volatile uint32_t TickCnt;

 

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

 

extern char str1[60];

//-------------------------------------------------

void Delay_MS_Tim(int ms)

{

  uint32_t start, now;

  int dif;

  start=TickCnt;

  do{

    now=TickCnt;

    dif= now -start;

  }

  while(dif<ms);

}

//-------------------------------------------------

 

В файле vl6180.h добавим макрос для ножки выбора датчика

 

#include "stmpe1600.h"

//-------------------------------------------------

#define VL6180_CHIPEN (1<<12)

//-------------------------------------------------

 

Вернёмся в файл vl6180.c и напишем функцию выбора датчика

 

//-------------------------------------------------

static void vl6180_SetChipEn(int state)

{

  if( state)

    gpio_data|=VL6180_CHIPEN;

  else

    gpio_data&=~VL6180_CHIPEN;

  stmpe1600_WriteReg(GPSR, (uint8_t*)&gpio_data,2);

}

//-------------------------------------------------

 

Используя данную функцию, а также функцию задержки, перезагрузим датчик в функции инициализации

 

int status;

vl6180_SetChipEn(0);

Delay_MS_Tim(10);

vl6180_SetChipEn(1);

Delay_MS_Tim(1);

 

Посмотрим адрес I2C нашего датчика

 

index18

 

Данный адрес с нулём в бите R/W, как того требует библиотека HAL в шестнадцатеричном выражении соответствует цифре 0x52

Добавим соответствующий макрос в заголовочный файл vl6180.h

 

#define VL6180_CHIPEN (1<<12)

//-------------------------------------------------

#define VL6180_ADDRESS 0x52 //what we use as API device

//-------------------------------------------------

 

В файле vl6180.c добавим функции чтения и записи через шину I2C

 

//-------------------------------------------------

int vl6180_I2C_Write(uint8_t *buff, uint8_t len)

{

  int status;

  status = HAL_I2C_Master_Transmit(&hi2c1, VL6180_ADDRESS, buff, len , 100);

  return status;

}

//-------------------------------------------------

int vl6180_I2C_Read(uint8_t *buff, uint8_t len)

{

  int status;

  status = HAL_I2C_Master_Receive(&hi2c1, VL6180_ADDRESS, buff, len , 100);

  return status;

}

//-------------------------------------------------

 

Теперь добавим функции чтения и записи одного байта в регистр датчика

 

//-------------------------------------------------

int vl6180_ReadByte(uint16_t index, uint8_t *data)

{

  int status;

  uint8_t buffer[2];

  buffer[0] = index>>8;

  buffer[1] = index&0xFF;

  status = vl6180_I2C_Write(buffer, (uint8_t)2);

  if( !status ){

    status=vl6180_I2C_Read(buffer,1);

    if( !status ){

      *data=buffer[0];

    }

  }

  return status;

}

//-------------------------------------------------

int vl6180_WriteByte(uint16_t index, uint8_t data)

{

  int status;

  uint8_t buffer[3];

  buffer[0] = index>>8;

  buffer[1] = index&0xFF;

  buffer[2] = data;

  status = vl6180_I2C_Write(buffer, (uint8_t)3);

  return status;

}

//-------------------------------------------------

 

Кстати, если вы заметили, то адрес регистра в отличие от предыдущей микросхемы здесь двухбайтный.

 

 

В файле vl6180.h напишем несколько макросов регистров, которые нам будут нужны впоследствии

 

#define VL6180_ADDRESS 0x52 //what we use as API device

//-------------------------------------------------

#define SYSTEM_INTERRUPT_CONFIG_GPIO 0x014

#define CONFIG_GPIO_ALS_SHIFT 3 /** ALS bits mask in #SYSTEM_INTERRUPT_CONFIG_GPIO (unshifted)*/

#define CONFIG_GPIO_ALS_MASK (0x7<<CONFIG_GPIO_ALS_SHIFT)

#define SYSTEM_INTERRUPT_CLEAR 0x015

#define SYSTEM_FRESH_OUT_OF_RESET 0x016

#define SYSALS_START 0x038

#define SYSALS_THRESH_HIGH 0x03A

#define SYSALS_THRESH_LOW 0x03C

#define SYSALS_ANALOGUE_GAIN 0x03F

#define SYSALS_INTERMEASUREMENT_PERIOD 0x03E

#define SYSALS_INTEGRATION_PERIOD 0x040

#define RESULT_ALS_STATUS 0x4E

#define RESULT_INTERRUPT_STATUS_GPIO 0x4F

#define RESULT_ALS_VAL 0x50

#define FW_ALS_RESULT_SCALER 0x120

//-------------------------------------------------

 

Вернёмся в файл vl6180.c и напишем функцию ожидания окончания загрузки датчика

 

//-------------------------------------------------

int vl6180_WaitDeviceBooted(void)

{

  int status;

  uint8_t FreshOutReset;

  do {

    status = vl6180_ReadByte(SYSTEM_FRESH_OUT_OF_RESET, &FreshOutReset);

  } while (FreshOutReset != 1 && status == 0);

  return status;

}

//-------------------------------------------------

 

Здесь мы используем регистр с адресом 0x16

 

index19

 

В данном регистре используется всего один бит, установка которого в единицу означает то, что загрузка успешно завершена.

Вызовем данную функцию в функции инициализации

 

Delay_MS_Tim(1);

status = vl6180_WaitDeviceBooted();

if(status) return -1;

 

Будем считать что датчик прошел успешный старт и можно теперь считать идентификатор.

Добавим ещё одну локальную переменную в функцию инициализации

 

int status=0;

uint8_t dt;

 

Добавим макрос регистра идентификатора в заголовочный файл vl6180.h

 

#define VL6180_ADDRESS 0x52 //what we use as API device

//-------------------------------------------------

#define IDENTIFICATION__MODEL_ID 0x0000

 

Вернёмся в файл vl6180.c в функцию инициализации и считаем идентификатор, а затем отобразим его в терминальной программе

 

if(status) return -1;

vl6180_ReadByte(IDENTIFICATION__MODEL_ID, &dt);

sprintf(str1,"vl6180 ID: 0x%02Xrn",dt);

HAL_UART_Transmit(&huart2, (uint8_t*)str1,strlen(str1),0x1000);

return status;

 

Запустим терминальную программу, соберём код, прошьём контроллер и посмотрим результат

 

image20

 

Сравним со значением в технической документации

 

image21

 

Всё сходится. Отлично!

Понятное дело, что это ещё не вся инициализация, причём далеко не вся, только дальнейшая инициализация будет происходить уже в другой функции, и вызываться она будет при совсем других условиях. Ну ладно, поживём — увидим. И для чего именно так нужно — я расскажу потом.

Зайдём опять в заголовочный файл vl6180.h и добавим там структуру для состояния датчика (и нашей программы). Состоять она будет только из одного поля для режима. Сделал я это не только на будущее, возможно ещё туда что-то добавим, но и для того, что по моим наблюдениям, в структуре значения переменных хранятся надёжнее.

 

#define FW_ALS_RESULT_SCALER 0x120

//-------------------------------------------------

typedef struct state_t {

  uint8_t mode;

}State_ptr;

//-------------------------------------------------

 

Также добавим варианты для режима

 

//-------------------------------------------------

#define RunRangePoll 0

#define RunAlsPoll 1

#define InitErr 2

#define ScaleSwap 3

#define WaitForReset 4

#define Start 5

#define Run 6

#define FromSwitch 7

//-------------------------------------------------

 

В файле vl6180.c добавим глобальную переменную типа нашей структуры

 

extern char str1[60];

State_ptr VL6180_State;

 

В файле main.c добавим нашу переменную, а также ещё две переменные для состояния переключателя

 

char str1[60]={0};

extern State_ptr VL6180_State;

volatile int8_t switch_state = -1;

volatile int8_t new_switch_state;

 

Инициализируем режим, а также состояние переключателя, в функции main()

 

  if(status) Error_Handler();

}

VL6180_State.mode = Start;

switch_state = -1;

/* USER CODE END 2 */

 

Добавим функцию чтения данных в фале vl6180.c прямо над функцией инициализации

 

//-------------------------------------------------

void vl6180_ReadData(void)

{

  int status;

}

//-------------------------------------------------

 

Добавим на данную функцию прототип и вызовем её в бесконечном цикле в функции main()

 

  /* USER CODE BEGIN 3 */

  vl6180_ReadData();

}

 

В файле stmpe1600.c добавим функцию, определяющую состояние переключателя.

 

//-------------------------------------------------

int stmpe1600_GetSwitch(void)

{

  int status;

  GPIO_PinState state;

  uint16_t Value;

  status=stmpe1600_ReadReg(GPMR, (uint8_t*)&Value,2);

  if(status ==0) Value&=V2_DISP_SEL;

  else Value=0;

  state = Value ? GPIO_PIN_SET : GPIO_PIN_RESET;

  return state;

}

//-------------------------------------------------

 

Данная функци определяет состояние ножки порта микросхемы, определяющей состояние переключателя.

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

В файле main.c добавим ещё один глобальный счётчик

 

volatile uint32_t TickCnt;

volatile uint16_t SwitchCnt=0;

 

Перейдём в этом же главном модуле в обработчик прерываний таймера и проинкрементируем данный счётчик

 

Tim2Cnt++;

SwitchCnt++;

 

И в этом же обработчике вызовем нашу функцию ниже

 

  led_off();

}

if(SwitchCnt>10)

{

  new_switch_state = stmpe1600_GetSwitch();

  SwitchCnt = 0;

}

TickCnt++;

 

То есть функция оценки состояния переключателя будет вызываться раз в 10 милисекунд. Можно, в принципе, и реже, но пока оставим так.

Перейдём в файл vl6180.c и подключим наши переменные

 

State_ptr VL6180_State;

extern volatile int8_t switch_state;

extern volatile int8_t new_switch_state;

 

Теперь в функции чтения данных узнаем состояние переключателя и если оно изменилось, то отобразим его в терминальной программе

 

int status;

if (new_switch_state != switch_state)

{

  sprintf(str1,"Switch state: %drn",new_switch_state);

  HAL_UART_Transmit(&huart2, (uint8_t*)str1,strlen(str1),0x1000);

  switch_state=new_switch_state;

}

 

Соберём код, прошьём контроллер, пощёлкаем переключателем и посмотрим изменение состояний в териминале

 

image22

 

Всё нормально переключается.

 

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

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Техническая документация:

Документация на датчик VL6180X

Документация на микросхему STMPE1600

Руководство пользователя на оценочную плату X-NUCLEO-6180XA1

 

 

Отладочную плату можно приобрести здесь Nucleo STM32F401RE

и здесь Nucleo STM32F401RE

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

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

 

 

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

 

STM Name

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*