Счетчик скорости вращения колеса белкой

в 16:45, , рубрики: arduino, diy или сделай сам, программирование микроконтроллеров, метки: , ,

Поселилась у меня дома обычная аналоговая белка.
Жила в клетке, жила себе жила, крутила свое колесо. С разной скоростью. С утра, обычно, энергичнее, к вечеру, подустав, помедленнее. И все это время не давал мне покоя один вопрос. А с какой скоростью бежит белка в колесе?
Я долго думал и однажды…
Решил я сделать счетчик скорости вращения колеса.

На фото — прототип. Поэтому не все еще сделано аккуратно.

image

Сорцы, видео и итоговые результаты измерений под катом.

Компоненты.

  1. Arduino Uno.
  2. Экран LKM-1638.
  3. Датчик Холла (подойдет любой).
  4. Плата расширения.
  5. Бонусом — Датчик температуры и влажности DHT11.

В принципе, все, что мне было нужно — микроконтроллер на плате, экран LKM-1638 и простой датчик Холла, но, выбирая все это, взял еще датчик влажности и температуры DHT11, чтобы уж сразу прокачать беличью клетку до «умной»! Заказывал на dx.com.

Все делал сам, своими руками.

Выбрал готовую плату Arduino Uno, чтоб не тратить время на разводку, протравку, программаторы и т.д., а сразу приступать к выполнению задуманного. Экран взял LKM-1638, потому что он яркий, чотке, с большими и понятными цифрами.

Попутно пригодились кнопки на дисплее для вывода статистической информации.

Нажатие соответствующей кнопки выводит:

  1. Общий пробег в километрах.
  2. Максимальная скорость круга.
  3. Среднесуточных пробег в километрах.
  4. Среднюю скорость за круг.
  5. UpTime в сутках
  6. Общее количество оборотов круга
  7. Текущую влажность.
  8. Текущую температуру.

Получилось почти как в Формуле-1. Если бы белок было несколько, можно было бы устроить свой блэкджек тотализатор.

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

Собрал все вместе, соединил провода и приступил к написанию кода.
Библиотеку для экрана взял готовую здесь.
Для датчика температуры и влажности примеры и библиотека здесь.
Для датчика Холла все брал из головы.

Логика работы счетчика скорости очень простая.
Датчик Холла прикреплен к клетке в точке, наиболее приближенной к колесу. На колесе стоит маленький, но очень сильный неодимовый магнит, который я вытащил из головки старого DVD -привода.
Когда колесо вращается, с каждым целым его оборотом магнит на короткое время изменяет магнитное поле в радиусе измерения датчика Холла, меняя в последнем состояние с 1 на 0. При обратном расположении датчика, значение бы менялось с 0 на 1 (что, вероятно, более правильно), но тогда датчик хуже бы держался на клетке.
Разница во времени между двумя появлениями магнита в зоне действия датчика признается за целый круг. Дальше, зная внутреннюю длину окружности колеса, можно легко рассчитать скорость круга и вывести ее на дисплей, что я и сделал.
Здесь необходимо отметить, что у такого способа измерения есть нюансы:

  1. Считается только полный оборот колеса. Если белка прокрутила, к примеру, 0.9 оборота и выскочила из него, то этот оборот не учитывается вообще. Или если она провернула колесо на 1.9, 2.9, 3.9 ...n.9 оборотов и так повторила много раз в процессе тестирования устройства, то набегает большая погрешность в вычислении общего пробега, и скорость этих неполных кругов не учитывается в расчете средней скорости.
  2. Когда белка выпрыгивает из еще вращающегося колеса в сторону, противоположную вращению, то противоходом, если магнит в это время только что прошел мимо датчика, магнит проходит мимо датчика еще раз в другую сторону, ошибочно фиксируя целый полный оборот колеса.

Погрешности измерений в этих случаях можно минимизировать, увеличив количество датчиков, к примеру, до 4 и расположив
их по разные стороны круга (через ?/2 радиана или 90 градусов). Тогда можно учитывать не только каждую четверть оборота, что определенно повысит точность измерений, но и знать направление вращения колеса (Т. е. куда белка бежит — домой или из дома к соседке).

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

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

Весь код

/*
скетч для подсчета оборотов колеса
и вывода значений на 8-ми цифровой экран
плюс вывода текущей температуры и влажности
релиз
*/

/************** Для цифрового датчика DHT11 ****************/
#define dht_dpin A2

byte bGlobalErr;

byte dht_dat[5];
/************** Конец для цифрового датчика DHT11 **********/

/******* переменные для цифрового мдуля*****************/

