1. Arduino Nano.
2. Дисплей TM1637.
3. Модуль заряда аккумуляторов TP4056.
4. DC-DC повышающий преобразователь MT3608.
5. Модуль датчика холла A3144.
6. Тактовые кнопки (2 шт.)
7. Выключатель клавишный.
8. Магнит неодимовый цилиндрической формы.
В качестве первоисточника использовался давний проект от AlexGyver: https://alexgyver.ru/speedometer/
Принципиальная схема устройства приведена на рисунке ниже.
Индикация скорости производится на 7-сегментный дисплей TM1637 размером 0,54''. Сегменты большие – меня это совершенно устроило.
Модуль датчика Холла размещен на вилке переднего колеса изнутри при помощи пластиковых стяжек.
Напротив датчика на спицах колеса установлен держатель с неодимовым магнитом. Размещение магнита таково, чтобы в действие магнитного поля попадал датчик Холла.
Различные виды велоспидометра, смоделированного при помощи 3D, показана на рисунках.
Ниже реальные фотографии устройства.
Кратко по функционалу устройства.
Верхняя кнопка – кнопка режима «скорость / расстояние».
При включении велоспидометр находится в «состоянии измерения скорости».
Формат показа: километры и сотни метров.
При кратковременном нажатии на верхнюю кнопку устройство переходит в «состояние измерения расстояния», которое проехал велосипед:
- если расстояние менее 5 км, то оно показывается в метрах;
- если расстояние более 5 км, но менее 10 км, то формат показа в километрах, сотнях метров и десятках метров;
- если расстояние более 10 км – формат показа в километрах и сотнях метров.
Длительное (более 1,5 с) нажатие верхней кнопки в режиме «состояние измерения расстояния» обнуляет пройденное расстояние.
Нижняя кнопка - кнопка изменения яркости дисплея.
Однократное нажатие на нижнюю кнопку увеличивает яркость дисплея на один шаг. Величина яркости дисплея TM1637 в соответствии с их спецификацией – 0…7. При достижении максимальной яркости (7) и еще одном нажатии яркость сбрасывается на минимальную (0). Данная опция по регулировке яркости введена для экономии потребления энергии от имеющегося в составе устройства аккумулятора. При поездках в вечернее время максимальная яркость дисплея избыточна и ее можно снизить. А вот при ярком солнце максимальная яркость позволяет считывать значение (иначе говоря цифры) на дисплее без труда.
Корпусные детали устройства (корпус и крышка) распечатаны на 3D-принтере из PETG-пластика. Магнит установлен в держатель (также распечатан из PETG-пластика), состоящий из двух деталей, скрепленных между собой. Держатель закреплен на двух соседних спицах переднего колеса. Для присоединения датчика Холла с корпусом устройства был использован четырехжильный телефонный кабель (одна из жил не используется).
Ниже скетч:
Code: Select all
#include <EEPROM.h> //библиотека для работы со внутренней памятью ардуино
//-----------дисплей-----------
#define CLK 7
#define DIO 6
#include "GyverTM1637.h"
GyverTM1637 disp(CLK, DIO);
//-----------дисплей-----------
unsigned long lastturn, time_press; // переменные хранения времени
float SPEED; // переменная хранения скорости в виде десятичной дроби
float DIST; // переменная хранения расстояния в виде десятичной дроби
float w_length=2.05; // длина окружности колеса в метрах
boolean flag; // флажок для хранения (что выводим на дисплее, скорость или расстояние)
// 0 - показ скорости
// 1 - показ дистации (пройденного расстояния)
boolean state, button; // флажки для обработчика нажатия кнопки
int lumin; // яркость отображения дисплея
uint16_t cel;
uint16_t edin, des, sot, tys, dest;
void setup() {
Serial.begin(9600); // открыть порт
lumin = 5; // начальная яркость дисплея
disp.brightness(lumin); // яркость дисплея
attachInterrupt(0,sens,RISING); // подключить прерывание на 2 пин при повышении сигнала
pinMode(3, OUTPUT); // 3 пин как выход
digitalWrite(3, HIGH); // подать 5 вольт на 3 пин
pinMode(8, INPUT); // подключена кнопка к D8 для управления режимами: скорость - дистанция
pinMode(4, INPUT); // подключена кнопка к D4 для изменения яркости
//--------------
byte val1 = EEPROM.read(0); //вспоминаем пройденное расстояние при запуске системы
byte val2 = EEPROM.read(1);
DIST = (float)((val1<<8)+val2);
}
void sens() {
if (millis()-lastturn > 80) { //защита от случайных измерений (основано на том, что велосипед не будет ехать быстрее 120 кмч)
SPEED=w_length/((float)(millis()-lastturn)/1000)*3.6; //расчет скорости, км/ч
lastturn=millis(); //запомнить время последнего оборота
DIST=DIST+w_length; //прибавляем длину колеса к дистанции при каждом обороте
}
}
void loop()
{
disp.brightness(lumin);
int cel_sp=floor(SPEED);
int sot_sp=(((float)cel_sp/1000)-floor((float)cel_sp/1000))*10;
int des_sp=(((float)cel_sp/100)-floor((float)cel_sp/100))*10;
int ed_sp=(((float)cel_sp/10)-floor((float)cel_sp/10))*10;
int dr_sp=(float)(SPEED-floor(SPEED))*10;
if (flag==0) {
//disp.set(LED_0F[sot_sp],3); // вывод сотен скорости (для велосипеда не нужно =)
disp.display(0, des_sp); // вывод десятков скорости
disp.display(1, ed_sp); // вывод единиц скорости
disp.displayByte(2, 0b00001000); // вывод разделителя скорости
disp.display(3, dr_sp); // вывод после разделителя
}
cel = (uint16_t)floor(DIST);
edin = cel % 10;
des = cel / 10 % 10;
sot = cel / 100 % 10;
tys = cel / 1000 % 10;
dest = cel / 10000 % 10;
if (flag==1) {
if (cel < 5000) // если расстояние меньше 5000 м (5 км)
{ disp.display(0, tys); // вывод тысяч метров расстояния
disp.display(1, sot); // вывод сотен метров расстояния
disp.display(2, des); // вывод десятков метров расстояния
disp.display(3, edin); // вывод единиц метров расстояния
}
else if (cel >=5000 && cel < 10000) // если 5 км <= расстояние < 10 км
{ disp.display(0, tys); // вывод тысяч метров расстояния
disp.displayByte(1, 0b1000000); // вывод разделителя расстояния
disp.display(2, sot); // вывод сотен метров расстояния
disp.display(3, des); // вывод десятков метров расстояния
}
else // если расстояние > 10 км
{ disp.display(0, dest); // вывод десятков тысяч метров расстояния
disp.display(1, tys); // вывод тысяч метров расстояния
disp.displayByte(2, 0b1000000); // вывод разделителя расстояния
disp.display(3, sot); // вывод сотен метров расстояния
}
}
if ((millis()-lastturn) > 2200){ //если сигнала нет больше 2,2 секунды
SPEED=0; //считаем что SPEED 0
byte hi = highByte(cel);
byte low = lowByte(cel);
EEPROM.update(0, hi); //записываем DIST во внутреннюю память по двум ячейкам
EEPROM.update(1, low);
}
if (digitalRead(8)==1 && state==0) //выбор режимов кнопкой. Если кнопка нажата
{ state=1;
button=1;
time_press=millis(); //запомнить время нажатия
while (millis()-time_press<1500)
{ //выполнять, пока кнопка нажата не менее 1500 миллисекунд
if (digitalRead(8)==0) { //если кнопка отпущена, выйти из цикла и принять button=0
button=0;
break;
}
}
// если кнопка не отпущена 1500 мс, то button будет равен 1
switch (button) {
case 0:
flag=!flag; state=0; disp.clear(); //просто переключение режима отображения скорости и расстояния
break;
case 1:
if (flag==1)
{ DIST=0; state=0; disp.clear();}; //если мы в режиме отображения расстояния и кнопка удерживается, то обнулить расстояние
break;
}
}
if (digitalRead(4)==1 && state==0)
{ state=1;
lumin++;
if (lumin > 7) lumin = 0;
time_press=millis();
while (millis()-time_press < 250) { };
state=0;
}
}
Не претендую на оптимальность работы скетча. Работа скетча и устройства в целом проверена в "полевых условиях".
Использованные библиотеки:
EEPROM.h
GyverTM1637.h
Библиотека EEPROM.h используется для записи обновления пройденного расстояния в энергонезависимой памяти каждый раз, когда скорость перемещения велосипеда (скорость вращения переднего колеса) равна нулю более 2 с. Это же ранее записанное расстояние достается из памяти после включения устройства.
У меня велосипед имеет колеса диаметром 26”. Все расчеты по нахождению скорости и расстояния завязаны через следующие формулы:
SPEED=w_length/((float)(millis()-lastturn)/1000)*3.6,
где
SPEED – текущая скорость велосипеда, км/ч;
w_length – длина окружности колеса, м (для 26” составляет примерно 2,05 м);
millis()-lastturn – промежуток времени между ближайшими активировании прерываний, мс.
DIST=DIST+w_length,
где
DIST – текущая пройденная дистанция (расстояние), м;
w_length – длина окружности колеса, м (для 26” составляет 2,05 м).
Датчик Холла подключен к пину D2 и изменение состояние датчика контролируется по прерыванию INT0 по изменению с LOW на HIGH (режим работы RISING – повышение уровня). При срабатывании прерывания производится вычисление значений SPEED и DIST по вышеперечисленным формулам, записанных в функции обработки прерывания sens.
В зависимости от выбранного режима работы производится вывод соответствующего значения на дисплей.
Я не заморачивался с креплением спидометра на руль велосипеда. Вырезал основание из OSB-плиты, установил на него спидометр и примотал это всё к рулю изолентой – достаточно надежно.
Еще раз ссылаюсь на Александр “AlexGyver” (https://alexgyver.ru/), как на первоисточник проекта, на основании которого я создал данный проект.