Спорт, здоровье http://myowndevice.ru Wed, 28 Aug 2024 12:10:41 +0000 Joomla! - Open Source Content Management ru-ru Bluetooth велокомпьютер /index.php/pribory/item/1-velokompyuter /index.php/pribory/item/1-velokompyuter Bluetooth велокомпьютер

Управление по bluetooth. Питание от li-ion аккумулятора. 2 датчика — на колесо и педали. Более 18 поездок по 3часа на одном заряде. Автоматическое фиксирование поездки. Расчёт и хранение за 3 последние поездки основных показателей — скорость, расход калорий, каденс. И самое главное звуковой контроль параметров — заданной скорости и каденса. Компактный размер 30х50х35мм

{autotoc}

Прежде чем начать

Прежде чем читать эту статью, рекомендуем вам ознакомится со следующей теорией:

Что такое и зачем нужен велокомпьютер

Велокомпьютер (англ. Cyclocomputer; в просторечии — Велосчётчик) — электронное устройство для измерения скорости и пробега велосипеда, а также дополнительных параметров, таких как средняя скорость, время в пути, максимальная скорость, пульс, передача (на многоскоростных велосипедах), текущее время, температура, давление, каденс (частота вращения педалей) и другие.

Учёт каких параметров может вести велокомпьютер:

  • скорость и её производные (средняя скорость, максимальная, основная скорость движения и т. д.)

  • пройденное расстояние

  • время в пути — активное (то есть когда именно ехали), все время поездки (с учётом перерывов)

  • пульс — средний, максимальный и т. д.

  • Потраченные калории за поездку

  • Частота вращения педалей - оптимальная или нет

Основные компоненты

  • Основной блок (компьютер) — мозг всей системы

  • Датчики скорости — в основном крепится на колесо

  • Датчик каденса — крепится на педали

  • Датчик пульса — одевается на руку или крепится на теле

Анализ рынка, какой будет наш прибор

Прежде чем делать прибор изучим немного рынок, что продаётся и примерно почём. Все модели можно поделить на 3 категории:

  • дешевые модели — один проводной датчик, небольшой размер, жк экранчик, работа от батареек около 1года. Функции очень простые — средняя скорость, текущая скорость, часы, пробег. Цена — около 800 руб.

  • средние модели — два датчика, проводные или беспроводные, жк экран, работа от батареек. Функции — каденс, скорость, средняя скорость, пробег, часы, автоматический старт и стоп. Цена — около 1500 руб.

  • дорогие модели — наличие bluetooth, два датчика, плюс возможность подключения различных датчиков (сила нажима на педали, пульс, скорость насыщения организма кислородом и другие). Функции — каденс, калории, скорость, пробег, и в зависимости от датчиков — около 5000 руб.

Будем ориентироваться на средний или даже дорогой сегмент по функциям, а по цене не дороже дешёвых моделей — дешевле 700 руб. Так как мы сами будем паять прибор, то будет проще, если мы исключим экран (это прибор для новичков, и начинать сразу работать с дисплеем — это плохое начало) и заменим его на bluetooth модуль. От этого мы только выиграем. Это даст нам возможность не только выводить любую информацию, но и программировать прибор с телефона по bluetooth. Также это сильно снизит габариты прибора. Добавим возможность вести историю поездок, и звуковой контроль каденса — это очень полезная функция, позволяет не перегружать суставы на многоскоростных велосипедах. Также в случае, например, тренировки на скорость — добавим звуковой контроль минимальной скорости. Ну и естественно, так как у нас большой экран смартфона, то будем выводить всю возможную статистику. Средняя скорость - не очень информативный показатель, если мы делаем частые остановки. Гораздо интереснее знать скорость, с которой мы ехали большую часть времени. Обязательно будем считать калории, и только в тот момент, когда мы крутим педали. Часы есть на телефоне, и нам не нужны. А вот возможность автоматически определять поездки и фиксировать по ним информацию — очень.

В итоге, мы должны получить велокомпьютер, который всегда находится на велосипеде, всегда готов к работе. Аккумулятора должно хватать на очень долго. Сели на велосипед и поехали, он сам понял, что началась новая поездка и начал запись. В любой времени остановились (или на ходу) посмотрели параметры текущей поездки, сравнили с прошлыми поездками, задали параметры контроля — и поехали дальше. В случае нарушения контролируемых параметров - он просигналит. Приехали — загрузили на телефон, компьютер, всю информацию для анализа. Такой незаметный, нужный помощник.

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

{product id=1}

Постановка задачи

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

Параметр

Требования

Питание

Аккумулятор, зарядка по miniusb, одного заряда должно хватать на 10-20 поездок по 3 часа.

Измеряемые показатели

Пройденное расстояние, скорость, потраченные калории, каденс

Особенности

Контроль каденса — с помощью звука, экрана у нас нет, так что максимально будем использовать звук.

Интерфейс

Bluetooth serial port 2.0

Корпус

Небольшой корпус с удобным креплением к велосипеду

Подбор компонентов

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

Датчики

Для измерения скорости велосипеда и пройденного расстояния можно использовать:

  • gps модуль — спутники дают данную информацию

  • инерциальная система позиционирования

  • счётчик оборотов колеса

Для измерения каденса — нужен счётчик оборотов педалей.

Мы выберем самый простой вариант — счётчик оборотов колеса и педалей. Для подсчёта оборотов удобнее всего использовать магнит закрепленный на колесе и датчик магнитного поля закрепленный на раме и такой же вариант для педалей. Возможные датчики магнитного поля:

Датчик

Преимущества - недостатки

Датчик холла

Миниатюрный, точный, нет физических контактов - нет износа, нет дребезга контактов. Высока чувствительность. Высокая частота срабатывания.


Основной недостаток — энергопотребление от 2ма. Что не годится для спящего режима.

Геркон

Относительно небольшой, нет энергопотребления (по сути кнопка).


Невысокая чувствительность, дребезг контактов — ограничение по частоте срабатывания 400гц.



Чтобы рассчитать подойдёт ли нам геркон, нужно посмотреть не превысили ли мы его максимальную частоту срабатывания на максимальной скорости движения велосипеда. Допустим диаметр колеса у нас 50см, максимальная скорость велосипедиста 100 км\ч. За один оборот колесо проходит, по известной формуле длины окружности:

π D = 3,14 * 50 = 157см = 1,57м.

Чтобы найти частоту срабатывания в секунду надо перевести максимальную скорость в метры в секунду

100 км\ч = 100000м / 3600с = 27,8 м\с.

Частота срабатывания геркона будет равна 27,8м\с / 1,57м = 17,7 оборотов\с = 17,7Гц. Геркон отлично подходит для данного прибора, его частота 400Гц.

Для прибора нам необходимо 2 геркона — один будет учитывать кол-во оборотов колеса — другой педалей. Можно оба датчика сделать выносными на проводе, но так как у нас нет необходимости в экране (интерфейс будет по bluetooth) — то для минимизации проводов — мы разместим прибор на задней вилке. При этом один геркон разместим в корпусе прибора — другой на небольшом удалении с помощью провода. Если вы эстет, то можете использовать черные стяжки. Для того чтобы прибор не скользил по вилке велосипеда, можно использовать специальную подложку сетку под ковры.




Интерфейс общения

Как мы видели ранее, в велокомпьютерах используется экран на который выводится нужная информация. Мы решили использовать bluetooth. Практически у всех сейчас есть смартфон, в котором он есть (к сожалению, iphone не работает с протоколом Bluetooth SPP). Учитывая тот факт, что критическую информацию можно передать звуком (пищалка), а все показатели снять по окончанию поездки или во время остановок — bluetooth отличная замена экрана (вы также можете легко модернизировать прибор на работу с экраном — далее разбираются приборы с ЖК экранами).

Будем использовать Bluetooth Serial Port для передачи данных — соответственно нужен протокол UART. Возьмём модуль Bluetooth SPP-C. Данный модуль обеспечивает двух стороннюю прозрачную связь между телефоном и велокомпьютером. Для отображения информации на экране телефона мы будем использовать простейшую программу bluetooth terminal (вы можете использовать другие более симпатичные программы или написать свою). Информацию будем передавать каждые 2 секунды в виде форматированной строки текста.

Что будем передавать:

Велокомпьютер будет передавать информацию:

  • Текущая скорость движения в км\ч

  • Каденс - скорость вращения педалей в об\мин

  • Макс скорость за поездку

  • Длительность поездки полная и активная

  • Основная скорость — та скорость с которой едем большую часть поездки

  • Время проезда со скоростью в определённом диапазоне— интервалы по 4км\ч до 40км\ч

Что будем получать:

Настройку прибора будем производить с телефона. Можно будет задать следующие параметры:

  • диаметр колеса для проведения всех расчетов — в см.

  • вес человека (для расчёта калорий) в кг

  • настройка контроля каденса — минимальный каденс при котором необходимо подать звуковой сигнал (при езде на велосипеде необходимо так выбирать скоростной режим, чтобы каденс был больше 80 об\мин — вот это и будем отслеживать)

  • Запрос на получение итоговых данных - подробные за 3 последние поездки и итоговые за все время пользования прибором

Использование пищалки:

  • при включении

  • при переходе в спящий режим

  • при нарушении скорости каденса на 10 об\с в течение 2 мин

Микроконтроллер

Выберем микроконтроллер. Какие требования к микроконтроллеру в нашем проекте?

  • считывать показания 3 кнопок (наши датчики — герконы и еще одна кнопка)

  • иметь интерфейс UART для общения с bluetooth модулем

  • наличие спящего режима для энергосбережения

  • для более полного погружения в программирование, необходим полноценный отладчик

  • удобный корпус для пайки

  • точный подсчёт интервалов времени, работа от кварца

  • EEPROM память для хранения настроек

Выберем серию STM8. Она имеет отличный отладчик ST-Link по 2-ум проводам, симулятор и всю необходимую периферию. Программа будет небольшая, выберем самый дешёвую модель - наш выбор STM8S003F3P6. Можно выбрать линейку STM8L — но они дороже, а энергопотребление у нас будет минимальное.




Аккумулятор

Для выбора аккумулятора рассчитаем примерный расход энергии. Основные потребители энергии:

Модуль

Потребление

Микроконтроллер

1мА на частоте 1мгц (нам больше не надо)

4мкА в спящем режиме

Bluetooth модуль

8мА в режиме активного соединения

30мА в режиме поиска соединения

Прочая утечка — LDO в режиме отключения и прочие утечки

3мкА



Таким образом, максимальный расход энергии — 40мА. Рассчитаем сколько миллиампер будет тратиться на одну поездку длительностью 3ч при следующем профиле работы прибора:

  • 1 поездка 3часа

  • bluetooth работает за время поездки 2мин поиск, 30мин активное соединение

  • во время поездки 1ч спим

получается 2ч потребление 1ма, 1ч потребление 7мкА, 30мин потребление 8мА, 2мин потребление 30мА. Если все привести к мА\ч то получается:

2 мА\ч + 0,007мА\ч + 4 мА\ч + 1ма\ч = 7,007 мА\ч.

Надо необходимо, чтобы заряда хватало минимум на 10 поездок. Значит нужен аккумулятор ёмкостью более 70 мА\ч. Будем использовать Li-ion аккумулятор LIR-2450 ёмкостью 120мА\ч.




Рассчитаем расход в спящем режиме. Расход 7 мкА\ч. Значит прибор может проработать на оставшихся 50 мА\ч около 7143 часов = 300 дней.

Составляем схему

Начнём с питания. Аккумулятор мы выбрали. Соответственно нужна схема его заряда. Заряжать наш прибор будем через разъем micro-usb, микросхему для заряда возьмём самую дешёвую — TP4056. Ток заряда установим 100 мА (примерно 1C для выбранного аккумулятора).

Bluetooth модуль — самый простой UART slave модуль на базе BK3231




По datasheet на модуль ему необходимо питание от 2 до 3.6в. У нас аккумулятор выдаёт от 3 до 4.2В. Соответственно нужен LDO для понижения напряжения до 3.3В. Возьмём серию NCP603 на 3.3в.




Микроконтроллер мы выбрали — STM8S003F3 — его питание от 3 до 5В. Ему LDO не нужен. Таким образом, МК будет питаться сразу от аккумулятора, LDO будет питать bluetooth модуль, включать LDO будем только на время работы модуля.

Герконы — подключаем стандартно, как кнопки, сразу на вывод и на GND. Будет использовать встроенный в МК PULL-UP резистор. Точно также подключим тактовую кнопку.

UART — так как у нас разное напряжение питание модулей то нужно согласование напряжения. Разница между уровнями 4.2В — 3.6В = 0.6В. Проблема будет только в канале TX, когда передаём сигнал с МК на модуль. Так как в модуле стоят защитные диоды на всех входах, то достаточно использовать простой резистор на 10 кОм.

Пищалка. Возьмём очень громкую пищалку — HC0903A, на 3В — но будет работать и на 4В. Ток работы — 100ма.




Соответственно нужен будет транзистор для управления пищалкой. Возьмём маломощный N-Mosfet 2N7002 (до 300ма ток), так можно сэкономить на резисторе на базе транзистора.

Для МК нужна стандартная обвязка плюс кварцевый резонатор на 8МГц.

Схема определена. Теперь нарисуем ее в Kicad.


И вторая часть




Размещение велокомпьютера, Корпус и плата

Подберём корпус для нашего прибора. Для этого прикинем размер платы. Размещение будет такое — сверху на плате — МК, micro-usb, микросхемы заряда и все прочее, снизу — кварц и bluetooth модуль. На краю платы угловая тактовая кнопка:


Получаются примерные размеры 50х30 мм. Мне понравился корпус фирмы GAINTA — NUB503522BK:


