Копилка со счетчиком монет на Arduino с OLED дисплеем

<pre class="lang:arduino decode:true " >
/*
Created 2017
Remixed from AlexGyver
*/
//-------НАСТРОЙКИ---------
#define coin_amount 4    // число монет, которые нужно распознать
float coin_value[coin_amount] = {1.0, 3.0, 5.0, 10.0};  // стоимость монет
String currency = "SOM"; // валюта (английские буквы!!!)
int stb_time = 10000;    // время бездействия, через которое система уйдёт в сон (миллисекунды)
//-------НАСТРОЙКИ---------
int coin_signal[coin_amount];    // тут хранится значение сигнала для каждого размера монет
int coin_quantity[coin_amount];  // количество монет
byte empty_signal;               // храним уровень пустого сигнала
unsigned long standby_timer, reset_timer; // таймеры
float summ_money = 0;            // сумма монет в копилке
//-------БИБЛИОТЕКИ---------
#include "LowPower.h"
#include "EEPROMex.h"
#include "U8glib.h"
#include <Wire.h>
//-------БИБЛИОТЕКИ---------
U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NONE);
boolean recogn_flag, sleep_flag = true;   // флажки
// резисторы делителя напряжения
const float r1 = 1;  // 100K
const float r2 =  500;  // 10K
float Vcc = 0.0;
float MaxVoltage = 0.0;
float v;
float v2;
int i;
float curVoltage;
char *stime;
#define A_PIN A1
#define COUNT 5
// эту константу (typVbg) необходимо откалибровать индивидуально
const float typVbg = 1.125; // 1.0 -- 1.2
//-------КНОПКИ---------
byte button = 2;         // кнопка "проснуться"
byte calibr_button = 3;  // скрытая кнопка калибровкии сброса
byte disp_power = 10;    // питание дисплея
byte LEDpin = 11;        // питание светодиода
byte IRpin = 17;         // питание фототранзистора
byte IRsens = 14;        // сигнал фототранзистора
//-------КНОПКИ---------
int sens_signal, last_sens_signal;
boolean coin_flag = false;
void setup() {
Serial.begin(9600);
delay(500);
analogReference(DEFAULT);
Vcc = readVcc();
MaxVoltage = Vcc / (r2 / (r1 + r2));
analogWrite(A_PIN, 0);
// подтягиваем кнопки
pinMode(button, INPUT_PULLUP);
pinMode(calibr_button, INPUT_PULLUP);
// пины питания как выходы
pinMode(disp_power, OUTPUT);
pinMode(LEDpin, OUTPUT);
pinMode(IRpin, OUTPUT);
// подать питание на дисплей и датчик
digitalWrite(disp_power, HIGH);
digitalWrite(LEDpin, HIGH);
digitalWrite(IRpin,HIGH);
// подключить прерывание
attachInterrupt(0, wake_up, CHANGE);
empty_signal = analogRead(IRsens);  // считать пустой (опорный) сигнал
if (!digitalRead(calibr_button)) {  // если при запуске нажата кнопка калибровки
u8g.firstPage();  
do {
u8g.setFont(u8g_font_profont11);
u8g.drawStr(0,9, "Services");
} while( u8g.nextPage() );
delay(500);
reset_timer = millis();
while (1) {                                   // бесконечный цикл
if (millis() - reset_timer > 5000) {        // если кнопка всё ещё удерживается и прошло 5 секунд
// очистить количество монет
for (byte i = 0; i < coin_amount; i++) {
coin_quantity[i] = 0;
EEPROM.writeInt(20 + i * 2, 0);
}
u8g.firstPage();  
do {
u8g.setFont(u8g_font_profont11);
u8g.drawStr(0,9, "Clear memory");
} while( u8g.nextPage() );        
delay(100);
}
if (digitalRead(calibr_button)) {   // если отпустили кнопку, перейти к калибровке
u8g.firstPage();
do {
u8g.setFont(u8g_font_profont11);
u8g.drawStr(0,9, "Calibration");
} while( u8g.nextPage() );
break;
}
}
while (1) {
for (byte i = 0; i < coin_amount; i++) {
u8g.firstPage();  
do {
u8g.setFont(u8g_font_profont11);
u8g.setPrintPos(0,20);
u8g.print(coin_value[i]);
u8g.setFont(u8g_font_profont11);
u8g.setPrintPos(40,20);
u8g.print(currency);   
} while( u8g.nextPage() );
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);
Vcc = readVcc();
stime = TimeToString(millis()/1000);
// считываем точное напряжение с A0, где будет находиться наш вольтметр с делителем напряжения
curVoltage = 0.0;
for (i = 0; i < COUNT; i++) {
curVoltage = curVoltage + analogRead(A_PIN);
delay(10);
}
curVoltage = curVoltage / COUNT;
v  = (curVoltage * Vcc) / 1024.0;
v2 = v / (r2 / (r1 + r2));
v2 = v2*100;
v2 = constrain(v2, 240, 420);
v2 = map(v2, 240, 420, 0, 99);
u8g.firstPage();
do {
u8g.drawRFrame(107,0,19,11,1);
u8g.drawBox(126,2,2,7);
if (v2 < 10) {
u8g.setFont(u8g_font_profont11);
u8g.setPrintPos(114,9);
u8g.print(v2,0);
} else {
u8g.setFont(u8g_font_profont11);
u8g.setPrintPos(111,9);
u8g.print(v2,0);
}
u8g.setFont(u8g_font_helvB12);
u8g.drawStr(0,35, "na iPhone");
u8g.setFont(u8g_font_helvB10);
u8g.setPrintPos(35,47);
u8g.print(summ_money,0);
u8g.setFont(u8g_font_helvB10);
u8g.setPrintPos(55,47);
u8g.print(currency);
} while( u8g.nextPage() );
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)) < 2) {
recogn_flag = false;  // флажок ошибки, пока что не используется
// в общем нашли максимум для пролетевшей монетки, записали в last_sens_signal
// далее начинаем сравнивать со значениями для монет, хранящимися в памяти
for (byte i = 0; i < coin_amount; i++) {
int delta = abs(last_sens_signal - coin_signal[i]);   // вот самое главное! ищем АБСОЛЮТНОЕ (то бишь по модулю) 
// значение разности полученного сигнала с нашими значениями из памяти
if (delta < 30) {   // и вот тут если эта разность попадает в диапазон, то считаем монетку распознанной
summ_money += coin_value[i];  // к сумме тупо прибавляем цену монетки (дада, сумма считается двумя разными способами. При старте системы суммой всех монет, а тут прибавление
u8g.firstPage();
do {
u8g.drawRFrame(107,0,19,11,1);
u8g.drawBox(126,2,2,7);
if (v2 < 10) {
u8g.setFont(u8g_font_profont11);
u8g.setPrintPos(114,9);
u8g.print(v2,0);
} else {
u8g.setFont(u8g_font_profont11);
u8g.setPrintPos(111,9);
u8g.print(v2,0);
}
u8g.setFont(u8g_font_helvB12);
u8g.drawStr(0,35, "na iPhone");
u8g.setFont(u8g_font_helvB10);
u8g.setPrintPos(35,47);
u8g.print(summ_money,0);
u8g.setFont(u8g_font_helvB10);
u8g.setPrintPos(55,47);
u8g.print(currency);
} while( u8g.nextPage() );
coin_quantity[i]++;  // для распознанного номера монетки прибавляем количество
recogn_flag = true;
break;
}
}
coin_flag = false;
standby_timer = millis();  // сбросить таймер
break;
}
// если ничего не делали, время таймера вышло, спим
if (millis() - standby_timer > stb_time) {
good_night();
break;
}
}
}
// функция сна
void good_night() {
// перед тем как пойти спать, записываем в EEPROM новые полученные количества монет по адресам начиная с 20го (пук кек)
for (byte i = 0; i < coin_amount; i++) {
EEPROM.updateInt(20 + i * 2, coin_quantity[i]);
}
sleep_flag = true;
// вырубить питание со всех дисплеев и датчиковs
digitalWrite(disp_power, LOW);
digitalWrite(LEDpin, LOW);
digitalWrite(IRpin, LOW);
delay(100);
// и вот теперь спать
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
// просыпаемся по ПРЕРЫВАНИЮ (эта функция - обработчик прерывания)
void wake_up() {
// возвращаем питание на дисплей и датчик
digitalWrite(disp_power, HIGH);
digitalWrite(LEDpin, HIGH);
digitalWrite(IRpin, HIGH);
standby_timer = millis();  // и обнуляем таймер
}
// Функции
float readVcc() {
byte i;
float result = 0.0;
float tmp = 0.0;
for (i = 0; i < 5; i++) {
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
// works on an Arduino 168 or 328
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(3); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
tmp = (high<<8) | low;
tmp = (typVbg * 1023.0) / tmp;
result = result + tmp;
delay(5);
}
result = result / 5;
return result;
}
// t is time in seconds = millis()/1000;
char * TimeToString(unsigned long t)
{
static char str[12];
long h = t / 3600;
t = t % 3600;
int m = t / 60;
int s = t % 60;
sprintf(str, "%04ld:%02d:%02d", h, m, s);
return str;
}
</pre>