//библиотека для 8-сегментного цифрового модуля
#include «TM1638.h»
// определеям пины в модуле data pin 5, clock pin 4 and strobe pin 6
TM1638 module(5, 4, 6);
#define Red TM1638_COLOR_RED
#define Green TM1638_COLOR_GREEN
#define Red_green TM1638_COLOR_GREEN + TM1638_COLOR_RED

/************конец переменных для модуля***************/

/********* переменные для датчика Холла *******************/

//длина окружности колеса в метрах (необходимо уточнить)
static float One_round = 0.91;

//определяем порт для подключения датчика Холла
#define Hall_port 2
#define Max_led_light_speed 8
//интервал 10 секунд, если больше колесо признано только остановившимся,
//если меньше, то считается, что оно крутицца
#define Wait_interval 10000
//интервал после которого выключается экран
#define Do_sleeping_interval 60000
//пауза в опросах датчика в обычном режиме
#define Delay_in_work 5
//пауза в опросах датчика в спящем режиме
#define Delay_in_sleep 50

//счетчик количества оборотов колеса
unsigned long Round_counter = 0;
//здесь храним предыдущее время для подсчета скорости
unsigned long Prev_time = 0;
//здесь храним предыдущее время для постоянного мониторинга
unsigned long Prev_time_mon = 0;
//здесь данные для мониторинга пауз для обнуления и выключения экрана и т.д
unsigned long time_span_mon = 0;
//предыдущее значение датчика
byte PrevValue = 1;
//текущая скорость вращения колеса cм/c
double Curr_speed = 0;
//максимальная скорость вращения колеса м/c
double Max_speed = 0;
//общий пробег в метрах для подсчета по нажатиям кнопок
double Total_run = 0;
//скорости всех кругов сюда кладем для подсчета средней
double All_speeds = 0;
//сколько прошло дней
double Day_passed = 0;
//переменная для хранения состояния системы
//0- система в режиме экономии энергии и ожидания — колесо давно не крутицца — экран выключен
//1- колесо только что перестало крутицца
//2- колесо крутицца
byte Sys_status = 0;

/***************конец переменныъх для Холла******************/

void setup() {

Serial.begin(9600);
InitDHT();//инициализиреум датчик DHT11
Serial.println(«Run!»);

//включаем дисплей, выводя на него 0
module.setDisplayToString(«0.», 0, 7);
delay(1000);

}

void loop() {

if(Sys_status == 0)
{
//если спящий режим то реже опрашиваем датчик
delay(Delay_in_sleep);
}
else
{
//если рабочий режим то чаще опрашиваем датчик
delay(Delay_in_work);
}
/************** начало кнопок ****************************************/

//слушаем кнопки
byte keys = module.getButtons();

//если нажата кнопка и время больше таймаута то включаем диспей
if(keys!=0 && Sys_status ==0)
{
module.setupDisplay(true, 7);
}

switch (keys){
//если нажата крайняя левая
case 0b00000001:

//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 0);
module.clearDisplay();
//выводим на дисплей общий пробег в километрах
Total_run = Round_counter * (double)One_round / 1000.00;

DoubleToDisp(Total_run);
//module.setDisplayToDecNumber(Total_run, 1, false);
delay(1000);
//если статус нулевой то выключаем экран
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}

break;

//если нажата 2-я слева
case 0b00000010:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 1);
module.clearDisplay();
//выводим на дисплей максимальную скорость
DoubleToDisp(Max_speed);
//module.setDisplayToDecNumber(Max_speed, 1, false);
delay(1000);
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}
break;

//если нажата 3-я слева
case 0b00000100:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 2);
module.clearDisplay();
//сколько прошло дней щитаем с момента старта
Day_passed = millis()/86400000.00;
//если сутки целые прошли
if(Day_passed > 1)
{

//считаем общий пробег в километрах
Total_run = Round_counter * (double)One_round / 1000.00;
//выводим среднесуточный прбег
DoubleToDisp(Total_run / Day_passed);
delay(1000);
module.clearDisplay();
}
else //если целые сутки еще не прошли
{
module.setDisplayToString(«0», 0);
}
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}

break;

//если нажата 4-я слева
case 0b00001000:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 3);
module.clearDisplay();
//считаем и выводим на дисплей среднюю скорость за круг :)
DoubleToDisp(All_speeds/Round_counter);

delay(1000);
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}
break;

//если нажата 5-я слева
case 0b00010000:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 4);
module.clearDisplay();
//сколько прошло дней щитаем с момента старта
Day_passed = millis()/86400000.00;
DoubleToDisp(Day_passed);

delay(1000);
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}
break;

//если нажата 6-я слева
case 0b00100000:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 4);
module.clearDisplay();
//выводим кол-во кругов
module.setDisplayToDecNumber(Round_counter, 0, false);