Внутренние размеры платы под корпус — 45х30мм, что вполне подходит. Корпус имеет 2 крепёжных отверстия для платы, что очень удобно, а также ушки — за них можно крепить к велосипеду. Так как у нас нет экрана и смотреть нам не надо, то удобнее разместить велокомпьютер на задней вилке велосипеда. При таком размещении один геркон можно разместить прямо на плате, а второй на коротком проводе — педали рядом. Кнопка должна быть на противоположном боку от геркона. Второй геркон можно просто припаять внутри прибора или использовать любой разъем в разрыв провода.


Платы мы уже научились разводить на приборах ST-Link и UART, так что готовая плата находится на github проекта папке Kicad.

Математика

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

Первое, что нам надо уметь - это переводить количество оборотов колеса в пройденное расстояние. Тут будем использовать всем известную формулу длины окружности.

C (длина окружности) = π (константа ПИ = 3,1415926535) * D (диаметр колеса).

Далее нам необходимо переводить единицы времени — миллисекунды в часы. Ну тут просто используем тот факт, что в одной минуте 60 секунд, а в одном часе 60 минут.

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

(Калории в ккал) = (Скорость в км\ч) * (Время в ч) * (Вес в кг).

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

Вместо средней скорости мы будем считать основную скорость — ту скорость, с которой мы едем наибольшее время во время поездки, с точностью до 4 км\ч.

Также мы будем считать, что диапазон скоростей у нас от 0 до 60 км\ч, конечно велосипед может развивать большую скорость, но только если он в руках спортсмена.

Программа

Программа написана в среде ST Visual develop IDE. Полный проект вы можете скачать с github данного прибора, папка velo. В статье мы разберём ключевые моменты работы программы.

Структура файлов проекта

Проект создан на основе библиотеки SPL для STM8 от ST. Подробное описание как развернуть проект вы найдёте в этой статье. Основная программа содержится в файле main.c, работа с прерываниями в файле stm8s_it.c, частота кварца задана в файле stm8s.h. Остальные файлы остались без изменений.

Общее описание программы

Упрощённый алгоритм работы программы.

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

  2. После нажатия кнопки или срабатывания прерывания от датчиков, система стартует, о чем оповещает звуковым сигналом

  3. Из EEPROM считываются записанные настройки, если они равны 0, то туда записываются типовые настройки, обнуляются все данные о поездках, идет подготовка EEPROM

  4. Фиксируется начало поездки, обнуляются все переменные по поездке

  5. Включается bluetooth модуль для экономии энергии на 2мин, включение и выключение модуля фиксируется звуковым сигналом

  6. Через прерывания фиксируются показания датчиков и рассчитываются все необходимые параметры

  7. Каждые 2с на bluetooth передаётся текущая информация о поездке, если он включён

  8. Если в течение 2мин не было прерываний от датчиков, то МК переходит в спящий режим, выход по прерыванию от датчиков или кнопки

  9. Кнопка включает\выключает bluetooth модуль, а также будит МК

  10. Если МК находится в спящем режиме больше 2часов, то считается, что поездка завершилась. Идёт анализ данных поездки, если расстояние меньше 500метров, то данные по поездке обнуляются и не сохраняются, иначе данные о поездке сохраняются в EEPROM

  11. При получении данных от bluetooth модуля, идёт их анализ и разбор фраз настройки МК, данные записываются в EEPROM

  12. Во время активного режима работы МК идёт анализ скорости и каденса и в случае превышения заданных параметров идёт оповещение звуковым сигналом

Инициализация, прерывания

Рассмотрим более подробно исходный текст программы. Основная программа находится в функции main(). Первым делом необходимо произвести все настройки периферии.

[code]CLK->PCKENR1 = 0;
CLK->PCKENR1 |= CLK_PCKENR1_UART2|CLK_PCKENR1_TIM4;
CLK->PCKENR2 = 0xFF & 0b01110111;
[/code]

Данный регистр отвечает за использование необходимой периферии, каждая периферия потребляет энергию, если она включена, даже если мы ей не пользуемся. Для экономии энергии отключим все, кроме UART(используется для обмена с bluetooth модулем) и TIM4 (таймер 4 используется как миллисекундный таймер).

[code]CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE,DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV8);
[/code]

Задаём работу от внешнего кварца. Кварц у нас в проекте — 8Мгц, делитель CPU устанавливаем на 8, чтобы МК работал на частоте 1Мгц для экономии энергии. В STM8 все настройки частоты производятся прямо в коде программе, необходимости в прошивке фьзов, как например, в AVR, нет. Это очень удобно. Автоматический (CLK_SWITCHMODE_AUTO) режим переключения кварца означает, что система сама дождётся корректного запуска внешнего кварца и потом перейдёт на него.

[code]GPIO_Init(GPIOD,GPIO_PIN_3,GPIO_MODE_IN_PU_IT);//knopka
GPIO_Init(GPIOC,GPIO_PIN_7,GPIO_MODE_IN_PU_IT);//H1
GPIO_Init(GPIOC,GPIO_PIN_6,GPIO_MODE_IN_PU_IT);//H2

//прерывания по падающему фронту
EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD,EXTI_SENSITIVITY_FALL_ONLY);
EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOC,EXTI_SENSITIVITY_FALL_ONLY);
[/code]

Настройка работы выводов МК датчиков и кнопки. Используется режим PULL UP и включаются внешние прерывания на этих выводах (GPIO_MODE_IN_PU_IT). Принцип срабатывания внешних прерываний на выводах задается в целом для порта. EXTI_SENSITIVITY_FALL_ONLY — означает прерывание по падающего сигналу, когда на выводе сигнал меняется с HIGH на LOW, для нас это будет соответствовать нажатию кнопки или срабатыванию геркона.

[code]UART1_DeInit();
UART1_Init((uint32_t)9600, UART1_WORDLENGTH_8D, UART1_STOPBITS_1,UART1_PARITY_NO,
UART1_SYNCMODE_CLOCK_DISABLE,UART1_MODE_TXRX_ENABLE);

UART1_ITConfig(UART1_IT_RXNE_OR,ENABLE);
[/code]

Настраиваем UART для связи с bluetooth модулем. Вся работа будет вестись на прерываниях. Здесь задаем скорость порта — 9600, работу TX и RX, контроль четности, асинхронный режим, передачу данных по 8 бит. То есть так, как этого требует bluetooth модуль. Последней командой включаем работу прерываний по UART событиям.

[code]TIM4_TimeBaseInit(TIM4_PRESCALER_64, 125);
TIM4_ClearFlag(TIM4_FLAG_UPDATE);
TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
enableInterrupts();
TIM4_Cmd(ENABLE);
[/code]

Запускаем миллисекундный таймер. Обратите внимание, что частота кварца 8Мгц, не смотря на установленный делитель для CPU, вся периферия работает на 8Мгц, в том числе и все таймеры. Поэтому устанавливаем предделитель 64, а счетчик — 125 (8 000 000/64/125 = 1000 = 1мс). Включаем прерывания на таймере 4 и запускаем работу прерываний. После этого включаем сам таймер 4.

[code]GPIO_Init(GPIOC,GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5,GPIO_MODE_OUT_PP_LOW_SLOW);
GPIO_Init(GPIOB,GPIO_PIN_5|GPIO_PIN_4,GPIO_MODE_OUT_OD_HIZ_SLOW);

GPIO_Init(GPIOD,GPIO_PIN_4,GPIO_MODE_OUT_PP_LOW_SLOW);//speaker
GPIO_Init(GPIOD,GPIO_PIN_2,GPIO_MODE_OUT_PP_LOW_SLOW);//blue onoff
[/code]

Настраиваем работу выводов как LOW, для энергосбережения. Если их оставить в неопределённом состоянии, триггеры Шмита на входах будут тратить энергию. Ножки 4,5 порта B не имеют триггеров Шмита, оставим их в неопределённом режиме. Динамик выключаем. Bluetooth модуль выключаем.

Переводим МК в спящий режим по команде halt(). Это сделано для того, чтобы в случае разряда аккумулятора при включении зарядного устройства не было попыток проиграть музыку, это требует много энергии, а зарядное устройство на столько не рассчитано, аккумулятор еще слабый и это вызывает перегрузку МК, мешая заряду аккумулятора.

После выхода из спящего режима по прерыванию, программа продолжает выполнение после инструкции halt().

[code]playmusic(1);

GPIO_WriteHigh(GPIOD,GPIO_PIN_2);
blueen = TRUE;
kn = FALSE;

timeblueoff = 120;
timehalt=120;
[/code]

Проигрываем музыку, включаем bluetooth на 2 минуты. Запускаем счетчик перехода в спящий режим.

На этом настройка выполнена, программа переходит в основной цикл.

Миллисекундный таймер, события по временным интервалам

Обработка всех временных событий ведётся в прерывании таймера 4. Сам обработчик прерывания проще перенести в файл main.c, потому что, в нем будет много общих переменных. Предопределённая процедура INTERRUPT_HANDLER(TIM4_UPD_OVF_IRQHandler, 23). Разберём обработку этого прерывания. Данное прерывание срабатывает каждую миллисекунду, согласно настройкам таймера 4.

[code]TIM4_ClearITPendingBit(TIM4_IT_UPDATE);
[/code]

Первым делом при входе в прерывание оповещаем МК, что мы его обработали. Без этой команды вызов прерывания будет продолжаться бесконечно и МК зависнет. Далее идёт обработка событий по времени. Для того чтобы посчитать прошедшее время используется очень простая методика. Если нам необходимо выполнить какое действие по прошествую времени Х мс, то заводим глобальную переменную timeX, и в момент начала события записываем в эту переменную нужное количество миллисекунд. В обработке прерывания таймера необходимо написать следующий код:

[code]if (timeX) timeX--;
[/code]

Уменьшаем на 1 значение переменной пока там не станет 0. В глобальном цикле, в основной программе далее проверяем данную переменную на 0 и когда она станет равной 0 выполняем нужное действие. Например, так мы выключаем bluetooth через 2 минуты.

[code]//В основной программе:
if (timeblueoff==0 && blueen==TRUE)
{
//через 2 мин выключаем блютус!
blueen=FALSE;
GPIO_WriteHigh(GPIOD,GPIO_PIN_2);
playmusic(3);
}

//в прерывании
if (timeblueoff) timeblueoff--;
[/code]

Дополнительно для удобства вводим секундные события

[code]if (nextsec==0) {
//раз в секунду!
Nextsec = 1000;
//обработка секундных событий
}
[/code]

Если же нам необходимо посчитать какое-то время от прошедшего события, то используем увеличение на 1 переменной каждую мс. Например, для расчета времени одного оборота колеса используется такой код

[code]//в прерывании обработки датчика, присрабатывании датчика
h2time = 1; //начинаем замер с 1, для того,чтобы можно было отследить, что замервообще начат, если будет 0 — то замер неидет.

//в прервании таймера
if (h2time)
{
h2time++; //если замер начат, то добавляем1
}

if (h2time > 3000) { //обязательно необходимоограничение, если больше 3000, то замеростановили
scor2 = 0;
h2time=0;
}
[/code]

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

Работы пищалки, звуковое оповещение

Для звукового оповещения мы используем мини пищалку. Для воспроизведения звука необходимо подать меняющийся сигнал 0 — 1 с нужной частотой. Наша пищалка имеет самую большую громкость в районе частоты 3Кгц. Но в принципе будет пищать с любой частотой. Для создания на выводе МК меняющего сигнала можно использовать таймер. Например, для частоты 3Кгц нам необходимо иметь таймер срабатывающий 6тыс раз в сек. И каждое срабатывание менять состояние вывода МК. STM8 специальную периферию для работы пищалок. Ей мы и воспользуемся. Звук может выводится только на вывод №1 и с частотой только 1, 2 или 4 Кгц. Нам этого хватит, чтобы издавать различные несложные мелодии. В стандартной библиотеке в примерах есть специальная функция, которая может калибровать данный генератор звука по кварцу, но можно ей не пользоваться.

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

