Coin Acceptor abruptly stops interrupting

Hello everyone! I'd like to ask for help with my coin acceptor. So my coin acceptor is wired to D2, every time it powers up, it accepts coins and the blue LED on my ESP8266 pulses depending on how much was inserted (i.e. 5 peso coin -> 5 pulses), but for some reason, at times it just stops counting/crediting. For example, I put 10 pesos, it should count to 10, but stops at 5 or 6 randomly. Then when I insert another coin, it accepts it, but it no longer counts the credit. When I restart the system, it returns to normal, but after some time, it stops abruptly again. Here's my entire code for the system:

#include <Adafruit_Thermal.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal_I2C.h>
#include "vglogoII.h"

//Network time
#include "NTPClient.h"
#include "ESP8266WiFi.h"
#include "WiFiUdp.h"

//Wifi, Date & Time
const char *ssid = "Wifi";
const char *password = "********";
WiFiUDP ntpUDP;
const long utcOffsetInSeconds = 28800;
NTPClient timeClient(ntpUDP, "time.nist.gov", utcOffsetInSeconds);

//Pin assignment
const int BTN_LED_PIN = 0;
const int COIN_PIN = 2;
const int INSERT_COIN_BTN_PIN = 3;
const int COIN_SET_PIN = 15;
int TX_PIN = 14; //Green Wire
int RX_PIN = 16; //Yellow Wire

LiquidCrystal_I2C lcd(0x27, 16, 2);
SoftwareSerial mySerial(RX_PIN, TX_PIN);
Adafruit_Thermal printer(&mySerial);

//Button variables
int btnState = 0;
volatile bool btnPressed = false;
bool btnDisabled = false;

//Coin/coinslot variables
volatile int impulseCount = 0;
int coinType = 0;
int totalAmount = 0;
int coinValue;
const int coinPulses[] = { 1, 5, 10 };
bool firstCoinOn = true;

//Countdown variables
unsigned long previousMillis = 0;
int countdown = 16;
int countdownII = 11;
const long interval = 1000;
bool countdownStarted = false;
bool countdownIIStarted = false;

//Transaction flag
bool ongoingTransaction = false;

int lookup(int pulses) {
  for(int i = 0; i < sizeof(coinPulses) / sizeof(coinPulses[0]); i++) {
    if (pulses == coinPulses[i]) {
      return coinPulses[i];
    }
  }
  return -1;
}

void IRAM_ATTR coinInterrupt() {
  if(firstCoinOn) {
    countdownStarted = true;
  } else {
    impulseCount++;
  }
  firstCoinOn = false;
}

void IRAM_ATTR btnInterrupt() {
  static unsigned long lastExec = 0;
  unsigned long currentExec = millis();
  if(currentExec - lastExec > 500 && !btnDisabled) {
    lcd.clear();
    btnPressed = true;
    btnDisabled = true;
    btnState++;
  }
  lastExec = currentExec;
}

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(BTN_LED_PIN, OUTPUT);
  pinMode(COIN_PIN, INPUT_PULLUP);
  pinMode(INSERT_COIN_BTN_PIN, INPUT_PULLUP);
  pinMode(COIN_SET_PIN, OUTPUT);
  digitalWrite(BTN_LED_PIN, LOW);
  digitalWrite(COIN_SET_PIN, LOW);
  attachInterrupt(digitalPinToInterrupt(COIN_PIN), coinInterrupt, RISING);
  attachInterrupt(digitalPinToInterrupt(INSERT_COIN_BTN_PIN), btnInterrupt, FALLING);
  lcd.init();
  lcd.clear();
  lcd.backlight();
  printer.begin();
  printer.sleep();
  delay(1000L);
  printer.wake();
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  timeClient.begin();
}

