Version 9
    Bank to the Future with Arduino & TI

    element14 Presents  |  Bald Engineer: James Lewis' VCP Profile |  Project Videos

     

    Until cryptocurrency replaces money, we have to deal with coins. Instead of just collecting them in a jar, this electronic coin bank lets you know how much you have. The simple part is the count counter module which detects up to 4 coin types. The tricky part is making it battery powered. Thanks to capacitive sensing technology, this coin bank can run off a single charge for months.

     

    Buy KitBuy Kit

     

    Capacitive Coin Bank

     

    Product NameManufacturerQuantity
    Product Link
    MSP-CAPT-FR2633MSP-CAPT-FR2633Evaluation Board CAPTIVATE Capacitive Touch Technology MCU Texas Instruments1Buy NowBuy Now
    Perma-Proto Breadboard Half SizeAdafruit1NA
    Coin AcceptorAdafruit1NA
    2465 -  Development Board, PowerBoost 1000. LiPo Battery Charger, 5V, 1A, USB BoostAdafruit1Buy NowBuy Now
    1578 -  Rechargeable Battery, 3.7 V, Lithium Polymer, 500 mAh Adafruit1Buy NowBuy Now
    6mm Copper Foil Tape3M1Buy NowBuy Now

     

    5v to 12v Boost

     

    Product NameManufacturerQuantity
    Product Link
    C0805C106K4PACTU -  Multilayer Ceramic Capacitor, C Series, 10 - F, - 10%, X5R, 16 V, 0805 [2012 Metric]KEMET1Buy NowBuy Now
    MBR0540 -  Schottky Rectifier, 40 V, 500 mA, Single, SOD-123, 2 Pins, 620 mVON1Buy NowBuy Now
    4.7uH 0805 Inductor 600mATaiyo-Yuden1Buy NowBuy Now
    DC-DC Boost, SOT-23-5, 200mA outMicrochip1Buy NowBuy Now

     

    Pyramiduino

     

    Product NameManufacturerQuantity
    Product Link
    100nF 0805 Ceramic 16V X7RKEMET1Buy NowBuy Now
    10nF 0805 Ceramic 16V X7RKEMET1Buy NowBuy Now
    22pF 0805 Ceramic 50V C0GKEMET2Buy NowBuy Now
    1uF 0805 Ceramic 16V X7RKEMET1Buy NowBuy Now
    ATmega328 QFN (Or MLF) PackageMicrochip1Buy NowBuy Now
    LDO 3v3 Regulator, SOT-23-5Microchip1Buy NowBuy Now
    Crystal 16M FA-238EPSON1Buy NowBuy Now

     

    Also Featured:

     

    Product NameManufacturerQuantity
    Product Link
    RTM3K-104 -  Oscilloscope, RTM3000 Series, 4 Channel, 1 GHz, 2.5 GSPS, 40 Mpts, 350 psRohde & Schwartz1Buy NowBuy Now

     

     

    Touch Tutorial: Capacitive Touch Tutorial from James Lewis - Bald Engineer

     

    MSP430FR2633 MCU Development Kit: Capacitive touch evaluation and rapid prototyping for MSP430FR2633 microcontroller MCU Development Kit

     

    Autodesk Fusion 360: Autodesk Fusion 360

     

    12 V BoostBreadboard Circuits

     

    Arduino Code:

     

    // Pins
    #include <Arduino.h>
    
    
    const byte coinSig = A0;
    const byte hvSolenoid = A1; // for turning on solenoid/coin counter
    const byte battControl = 4;
    const byte battRead = A3;
    const byte resetButton = 3;
    
    
    bool previousCoinSignal = false;
    const float coinValue = 0.05;
    float bankValue = 0.00;
    unsigned long bankCount = 0;
    const unsigned long pulseTimeOut = 75;
    
    
    unsigned long armedForCount = 0;
    unsigned long previousUpdateMillis = 0;
    unsigned long updateInterval = 110;
    bool printOnceFlag = false;
    
    
    unsigned long previousEEPROMwrite = 0;  // delays writing to EEPROM
    unsigned long updateEEPROMinterval = 1000;
    //float EEPROMbankValue = 0.00;
    unsigned long EEPROMbankCount;
    
    
    unsigned long previousBatteryMillis = 0;
    const unsigned long batteryInterval = 1000;
    const float adcResolution = 5.00/1023.0;
    bool enableCounter = true;

     

    #include <Arduino.h>
    #include <EEPROM.h>
    #include <Wire.h>
    #include <U8g2lib.h>
    #include "coin-slot-arduino.h"
    
    
    
    
    // u8g2 constructor
    U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE);
    
    
    void loadEEPROM() {
    EEPROM.get(0, bankCount);
    EEPROMbankCount = bankCount;
    }
    
    
    byte getValueWidth(float displayValue) {
      // average character width for u8g2_font_logisoso24_tn is 14
      const byte charWidth = u8g2.getMaxCharWidth();
      byte theWidth = 4 * charWidth; //1.00
      if (displayValue >= 10.00)
        theWidth = 5 * charWidth;
      if (displayValue >= 100.00)
        theWidth = 6 * charWidth; //1.00
      if (displayValue >= 1000.00)
        theWidth = 7 * charWidth; //1.00
    return ((u8g2.getDisplayWidth()-theWidth)/2);
    }
    
    
    
    
    void printOLED() {
    u8g2.clearBuffer();     // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tn); // choose a suitable font
    byte centerHeight = (u8g2.getDisplayHeight()/2) + (u8g2.getMaxCharHeight()/2);
    u8g2.setCursor(getValueWidth(bankValue),centerHeight);
    u8g2.print(bankValue);
    u8g2.sendBuffer();     // transfer internal memory to the display
    }
    
    
    void printBank() {
    bankValue = bankCount * coinValue;
    printOLED();
    Serial.print(F("Bank Value: "));
    Serial.println(bankValue);
    }
    
    
    void printPulses() {
    Serial.print(F("Pulse coutn: "));
    Serial.println(bankCount);
    }
    
    
    
    
    void handleSerial() {
    char incomingChar = Serial.read();
    
    
    switch(incomingChar) {
    case '!':
    Serial.println(F("Clearing..."));
    bankCount = 0;
          bankValue = bankCount * coinValue;
          printBank();
          break;
        case '.':
          printPulses();
          printBank();
          break;
    }
    }
    
    
    void checkBattery() {
    
    
    // hceck battery voltage
    digitalWrite(battControl, HIGH);
    delay(10);
    int battReading = analogRead(battRead);
    digitalWrite(battControl, LOW);
    // Serial.print(F("Battery: "));
    // Serial.println(battReading);
    if (battReading < 700) {
    // battery is too low, turn off solenoid
    enableCounter = false;
    // turn off enableCounter
    digitalWrite(hvSolenoid, LOW);
    //print message
    // Serial.println(F("Low Battery Disable"));
    u8g2.clearBuffer(); // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tr); // choose a suitable font
    u8g2.drawStr(0, 50, "lo batt");
    u8g2.sendBuffer(); // transfer internal memory to the display
    while(1);
    }
    
    
    return;
    }
    
    
    void handleBankReset() {
    unsigned long resetCounter = millis();
    bool waiting = true;
    while((digitalRead(resetButton) == HIGH) && waiting) {
    if (millis() - resetCounter < 1000) {
    u8g2.clearBuffer(); // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tr); // choose a suitable font
    u8g2.drawStr(0, 50, "3");
    u8g2.sendBuffer(); // transfer internal memory to the display
    }
    
    
    if ((millis() - resetCounter > 1000) && (millis() - resetCounter <= 2000)) {
    u8g2.clearBuffer(); // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tr); // choose a suitable font
    u8g2.drawStr(0, 50, "2");
    u8g2.sendBuffer(); // transfer internal memory to the display
    }
    
    
    if ((millis() - resetCounter > 2000) && (millis() - resetCounter <= 3000)) {
    u8g2.clearBuffer(); // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tr); // choose a suitable font
    u8g2.drawStr(0, 50, "1");
    u8g2.sendBuffer(); // transfer internal memory to the display
    }
    
    
    if ((millis() - resetCounter > 3000) && (millis() - resetCounter <= 3500)) {
    u8g2.clearBuffer(); // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tr); // choose a suitable font
    u8g2.drawStr(0, 50, "0");
    u8g2.sendBuffer(); // transfer internal memory to the display
    }
    
    
    if ((millis() - resetCounter > 3500) && (millis() - resetCounter <= 4000)) {
    bankCount = 0;
    }
    
    
    if ((millis() - resetCounter > 4000)) {
    waiting = false;
    }
    
    
    }
    printBank();
    }
    
    
    void setup() {
    pinMode(battControl, OUTPUT);   // battery transistor
    digitalWrite(battControl, LOW);
    u8g2.begin();
    checkBattery();
    // init coin counter
    pinMode(hvSolenoid, OUTPUT);
    //Serial.print(F("Turning on counter..."));
      digitalWrite(hvSolenoid, HIGH);
    pinMode(coinSig, INPUT_PULLUP);
    u8g2.clearBuffer(); // clear the internal memory
    u8g2.setFont(u8g2_font_logisoso34_tr); // choose a suitable font
    u8g2.drawStr(0, 50, "Wait");
    u8g2.sendBuffer(); // transfer internal memory to the display
    Serial.begin(115200);
      //delay(100); // TODO is there a way to tell when coin acceptor is ready
      while(digitalRead(coinSig));  // add timeout and error.
    previousCoinSignal = digitalRead(coinSig);
      //Serial.println(F("done!"));
    
    
    pinMode(resetButton, INPUT);
    
    
      // init screen
      //pinMode(hvOLED, OUTPUT);
    loadEEPROM();
    //Serial.print(F("After Setup: "));
    //Serial.println(freeMemory());
    printBank();
    }
    
    
    void loop() {
    handleSerial();
    
    
    if (digitalRead(resetButton) == HIGH)
    handleBankReset();
    
    
    if (millis() - previousBatteryMillis >= batteryInterval) {
    previousBatteryMillis = millis();
    checkBattery();
    }
    
    
    
    
    byte currentCoinSignal = digitalRead(coinSig);
    
    
    if (currentCoinSignal == HIGH) {
    previousUpdateMillis = millis(); // print after pulse train is done
        previousEEPROMwrite = millis(); // don't write until pulse train is over
    printOnceFlag = true;
    }
    
    
      // only print after pulses stop
    if (printOnceFlag && (millis() - previousUpdateMillis >= updateInterval)) {
    printBank();
    printOnceFlag = false;
    }
    
    
    // is time to update the EEPROM?
    
    
    if (millis() - previousEEPROMwrite >= updateEEPROMinterval) {
    // reset the clock
    previousEEPROMwrite = millis();
    
    
    // only write to EEPROM when a new value
    if (bankCount != EEPROMbankCount) {
    EEPROM.put(0,bankCount);  // writes a float to EEPROM
    Serial.println(F("!!! EEPROM WRITE !!!"));
    EEPROMbankCount = bankCount;
    }
    }
    
    
    if (currentCoinSignal != previousCoinSignal) {
    // Save the state for next iteration
    previousCoinSignal = currentCoinSignal;
    
    
    if (currentCoinSignal == HIGH) {
          // we see an edge, but is it a coin pulse?
          armedForCount = millis();
    } else {
          // short timeout to make sure it was a full pulse
          if (millis() - armedForCount < pulseTimeOut) {
            bankCount++;
          }
        }
    }
    }

     

    Full source Code, Design Files, and Schematics Attached!