Урок 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);
}
Соберём код, прошьём контроллер и посмотрим на результат
Ну, наконец то мы дошли до датчика освещённости.
Для него также мы сделаем отдельную библиотеку, добавив два файла следующего содержимого
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 нашего датчика
Данный адрес с нулём в бите 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
В данном регистре используется всего один бит, установка которого в единицу означает то, что загрузка успешно завершена.
Вызовем данную функцию в функции инициализации
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;
Запустим терминальную программу, соберём код, прошьём контроллер и посмотрим результат
Сравним со значением в технической документации
Всё сходится. Отлично!
Понятное дело, что это ещё не вся инициализация, причём далеко не вся, только дальнейшая инициализация будет происходить уже в другой функции, и вызываться она будет при совсем других условиях. Ну ладно, поживём — увидим. И для чего именно так нужно — я расскажу потом.
Зайдём опять в заголовочный файл 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;
}
Соберём код, прошьём контроллер, пощёлкаем переключателем и посмотрим изменение состояний в териминале
Всё нормально переключается.
В следующей части нашего занятия мы напишем ещё ряд фунций по работе с датчиком освещённости.
Предыдущая часть Программирование МК STM32 Следующая часть
Техническая документация:
Документация на датчик VL6180X
Документация на микросхему STMPE1600
Руководство пользователя на оценочную плату X-NUCLEO-6180XA1
Отладочную плату можно приобрести здесь Nucleo STM32F401RE
Оценочную плату можно приобрести здесь X-NUCLEO-6180XA1
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий