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

 

 

 

 

Урок 90

 

Часть 2

 

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

 

 

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

 

Откроем файл main.c и в главной функции main() запустим таймер

 

/* USER CODE BEGIN 2 */

HAL_TIM_Base_Start_IT(&htim2);

/* USER CODE END 2 */

 

Также добавим обработчик прерывания от таймера, пока просто заготовку

 

/* USER CODE BEGIN 4 */

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

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim2)

  {

  }

}

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

/* USER CODE END 4 */

 

Начнём с индикатора.

Для этого нужно написать инициализацию микросхемы STMPE1600.

Создадим два файла библиотеки для данной микросхемы с заранее созданной функцией инициализации и прототипом следующего содержания:

 

stmpe1600.h:

 

#ifndef STMPE1600_H_

#define STMPE1600_H_

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

#include "stm32f4xx_hal.h"

#include "stdint.h"

#include <stdio.h>

#include <string.h>

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

int stmpe1600_ini(void);

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

#endif /* STMPE1600_H_ */

 

stmpe1600.c:

 

#include "stmpe1600.h"

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

extern UART_HandleTypeDef huart2;

extern I2C_HandleTypeDef hi2c1;

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

int stmpe1600_ini(void)

{

  int status;

  return status;

}

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

 

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

 

/* USER CODE BEGIN Includes */

#include "stmpe1600.h"

/* USER CODE END Includes */

 

В файле main.c в главной функции main() добавим локальную переменную для отслеживания разных временных состояний

 

/* USER CODE BEGIN 1 */

int status;

/* USER CODE END 1 */

 

И в этой же функции вызовем функцию инициализации микросхемы

 

HAL_TIM_Base_Start_IT(&htim2);

status = stmpe1600_ini();

 

В файле stmpe1600.h добавим адрес I2C нашей микросхемы

 

#include <string.h>

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

#define EXPANDER_I2C_ADDRESS (0x84)

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

 

Общение по I2C микросхема осуществляет стандартным образом. Сначала передаётся байт адреса регистра с битом записи/чтения, а затем либо читаются, либо отправляются данные в нужном количестве.

Поэтому пока считаем идентификатор чтобы убедиться, что мы общаемся с нашей микросхемой

 

index15

 

Для этого напишем соответствующий код в функцию инициализации в файл stmpe1600.c

 

int status;

uint8_t RegAddr=0;

uint8_t data[3];

do{

  status=HAL_I2C_Master_Transmit(&hi2c1, EXPANDER_I2C_ADDRESS, &RegAddr, 1, 100);

  if( status )

    break;

  status =HAL_I2C_Master_Receive(&hi2c1, EXPANDER_I2C_ADDRESS, data, 2, 200);

} while(0);

 

Я думаю, что код здесь понятен. Сначала мы создаём и инициализируем нулём переменную адреса регистра, так как адрес у нас 0, затем отправляем адрес в шину, а потом читаем оттуда два байта. Цикл организован для того, чтобы можно было выйти из него досрочно в случае, если мы не сможем отправить адрес. Также можно использовать оператор goto, что в последнее время не приветствуется, но всё же впоследствии мы к нему также прибегнем.

Теперь проверим наши считанные циферки и, в случае совпадения зажжем зелёный светодиод

 

} while(0);

if( status == 0 && data[0]==0 && data[1]==0x16)

{

  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);

}

return status;

 

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

 

index01

 

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

 

index17

 

Мы видим, что первые 7 ножек подключены к сегментам индикатора, следующие 4 — к разрядам индикатора, 11 ножка — к переключателю, который находится справа от индикатора, и которым мы также воспользуемся (зачем добру пропадать), 12 ножка подключена к порту выбора датчика который установлен на плате, и оставшиеся три — к аналогичным портам сателлитов (Bottom, Left, Right).

Поэтому мы должны включить на вход только одну ножку — DISPLAY_SEL

Сначала зайдём в заголовочный файл stmpe1600.h и напишем там удобные макросы для всех ножек порта микросхемы

 

#define EXPANDER_I2C_ADDRESS (0x84)

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

#define S0 (1<<0)

#define S1 (1<<1)

#define S2 (1<<2)

#define S3 (1<<3)

#define S4 (1<<4)

#define S5 (1<<5)

#define S6 (1<<6)

#define V2_D1 (1<<7)

// second byte or word MSB

#define V2_D2 (1<<8)

#define V2_D3 (1<<9)

#define V2_D4 (1<<10)

#define V2_DISP_SEL (1<<11)

#define V2_CHIPEN (1<<12)

#define V2_CHIPEN_B (1<<13)

#define V2_CHIPEN_L (1<<14)

#define V2_CHIPEN_R (1<<15)

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

 

Также у микросхемы существует три регистра, которые, работают с ножками портов определённым образом. Добавим также для них макросы

 

#define EXPANDER_I2C_ADDRESS (0x84)

#define GPMR 0x10

#define GPSR 0x12

#define GPDR 0x14

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

 

Назначение данных регистров следующее:

GPMR (GPIO monitor pin state register) — регистр, в котором хранятся текущие состояния ножек,

GPSR (GPIO set pin state register) — регистр установки состояния ножек,

GPDR (GPIO set pin direction register) — регистр для установки направления (вход, выход) работы ножек.

 

 

Вернёмся в файл stmpe1600.c.

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

 

extern I2C_HandleTypeDef hi2c1;

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

uint16_t gpio_data;

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

 

Ещё добавим функции для чтения и записи регистров микросхемы

 

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

int stmpe1600_WriteReg( int index, uint8_t *data, int n_data)

