Электронная копилка для монет

Электронная копилка для монет

Процесс накопления монет долгий и часто забываешь, а сколько у тебя там в коробочке, или - просто лень сосчитать. В наш век это можно поручить микроконтроллеру, собрав несложную схему на базе Arduino и закачав соответствующий софт. Включил и на дисплее сумма денег в копилке, да ещё и раскладка по номиналам монет. А для современных детей - самосчитающая копилка даже будет выглядеть "прикольно". Идею я позаимствовал у AlexGyver (https://youtu.be/lH4qfGlK2Qk), но внёс некоторые изменения в алгоритм обработки размера монет, что позволило уверенно опознавать не только большие по размеру монеты, но и совсем маленькие. Такие, как, например, европейские центы. Номиналы и тип валюты легко меняются в тексте программы.

В качестве датчика использованы инфракрасные светодиоды. Для рефлектора использовал пробки от пластиковых бутылок. Идеально работают. Для приёмника монет использовал куски ненужного гетинакса (можно и от распаянных плат). Для контактов наличия монеты в приёмнике - металлическая фольга. Инфракрасный излучающий светодиод можно позаимствовать из старого пульта от телевизора. Можно от любой другой безделицы, использующей ИК для управления. Приёмный светодиод, как и контролер приобрёл у китайских купцов в интернете. Коробка копилки сделана из плотного картона и украшена для внука "завлекалками".

Вот так выглядит скетч программы. Копируем и заливаем в Arduino Mini:

/*
Электронный распознаватель монет (по размеру) для копилки со счётчиком суммы и статистикой по каждому типу монет.
Функционал:
Распознавание размера монет с высокой точностью и его привязка к стоимости каждой монеты
Вычисление общей суммы монет в копилке
Статистика по числу монет каждого типа
Все настройки сохраняются в энергонезависимую память и не сбрасываются при питании
Накопленная сумма тоже хранится в энергонезависимой памяти и не боится сбоев питания
Режим глубокого энергосбережения: в спящем режиме потребляется 0.07 мА, в схеме без преобразователя 0.02 мА
Поддержка любого числа монет разного размера
Автоматическая калибровка типов монет
Сброс накопленного количества
Подробности в видео: https://youtu.be/lH4qfGlK2Qk
Created 2017
by AlexGyver
AlexGyver Home Labs Inc.
*/

//-------НАСТРОЙКИ---------
#define coin_amount 8 // число монет, которые нужно распознать
float coin_value[coin_amount] = {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0}; // стоимость монет
String currency = "EUR"; // валюта (английские буквы!!!)
int stb_time = 20000; // время бездействия, через которое система уйдёт в сон (миллисекунды)
//-------НАСТРОЙКИ---------

int coin_signal[coin_amount]; // тут хранится значение сигнала для каждого размера монет
int coin_quantity[coin_amount]; // количество монет
int delta [coin_amount]; // разница между найденным значением сигнал от монеты и калибровочным сигналом всех монет
int min; // минимальное значение разницы между замеренным уровнем текущей монеты и уровней базовых значений для монет всех достоинств
int r;
byte empty_signal; // храним уровень пустого сигнала
unsigned long standby_timer, reset_timer; // таймеры
float summ_money = 0; // сумма монет в копилке
void wake_up();
void good_night();
//-------БИБЛИОТЕКИ---------
#include "LowPower.h"
#include "EEPROMex.h"
#include "LCD_1602_RUS.h"
//-------БИБЛИОТЕКИ---------

LCD_1602_RUS lcd(0x27, 16, 2); // создать дисплей
// если дисплей не работает, замени 0x27 на 0x3f

boolean recogn_flag, sleep_flag = true; // флажки
//-------КНОПКИ---------
#define button 2 // кнопка "проснуться"
#define calibr_button 3 // скрытая кнопка калибровкии сброса
#define disp_power 12 // питание дисплея
#define LEDpin 11 // питание светодиода
#define IRpin 17 // питание фототранзистора
#define IRsens 14 // сигнал фототранзистора
//-------КНОПКИ---------
int sens_signal, last_sens_signal;
boolean coin_flag = false;



void setup() {
Serial.begin(9600); // открыть порт для связи с ПК для отладки
delay(500);

// подтягиваем кнопки
pinMode(button, INPUT_PULLUP);
pinMode(calibr_button, INPUT_PULLUP);

// пины питания как выходы
pinMode(disp_power, OUTPUT);
pinMode(LEDpin, OUTPUT);
pinMode(IRpin, OUTPUT);

// подать питание на дисплей и датчик
digitalWrite(disp_power, 1);
digitalWrite(LEDpin, 1);
digitalWrite(IRpin, 1);

// подключить прерывание
attachInterrupt(0, wake_up, CHANGE);

empty_signal = analogRead(IRsens); // считать пустой (опорный) сигнал

// инициализация дисплея
lcd.init();
lcd.backlight();

if (!digitalRead(calibr_button)) { // если при запуске нажата кнопка калибровки
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(L"Калибровка монет");
delay(300);
reset_timer = millis();
while (1) { // бесконечный цикл
if (millis() - reset_timer > 3000) { // если кнопка всё ещё удерживается и прошло 3 секунды
// очистить количество монет
for (byte i = 0; i < coin_amount; i++) {
coin_quantity[i] = 0;
EEPROM.writeInt(20 + i * 2, 0);
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(L"Память очищена");
delay(100);
}
if (digitalRead(calibr_button)) { // если отпустили кнопку, перейти к калибровке
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(L"Калибровка");
break;
}
}
while (1) {
for (byte i = 0; i < coin_amount; i++) {
lcd.setCursor(0, 1); lcd.print(coin_value[i]); // отобразить цену монеты, размер которой калибруется
lcd.setCursor(13, 1); lcd.print(currency); // отобразить валюту
last_sens_signal = empty_signal;
while (1) {
sens_signal = analogRead(IRsens); // считать датчик
if (sens_signal > last_sens_signal) last_sens_signal = sens_signal; // если текущее значение больше предыдущего
if (sens_signal - empty_signal > 3) coin_flag = true; // если значение упало почти до "пустого", считать что монета улетела
if (coin_flag && (abs(sens_signal - empty_signal)) < 2) { // если монета точно улетела
coin_signal[i] = last_sens_signal; // записать максимальное значение в память
EEPROM.writeInt(i * 2, coin_signal[i]);
coin_flag = false;
break;
}
}
}
break;
}
}

// при старте системы считать из памяти сигналы монет для дальнейшей работы, а также их количество в банке
for (byte i = 0; i < coin_amount; i++) {
coin_signal[i] = EEPROM.readInt(i * 2);
coin_quantity[i] = EEPROM.readInt(20 + i * 2);
summ_money += coin_quantity[i] * coin_value[i]; // ну и сумму сразу посчитать, как произведение цены монеты на количество
}
standby_timer = millis(); // обнулить таймер ухода в сон
}

void loop() {
if (sleep_flag) { // если проснулись после сна, инициализировать дисплей и вывести текст, сумму и валюту
delay(500);
lcd.init();
lcd.clear();
lcd.setCursor(0, 0); lcd.print(L"Накоплено:");
lcd.setCursor(0, 1); lcd.print(summ_money);
lcd.setCursor(13, 1); lcd.print(currency);
empty_signal = analogRead(IRsens);
sleep_flag = false;
}

// далее работаем в бесконечном цикле
last_sens_signal = empty_signal;
while (1) {
sens_signal = analogRead(IRsens); // далее такой же алгоритм, как при калибровке
if (sens_signal > last_sens_signal) last_sens_signal = sens_signal;
if (sens_signal - empty_signal > 3) coin_flag = true;
if (coin_flag && (abs(sens_signal - empty_signal)) < 1) {
recogn_flag = false; // флажок ошибки, пока что не используется
// в общем нашли максимум для пролетевшей монетки, записали в last_sens_signal
// далее начинаем сравнивать со значениями для монет, хранящимися в памяти
r=0;
for (byte i = 0; i < coin_amount; i++) {
delta [i] = abs(last_sens_signal - coin_signal[i]); // вот самое главное! ищем АБСОЛЮТНОЕ (то бишь по модулю) и создаём таблицу разности относительно всех монет уровней для текущей монеты
}
{
min = delta[0];//первое значение массива в переменные
for (byte i = 0; i < coin_amount; i++)
{
if (min > delta[i]) { min = delta[i];
r=i;
}
//определяем min
// Вывод данных в порт

/*
Serial.print("coin amount=");
Serial.print(coin_amount);
Serial.print(" ");
Serial.print("coin value=");
Serial.print(coin_value[i]);
Serial.print(" signal=");
Serial.print(coin_signal[i]);
Serial.print(" ");
Serial.print(" last sens signal=");
Serial.print(last_sens_signal);
Serial.print(" ");
Serial.print(" delta i=");
Serial.print(i);
Serial.print(" ");
Serial.print(delta[i]);
Serial.print(" ");
Serial.print(" minimum=");
Serial.print(min);
Serial.println(" ");
*/
}
// значение разности полученного сигнала с нашими значениями из памяти
if (delta[r] < 100 && last_sens_signal>30) { // и вот тут если эта разность попадает в диапазон, то считаем монетку распознанной
summ_money += coin_value[r]; // к сумме тупо прибавляем цену монетки (дада, сумма считается двумя разными способами. При старте системы суммой всех монет, а тут прибавление
lcd.setCursor(0, 1); lcd.print(summ_money);
coin_quantity[r]++; // для распознанного номера монетки прибавляем количество

/*
Serial.print("Delta r");
Serial.print( r );
Serial.print("=");
Serial.print( delta [r]);
Serial.print(" ");
Serial.print(" system recognize coin value as=");
Serial.print(coin_value[r]);
Serial.println(" ");
*/

recogn_flag = true;
break;
}

}
coin_flag = false;
standby_timer = millis(); // сбросить таймер
break;
}


// если ничего не делали, времят аймера вышло, спим
if (millis() - standby_timer > stb_time) {
good_night();
break;
}

// если монетка вставлена (замыкает контакты) и удерживается 2 секунды
while (!digitalRead(button)) {
if (millis() - standby_timer > 2000) {
lcd.clear();

// отобразить на дисплее: сверху цены монет (округлено до целых!!!!), снизу их количество
for (byte i = 0; i < coin_amount; i++) {
lcd.setCursor(i * 2, 0); lcd.print((int)(coin_value[i]*100));
lcd.setCursor(i * 2, 1); lcd.print(coin_quantity[i]);
}
}
}
}
}

// функция сна
void good_night() {
// перед тем как пойти спать, записываем в EEPROM новые полученные количества монет по адресам начиная с 20го (пук кек)
for (byte i = 0; i < coin_amount; i++) {
EEPROM.updateInt(20 + i * 2, coin_quantity[i]);
}
sleep_flag = true;
// вырубить питание со всех дисплеев и датчиков
digitalWrite(disp_power, 0);
digitalWrite(LEDpin, 0);
digitalWrite(IRpin, 0);
delay(100);
// и вот теперь спать
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}

// просыпаемся по ПРЕРЫВАНИЮ (эта функция - обработчик прерывания)
void wake_up() {
// возвращаем питание на дисплей и датчик
digitalWrite(disp_power, 1);
digitalWrite(LEDpin, 1);
digitalWrite(IRpin, 1);
standby_timer = millis(); // и обнуляем таймер
}

Схема подключения


11:24
Нет комментариев. Ваш будет первым!