delay(1000);
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}
break;

//если нажата 1-я справа
case 0b10000000:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 7);
module.clearDisplay();
//выводим на дисплей текущую влажность
ReadDHT();//читаем датчик
//если нет ошибок
if(bGlobalErr ==0)
{
byte humidity = dht_dat[0];
module.setDisplayToDecNumber(humidity, 0, false);
//DoubleToDisp(humidity);
}
delay(1000);
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}
break;

//если нажата 2-я справа
case 0b01000000:
//мигаем соот-щим диодом остальные гасим
module.setLEDs(0);
module.setLED(Red, 6);
module.clearDisplay();
//выводим на дисплей текущую температуру
ReadDHT();//читаем датчик
//если нет ошибок
if(bGlobalErr ==0)
{
byte temperature = dht_dat[2];
module.setDisplayToDecNumber(temperature, 0, false);
}
delay(1000);
if(Sys_status ==0)
{
module.setupDisplay(false, 0);
}
break;
}
//конец кнопок
/**************** конец кнопок **************************************/

/****************здесь вся логика считывания инфы**************************/

//считываем данные — датчик холла подключаем на 3 цифровой порт
int sensorValue = digitalRead(Hall_port);

//если изменилось состояние датчика
if(sensorValue != PrevValue)
{
PrevValue = sensorValue;
//если сенсор дает 0 т.е. магнит тут
if (!sensorValue)
{
Sys_status = 2;

//включаем дисплей если пауза была больше слипинг интервала и он уже выключился
if(time_span_mon > Do_sleeping_interval)
{
module.setupDisplay(true, 7);
}
//прибавляем счетчик
Round_counter++;

//текущее время берем в милисеках
unsigned long time_now = millis();

//считаем разницу в мсек во времени между текущим и предыдущим
unsigned long time_span = time_now — Prev_time;
//текущее пишем в предыдущее
Prev_time = time_now;
//если пауза меньше 10 секунд то признаем, что колесо крутицца
if(time_span < Wait_interval)
{
if(Curr_speed > 10.00)
{
module.clearDisplay();
}

Curr_speed = One_round/time_span*1000*3,6; //километры в час получаем скорость
All_speeds += Curr_speed;//для подсчета средней

//на максимум проверяем
if(Curr_speed > Max_speed)
Max_speed= Curr_speed;

byte led_speed = map(Curr_speed, 0, Max_led_light_speed, 0, 7);
//вызываем подсвет ледов
Led_speed_light(led_speed);

// отладка
// Serial.print(" Round_counter= ");
//Serial.println(Round_counter);
//Serial.print(" curr_speed(km/h)= ");
//Serial.println(Curr_speed, 4);

//выводим на дисплей текущую скорость
DoubleToDisp(Curr_speed);

}
//если пауза больше 10 секунд то считаем, что колесо стояло до этого
// и данные предыдущего времени не учитываем, а начинаем
//разницу считать сначала
else
{
Prev_time = time_now;

//отладка
//Serial.print(" time_span reset ");
}

}

}

else //если не изменилось состояние датчика
{
//текущее время берем
unsigned long time_now = millis();

//разницу во времени между текущим и предыдущим для пост мониторинга в мсек считаем
time_span_mon = time_now — Prev_time;

//если промежуток между касаниями больше 10 секунд — то считаем, что колесо
//остановилось и выводим 0 (интервал необходимо уточнить)
if(time_span_mon > Wait_interval && Sys_status!=1)
{
Sys_status = 1;
Curr_speed = 0;
module.clearDisplay();
module.setDisplayDigit(0, 7, true);
module.setLEDs(0);
}

//если пауза больше 60 секунд то гасим экран
//если промежуток между касаниями больше 10 секунд — то считаем, что колесо
//остановилось и выводим 0 (интервал необходимо уточнить)
if(time_span_mon > Do_sleeping_interval && Sys_status !=0)
{
Sys_status=0;
module.setupDisplay(false, 0);
}

}

}
///подсветка диодов в зависисимости от скорости вращения колеса
// функция
void Led_speed_light(byte led_speed)
{
constrain(led_speed, 0, 7);

switch (led_speed){
case 0:
module.setLEDs(0);
module.setLED(Green, 0);
delay(100);
module.setLED(0, 0);
break;
case 1:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
delay(100);
module.setLED(0, 1);
break;
case 2:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
module.setLED(Green, 2);
delay(100);
for(int i=2; i > 0;i--){
module.setLED(0, i);
delay(50);
}
break;
case 3:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
module.setLED(Green, 2);
module.setLED(Green, 3);
delay(100);
for(int i=3; i > 0;i--){
module.setLED(0, i);
delay(50);
}
break;
case 4:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
module.setLED(Green, 2);
module.setLED(Green, 3);
module.setLED(Green, 4);
delay(100);
for(int i=4; i > 0;i--){
module.setLED(0, i);
delay(50);
}
break;
case 5:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
module.setLED(Green, 2);
module.setLED(Green, 3);
module.setLED(Green, 4);
module.setLED(Red, 5);
delay(100);
for(int i=5; i > 0;i--){
module.setLED(0, i);
delay(50);
}
break;
case 6:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
module.setLED(Green, 2);
module.setLED(Green, 3);
module.setLED(Green, 4);
module.setLED(Red, 5);
module.setLED(Red, 6);
delay(100);
for(int i=6; i > 0;i--){
module.setLED(0, i);
delay(50);
}
break;
case 7:
module.setLEDs(0);
module.setLED(Green, 0);
module.setLED(Green, 1);
module.setLED(Green, 2);
module.setLED(Green, 3);
module.setLED(Green, 4);
module.setLED(Red, 5);
module.setLED(Red, 6);
module.setLED(Red, 7);
delay(100);
for(int i=7; i > 0;i--){
module.setLED(0, i);
delay(50);
}

break;
}

}