[code]void playmusic(int t)
{
if (t==1)
{
//turn on
BEEP_Init(BEEP_FREQUENCY_1KHZ);
BEEP_Cmd(ENABLE);
Delay(100);
BEEP_Init(BEEP_FREQUENCY_2KHZ);
Delay(200);
BEEP_Init(BEEP_FREQUENCY_4KHZ);
Delay(100);
BEEP_Cmd(DISABLE);
}
[/code]

Все очень просто. Задаем нужную частоту и запускаем генератор. Через определённую в миллисекундах задержку выключаем, включаем опять с другой частотой и т. д. Получается простая мелодия — два писка, три писка разной тональности. Нам этого вполне достаточно.

Обработка кнопок и датчиков, дребезг контактов

Обработку кнопок будет делать с помощью прерываний. В начале программы мы уже инициализировали работу прерываний. Срабатывать они будут только по нисходящему фронту — то есть когда мы нажимаем кнопку (так как кнопки у нас подтянуты через PULL-UP, то там всегда HIGH, когда нажимаем — получается LOW). В момент срабатывания кнопки для устранения дребезга контакта мы отключаем работу прерываний на нужное количество миллисекунд.

[code]INTERRUPT_HANDLER(EXTI_PORTD_IRQHandler, 6)
{
/* In order to detect unexpected events during development,
it is recommended to set a breakpoint on the followinginstruction.
*/
if ((BitStatus)(GPIO_ReadInputPin(GPIOD,GPIO_PIN_3)) == RESET) {
//защита от дребезга контактов -отключим прервание на 50мс
if( haltstart==0)GPIO_Init(GPIOD,GPIO_PIN_3,GPIO_MODE_IN_PU_NO_IT);
kn = TRUE;
kntime = 200;
}
}
[/code]

Так как обработчик прерывания у нас один на весь порт, то необходимо проверить наш ли вывод его вызвал. Для этого считываем состояние нужного нам вывода и есть там LOW, то кнопка нажата. Далее мы отключаем прерывание на выводе D3 и устанавливаем флажок kn в TRUE, для обработки события в основном цикле. Тут же запускаем таймер защиты от дребезга контакта. Заметим, что отключать прерывание мы будем только тогда, когда переменная haltstart у нас равна 0. Это нам необходимо для того, чтобы случайно не выключить прерывания прямо перед уходом в спящий режим, а то мы не сможем разбудить наш МК.

По прошествию этого времени, включаем прерывание обратно в обработчике прерываний таймера 4.

[code]	if (kntime)	
{
kntime--;
if (kntime==0)
GPIO_Init(GPIOD,GPIO_PIN_3,GPIO_MODE_IN_PU_IT);
}
[/code]

Каждую миллисекунду уменьшаем значение счётчика и когда дошли до 0, то включаем прерывания обратно. Аналогично обрабатываем срабатывания датчиков холла. Но там нам необходимо считать время, прошедшее с последнего срабатывания датчика, поэтому таймер там будет увеличивающимся с 1, значение 0 оставим специально для признака того, что предыдущего срабатывания не было.

[code]		if ((BitStatus)(GPIO_ReadInputPin(GPIOC,GPIO_PIN_7)) == RESET) {
//защита от дребезга контактов -отключим прервание на 50мс
if (haltstart==0)
GPIO_Init(GPIOC,GPIO_PIN_7,GPIO_MODE_IN_PU_NO_IT);

if (h1time>1) {
//скорость будем считать толькокогда прошло больше одной миллисекундыс прошлого замера
scor1 = 60000/(h1time - 1);//в оборотах в мин
} else scor1 = 0;

h1time = 1;
hall[0]++;
timehalt=120;
}
[/code]

В обработчике таймера делаем аналогично, но h1time растет, а не уменьшается

[code]		if (h1time)	
{
h1time++;
if (h1time==30)
{
GPIO_ReadInputPin(GPIOC,GPIO_PIN_7);
GPIO_Init(GPIOC,GPIO_PIN_7,GPIO_MODE_IN_PU_IT);
}
}
[/code]

Дополнительно в той же процедуре обнуляем скорость и показания h1time в случае превышения 3сек

[code]	if (h1time > 5000) {
scor1 = 0;
h1time=0;
}
[/code]

Вычисления, расчёт показателей

Основой всех вычислений у нас является показатель скорости вращения педалей и скорости велосипеда. Переменные scor1 и scor2. Для расчёта скорости вращения педалей в обработчике прерывания, при обработке соответствующего датчика, вычисляем scor1 по формуле

[code]	if (h1time>1) {
scor1 = 60000/(h1time - 1);//в оборотах в мин
} else scor1 = 0;
[/code]

Так как время h1time, прошедшее с последнего срабатывания датчика у нас измеряется в миллисекундах, то необходимо 60сек перевести в миллисекунды и поделить на время одного оборота. Это будет мгновенная скорость вращения педалей.

Для расчета скорости велосипеда используется формула длины окружности

[code]	if (h2time>1) {
scor2 = 36*(uint32_t)dkoles*314/100/(h2time - 1);//в км/ч
//1/ms * dkol * 314/10 * 36 / 10
} else scor2 = 0;
[/code]

Так как мы работаем с целыми числами, то необходимо использовать большие форматы uint32_t, для помещения результата всех умножений перед делением, а потом поделить. В целочисленной математике порядок операций имеет очень большое значение. Например, 2*100/4 = 50, а вот 2/4*100 = 0. Изначально полная формула у нас такая:

=

таким образом получается наша формула. Можно использовать большую точность числа PI, но в общем принцип понятен. Дополнительно в массиве hall[] мы считаем количество оборотов педалей и оборотов колеса.

Дополнительно раз в секунду мы считаем скорость велосипеда по границам 0-4км\ч, 4-8км\ч и т.д. В отдельном массиве obs[] мы получаем количество секунд, которое мы ехали с определёнными интервалами скорости, а также общее количество секунд, которое мы ехали - activetime. Конечно это не совсем хорошо, ведь скорость может меняться чаще чем раз в секунду. Но велосипед очень инертен, мы не можем очень быстро тормозить и разгоняться, так что этим можно пренебречь. Делаем это в обработчике таймера 4.

[code]if (scor2)
{
uint32_t ind = scor2/4; //по 4 км\ч
if (ind>9) ind=9; //все что больше 40км\ч пишемв последнюю градацию скорости
obs[ind]++; //добавляем одну секунду

activetime++;
}
[/code]

Также раз в секунду мы будем считать потраченные калории. Так как нам нужна тут большая точность, то будем использовать тип float. Формула была описана выше.

[code]	if (scor1)
power += (float)ves * (float)scor2 / 3600;
[/code]

Расстояние, которое мы проехали, считаем уже в основном цикле исходя из массива hall[] , при форматировании конечного результата. Её мы передаём на bluetooth, если он включён каждые 2сек. См раздел по работе с bluetooth.

Работа с EEPROM, хранение значений в энергонезависимой памяти

STM8 позволяет работать с EEPROM в нескольких режимах. Первый — автоматическая перезапись — вы записываете новое значение во флеш, старое автоматически стирается. Второй — вы сначала стираете ячейку, а потом записываете новое значение. Во втором случае, скорость записи выше, а обнулить значения можно заранее. Когда работаете с EEPROM необходимо внимательно читать Datasheet, у каждого МК своя методика, а также свой минимальный размер записываемой информации. В STM8 минимальный объем это 1 байт, например, в STM32 — это страница — 256 байт. Объем EEPROM очень небольшой, поэтому надо экономить этот вид памяти — у нас всего 128 байт.

Для упрощения работы с EEPROM в СИ удобно использовать структуры, которые будут описывать записываемые параметры. Компилятор размещает структуру в оперативной памяти последовательно, как она заявлена в СИ. Таким образом, чтобы записать или прочитать структуру в EEPROM, достаточно записать\прочитать нужное количество байт, начиная с адреса структуры. Напишем вспомогательные функции для чтения и записи во флеш из оперативной памяти.

[code]void saveflash(u32 addrf,u32 addr,u8 nbyte)
{
FLASH_Unlock(FLASH_MEMTYPE_DATA);

while (nbyte--)
{
FLASH_ProgramByte(addrf, *((u8 *)addr));
addr++;
addrf++;
}

FLASH_Lock(FLASH_MEMTYPE_DATA);
}

void readflash(u32 addrf,u32 addr,u8 nbyte)
{
FLASH_Unlock(FLASH_MEMTYPE_DATA);

while (nbyte--)
{
*((u8 *)addr) = FLASH_ReadByte(addrf);
addr++;
addrf++;
}

FLASH_Lock(FLASH_MEMTYPE_DATA);
}
[/code]

Перед началом работы с EEPROM, необходимо разблокировать для записи данный вид памяти, если этого не сделать, то данные записать не получится. Это специальная защита от случайной перезаписи памяти. Далее мы используем функции из библиотеки для записи и чтения одного байта. Теперь, например, чтобы записать во флеш структуру данных, необходимо так вызвать нашу функцию:

[code]saveflash(0x4000,(u32)¶m,14);
[/code]

Записать во флеш 14 байт, начиная с адреса 0x4000h, данные из памяти по адресу структуры param.

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

[code]struct s_param {
u8 ves;
u8 dkoles;
u32 probeg;//пробег в метрах за все времяпользования прибором
u32 power;//потраченные калории за всевремя пользования прибором
u8 numpoezdki;//номер последней поездки
int ks;//контроль скорости
int kk;//контроль каденса
//итого 14байт!
} param;

struct s_poezdka {
u8 fix;//0 — пустая поездка, 1 — частичнозаписанная, 2 - фиксированная
u32 nump;//номер поездки по порядку
u32 probeg;//пробег за поездку
u32 power;//калории за поездку
u8 obs[10];//время в процентах от активноговремени на каждой скорости для экономииместа!
u8 maxspeed;//максимальная скорость в км\чза время поездки
u32 activetime;//активное время поездки вминутах, когда скорость была больше 4км\ч
//итого 28 байта! получается 4 поездки!!!
} poezdka;
[/code]

Мы храним вес, диаметр колеса, общий пробег, общие потраченные калории, номер поездки для записи во флеш, настройки контроля. А по каждой поездке — номер поездки по порядку, чтобы видеть сколько вообще раз ездили, пробег за поездку, калории, активное время в секундах, проценты времени (для экономии места в памяти 100% занимает 1 байт) с определённым диапазоном скорости от активного времени.

При включении МК мы проверяем, что у нас есть заданные параметры и считываем их оперативную память, если их нет, то записываем стандартные настройки.

[code]	readflash(0x4000,(u32)¶m,14);

if (param.ves==255 || param.ves==0)
{
cleareeprom(1);
}
[/code]

Для хранения последних поездок мы организуем во флеше кольцевой буфер данных поездок. Данные пишутся в память следующим образом — 1 2 3 4 1 2 3 4, это самый удобный формат хранения данных во флеше, для минимизации циклов перезаписи флеша. Мы помним, что количество этих циклов ограничено.

С данными о поездках мы работаем следующим образом, если МК переходит в спящий режим, то мы записываем во флеш текущие данные о поездке, признак fix в таких данных выставляем в 1. Это гарантирует нам, что если вдруг сядет аккумулятор или произойдёт какое-то ЧП, то данные о поездке останутся в памяти и при следующем включении они зафиксируются в памяти. В случае выхода из спящего режима до окончания поездке, мы продолжаем накапливать данные, если же поездка закончилась, то сдвигаем номер поездки на следующий. Как мы будем определять поездки - разберём в следующем разделе.

Спящий режим, фиксирование одной поездки

Для экономии энергии мы активно используем спящий режим. Если в течение двух минут не было данных с датчиков, то мы переводим МК в спящий режим. В других программах вы увидите, что МК может работать, практически все время проводя в спящем режиме, просыпаясь на несколько миллисекунд. Но тут мы не будем усложнять программу.

STM8 имеет несколько режимов сна, они подробно описаны, в datasheet и отличаются скоростью выхода из спящего режима и энергопотреблением. Мы будем использовать два режима: halt, active halt. В первом режиме МК полностью спит, выход из режима только по внешнему прерыванию. Во втором режиме работает специальный таймер, который будет будить МК, а также внешние прерывания.

Алгоритм работы у нас такой с поездками такой:

  1. после двух минут бездействия, переводим МК в activehalt, интервал пробуждения 30 секунд

  2. если нас разбудили внешние прерывания, значит поездка продолжилась, работаем дальше, у нас та же поездка

  3. если внешних прерываний нет, то каждые 30 секунд мы просыпаемся и считаем пока не пройдёт 3 часа

  4. если в течение 3 часов нас никто не разбудил, то поездка закончилась, и велосипед стоит в гараже.

Для экономии энергии перед переходом в спящий режим мы переключаем МК на работу от внутреннего генератора LSI 128 кГц и включаем AWU (auto wake up — пробуждающий) таймер на 30 секунд.

[code]	if (timehalt==0) {
haltstart=1;
timehalt=120;
GPIO_WriteLow(GPIOD,GPIO_PIN_2);//выкл блютус
blueen=FALSE;

//пришло время спать запишем данныепоездки во флеш на всякий случай!!
if (activetime>1200) //маленькие поездки нефиксируем!
{
int u;
poezdka.activetime = activetime;
poezdka.power = (u32)power;//оставим целую частьот калорий
for (u=0;u<10;u++)
{
poezdka.obs[u]=0;
if(obs[u])
poezdka.obs[u] = obs[u]*100/activetime;//переведемв проценты от общего активного времени!

}
poezdka.maxspeed = maxspeed;
poezdka.fix= 1;
poezdka.probeg = (long)hall[1] * param.dkoles * 314 / 10000;

saveflash(ADDRP+param.numpoezdki*NUMBYTEP,(u32)&poezdka,NUMBYTEP);
}


playmusic(4);

//на всякий случай включим прерывания!вдруг отключили!
GPIO_Init(GPIOD,GPIO_PIN_3,GPIO_MODE_IN_PU_IT);//knopka
GPIO_Init(GPIOC,GPIO_PIN_7,GPIO_MODE_IN_PU_IT);//H1
GPIO_Init(GPIOC,GPIO_PIN_6,GPIO_MODE_IN_PU_IT);//H2

current_millis=0;
[/code]

Устанавливаем флажок haltstart, чтобы случайно перед сном не отключить прерывания, при работе с дребезгом контактов. Выключаем bluetooth. Если поездка длилась более 1200 секунд, то запишем данные во флеш, маленькие поездки не фиксируем. Проиграем музыку. На всякий случай включим прерывания. Мы готовы ко сну. Теперь переходим на LSI генератор.

[code]CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_LSI,DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
AWU_Init(AWU_TIMEBASE_30S);

timeawu=255;//255 * 30 = 7600 сек = 2часа

halt();
while (isawu)
{
int i=0;
isawu = i;
halt();
}
isawu=0;
[/code]

И активируем AWU таймер, специальная переменная isawu будет говорить нам, что мы проснулись от AWU, в этом случае будем опять засыпать. Разберём обработчик прерывания AWU.

[code]INTERRUPT_HANDLER(AWU_IRQHandler, 1)
{
AWU_GetFlagStatus();
isawu=1;
if (timeawu) timeawu--;
else {
if (poezdka.fix) { //если есть поездка!
poezdka.fix=2;
saveflash(ADDRP+param.numpoezdki*NUMBYTEP,(u32)&poezdka,NUMBYTEP);

nextp();
param.power += poezdka.power;
param.probeg += poezdka.probeg;
saveflash(0x4000,(u32)¶m,14);

poezdka.fix = 0;
poezdka.nump++;
saveflash(ADDRP+param.numpoezdki*NUMBYTEP,(u32)&poezdka,NUMBYTEP);
}
//обнулим все данные по текущей поездке
clearlocal();

AWU_DeInit();
}
}
[/code]

Сначала сбросим флаг, чтобы оповестить МК, что мы обработали прерывание, не забываем это делать. В обработчике прерывания AWU таймера проверим закончилась поездка или нет. Если нет, то спим дальше — признак isawu стоит. Если поездка закончилась, то фиксируем ее в памяти, fix = 2, и начинаем следующую пустую поездку, обнуляем все данные, отключаем AWU таймер и спим дальше.

При включении МК проверим не было ли сбоя, в тек поездке стоит признак fix=1, в этом случае зафиксируем поездку и начнём следующую.

[code]	readflash(ADDRP+param.numpoezdki*NUMBYTEP,(u32)&poezdka,NUMBYTEP);
if (poezdka.fix==1)
{
param.power+=poezdka.power;
param.probeg+=poezdka.probeg;
nextp();
saveflash(0x4000,(u32)¶m,14);
//начнем новую поездку
poezdka.fix = 0;
poezdka.nump++;
saveflash(ADDRP+param.numpoezdki*NUMBYTEP,(u32)&poezdka,NUMBYTEP);
}
[/code]

Работа с bluetooth - получение данных

По bluetooth мы можем получать различные команды, для управления нашим прибором. Для их обработки создаем специальный массив Rxbuff[], в нем будет хранится строка для обработки. Также будет необходим специальный флажок — Rxready, который мы будем выставлять, когда строка получена. Заполнение буфера реализовано в прерывании в файле stm8s_it.c, а обработка буфера в основном цикле, чтобы не загружать прерывания.

[code] INTERRUPT_HANDLER(UART1_RX_IRQHandler, 18)
{
char c=0;
if (UART1_GetFlagStatus(UART1_FLAG_RXNE) != RESET )
c = UART1_ReceiveData8();

if (RXready==FALSE) {
if (c == '\n' || c == '\r') {
RXready=TRUE;
}
else
RXbuff[RXtek] = c;
RXtek++;
if (RXtek==5) RXready=TRUE;
}
}
[/code]

Прерывание срабатывает в тот момент, когда периферия получила один байт информации. Проверяем, что байт получен и считываем его в переменную «c». В момент считывания байта происходит автоматическое снятие флага сработавшего прерывания, так что выставлять какой-то флажок, чтобы сбросить прерывание, нам нет необходимости. Далее мы предполагаем, что любая команда состоит из 4 байт и заканчивается символом перевод строки. Если мы еще не успели обработать буфер, то новые данные не получаем. Если получили конец строки, то выставим RXready в истину. Если буфер заполнен весь, то будем считать, что строку получили, иначе запишем очередной байт в буфер. На этом работа с прерываниями закончена. Теперь в основном цикле осталось разобрать строку.

Прежде чем посмотреть, как мы будем разбирать строку, опишем возможные команды, которые будет понимать наш прибор. Вот наши команды:

  • clr — обнулить все данные в памяти, полный сброс. Чтобы случай не стереть все, запросим подтверждение этой команды.

  • yes — подтверждение команды clr

  • v256 — установка веса в кг, например, для веса 65 кг, команда будет такой — «v65\n», далее аналогично

  • d256 — установка диаметра колеса в см

  • s127 — установка контроля скорости, не меньше заданной, 0 — нет контроля

  • s-127 — установка контроля скорости не больше заданной, 0 — нет контроля

  • k127 — установка контроля каденса, не меньше заданной, 0 — нет контроля

  • k-127 — установка контроля каденса, не больше заданной , 0 — нет контроля

  • info — вывод информации о предыдущих поездках из памяти.

Сам модуль в этой части большой. Подробно описывать его не будем. Вы можете посмотреть исходный код самостоятельно. Приведем лишь общий смысл.

[code]	if (RXready) {
if (flagclr==1 && RXbuff[0]=='y' &&RXbuff[1]=='e' && RXbuff[2]=='s')
{
//сотрем все поездки
cleareeprom(0);
}
flagclr=0;

switch (RXbuff[0]) {
case 'c':
flagclr = 1;
break;
case 'i':
//..//
case 'v':
//..//
default:
break;
}

if (cmd)
{
int ind = RXtek-2;

for(u=start;u<RXtek-1;u++)
{
//v256 start = 2 Rxtek = 5
if (RXbuff[ind] < 0x30 || RXbuff[ind] > 0x39)
{
//ошибка тут должно быть толькочисло!
noerr=0;
break;
}

rez = rez + (RXbuff[ind] - 0x30) * r;
r *= 10;
ind--;
}
if (noerr)
{
//установим параметр и выведемсообщение о всех параметрах!
if (znak>0) *pp = rez;
else *(int *)pp = (int)rez * (-1);

saveflash(0x4000,(u32)¶m,11);

printf("(ves-kg)%i (d-cm)%li (ks)%i(kk)%i\n\r",(int)param.ves,(long)param.dkoles,param.ks,param.kk);
}
}
RXtek = 0;
RXready = FALSE;
}
[/code]

Процедура начинается тогда, когда флаг RXready выставлен. Flagclr — используется для подтверждения получения предыдущей команды «clr», и если все подтверждено, введена команда «yes», то очищаем данные о поездках во флеш. После получения «clr» выведем, что мы ждем подтверждения.

Далее по первой букве команды мы определяем, что необходимо выполнить. Если это команда по установке параметров, то далее переводим текст в число, а заодно проверяем что это именно число. По окончанию установки параметра, записываем данные о параметрах во флеш и выводим их по bluetooth, чтобы подтвердить факт установки параметров.

В самом конце снимаем признак RXready и готовимся принять следующую команду.

Работа с bluetooth - вывод данных

Для передачи данных по bluetooth мы будем использовать стандартную функцию printf. Стандартная библиотека проекта на STM8 (на других МК это работает похожим образом) позволяет настроить вывод данную функции на любую периферию. Для этого созданы специальные макросы PUTCHAR_PROTOTYPE и GETCHAR_PROTOTYPE, в которых необходимо написать, как МК будет передавать или получать один байт с периферии. Для получения данных мы самостоятельно обрабатывали прерывания, а вот для вывода данных, воспользуемся этим механизмом.

[code]PUTCHAR_PROTOTYPE
{
/* Write a character to the UART1 */
UART1_SendData8(c);
/* Loop until the end of transmission */
while (UART1_GetFlagStatus(UART1_FLAG_TXE) == RESET);

return (c);
}
[/code]

Так выглядит функция передачи одного байта по UART. Записываем байт в специальный регистры данных UART и ждем окончания передачи — флаг TXE сброшен.

После такой доработки пользуемся стандартной функцией форматированного вывода printf и получаем на смартфоне переданный текст. Для снижения нагрузки на МК, передавать данные мы будем 1 раз в 2 секунды, сама передача данных идет в основном цикле. Вот так выглядит наш код.

[code]		if (timeblue==0 && blueen) {
//каждые 2 сек будем выводить на экранчто-то и считать все подряд
timeblue = 2;

printf("(cad)%i (SCOR)%i (all)%li (ob)%li (m)%li\n\r",(int)scor1,(int)scor2,(long)(hall[0]+hall[1]),(long)(hall[0]),(long)(hall[1]*(long)param.dkoles*PI/100/100));

printf("t");
{
int j;
for (j=0;j<10;j++)
{
printf(":(%li)%li",(long)(j+1)*4,(long)obs[j]);
}
printf("\n\r");
}
}
[/code]

Если bluetooth включен, и время 2 секунды вышло, то запускаем заново таймер и передаем данные. Сначала передаем одной строкой основные параметры — скорость, каденс, общее расстояние и т. д. Потом в цикле передаем данные по интервалам скорости. В данной функции важно использовать приведение типов, то есть если используется форматный символ %li — long — то при передаче параметра, необходимо его привести к типу long. Вот так просто осуществляется вывод информации.

Контроль скорости и каденса

Скорость мы будем контролировать следующим образом. В течение 30 секунд мы будем проверять каждую секунду нарушение режима. Если оно было 80%, то выставляем флаг нарушения. Для этого воспользуемся функцию СИ по сдвигу битов в переменной. Каждый бит кодировать было нарушение или нет. 32 битного числа достаточно, чтобы вести историю за 30 секунд.

[code]		if (param.ks)
{
int bit=0;
if ((param.ks > 0) ? (scor2 < (u8)param.ks) : (scor2 >(u8) (-param.ks)) )
{
errspeed++;//есть нарушение скорости!
bit=1;
}
errspeed = errspeed - (u8)((errspeed32 & (1L <<31))?1:0);

errspeed32 <<= 1;
errspeed32 |= bit;
}
[/code]

Если задан контроль скорости, то для случая положительного параметра, нарушением является скорость меньшая чем параметр, для отрицательного, нарушением является большая чем параметр скорость. Если есть нарушение, то добавляем один к переменной errspeed, в итоге в ней имеем сколько было нарушений за последние 32 секунды. Далее уменьшаем на один количество нарушений, если 31 секунду назад оно было, сдвигаем влево весь результат и правый бит устанавливаем в один, если было нарушение. Так мы в итоге реализуем скользящее окно в 32 секунды. Тоже самое для каденса. Далее в основном цикле остаётся только следить за errspeed, и когда значение больше 20, то пищать нужную мелодию каждые 30 секунд.

[code]if (errspeed > 20 && time_errspeed == 0)
{
time_errspeed = 30;
playmusic(5);
}
[/code]

Работа с симулятором

На данном проекте вы можете испытать в работе симулятор. Для работы с симулятором вам необходимо в модуле main.с раскомментировать строку

[code]#define SIMUL
[/code]

В итоге будут отключены вызовы функций, не работающие в симуляторе. Для начала работы в симуляторе запускаем отладчик, предварительно установив средства отладки — симулятор. В симуляторе не работают таймеры, прерывания, но их тоже можно симулировать. Делается через специальное окно — View — I\O Simulation. В данном окне можно симулировать любое прерывание по номеру, номер можно найти в функции обработчике прерывания. Например, для таймера TIM4 — это 23. Также для симуляции прерывания можно задать периодическое событие, делается это по двойному нажатию на поле «Current Value». В окне необходимо задать нужную периодичность в тактах МК.


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

Как изготовить велокомпьютер

Делаем плату и все запаиваем

  1. Подготовить или приобрести необходимые инструменты: все для пайки, ST-LINK (будет нужен для программирования и отладки МК)

  2. Внимательно прочитать статьи из раздела Обязательная теория.

  3. Скачать необходимые файлы по данному прибору с github.

  4. Изготовить плату для прибора самостоятельно (это совсем несложно, в нашей инструкции все подробно описано). Отметим, что для упрощения построения платы, вывод подключения кнопки необходимо подключить перемычкой из провода — площадки kn1. Kicad показывает это как не подключённую сеть — это не является ошибкой.

  5. Приобрести все необходимые комплектующие.

  6. Запаять все компоненты на плату, смотри наше видео.

  7. Плата готова!

Для программирования лучше припаять (на специальную площадку PRG) провод с разъемом пин терминал, запаянный в термоусадочную трубку — получится удобный разъем для программирования. Программировать удобнее сразу подключив прибор через USB разъем и ST LINK в другой USB разъем, тогда достаточно только подключить SWIM в разъему программирования.

Установка в корпус

Для установки в корпус вам понадобятся 2 небольших шурупа, чтобы прикрутить плату (в комплекте они не идут). Также для исключения вибраций будет необходим кусочек поролона или другой уплотнитель. Необходимо сделать бутерброд — нижняя часть корпуса - плата — поролон — аккумулятор — верхняя часть корпуса. Под провод для датчика необходимо просверлить отверстие в корпусе. Под usb разъем вырезать паз ножом и доработать надфилем. Под кнопку необходимо просверлить отверстие. Кнопка занимает чуть больше места, чем высота стоек в корпусе, поэтому лучше подложить шайбочки под плату до получения нужной высоты. Шайбы можно сделать из оставшегося текстолита или ненужных пластиковых карточек. Если у вас есть желание, кнопку можно разместить сверху платы, но так она будет мешать аккумулятору. Под провод для подключения аккумулятора можно сделать пропил в плате.

Делаем датчик

Один геркон будет распаян на плате, а второй будет припаян на коротком проводе, и будет вне прибора. Геркон, который будет у нас на плате лучше залить каплей термоклея, также разместить его лучше на расстоянии не менее 5 мм от платы, снизу находится динамик, и он может влиять на геркон. Геркон надо разместить как можно ближе к боковой стенке корпуса.

Для того, чтобы сделать датчик на проводе, лучше всего воспользоваться термоклеевым пистолетом. Берём ненужную прозрачную пластиковую ручку, отрезаем носик, припаиваем к проводу датчик, откусываем лишние ножки. (ВНИМАНИЕ! Когда будете паять герконы не гните его ножки, лучше припаять провод параллельно ножкам и их просто откусить. Гнуть их строго придерживая пинцетом, иначе можно разбить стеклянную колбу и сломать геркон.!). Далее вставляем геркон и часть провода в ручку и заливаем под давлением термоклей, так чтобы он заполнил все внутренности. Это сделать наш датчик влагостойким и защитит от повреждений. В принципе вы можете сделать оба датчика на проводах, такая конструкция проще.




Для работы датчика необходим сильный магнит. Можно купить магнит для велокомпьютера (100-200руб), а можно взять магнит из детского магнитного конструктора Geomag. Там очень сильные магниты и их можно прикрепить к спице велосипеда обычной стяжкой. Также бывают очень сильные магниты шайбы. Магнит надо подобрать так, чтобы датчик срабатывал на расстоянии 3-5 см, и проверить при вращении колеса и педалей.

Переходим к программированию

Данный прибор достаточно простой в программировании. В этой статье подробно разобрана вся программа. Мы настоятельно рекомендуем написать программу САМОСТОЯТЕЛЬНО. Пройти полный путь от скачивания библиотеки с сайта ST, до финальной программы. Только так вы сможете изучить программирование микронтроллеров. Если вы не можете справиться самостоятельно, или хотите просто сделать прибор и просто пользоваться им, на github есть все исходные текст. Обязательно поработайте с симулятором и отладчиком. Выполните задания для развития. И самое главное не сдавайтесь. В начале будет тяжело, но интересно!

Написанная программа с нуля, с разворачивания проекта, настроек всех необходимых параметров, будет более полезна, чем просто процедура прошивки МК готовой программой. Ошибки, которые вы найдёте с помощью отладчика и исправите сами — это самое ценное в этом проекте. А готовый прибор будет долго радовать вас на велопрогулках.

Вариант с индикатором

Данный велокомпьютер можно дополнить индикатором. Плату и описание как его подключить можно скачать в этой статье.

На индикаторе можно отображать скорость - шкала из 8-ми красных светодиодов, каденс - вторая шкала из 8-ми зеленых светодиодов, 4 информационных светодиода - отображение статуса bluetooth (синий светодиод), отображение нарушение контроля скорости и каденса (красные светодиоды).

На плате специально выделены площадки для подключения провода управления - J7 или J8.

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

Под светодиоды надо будет в крышке корпуса просверлить отверстия. Припаять питание индикатора необходимо к свободному выводу микроконтроллера, чтобы в спящем режим его можно было отключать.

Программу надо будет самостоятельно изменить для отображения на индикаторе скорости и других показателей. Удобно, например отображать на второй шкале, процент пройденного расстояния от заданного.

Приобретённые навыки

Пайка: пайка модулей, пайка корпуса TTSOP, пайка корпуса SOT-23-5, пайка micro USB разъема.

Схемотехника: LDO, схема зарядки Liion аккумулятора, подключение модуля bluetooth, типовая схема подключения STM8, подключение пищалки, работа с кнопками.

Программирование: обработка кнопок по прерываниям, миллисекундный таймер, спящий режим, AWU таймер, работа с EEPROM, UART

Самостоятельная работа

Вы дополнительно можете доработать функции велокомпьютера. Идеи для доработки:

  • Дописать функцию контроля скорости, аналогично контролю каденса.

  • Реализовать различные режимы передачи информации — при движении — передавать только скорость, при остановке передавать итоговые параметры.

  • Реализовать контроль расхода калорий — устанавливать цель и пищать при её достижении на половину, и по окончательному достижению цели

  • Расширьте диапазон хранимых скоростей до 120км\ч.

  • Оповещать звуком каждый км.

  • Сделайте хранение скорости не по интервалам, а в абсолютном значении, 4 скорости, с которыми вы ехали наибольшее время.

]]>
(Super User) Приборы Sat, 01 Jul 2017 21:04:00 +0000
Измеритель УФ-индекса и температуры /index.php/pribory/item/46-izmeritel-uf-indeksa-i-temperatury /index.php/pribory/item/46-izmeritel-uf-indeksa-i-temperatury Измеритель УФ-индекса и температуры

