Копилка со счетчиком монет на 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>