//функция выводит на экран LKM1638 дробные значения (точность — 2 зн)
void DoubleToDisp (double num)
{
double mult_num = (double)num * 100.00;
long int_mult_num = (long)mult_num;
String string_mult_num = String(int_mult_num);
int length = string_mult_num.length();
char str[length+1];

string_mult_num.toCharArray(str, length+1);

//если число было меньше 1 то колхозим затычку для выведения правильной дроби
if(num < 1)
{
module.setDisplayDigit(0, 5, true);
module.setDisplayDigit(str[0], 6, false);
module.setDisplayDigit(str[1], 7, false);
}

else//если больше 1 то так выводим
{
for (int i=0; i<length; i++)
{
if (i==length — 3) {
module.setDisplayDigit(str[i], i+8-length, true);
}
else
{
module.setDisplayDigit(str[i], i+8-length, false);
}
}
}

}

/****** функции для DHT11 ***/

void InitDHT(){

pinMode(dht_dpin,OUTPUT);

digitalWrite(dht_dpin,HIGH);

}

void ReadDHT(){

bGlobalErr=0;

byte dht_in;

byte i;

digitalWrite(dht_dpin,LOW);

delay(20);

digitalWrite(dht_dpin,HIGH);

delayMicroseconds(40);

pinMode(dht_dpin,INPUT);

//delayMicroseconds(40);

dht_in=digitalRead(dht_dpin);

if(dht_in){

bGlobalErr=1;

return;

}

delayMicroseconds(80);

dht_in=digitalRead(dht_dpin);

if(!dht_in){

bGlobalErr=2;

return;

}

delayMicroseconds(80);

for (i=0; i<5; i++)

dht_dat[i] = read_dht_dat();

pinMode(dht_dpin,OUTPUT);

digitalWrite(dht_dpin,HIGH);

byte dht_check_sum =

dht_dat[0]+dht_dat[1]+dht_dat[2]+dht_dat[3];

if(dht_dat[4]!= dht_check_sum)

{
bGlobalErr=3;
}

};

byte read_dht_dat(){

byte i = 0;

byte result=0;

for(i=0; i< 8; i++){

while(digitalRead(dht_dpin)==LOW);

delayMicroseconds(30);

if (digitalRead(dht_dpin)==HIGH)

result |=(1<<(7-i));

while (digitalRead(dht_dpin)==HIGH);

}

return result;

}

/****** конец функций для DHT11 ****/

Далее немного видео, чтобы сформировать общее представление о полученной конструкции.

Результаты, интересные факты и выводы

  1. С раннего утра белка бежит обычно со средней скоростью ~5-6 км/ч (видимо, в поисках еды и лучшей доли).
  2. После обязательного дневного сна и приема калорийных орехов беличья скорость падает до 3-4 км/ч (еду уже нашла, все важное по жизни сделала).
  3. При работающем рядом пылесосе скорость ВНЕЗАПНО увеличивается до 12-14 км/ч! Хотя я и встречал где-то на зарубежных ресурсах про максимальную скорость белок в 30 км/ч, могу ответственно заявить, что все это враки. Ну или моя белка не относится к породе гончих (африканских олимпийских).
  4. Максимальная скорость круга 18 км/ч.
  5. Среднесуточный пробег у белки, измеряемый в течение двух месяцев, ~ 15 километров.

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

Автор: misa77

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js