Собрались на море? Возьмите с собой этот прибор. С его помощью вы никогда не обгорите и будете загорать правильно — точное измерение мгновенного и накопленного индекса ультра фиолетового излучения. Вы будете знать прогрелось ли море — точное измерение температуры воды, с помощью термопары К типа. Насколько жарко на улице — измерение температуры воздуха. Также он пригодится вам на даче — правильное приготовление шашлыка. Миниатюрный размер 35х45х18, встроенный аккумулятор, а также масса других полезных функций.

{autotoc}

Прежде чем начать

Прежде чем читать эту статью, рекомендуем вам ознакомится со следующей теорией:

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

Вы собрались в жаркие страны, отдохнуть на море. Задумывались ли вы о том, насколько вредно наше солнце? Солнечное излучение - это весь свет и энергия, которые исходят от солнца, и есть много различных его форм. В электромагнитном спектре выделяют различные типы световых волн, излучаемых солнцем. Они похожи на волны, которые вы видите в океане: они перемещаются вверх и вниз и из одного места в другое. Спектр солнечного изучения может иметь разную интенсивность. Различают ультрафиолетовое, видимое и инфракрасное излучение. От самого опасного вид излучения - гамма-лучей, нас защищает озоновый слой. Инфракрасное излучение даёт тепло, видимое — свет, а вот ультрафиолетовое излучение в больших дозах может быть опасно для здоровья человека. Оно способно проникать глубоко под кожу и повреждать клетки, вносить изменения в ДНК, приводить к такому заболеванию как РАК. На следующей картинке можно видеть все виды солнечного излучения и те виды, которые достигают земной поверхности.




