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