void loop() {
  timeClient.update();
  if(!ongoingTransaction) {
    lcd.setCursor(1, 0);
    lcd.print("VGWifi Hotspot");
    lcd.setCursor(2, 1);
    lcd.print("                ");
    lcd.setCursor(2, 1);
    lcd.print("Press button");
    digitalWrite(BTN_LED_PIN, HIGH);
    delay(600);
    lcd.setCursor(2, 1);
    lcd.print("                ");
    digitalWrite(BTN_LED_PIN, LOW);
    delay(600);
  }
  //Credit counter
  if(impulseCount == coinPulses[coinType]) {
    Serial.print("Impulse: ");
    Serial.println(impulseCount);

    coinValue = lookup(impulseCount);
    if(totalAmount == 0 || countdownIIStarted) {
      lcd.clear();
    }
    if(coinValue > 0) {
      totalAmount += coinValue;
    }
    Serial.print("Total Amount: ");
    Serial.println(totalAmount);
    impulseCount = 0;
    lcd.setCursor(0, 0);
    lcd.print("Total credits: ");
    lcd.setCursor(0, 1);
    lcd.print("P");
    lcd.print(totalAmount);
    lcd.print(".00");
  }
  if(btnPressed) {
    time_t epochTime = timeClient.getEpochTime();
    struct tm *ptm = gmtime ((time_t *)&epochTime);
    int currentMonth = ptm->tm_mon+1;
    int monthDay = ptm->tm_mday;
    int currentYear = ptm->tm_year+1900;
    String date = String(currentMonth) +"-" +monthDay +"-" +currentYear +"  " + String(timeClient.getFormattedTime());
    switch(btnState) {
      case 1:
        enableCoinSlot();
        lcd.begin(16, 2);
        ongoingTransaction = true;
        btnPressed = false;
        digitalWrite(BTN_LED_PIN, LOW);
        lcd.setCursor(0, 0);
        lcd.print("Insert coins now");
        break;
      case 2:
        disableCoinSlot();
        getVoucher(totalAmount, date);
        btnPressed = false;
        digitalWrite(BTN_LED_PIN, LOW);
        delay(5000);
        btnDisabled = false;
        lcd.clear();
        btnState = 0;
        firstCoinOn = true;
        ongoingTransaction = false;
        totalAmount = 0;
        break;
    }
  }

  unsigned long currentMillis = millis();
  //Countdown for insert coins
  if(countdownStarted && (currentMillis - previousMillis >= interval)) {
    previousMillis = currentMillis;
    countdown--;
    if(countdown < 10) {
      lcd.setCursor(15, 1);
      lcd.print(" ");
    }
    lcd.setCursor(11, 1);  // Set cursor position
    lcd.print("(");
    lcd.print(countdown);
    lcd.print("s)");
  }
  if(countdownIIStarted && (currentMillis - previousMillis >= interval)) {
    previousMillis = currentMillis;
    countdownII--;
    if (countdownII < 10) {
      lcd.setCursor(15, 1);
      lcd.print(" ");
    }
    lcd.setCursor(14, 1);  // Set cursor position
    lcd.print(countdownII);
  }
  if(countdown <= 0) {
    if(totalAmount >= 5) {
      displayPrintPrompt();
    } else {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Not enough coins");
      lcd.setCursor(0, 1);
      lcd.print("Insert more");
      countdownIIStarted = true;
    }
    countdown = 16;
    countdownStarted = false;
  }
  if(countdownII <= 0) {
    //Still insuff balance, return home
    if(totalAmount < 5) {
      lcd.clear();
      disableCoinSlot();
      btnState = 0;
      btnDisabled = false;
      countdownII = 11;
      countdownIIStarted = false;
      ongoingTransaction = false;
      firstCoinOn = true;
    } else {
      countdownII = 11;
      countdownIIStarted = false;
      displayPrintPrompt();
    }
  }
}

void displayPrintPrompt() {
  btnDisabled = false;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Print voucher?");
  lcd.setCursor(0, 1);
  lcd.print("Press button");
  disableCoinSlot();
  digitalWrite(BTN_LED_PIN, HIGH);
}

void enableCoinSlot() {
  digitalWrite(COIN_SET_PIN, HIGH);
}

void disableCoinSlot() {
  digitalWrite(COIN_SET_PIN, LOW);
}