В небольших дозах УФ излучение полезно — под его воздействием в организме вырабатывается витамин Д. Однако в больших дозах оно приводит к ожогам кожи, эритеме(подробнее про вред и пользу УФ излучения можно прочитать в этой статье).

Уровень УФ излучения на поверхности нашей планеты принято измерять УФ индексом (UV Index). Этот показатель отражает степень ультрафиолетового излучения, он варьируется от 0 до 11. Чем выше его величина, тем больше потенциальная угроза для кожных покровов и глаз человека, и, соответственно, тем меньше времени можно находиться на солнце. Чтобы рассчитать данный индекс необходимы знания об интенсивности УФ излучения. Причём для корректного подсчёта нужно учитывать весь спектр УФ излучения - от 100 до 400 нм.


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

Учёными была установлена максимальная суточная доза УФ излучения (1 МЭД — минимальная эритемная доза), которая не приводит к отрицательным последствиям. Она зависит от типа кожи. Данные приведены в следующей таблице.

Тип кожи

Загар

Солнечный ожог

Цвет волос

Цвет глаз

1 МЭД, Дж/м2

I

никогда

всегда

рыжий / соломенный

голубой

200

II

иногда

иногда

светло русый

голубой / зелёный

250

III

всегда

редко

тёмно русый / каштановый

серый / карий

300

IV

всегда

никогда

чёрный

карий / чёрный

450



Индекс УФ напрямую влияет на время проведения на солнце. На следующем графике видна зависимость времени пребывания на солнце до покраснения кожи от индекса УФ и типа кожи.


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



Вид деятельности

Коэффициент C

Плавание:

море

2

пресноводные водоёмы

4

Спортивные игры

4

Пешеходные прогулки

3

Гребля, прогулки на лодке

3

Работа на приусадебном участке

6



Итак, теперь мы владеем всей информацией, чтобы правильно проводить время на солнце в отпуске. Нам нужен датчик, который позволит рассчитать индекс УФ излучения. А дальше мы сможем получить безопасное время нахождения на солнце.

Измеряем температуру воды и воздуха

Как правило, все едут в отпуск на море — понежиться на солнце и поплавать в ласковом теплом море. Чтобы рассчитать сколько времени можно находится в воде нужно знать её температуру. Наиболее комфортной считается температура 24-28 градусов. Вторая полезная функция прибора для отдыха — точное и быстрое измерение температуры воды.

В качестве датчика температуры воды идеально подходит термопара, например К типа (она самая распространённая). Во первых, такой датчик является достаточно точным, а во вторых он не боится влаги. Ну и самое главное — термопара имеет очень маленькую теплоёмкость, а это значит, что значения температуры воды вы получите практически мгновенно — 1-2 секунды достаточно для замера. Длина датчика может быть больше 1 метра, что позволит измерить температуру воды не только на поверхности, но и на глубине.

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

Вот теперь можно смело переходить к требованиям к прибору «отпускника».

Прочие полезные функции

Для измерения термопары нужен будет высокоточный ADC. Его можно использовать и для других полезных функций. Можно сделать вольтметр, амперметр, миллиомметр, можно измерять внутреннее сопротивление аккумуляторов. Для реализации этих функций потребуется сделать мини плату. Но зато прибор можно будет использовать не только летом, но и зимой!

Для работы с датчиками используется протокол I2C, а это значит, что если вывести выводы SDA SCL на внешний разъем, то можно будет подключать дополнительные датчики по этой шине, а также может сделать I2C сканер.

{product id=35}

Постановка задачи, требования к прибору

Основные требования к прибору.

  • Компактный размер

  • Питание от аккумулятора, длительная работа от одного заряда

  • Измерения индекса УФ излучения в двух диапазонах UVA и UVB

  • Измерение температуры с помощью термопары К типа, подключение ее через разъем

  • Измерение температуры воздуха

  • Индикация измеренных данных — миниатюрный ЖК (на солнце он читается лучше других индикаторов) дисплей

  • Возможность индикации звуком о различных событиях

  • Широкий набор функций и возможность доработки.

Подбор компонентов

Выбираем датчик УФ излучения

В качестве датчика УФ излучения будем использовать один из самых современных и миниатюрных датчиков уф излучения — VEML6075 от компании Vishay.




Данный датчик имеет очень маленький размер 2х2 мм, низкое энергопотребление 500 мкА в режиме работы и 800 нА в режиме сна. И самое главное - он умеет измерять UVA и UVB излучение одновременно. К датчику идёт подробный datasheet, а также дополнительная документация, в которой подробно рассмотрено как его калибровать и использовать в проектах. Датчик подключается по шине I2C, поэтому микроконтроллер должен уметь работать с этой шиной.

Датчик измерения термопары

Термопара — датчик температуры, принцип работы которого основывается на термоэлектрическом эффекте. Точность такого датчика очень высока — до 0.01 градуса, он имеет очень большой диапазон измерения — от -250 до 2000 градусов. Однако получить данные с него не так-то просто. Основным показателем температуры является напряжение на концах термопары (ТЭДС), по специальным формулам по нему можно определить температуру спая.

На показания датчика оказывают влияние:

  • Температура свободных концов (которые подключаются к разъему). Ее необходимо добавить к вычисленной температуре спая.

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

  • Зависимость ТЭДС от температуры существенно не линейна. Это требует сложных вычислений значения температуры по ТЭДС.

  • На большой длине термопарных и удлинительных проводов может возникать эффект «антенны» для существующих электромагнитных полей.

  • На один градус температуры для термопары типа К — ТЭДС составляет всего лишь 40 мкВ. Нужен очень точный ADC, чтобы измерить такое напряжение, или малошумящий операционный усилитель для усиления данного сигнала.

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

Самый современный путь для измерения температуры термопары - использовать внешний очень точный сигма дельта ADC — mcp3421. Данный ADC умеет измерять напряжение с разрешением до 2 мкВ (что дает разрешение в 0.05 градусов), при этом для его работы практически не нужны внешние компоненты. И что более важно, он не требует никакой калибровки, настройки и т. д. Таким образом, мы сразу получим очень точные данные с термопары. Такой подход сильно упрощает схему. Все вычисления произведёт микроконтроллер с помощью таблицы коэффициентов для многочленов. Скорость вычислений тут не нужна, поэтому даже слабый микроконтроллер справится с данной задачей. Датчик подключается также по шине I2C.

Наличие в данном приборе такого точного вольтметра позволяет использовать его (с помощью небольшой внешней платы) как прибор для следующих измерений:

  • напряжение

  • ток

  • сопротивление в диапазоне от 1 мОм и выше

  • ESR аккумуляторов

  • емкости конденсаторов

Датчик измерения температуры воздуха

К сожалению, ТЭДС отражает разницу температуры между точкой подключения датчика и спаем. Поэтому для получения реальной температуры в месте спая необходимо знать температуру разъёма, куда подключена термопара. Будем использовать цифровой интегральный датчик температуры NCT75. Он имеет разрешение до 0.06 градусов и погрешность в один градус. Так что возможная погрешность всего измерения составит 1 градус. В настоящее время есть интегральные датчики с погрешностью до 0.1 градуса, но применение такого датчика здесь не оправдано.

Этот датчик опять же работает по шине I2C, что очень удобно.

Индикатор

Индекс УФ излучения имеет смысл измерять в погожий солнечный день. А это означает, что индикатор должно быть хорошо видно на ярком солнце. Лучше всего для этого подойдёт LCD индикатор, например Gdc0209 — 4 цифры.


Он имеет всего 13 выводов на все сегменты. Используется динамическая индикация. Управляющее напряжение 2.5В. Сегменты подключены следующим образом.




Управление таким индикатором без специализированной периферии довольно сложное. Но в этом приборе микроконтроллеру будет скучно — пусть им и управляет.

Пищалка

Чтобы иметь возможность предупредить о высоком уровне излучения или низкой температуре нужна пищалка. Используем традиционно — HC0903A.

Микроконтроллер

Нам обязательно нужна периферия I2C, но она есть практически везде. Так что подойдёт относительно любой микроконтроллер. Возьмём уже знакомый по другим приборам — STM8S003F3.

Аккумулятор

Питаться прибор будет от аккумулятора. Потребление за счёт экономичного индикатора в рабочем режиме составляет около 1-2 мА. Достаточно будет самого маленького аккумулятора, например LIR2032. Плату будем делать как можно меньше, поэтому заряжать будем через внешний разъем с помощью небольшой отдельной платы зарядки. По всем расчётам это понадобиться делать раз в месяц при активном использовании. Для защиты аккумулятора от разряда можно использовать внешнюю мини платку. Вполне можно обойтись и без неё, достаточно только внимательно следить за прибором.