{

  int status;

  uint8_t RegAddr[16];

  RegAddr[0]=index;

  memcpy(RegAddr+1, data, n_data);

  status=HAL_I2C_Master_Transmit(&hi2c1, EXPANDER_I2C_ADDRESS, RegAddr, n_data+1, 100);

  return status;

}

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

int stmpe1600_ReadReg(int index, uint8_t *data, int n_data)

{

  int status;

  uint8_t RegAddr;

  RegAddr=index;

  do{

    status=HAL_I2C_Master_Transmit(&hi2c1, EXPANDER_I2C_ADDRESS, &RegAddr, 1, 1000);

    if( status )

      break;

    status =HAL_I2C_Master_Receive(&hi2c1, EXPANDER_I2C_ADDRESS, data, n_data, 1000);

  }while(0);

  return status;

}

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

 

Код в данных функциях простейший и не нуждается ни в каких объяснениях.

Также на функцию записи добавим прототип.

 

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

 

uint8_t RegAddr=0;

uint16_t gpio_direction;

 

Строку с включением ножки светодиода можно теперь удалить, так как мы всё проверили. Вместо этого напишем следующий код

 

if( status == 0 && data[0]==0 && data[1]==0x16)

{

  data[0] = GPDR; //адрес регистра

  gpio_direction = ~V2_DISP_SEL; //На вход устанавливаем только ножку переключателя, остальные на выход

  memcpy(data+1, &gpio_direction, 2);

  status=HAL_I2C_Master_Transmit(&hi2c1, EXPANDER_I2C_ADDRESS, data, 3, 100);

  gpio_data = (V2_D1|V2_D2|V2_D3| V2_D4);

  gpio_data |= 0x7F; // clear 7 seg bits

  gpio_data |= V2_D1|V2_D2|V2_D3|V2_D4; // all segment off

  gpio_data &= ~(V2_D1<<1); // digit on

  gpio_data &= ~(0x0F);

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

}

return status;

 

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

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

 

index02

 

Раз уж мы теперь умеем работать с микросхемой, которая будет управлять индикаоторм, то теперь напишем библиотеку для данного индикатора. За основу мы возьмём старую добрую библиотеку из урока 12 по динамической индикации.

Скопируем себе в новый проект и подключим отуда файлы led.h и led.c, затем подключим данную библиотеку ещё и в файле main.h

 

#include "stmpe1600.h"

#include "led.h"

 

В файле led.h немно подредактируем код, убрав оттуда всё лишнее и кое что добавив

 

#ifndef LED_H_

#define LED_H_

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

#include "stm32f4xx_hal.h"

#include "stdint.h"

#include "stmpe1600.h"

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

void segchar (uint8_t seg);

void ledprint(uint16_t number);

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

#endif /* LED_H_ */

 

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

 

#include "led.h"

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

extern UART_HandleTypeDef huart2;

extern I2C_HandleTypeDef hi2c1;

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

 

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

 

extern I2C_HandleTypeDef hi2c1;

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

extern uint16_t gpio_data;

uint8_t n_count=0;

uint8_t R1=0,R2=0,R3=0,R4=0;

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

 

В уроке 12 мы переключали сегменты индикатора прямо из обработки таймера. Пользуясь тем кодом, напишем обработчик таймера в файле led.c

 

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

void LED_TIM_Callback(void)

{

  gpio_data |= 0x7F; // clear 7 seg bits

  gpio_data |= V2_D1|V2_D2|V2_D3|V2_D4; // all segment off

  gpio_data &= ~(V2_D1<<n_count); // digit on

  if(n_count==0)

  {

    segchar(R1);

  }

  if(n_count==1)

  {

    segchar(R2);

  }

  if(n_count==2)

  {

    segchar(R3);

  }

  if(n_count==3)

  {

    segchar(R4);

  }

  n_count++;

  if(n_count>3) n_count=0;

}

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

 

Также напишем на данную функцию прототип в заголовочном файле.

Также мы немного поправим функцию включения нужных сегментов в зависимости от цифры с учётом требования микросхемы-преобразователя

 

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

void segchar (uint8_t seg)

{

  switch(seg)

  {

    case 1:

      gpio_data &= ~(0x06);

      break;

    case 2:

      gpio_data &= ~(0x5B);

      break;

    case 3:

      gpio_data &= ~(0x4F);

      break;

    case 4:

      gpio_data &= ~(0x66);

      break;

  case 5:

      gpio_data &= ~(0x6D);

      break;

    case 6:

      gpio_data &= ~(0x7D);

      break;

    case 7:

      gpio_data &= ~(0x07);

      break;

    case 8:

      gpio_data &= ~(0x7F);

      break;

    case 9:

      gpio_data &= ~(0x6F);

      break;

    case 0:

      gpio_data &= ~(0x3F);

      break;

    case 10: //Пробел

      gpio_data &= ~(0x00);

      break;

  }

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

}

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

 

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

 

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

void ledprint(uint16_t number)

{

  R1 = number/1000;

  R2 = number%1000/100;

  R3 = number%100/10;

  R4 = number%10;

  //гасим лидирующие нули

  if (number<1000) R1=10;

  if (number<100) R2=10;

  if (number<10) R3=10;

}

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

 

Напишем функцию выключения всех разрядов индикатора

 

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

void led_off(void)

{

  gpio_data |= (V2_D1|V2_D2|V2_D3| V2_D4); // segment en off

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

}

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

 

Напишем на данную функцию прототип и вызовем её в функции main();

 

status = stmpe1600_ini();

if(!status)

{

  led_off();

}

 

Соберём код и прошьём контроллер, чтобы убедиться, что наш индикатор погаснет.

 

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

 

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

 

 

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

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

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

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

 

 

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

и здесь Nucleo STM32F401RE

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

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

 

 

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

 

STM Name

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

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

*