void getVoucher(int amt, String dateTime) {
  Serial.print("AMT: ");
  Serial.println(String(amt));
  String code;
  int tier;
  String valid;
  int change;
  if(amt >= 5 && amt < 10) {
    tier = 5;
    valid = "30 MINS";
  } else if(amt >= 10 && amt < 15) {
    tier = 10;
    valid = "1 HOUR";
  } else if(amt >= 15 && amt < 25) {
    tier = 15;
    valid = "3 HOURS";
  } else if(amt >= 25 && amt < 35) {
    tier = 25;
    valid = "1 DAY";
  } else if(amt >= 35 && amt <50) {
    tier = 35;
    valid = "3 DAYS";
  } else {
    tier = 50;
    valid = "7 DAYS";
  }
  change = amt-tier;
  lcd.print("Voucher code:");
  lcd.setCursor(1, 1);
  lcd.print("885912 ");
  lcd.print(valid);
  printVoucher("885912", tier, valid, change, dateTime);
}

void printVoucher(String code, int amount, String valid, int change, String dateTime) {
  String tier = " (P" +String(amount) +".00)";
  printer.printBitmap(232, 82, VGWifi);
  printer.setSize('S');
  printer.println("");
  printer.justify('C');
  printer.setFont('C');
  printer.setSize('M');
  printer.println("Voucher Code:");
  printer.setSize('L');
  printer.println(code);
  printer.setSize('S');
  printer.println("");
  printer.setSize('M');
  printer.print("Valid for: ");
  printer.print(valid);
  printer.println(tier);
  printer.boldOff();
  printer.setSize('S');
  if(change != 0) {
    printer.print("Your change is: P");
    printer.print(change);
    printer.println(".00");
  }
  printer.println("Thank you for purchasing!");
  printer.print(dateTime);
  printer.println("\n");
  printer.setSize('L');
  printer.println(F("\n"));
  printer.sleep();
  delay(3000L);
  printer.wake();
  printer.setDefault();
}

Sorry for the long code and the question. Thank you for your responses!

You might edit your code and remove ssid + password string (or are these dummies)

Some quick points

bool firstCoinOn = true;
should be volatile

void IRAM_ATTR btnInterrupt()
should be shorter, clearing an LCD within an interrupt routine takes too much time.
Just set a flag to clear the display and handle that in loop()

1 Like

Can you post an annotated schematic? Also is there any magnetic devices involved such as solenoids, motors, etc? If so post a picture of your layout as you are having the problems.

Key Elements for the Schematic:

  1. Power Sources: Clearly show all power sources and their voltage levels.
  2. Ground Connections: Indicate all ground points and ensure they are consistent throughout the system.
  3. Components:
  • Motors, solenoids, or other magnetic devices.
  • Any sensors or ICs involved.
  1. Wire Lengths: Label any wire longer than 25mm (or 10 inches) as they could contribute to voltage drops or interference issues, particularly for signals or low-power components.
  2. Annotations: Include notes about the current ratings for wires, whether you are using shielded or unshielded cables, and how your power and signal wires are routed.
  3. Magnetic Devices: If you have motors or solenoids, they can introduce electromagnetic interference (EMI). Show where they are located and how they are wired, including any flyback diodes for inductive loads.

Once you have your schematic drawn, you can share it to better troubleshoot and diagnose your issue. Let me know if you need help with choosing a schematic tool or how to arrange the components in the layout!

1 Like

Hello Rob, I completely removed the LCD.clear in button interrupt, and the interrupts now only contain the following code:

void IRAM_ATTR coinInterrupt() {
  impulseCount++;
}

void IRAM_ATTR btnInterrupt() {
  static unsigned long lastExec = 0;
  unsigned long currentExec = millis();
  if(currentExec - lastExec > 500 && !btnDisabled) {
    btnPressed = true;
    btnDisabled = true;
    btnState++;
  }
  lastExec = currentExec;
}

Hello gilshutz, I'm currently working on the annotated schematic. Will post it here when its ready. Thank you!

For trouble shooting, I'd consider switching a led on in the pulse counter interrupt service routine and switching it off again after a short time in the loop(). This would identify problems with the coin counter. Something like this:

void IRAM_ATTR coinInterrupt() {
    digitalWrite( led, HIGH) ;
    ledOnAtMs = millis() ; // define global variable: volatile uint32_t;
    . . .
}