Регулятор питания

Так как на аккумуляторе максимально может быть 4.2В, а для датчиков максимальное напряжение составляет 3.6В, то необходим регулятор питания. Козьмем серию NCP551. Эти недорогие регуляторы имеют очень низкий ток собственного потребления — всего 4мкА, что позволяет питать устройства, которые будут большую часть времени спать.

Подбираем корпус и разъёмы

Корпус возьмём аналогично кухонному таймеру «Sanhe 20-31». В качестве разъёма используем самые дешёвые угловые PIN, сделаем так, чтобы они были утоплены в корпус и не торчали наружу. На разъем выведем:

  • 2 вывода для подключения термопары

  • 2 вывода — контакты батарейки для подключения внешней зарядки

  • I2C и 3.3в - для подключения внешних датчиков (в случае расширения функционала)

  • SWIM — для программирования

Наличие разъёма позволит подключать к прибору внешние датчики, что позволит расширить его функционал.

Составляем схему

Общая часть - питание, МК, пищалка, разъемы.




И датчики.


Все подключения согласно datasheet, ничего особенного нет. Печатную платы вы сможете найти в проекте на github.

Программа

Программа для STM8 написана в среде ST Visual develop IDE. Полный проект вы можете скачать с github данного прибора, папка uv. В статье мы разберём ключевые моменты работы программы.

Общая логика работы программы

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

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

  3. После смены режима работы в течение 2с отображается текущий режим в виде символов режима

  4. Предусмотрены следующие режимы работы:

    1. Измерение температуры (символ 1хх)

      1. воздуха, с помощью цифрового датчика (символ режима 101)

      2. с датчика термопары (символ режима 102)

    2. измерение UV индекса (символ режима 201)

  5. На экране отображаются измеряемые значения

  6. Через 30с неактивности прибор переходит в спящий режим

Инициализация

Установим частоту работы микроконтроллера в 2 МГц и отключим всю периферию, кроме I2C и TIM2 и TIM4.

[code]	CLK->PCKENR1 = CLK_PCKENR1_TIM2+CLK_PCKENR1_I2C; 	CLK->PCKENR2= 0b11110011; 
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);//2mgh
//internal clock
GPIO_Init(GPIOD,ALL_PORTD,GPIO_MODE_OUT_PP_LOW_SLOW);
GPIO_Init(GPIOA,ALL_PORTA,GPIO_MODE_OUT_PP_LOW_SLOW);
GPIO_Init(GPIOC,ALL_PORTC,GPIO_MODE_OUT_PP_LOW_SLOW);
[/code]

Настроим внешние прерывания для возможности выхода из сна по кнопке.

[code]	EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOA,EXTI_SENSITIVITY_FALL_ONLY);
GPIO_Init(GPIOA,GPIO_PIN_2,GPIO_MODE_IN_PU_IT);
[/code]

Инициализируем I2C.

[code] I2C_Init(I2C_MAX_STANDARD_FREQ, (uint8_t)0xA0, I2C_DUTYCYCLE_2,I2C_ACK_CURR, I2C_ADDMODE_7BIT, 7);
[/code]

Настроим таймеры на 1000 Гц (основной миллисекундный таймер) и 2000 Гц (для индикатора).

[code]	TIM2_TimeBaseInit(TIM2_PRESCALER_8, 124);//2000Hz
TIM2_ClearFlag(TIM2_FLAG_UPDATE);
TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);
TIM2->IER |= (uint8_t)TIM2_IT_UPDATE;
TIM2_Cmd(ENABLE);

TIM4_TimeBaseInit(TIM4_PRESCALER_8, 249);//1000Hz
TIM4_ClearFlag(TIM4_FLAG_UPDATE);
TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
TIM4->IER |= (uint8_t)TIM4_IT_UPDATE;
TIM4_Cmd(ENABLE);
[/code]

Дальше переходим к запуску периферии и инициализации датчиков.

[code]	enableInterrupts();
Delay(100);
bmeT=0;
I2C_Cmd( ENABLE);
mcpinit();
uvinit();
nctinit();
[/code]

На этом все. Процедуры инициализации датчиков рассмотрим ниже.

I2C драйвер

Компания ST не предоставляет готовой библиотеки для работы с I2C, только низкоуровневые функции для настройки периферии и доступа к регистрам. В их примерах есть куски кода обмена данными по шине I2C. На их основе напишем необходимые функции передачи и получения данных по шине I2C. Обмен будет работать без использования прерываний. Для записи данных используется функция I2C_writenbyte, а для чтения I2C_readnbyte. Начнём с первой, она проще.

[code]int I2C_writenbyte(uint8_t addr, uint8_t* buff, int nbyte, intnostop)
{
uint32_t timeout;
timeout = current_millis + 1000;

while (I2C_GetFlagStatus(I2C_FLAG_BUSBUSY))
{
if (current_millis>timeout) return 0;
}

I2C->CR2 |= I2C_CR2_START;//I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
{
if (current_millis>timeout) return 0;
}
[/code]

Перед записью необходимо убедиться, что шина I2C не занята, за это отвечает флаг I2C_FLAG_BUSBUSY в регистре I2C. Подождём пока шина не освободиться. Если этого не произойдёт за разумное время, то вернём состояние ошибки. Далее сгенерируем событие START, запишем соответствующий бит в регистр CR2. Проверим, что шина перешла в режим мастер. Можно передавать данные. В функцию передаётся адрес массива с байтами buff и количество байт для передачи, а также флаг — nostop, который отвечает за передачу в конце сообщения сигнала STOP. Некоторые датчики требуют, чтобы за командой записи следовала сразу команда чтения, без передачи сигнала STOP.

[code]	I2C_Send7bitAddress((uint8_t)addr << 1, I2C_DIRECTION_TX);
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if (current_millis>timeout) return 0;
}

while (nbyte>0)
{
I2C->DR =(uint8_t)*buff;//I2C_SendData((uint8_t)*buff);//ctrl meas
while(!I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED))
{
if (current_millis>timeout) return 0;
}
*buff++;
nbyte--;
}

if(nostop==0)
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
}

return 1;
[/code]

Передаём адрес датчика, с которым будет производиться общение, и признак записи — первый бит. Ожидаем подтверждения, что режим мастера никто не отобрал. Далее, пока не закончатся все байты, записываем их в регистр DR периферии I2C и ждём пока очередной байт будет передан (I2C_FLAG_TRANSFERFINISHED). Остаётся только передать сигнал STOP, если он необходим. В случае возникновения ошибки, байт не будет считаться переданным, и через таймаут будет возвращён статус ошибки. Как видно, процедура совсем несложная.

Разберём теперь процедуру чтения данных.

[code]int I2C_readnbyte(uint8_t addr, uint8_t * buff, int nbyte,intnocheckbusy)
{
uint32_t timeout;
timeout = current_millis + 1000;

if (nocheckbusy==0)
{
while (I2C_GetFlagStatus(I2C_FLAG_BUSBUSY))
{
if (current_millis>timeout) return 0;
}
}

I2C->CR2 |= I2C_CR2_START;//I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
{
if (current_millis>timeout) return 0;
}

I2C_Send7bitAddress((uint8_t)addr << 1, I2C_DIRECTION_RX);
[/code]

Начало примерно такое же. Ожидаем освобождения линии (если это необходимо). Далее передаём команду START, и ждём подтверждения перехода в мастер режим. Передаём адрес с битом чтения и начинаем передавать данные. И вот тут необходимо разобрать три варианта чтения данных. Первый - когда байт для чтения больше трёх. Второй - когда всего два байта и третий когда один. В каждом случае необходимо по-разному отслеживать события чтения, потому, что шина I2C имеет теневой регистр, в котором могут уже находиться данные, когда заполнен основной регистра данных. То есть, когда мы разбираемся с полученным байтом, периферия может получить уже второй байт. Случай больше трёх байт.

[code]	if (nbyte >= 3) 
{
while (I2C_GetFlagStatus( I2C_FLAG_ADDRESSSENTMATCHED) == RESET)
{
if (current_millis>timeout) return 0;
}
disableInterrupts();
(void)I2C->SR3;
enableInterrupts();

while (nbyte > 3) {

while (I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED) == RESET)
{
if (current_millis>timeout) return 0;
}

*buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
*buff++;
nbyte--;
}
while (I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED) == RESET)
{
if (current_millis>timeout) return 0;
}
I2C->CR2 &=(uint8_t)(~I2C_CR2_ACK);//I2C_AcknowledgeConfig(I2C_ACK_NONE);
disableInterrupts();
*buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
*buff++;
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
*buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
enableInterrupts();
*buff++;
while (I2C_GetFlagStatus( I2C_FLAG_RXNOTEMPTY) == RESET)
{
if (current_millis>timeout) return 0;
}

*buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
nbyte=0;

while(I2C->CR2 & I2C_CR2_STOP)
{
if (current_millis>timeout) return 0;
}

/* Re-Enable Acknowledgement to be ready for another reception*/
I2C->CR2 |= I2C_CR2_ACK;
I2C->CR2 &=(uint8_t)(~I2C_CR2_POS);//I2C_AcknowledgeConfig( I2C_ACK_CURR);
}
[/code]

Проверяем, что датчик откликнулся на свой адрес. Теперь необходимо прочитать регистр SR3, и сделать это нужно так, чтобы нас никто не прервал. Запрещаем прерывания и читаем регистр. В этот момент периферия получает байт данных. Ждем окончания получения байта и записываем его в буфер. Так делаем, пока количество оставшихся байт не станет равно 3. Отключаем автоматическое подтверждение ACK при получении байте, чтобы обработать байт. Запрещаем прерывания и получаем байт. Далее сразу генерируем STOP сигнал. Ждем пока придет еще один байт и записываем его в буфер. Ждём пока будет передан стоп сигнал и генерируем ACK сигнал. Таким хитрым образом получаем все оставшиеся три байта.

Когда надо получить два байта.

[code]	if (nbyte == 2) 
{
I2C_AcknowledgeConfig(I2C_ACK_NEXT);
while (I2C_GetFlagStatus( I2C_FLAG_ADDRESSSENTMATCHED) == RESET)
{
if (current_millis>timeout) return 0;
}
(void)I2C->SR3;
I2C_AcknowledgeConfig(I2C_ACK_NONE);

while (I2C_GetFlagStatus( I2C_FLAG_TRANSFERFINISHED) == RESET)
{
if (current_millis>timeout) return 0;
}

disableInterrupts();
I2C_GenerateSTOP(ENABLE);
*buff= I2C_ReceiveData();
enableInterrupts();
// Point to the next location where the byte read will be saved
*buff++;
*buff= I2C_ReceiveData();
nbyte=0;

while(I2C->CR2 & I2C_CR2_STOP)
{
if (current_millis>timeout) return 0;
}

// Re-Enable Acknowledgement to be ready for another reception
I2C_AcknowledgeConfig( I2C_ACK_CURR);
}
[/code]

Включаем автогенерацию ACK бита, проверяем что адрес опознан. Читаем регистр SR3. Отключаем генерацию ACK. Ждем флага байт получен. Запрещаем прерывания. И уже генерируем STOP сигнал. Байт уже получен. Быстро его читаем. И периферия получает в теневой регистр еще один байт. Записываем его в буфер и ждём пока выставится STOP на линии. Возвращаем назад автогенерацию ACK сигнала. Остался случай одного байта.

[code]	if (nbyte == 1) 
{
I2C->CR2 &=(uint8_t)(~I2C_CR2_ACK);//I2C_AcknowledgeConfig(I2C_ACK_NONE);
while(I2C_GetFlagStatus( I2C_FLAG_ADDRESSSENTMATCHED) == RESET)
{
if (current_millis>timeout) return 0;
}
disableInterrupts();
(void)I2C->SR3;
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP( ENABLE);
enableInterrupts();
while(I2C_GetFlagStatus( I2C_FLAG_RXNOTEMPTY) == RESET)
{
if (current_millis>timeout) return 0;
}
*buff = ((uint8_t)I2C->DR);//I2C_ReceiveData();
nbyte=0;
while(I2C->CR2 & I2C_CR2_STOP)
{
if (current_millis>timeout) return 0;
}
I2C->CR2 |= I2C_CR2_ACK;
I2C->CR2 &=(uint8_t)(~I2C_CR2_POS);//I2C_AcknowledgeConfig( I2C_ACK_CURR);
}
[/code]

Сразу отключаем генерацию ACK. Ждем подтверждения адреса. Запрещаем прерывания и сразу генерируем STOP сигнал. Ждем получения байта и передачи STOP сигнала. Включаем обратно автогенерацию ACK.

Вот так хитро работает получение данных по шине I2C.

Управление LCD индикатором с 4-мя общими выводами

Для управления индикатором будет использоваться метод цифровой корреляции. Чтобы понять как работает этот метод, рассмотрим как нужно управлять ЖК индикаторами.

Во первых, ЖК индикатором необходимо управлять строго переменным напряжением. Чтобы сегмент стал чёрным (то есть видимым) необходимо приложить переменное напряжение больше рабочего (у нас это 2.5В), в случае если переменное напряжение будет меньше заданного, то сегмент будет невидим (или слабо видим). Это свойство ЖК и используется для управления. В простых индикаторах каждый сегмент выведен на свои выводы. Управлять одним сегментом легко, но нужно очень много выводов у микроконтроллера. Для уменьшения количества выводов, так же как и в светодиодных индикаторах, в выбранном ЖК индикаторе используются общие выводы, которые подключены ко всем сегментам. Их в данной модели четыре. В таком режиме управлять уже сложнее. Нужен ступенчатый аналоговый сигнал. На момент показа одного сегмента необходимо на другие подать переменное напряжение меньше 2.5В. Вот диаграмма управления выбранным индикатором в аналоговом режиме.




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

Для полностью цифрового управления индикатором данные ступеньки необходимо эмулировать ШИМ сигналом. Такая методика гораздо проще для МК без специализированной периферии. Вот её график.


Для управления используется ШИМ сигнал с 50% наполнением. Активный общий вывод задаётся ШИМ сигналом с вдвое меньшей частой, а не активный с вдвое большей. Для управления сегментами используется ШИМ сигнал меньшей частоты, при этом полярность ШИМ сигнала меняется для сегментов которые видимы — сигнал начинается с 0 и заканчивается 1, а для невидимых наоборот. Данная схема должна работать на довольно высокой частоте и с самым высоким приоритетом, иначе могут быть отклонения от схемы и будут появляться участки времени с постоянным напряжением, что будет приводить к деградации индикатора.

К сожалению, традиционным путём, расчётом и вычислением состояния выводов в прерывании, пойти не получится. У микроконтроллера не хватает скорости. Тем более, желательно работать на самой низкой частоте 2МГц для снижения энергопотребления.

Для управления показываемой информацией служит массив ind[]. Там находятся данные, которые необходимо отобразить на месте первой, второй, третьей цифры. А массив tchk[] содержит отображаемые точки. После изменения данных в этих массивах необходимо вызвать функцию calсlcd, которая рассчитает необходимое состояние выводов согласно схеме. Рассмотрим эту функцию построчно.

[code]	if (timeshowrezhim) {
//0- temp 1-termopara 2 - uv
if (rezhim==0) {
ind[0]=1;
ind[1]=0;
ind[2]=1;
}
[/code]

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

Рассмотрим предварительно логику вывода информации на экран (ориентируемся на предыдущую картинку). Вся информация на экран выводится фреймами. Каждый фрейм состоит из сегментов активных в общем выводе com1, потом в выводе com2, com3 и com4, а потом несколько пустых состояний (для управления яркостью свечения сегментов). Выводятся фреймы в прерывании таймера по кругу. В каждый момент срабатывания таймера выводится одна часть фрейма, которая в свою очередь состоит из четырёх возможных комбинаций общих битов и битов сегментов. В итоге на один фрейм нужен массив из 4*4 возможных состояний общих выводов и выводов сегментов. Так как сегменты цифр разбросаны по общим выводам не очень удобно, то для начала заполним отдельный массив, отражающий какие биты сегментов должны быть активированы в каждом общем выводе.

[code]	for (i=0;i<4;i++) com[i]=0;
//7-8
addnum(0, GPIO_PIN_1,GPIO_PIN_2);
//9-10
addnum(1, GPIO_PIN_3,GPIO_PIN_4);
//11-12
addnum(2, GPIO_PIN_5,GPIO_PIN_6);
[/code]

Массив com содержит 4 байта, каждый бит которого отвечает за светящийся сегмент в соответствующем общем выводе. Всего сегментов в одном общем выводе у нас не более 8, так как у нас светиться могут не все сегменты (мало выводов у выбранного микроконтроллера). Очистим массив с общим выводами, а потом добавим три цифры. Заметим, что на одну любую цифру приходится два каких то вывода и 4 общих вывода. Например на цифру 1 — это выводы 5 и 6, на цифру 2 — выводы 7,8 и так далее. Передаём в процедуру номера сегментов и включаем нужные биты в массиве com в зависимости от выводимого символа.


[code]void addnum(u8 index, u8 pin1,u8 pin2) {
u8 num;
num = ind[index];

if (tchk[index]) {
com[3] |= pin2;
}

switch (num) {
case 0:
//p1 - com0 com2 com3
//p2 - com0 com2 com1

com[0] |= pin1|pin2;
com[1] |= pin2;
com[2] |= pin1|pin2;
com[3] |= pin1;
break;
case 1:
//p2 - com1 com2
com[1] |= pin2;
com[2] |= pin2;
break;
[/code]

И так делаем для всех возможных символов. В итоге получаем в массиве com для каждого общего вывода какие сегменты должны светится. Осталось все это перевести в 0 и 1 на нужных выводах МК.

[code]	for (i=0;i<16;i++) {
lcdm[16+i].porta_odr = GPIOA->ODR & (~(ALL_PORTA));
lcdm[16+i].portd_odr = GPIOD->ODR & (~(ALL_PORTD));
lcdm[16+i].portc_odr = GPIOC->ODR & (~(ALL_PORTC));
}
[/code]

Так как выводы разбросаны по трём портам A, D, C, храним состояние ODR регистра каждого порта. При этом, используем режим двойного буфера, так как эти вычисления занимают много процессорного времени. Все расчёты сначала ведём в последних 16 значениях массива, а потом быстро копируем их в первые 16 элементов массива. Готовим состояния портов — все биты выводов индикатора равны 0.

[code]	for (i=0;i<4;i++) {
lcdm[16+i*4].porta_odr |= lcdpins[i].porta;
lcdm[16+i*4].portd_odr |= lcdpins[i].portd;
lcdm[16+i*4].portc_odr |= lcdpins[i].portc;

lcdm[16+i*4+1].porta_odr |= lcdpins[i].porta;
lcdm[16+i*4+1].portd_odr |= lcdpins[i].portd;
lcdm[16+i*4+1].portc_odr |= lcdpins[i].portc;

for (j=0;j<4;j++) {
if (i==j) continue;

lcdm[16+i*4].porta_odr |= lcdpins[j].porta;
lcdm[16+i*4].portd_odr |= lcdpins[j].portd;
lcdm[16+i*4].portc_odr |= lcdpins[j].portc;

lcdm[16+i*4+2].porta_odr |= lcdpins[j].porta;
lcdm[16+i*4+2].portd_odr |= lcdpins[j].portd;
lcdm[16+i*4+2].portc_odr |= lcdpins[j].portc;
}

for (j=4;j<11;j++) {
//1100 //all seg off
lcdm[16+i*4].porta_odr |= lcdpins[j].porta;
lcdm[16+i*4].portd_odr |= lcdpins[j].portd;
lcdm[16+i*4].portc_odr |= lcdpins[j].portc;

lcdm[16+i*4+1].porta_odr |= lcdpins[j].porta;
lcdm[16+i*4+1].portd_odr |= lcdpins[j].portd;
lcdm[16+i*4+1].portc_odr |= lcdpins[j].portc;
}
}
[/code]

Предварительно установим все выводы по com сигналам. Например, для com0 будет так «1100 1010 1010 1010».


А потом установим выводы сегментов согласно массиву com[].

[code]	for (i=0;i<4;i++) {
if (com[i]) {
u8 j,pin;
pin = 1;

for (j=0;j<7;j++) {
if (com[i] & pin) {
lcdm[16+i*4+0].porta_odr &= ~lcdpins[j+4].porta;
lcdm[16+i*4+1].porta_odr &= ~lcdpins[j+4].porta;
lcdm[16+i*4+2].porta_odr |= lcdpins[j+4].porta;
lcdm[16+i*4+3].porta_odr |= lcdpins[j+4].porta;

lcdm[16+i*4+0].portd_odr &= ~lcdpins[j+4].portd;
lcdm[16+i*4+1].portd_odr &= ~lcdpins[j+4].portd;
lcdm[16+i*4+2].portd_odr |= lcdpins[j+4].portd;
lcdm[16+i*4+3].portd_odr |= lcdpins[j+4].portd;

lcdm[16+i*4+0].portc_odr &= ~lcdpins[j+4].portc;
lcdm[16+i*4+1].portc_odr &= ~lcdpins[j+4].portc;
lcdm[16+i*4+2].portc_odr |= lcdpins[j+4].portc;
lcdm[16+i*4+3].portc_odr |= lcdpins[j+4].portc;
}
pin = pin << 1;
}
}
}
[/code]

В переменной pin сдвигаем 1 бит влево, пока не пройдём по всем битам. В самом конце скопируем данные в первые ячейки массива.

[code]	blockcopylcd=1;
for (i=0;i<16;i++) {
lcdm[i].porta_odr = lcdm[16+i].porta_odr;
lcdm[i].portd_odr = lcdm[16+i].portd_odr;
lcdm[i].portc_odr = lcdm[16+i].portc_odr;
}
blockcopylcd=0;
[/code]

При этом, в момент копирования не будем выводим ничего на индикатор. При установленной переменной blockcopylcd на все выводы индикатора выводится 0.

Все данные готовы для вывода, осталось в прерывании вывести всю эту последовательность.

[code]	lcdframe++;
if (lcdframe==75) lcdframe = 0;
ind = lcdframe;

//blockcopylcd=1;

if (readytosleep==0) {
if ((ind < 16) && (blockcopylcd==0)) {

GPIOA->ODR = lcdm[ind].porta_odr;
GPIOD->ODR = lcdm[ind].portd_odr;
GPIOC->ODR = lcdm[ind].portc_odr;
}
else {
GPIOA->ODR &= ~(ALL_PORTA);
GPIOD->ODR &= ~(ALL_PORTD);
GPIOC->ODR &= ~(ALL_PORTC);
}
}
[/code]

Таймер срабатывает 2000 раз в секунду. 16 мс выводим все общие выводы, которые программировали в массиве, потом 59мс не выводим ничего. В итоге 75мс на один проход. То есть обновление индикатора происходит с частотой около 100 Гц. Это более чем достаточно.

Обработка кнопок

Обработку кнопок возьмём из проекта кухонный таймер.

Обработка датчиков

Ntc75 — датчик температуры

Для работы с датчиком используются две процедуры: nctinit() - используется один раз и настраивает режим работы датчика, nctdata() - вызывается регулярно, считывает температуру и записывает ее в глобальную переменную bmeT.

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

[code]#define NCTaddr  (0x48)
int nctinit(void) {
buff[0] = (uint8_t) 0b1;
buff[1] = (uint8_t) 0b100000;
if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 2,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};
}
[/code]

Адрес датчика задаётся с помощью команды #define — это общая практика использования констант в Си. Регистр настройки имеет адрес 0x01. Датчик может работать в непрерывном режиме или однокаратном. Для меньшего потребления тока и меньшего нагрева датчика лучше использовать однократный режим, это повысит точность измерений. За этот режим отвечает 7-ой бит. Первый бит отвечает за рабочий режим (когда передаём 0, то включаем датчик). Записываем в регистр настроек 0b100000. На этом инициализация датчика завершена.

Обработка показаний датчика выполняется также несложно.

[code]int nctdata(void)
{
long tmcpT;

buff[0] = (uint8_t) 0x4;
buff[1] = (uint8_t) 0;
if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 2,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};
Delay(90);
[/code]

Активируем разовое измерения. В регистр 0x04 записываем любые данные, например 0. Одно измерение занимает 60мс, на всякий случай подождём 90мс. После этого прочитаем данные.

[code]	buff[0] = (uint8_t) 0x00;
if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 1,0) )
{
I2C_GenerateSTOP(ENABLE);
return 0;
};

if (! I2C_readnbyte((uint8_t)NCTaddr, buff, 2,0) )
{
return 0;
};
[/code]

Данные находятся по регистру 0x00. Для чтения используем две команды. Сначала в режиме записи передаём номер регистра, который будем читать. А потом в режиме чтения получаем нужное количество байт в буфер. Данные о температуре состоят из двух байт. Старший байт передаётся первым.

[code]	tmcpT = (u32) (buff[0]<<8) | buff[1]; 
[/code]

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

[code]	bmeT = (tmcpT>>4) * 625 / 100; 
[/code]

В переменной получаем температуру в формате целого числа градусов умноженного на 100. То есть температура 25.65 градусов представлена числом 2565. На данном микроконтроллере в вычислениях лучше использовать целочисленную математику для меньшего расхода памяти и более быстрого выполнения вычислений.

Veml6075 — датчик УФ излучения

Самый интересный датчик. Процедура инициализации аналогична датчику температуры.