void loop() {
    if (digitalRead( led ) && millis() - ledOnAtMs > 100 ) digitalWrite( led, LOW) ;
    . . .
}

@billygarcia you were given good advice by @robtillaart. In this case it did not fix the problem, but it might have done and the changes should be kept anyway. The advice was given from a high level of experience and expertise, and for free, and you should not dismiss it in a disrespectful way as you did.

Thanks for this feedback, the interrupt routines are now short and therefor far more robust, and can be removed from the list of possible causes. Apparently it did not solve the problem, so further analysis / debugging is needed. Be aware that remote debugging such problems is nearly always an iterative process, so it might takes days (or weeks or longer).

Will review your code once more to see if I recognize possible causes.

1 Like

Oh no. I'm terribly sorry, English is not my first language and I had no intention of disrespecting anyone in this community. I had no idea that "needless to say" is rude. I'm terribly sorry.

1 Like

Thank you so much sir, I am terribly sorry for my previous response with the phrase needless to say as I had no idea it was rude because English is not my first language. I am currently working on the circuit diagram which will give a better view of my system. Thank you and again I apologize for my previous response.

Found another point of attention

 //Credit counter
  if (impulseCount == coinPulses[coinType]) {

The variable coinType is initialized to zero and never updated as far as I can tell.


Find below a minimal sketch that only looks at the amount of pulses you get from the CA.
It prints the pulses per coin on one line (you might add timestamps).

You need to configure the separatorTime to match the time between two coins.

Can you run this sketch and see if you get all pulses per coin.
Note: Not tested with hardware, it just compiles,
Note: see I have guarded the access to the impulseCounter variable.

const int COIN_PIN = 2;

volatile int impulseCount = 0;
int lastCount = 0;

//  separatorTime is the time between two coin pulses.
//  to be tuned.
const int separatorTime = 2000;
volatile uint32_t lastPulse = 0;


void IRAM_ATTR coinInterrupt()
{
  impulseCount++;
  lastPulse = millis();
}


void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);

  //  configure interrupt handler
  pinMode(COIN_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(COIN_PIN), coinInterrupt, RISING);
}


void loop()
{
  //  make a local copy while temporary disable the interrupts
  noInterrupts();
  int localCount = impulseCount;
  interrupts();

  if (lastCount != localCount)
  {
    if (lastCount == 0) Serial.println();
    Serial.print(localCount);
    lastCount = localCount;
  }

  if ((localCount > 0) && ((millis() - lastPulse) > separatorTime))
  {
    //  disable interrupts while resetting the counter.
    noInterrupts();
    impulseCount = 0;
    interrupts();
  }
}

Just a side issue but I'm not sure this interlock is necessary on an ESP8266 which is a single core 32 bit MCU. A 32bit operation should, therefore, be atomic. Certainly on a Uno etc. this would be needed.

    //  disable interrupts while resetting the counter.
    noInterrupts();
    impulseCount = 0;
    interrupts();
1 Like

Ok, understood. The phrase is not rude in all situations, but in the context you used it, the implication was that @robtillaart 's suggestions were obviously worthless and you already knew it was a waste of time to try them.

It would not be rude to use the phrase like this, for example:

Needless to say, my old pants no longer fitted me.

Again a diversion from the main topic. I once worked in an international firm with a headquarters in Sweden. I once posed a question to a colleague there which resulted in the answer "You should know ...". He meant "we are now informing you ...", as I later realised because this is how they express themselves. I interpreted it as "You should already have known ... and didn't". The lesson is never to assume deliberate offence, especially across a language/cultural boundary, unless there are other corroborating factors.

1 Like

Hello, I'm back! Sorry for the long wait, but here's the wiring diagram of my system:

I have a custom board (JCB Juanfi Custom Board) ordered online:
https://tinyurl.com/6dvhuh3n

Here's it's PIN allocation:

Here's a picture of the actual wiring:

And the video of it abruptly stopping from reading coin pulses:

Hope these help us guys! And, again thank you for your responses :slight_smile:

Why do I see lots of junk wire splices where there are color changes? Use ONE wire directly between connection points and one color for power, one color for ground and various colors for various signals. One color per signal.

2 Likes