[code]	buff[0] = (uint8_t) 0b0;//config reg
buff[1] = (uint8_t) 0b01000000;//low byte 400ms
buff[2] = (uint8_t) 0b0;//high byte
if( ! I2C_writenbyte((uint8_t)UVaddr, buff, 3,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};
[/code]

Передаем адрес регистра настройки — 0x0, и значение настроек в виде двух байт.


Включаем датчик (бит 0), используем нормальный режим работы, нормальные установки динамического режима, задаем режим интеграции 800ms — в этом режиме датчик менее подвержен боковой засветке, которую трудно убрать без тефлонового фильтра.

Для более удобного получения данных с датчика напишем функцию readUVword(u8 addr). Функция читает сразу по два байта по переданному адресу и собирает из них беззнаковое целое размером 16 бит.

[code]	u16 readUVword(u8 addr) {
buff[0] = addr;
if( ! I2C_writenbyte((uint8_t)UVaddr, buff, 1,1) )
{
I2C_GenerateSTOP(ENABLE);
return 0;
};

if (! I2C_readnbyte((uint8_t)UVaddr, buff, 2,1) )
{
return 0;
};
return (u16) ((buff[1]<<8) | buff[0]);
}
[/code]

Отметим, что в данном датчике первым передаётся младший байт! Разберем процедуру uvdata(), в которой производятся все вычисления.

[code]int uvdata(void)
{
float uvia,uvib;
int i;
long tmcpT;

uva = readUVword(0x07);
uvb = readUVword(0x09);
uvcomp1 = readUVword(0x0A);
uvcomp2 = readUVword(0x0B);
[/code]

Для работы с данным датчиком необходимо использовать вычисления с плавающей точкой. Точности float будет достаточно. Получаем с датчика основные данные — uva (интенсивность уф излучения А типа), uvb (интенсивность уф излучения B типа), uvcomp1 (коэффициент компенсации 1), uvcomp2 (коэффициент компенсации 2). Используя эти коэффициенты, а также шесть констант можно вычислить необходимый индекс УФ излучения. К датчику идёт документ «Designing the VEML6075 into an Application», в котором подробно рассмотрена методика рассчета констант и проведения вычислений.

[code]	uvia = ((uva-UKa*uvcomp1-UKb*uvcomp2)*0.001461);//for 100ms
uvib = ((uvb-Ukc*uvcomp1-Ukd*uvcomp2)*0.002591);

if (uvia<0 || uvib<0) uindex=0;
else uindex = (uvia+uvib)*100/2/6;//800ms!!
[/code]

VEML6075 — как правильно вычислить индекс УФ излучения

Чтобы корректно рассчитать индекс УФ излучения по показаниям датчика необходимо использовать несколько констант. Как их вычислить не имея специального оборудования? Разберёмся в этом вопросе. Вот полная формула вычисления индекса:


Константы k1 и k2 без оборудования вычислить не получится. Но они необходимы для максимально точного расчёта, так что их можно принять за единицу. Константы чувствительности датчика можно взять из таблицы:


Нам нужны константы из первой строки, так как датчик у нас не закрыт тефлоном. UVAresp и UVBresp. А вот константы a, b, c, d необходимо вычислить. Для этого проще всего использовать отладчик. Необходимо получить данные uva, uvb, uvcomp1, uvcomp2, и в Excel рассчитать константы. Показывать их на дисплее неудобно — слишком мало разрядов. Можно использовать отладчик. Поставить точку останова после получения данных и посмотреть вычисленные значения. Также можно использовать возможность отключения ST-link от STM8 в процессе отладки и обратного подключения. При этом сеанс отладки не прерывается! Ещё один вариант — использовать STM8 STUDIO. Это специальная программа позволяет получить переменные в любой момент времени. Придётся немного модифицировать программу, например по кнопке записывать данные в отдельные переменные и в STUDIO или отладчике их уже прочитать.

Солнечный свет в своём спектре имеет УФ излучение, ИК излучение и видимое излучение. Чтобы рассчитать интенсивность УФ излучения, необходимо вычесть лишние данные про ИК излучение и видимое излучение. Для этого служат данные uvcomp1 (отвечает за видимое излучение) и uvcomp2 (отвечает за ИК излучение). Чтобы рассчитать константы в используемой формуле необходимы два искусственных источника света:

  • обычная лампа накаливания — она излучает в ИК спектре и видимом излучении;

  • светодиодная лампа — она излучает только в видимом излучении.

Начинаем со светодиодной лампы. Так как УФ излучение отсутствует, то соответствующий индекс будет равен нулю.


У светодиодной лампы нет ИК излучения, поэтому UVcomp2 будет равен 0. И в этом случае можно найти коэффициенты

Получив эти коэффициенты не составляет труда найти коэффициенты b и d, при использовании обычной лампы, когда UVcomp2 не равно нулю.

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

Правильно откалиброванный датчик очень легко проверить. В солнечный день, в полдень, когда на небе нет облаков необходимо измерить интенсивность, направив датчик точно на солнце. Показания должны совпадать с передаваемыми данными на сайтах погоды.

При загаре, для точного измерения излучения, датчик необходимо держать параллельно поверхности тела. Для получения максимального текущего индекса датчик нужно направлять на солнце.

Mcp3231 — измерение температуры термопары

Данный датчик имеет один регистр, который определяет поведение датчика. При инициализации используем следующий код:

[code]		buff[0] = (uint8_t) 0b00011100;
if( ! I2C_writenbyte((uint8_t)MCPaddr, buff, 2,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};


return 1;
[/code]

Передавать можно один байт или два. Учитывается только первый — байт конфигурации. Первые два бита задают использование режима усиления до 8 раз. Для измерения термопары нам такой точности не нужно, поэтому не будем использовать усиление. Следующие два бита задают разрядность, используем максимальную 18бит. И следующий бит включает непрерывный режим измерений. Скорость в таком режиме составляет 270 мс на одно измерение.

Данный ADC на своем борту имеет источник референсного напряжения и имеет очень высокую стабильность результата. Можно сказать он отдаёт результат до последнего бита. При использовании 18 битного разрешения можно измерить напряжение с точностью до 15мкВ. Этого более чем достаточно для термопары.

Чтобы прочитать данные с датчика необходимо просто получить от него 3 байта.

[code]	long tmcpT;

tmcpT = ((long)buff[0]<<24) + ((long)buff[1]<<16) +((long)buff[2]<<8);
tmcpT/=256;

e = (double) tmcpT/64;//512; //* 15.625/8000;
[/code]

18 бит данных содержит знак результата. Датчик дублирует этот бит до 24 бита, чтобы получить 24битное знаковое число. Однако в Си нет формата знакового числа 24 бита, а есть только 32 битное — long. Поэтому, все число необходимо сместить влево на 8бит, а потом поделить на 256.

Для вычисления температуры термопары необходимо провести вычисления с плавающей точкой. Поэтому переведём полученный результат в милливольты. Переменная «e» содержит нужное значение. В этой статье есть вся информация как посчитать температуру нужной термопары (у нас используется К типа) с помощью полиномов. ГОСТ Р. 8.585-2001 даёт нужные коэффициенты для разных диапазонов температур (стр. 71). В этом приборе будем использовать три диапазона температур: от -200 до 0 градусов, от 0 до 500 градусов и от 500 до 1372 градусов.

[code]const double cK[9]={
2.508355*10,
7.860106/100,
-2.503131/10,
8.315270/100,
-1.228034/100,
9.804036/10000,
-4.413030/100000,
1.057734/1000/1000,
-1.052755/10000/10000};
и т.д.
[/code]

Напишем свою функцию возведения в степень, типовая занимаем много места.

[code]double mypow(double a,int b) {
double x=a;
if (b==1) return a;
while (b--)
{
x *= a;
}

return x;
}
[/code]

И сами вычисления

[code]	t=0;
if (e>20)
{
t=t+cKf[0];
for (i=0;i<6;i++)
{
t=t + (cKf[i+1])*mypow(e,(i+1));
}
} else {
for (i=0;i<((e>0)? 9 : 8);i++)
{
t=t + ((e>0)?cK[i]:cKm[i])*mypow(e,i+1);
}
}
t*=100;
tmcpT = (long) t + bmeT;
mcpT = tmcpT;
[/code]

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

Спящий режим

В данном приборе используется много датчиков и ЖК экран. Все это нужно правильно «уложить» спать. Для этого используется функция sleep().

[code]u8 sleep(void) {
readytosleep = 1;

//off disp! need pullup PA1 !
GPIO_Init(GPIOD,ALL_PORTD,GPIO_MODE_IN_PU_NO_IT);
GPIO_Init(GPIOA,ALL_PORTA,GPIO_MODE_IN_PU_NO_IT);
GPIO_Init(GPIOC,ALL_PORTC,GPIO_MODE_IN_PU_NO_IT);
[/code]

Сначала выключим все выводы управления дисплеем. Переменная readytosleep нужна для того, чтобы выводы не были случайно включены в обработчике прерывания.

[code]	//nct datchik sleep
buff[0] = (uint8_t) 0b1;//config reg
buff[1] = (uint8_t) 0b1;//shutdown
if( ! I2C_writenbyte((uint8_t)NCTaddr, buff, 2,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};
[/code]

Усыпляем датчик температуры nct.

[code]	//uv datchik
buff[0] = (uint8_t) 0b0;//config reg
buff[1] = (uint8_t) 0b01000001;//low byte
buff[2] = (uint8_t) 0b0;//high byte - always 0
if( ! I2C_writenbyte((uint8_t)UVaddr, buff, 3,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};
[/code]

Следом УФ датчик. И потом внешний ADC.

[code]	//mcp adc
buff[0] = (uint8_t) 0b10001100;//one shot and sleep
if( ! I2C_writenbyte((uint8_t)MCPaddr, buff, 1,0) )
{
I2C->CR2 |= I2C_CR2_STOP;//I2C_GenerateSTOP(ENABLE);
return 0;
};

Delay(10);//need for end i2c!
[/code]

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

После выхода из сна, включаем все обратно.

[code]	halt();
Delay(100);

kn[0] = 0;

nctinit();
mcpinit();
uvinit();

GPIO_Init(GPIOD,ALL_PORTD,GPIO_MODE_OUT_PP_LOW_SLOW);
GPIO_Init(GPIOA,ALL_PORTA,GPIO_MODE_OUT_PP_LOW_SLOW);
GPIO_Init(GPIOC,ALL_PORTC,GPIO_MODE_OUT_PP_LOW_SLOW);

//on LCD
readytosleep = 0;
[/code]

Как изготовить наш прибор

Делаем плату и все запаиваем

  1. Подготовить или приобрести необходимые инструменты: все для пайки, ST-LINK (будет нужен для программирования и отладки МК)

  2. Внимательно прочитать статьи из раздела Обязательная теория.

  3. Скачать необходимые файлы по данному прибору с github.

  4. Изготовить плату для прибора самостоятельно (это совсем несложно, в нашей инструкции все подробно описано).

  5. Приобрести все необходимые комплектующие.

  6. Запаять все компоненты на плату, смотри наше видео.

  7. Плата готова!

Для программирования вам понадобятся провода мама-мама. Вы можете их сделать сами из данного набора. Для программирования, можно сразу подключить питание с ST-Link, вместо аккумулятора и SWIM вывод.

Размещение УФ датчика в корпусе

На поверхность датчика нанесено специальное покрытие, фильтрующее часть спектра. Поэтому в идеале датчик необходимо прикрывать матовым фильтром, который может пропускать УФ лучи и не даёт боковой засветки. На роль такого фильтра отлично подходит оптический тефлон. Однако купить его непросто. Если у вас такая возможность, то лучше это сделать. Согласно документации датчик можно использовать и без этого фильтра, но необходимо защитить его от боковой засветки. Так мы и будем делать. Вся медь вокруг датчика должна быть закрашена чёрным несмываемым маркером.

Ширина отверстия надо датчиком должна быть строго рассчитана по формуле:


Можно использовать как круглое так и квадратное отверстие. Круглое сделать проще — просверлить. Также необходимо исключить проникновение света внутрь корпуса.

Сам датчик можно окружить ПВХ пластиком, стенки окрасить в черный цвет. В итоге у вас должно получиться примерно так:




Запаивание датчика УФ обычным паяльником

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


Начинать запаивание всей платы в этом случае надо с датчика. Тогда можно будет легко проверить, что все припаяно качественно. Для начала необходимо подготовить чистое рабочее место. Лучше взять чистый лист А4, аккуратно достать датчик и положить на лист ножками вверх. Намазать флюсом ЛТИ-120 и аккуратно залудить контактные площадки датчика — оставив небольшие бугорки олова. Делать это нужно быстро, чтобы не перегреть датчик. Остатки флюса необходимо смыть спиртом. Много олова оставлять не нужно, его должно быть совсем чуть-чуть. Точно также залуживаем посадочное место датчика на плате. Олово на посадочном месте совсем не оставляем, если оно лишнее — можно его разогнать по дорожкам в стороны.

Добавляем флюс на плату (лучше TR-RMA), кладем датчик на посадочное место, внимательно смотрим на лицевую сторону, чтобы не перепутать местами выводы (отпаять его будет сложно). У датчика сверху отлично видно с одной стороны 3 контактные площадки, а с другой 2-е. Вот по ним и ориентируемся. Проще всего совмещать вывод GND.




После того как вы спозиционировали датчик, не трогая сам датчик, прогреваем паяльником одну дорожку! Олово на датчике тоже в итоге расплавится и он припаяется. После припаивания одного контакта, можно проверить что все верно (не трогая датчик, можно оторвать контактную площадку, зрительно проверить как лежит датчик). В этот момент еще можно все вернуть назад. Если все хорошо, то точно также прогреваем остальные дорожки и все — датчик припаян. Проверить качество пайки очень легко. Достаточно взять обычный мультиметр в режиме проверки диода. Плюс поместить на GND и проверить все остальные выводы — должно быть 500-600 единиц (у датчика есть защитные диоды по всем входам). Далее нужно проверить что нет замыканий между близлежащими контактами.

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

Отладка программы

Если вы решили проводить отладку программы, то необходимо на момент отладки отключать ЖК дисплей, иначе в момент остановки МК на выводах может быть постоянное напряжение. Ориентироваться в этом случае можно по переменным в отладчике.

Если у вас есть желание освоить программирование микроконтроллеров, то рекомендуем написать программу «с нуля» самостоятельно.

Приобретённые навыки

Пайка: пайка паяльником LGA датчика микроразмера.

Схемотехника: LDO, I2C, типовая схема подключения STM8, подключение пищалки, работа с кнопками.

Программирование: I2C, изменение температуры термопары, интегральные датчики температуры, внешний ADC, датчик УФ излучения, расчёт индекса UVA UVB, сложная обработка кнопок, миллисекундный таймер, динамическая индикация ЖК индикатора, спящий режим.

Самостоятельная работа

Вы можете расширить функционал прибора в части измерения UV индекса:

  • реализовать измерение накопленного UV излучения и предупреждение о превышении норматива за день

  • рассчитать время пребывания на солнце с учётом типа кожи и крема от загара

  • сделать функцию I2C сканера — показывает с какими адресами есть датчики на шине I2C

  • сделать символьное отображение режимов

  • реализовать измерение и отображение отрицательной температуры

  • реализовать прочие расширенные функции данного прибора.

]]>
(Super User) Приборы Sat, 09 Feb 2019 15:19:15 +0000