Pass the pulse from coin acceptor to coffee vending machine using arduino esp32

I have a coin operated coffee vending machine which get the pulses from the coin acceptor.
My project is to make the Arduino esp32 to communicate with the coffee vending machine by transfer the pulses from the coin acceptor to the coffee machine.
I followed this link to control and monitor the coin acceptor and doing some tricks but I need the code to transfer the pulses to the coffee vending machine.

your support is appreciated.

Why do you need a micro in the middle?

A piece of wire might suffice.

Or is there more you’re not telling ?

1 Like

I want to collect information like daily sales and count number of coins inserted and save the data to a memory or send it via httpclient to api.
and you're right about (A piece of wire might suffice) it's only one wire that need to be connected between the esp32 and the coffee machine but I don't know how to make the correct coding for this.

Any help guys?

This forum is for helping people with their own code, not writing it for them. Post what you have so far.

If you prefer to have someone write it for you, head over to the Jobs and Paid Consultancy section where you can negotiate a fee for someone to do it.

1 Like

Here is the code, what I need is how to send the impulse to the coffee machine, check the ***** code.

#include <Arduino.h>
	#include <EEPROM.h>
	#include <LiquidCrystal_I2C.h>
	const int coinPin = 2;
	volatile int impulsCount = 0;
	int coinType = 0;
	float totalAmount = 0.0;
	float coinValue;	
	const int coinPulses[] = {1, 2, 5, 10, 10};
	const float coinValues[] = {1.0, 2.0, 5.0, 10.0, 10.0};
	int lookup(int pulses) {
	  for (int i = 0; i < sizeof(coinPulses) / sizeof(coinPulses[0]); i++) {
	    if (pulses == coinPulses[i]) {
	      return coinValues[i];
	    }
	  }
	  return -1;
	}
	
	LiquidCrystal_I2C lcd(0x27, 16, 2);
	void setup() {
	  Serial.begin(9600);
	  attachInterrupt(digitalPinToInterrupt(coinPin), coinInterrupt, FALLING);
	  totalAmount = 0.0;
	  EEPROM.write(0, 0);
	  lcd.init();
	  lcd.clear();
	  lcd.backlight();
	}
	void coinInterrupt() {
	  impulsCount++;
	}
	
	void loop() {
	  if (impulsCount == coinPulses[coinType]) {
	    coinValue = lookup(impulsCount);
	    if (coinValue > 0) {
	      totalAmount += coinValue;
	    }
	    delay(100);
	    Serial.print("Total Amount: ");
	    Serial.println(totalAmount, 2);
            ///// *****Need a code here to send the impuls to the coffee machine*****
	    impulsCount = 0;
	    lcd.setCursor(0, 0);
	    lcd.print("Total Balance: ");
	    lcd.setCursor(0, 1);
	    lcd.print(totalAmount, 2);
	  }
	}

What is the coffee machine expecting in the way of pulses?

1 Like

To receive a pulses which is received or catched from the coin acceptor in the esp32 module.

It sounds like what you are doing is reading in pulses from the coin acceptor, doing your data analysis and storage, then recreating the pulses as output to the coffee machine. Why not just allow the pulses from the coin acceptor to the coffee machine and read the pulses as they go by? No need to intercept the pulses, just observe them.

1 Like

I know that I can do data analysis like what you say but I want to prompt a question to the person who put the coins and to force him to answer the question using my Arduino project and once he answered then I will allow pulses to proceed to the coffee machine.
I hope that you got my idea.

The questions are:

  • Just one pulse or a sequence?

  • Duration of the pulse/s

  • Active LOW or active HIGH pulse/s

  • Voltage required: 3.3V, 5V or anything else?

  • Do you need galvanic isolation?

BTW The sketch seems to be a quite rudimentary example (also on the linked page):

  • EEPROM.write() in setup but data never read or used
  • coinType set to zero in declaration but never changed (which leads to only using the first entry of the array coinPulses)
  • totalAmount set to zero in setup() but never reset
  • etc.

Could you test the sketch with the coin acceptor?

Good luck!

1 Like

That's putting it nicely. I'd say it's crap.

  • No differentiation between the coin types, even though it defines them. A coin that causes 10 pulses is registered as ten 1-pulse coins instead.
  • No accounting for the maximum total time required to receive all the pulses for any of the coins.
  • Poor handling of the volatile impulsCount variable. It needs a critical section in the main loop() function to avoid missing pulses (current code sets it to zero even though pulses might have occurred during processing of the current pulse).

I am really curious if the sketch has been tested with the hardware and how that turned out.

There is still some way to go...

I've tested it using a Function/Arbitrary Waveform Generator to simulate the coin acceptor.

I refered to the CH-926 Manual to set the pulse parameters.
Note that the CH-926 is the coin acceptor used in the link - we have no confirmation that malekses has the same model - there are lots of similar looking but differant ones available.

The pulse width is selectable via a switch on the coin acceptor - fast: 20ms , medium: 50ms , slow: 100ms.
The number of pulses for each coin type is programmable from 1 to 50 pulses.

I used 50 x 20ms pulses and the result was:


It counted 17 out of 50 pulses.

After removing the delay(100) in loop, it would count the full 50 pulses.

I strived to emulate the MCA and write some code that should be more complete than the example. The function(s) to ask questions and afterwards control the coffee machine should be easy to implement ... if you can provide answers to my post #11.

Feel free to check it out on Wokwi:

Main sketch:

/*

  Forum: https://forum.arduino.cc/t/pass-the-pulse-from-coin-acceptor-to-coffee-vending-machine-using-arduino-esp32/1178550
  Wokwi: https://wokwi.com/projects/378822992815664129

*/

#include "MCAEmulation.h"

#include <LiquidCrystal_I2C.h>
#define I2C_ADDR    0x27
#define LCD_COLUMNS 20
#define LCD_LINES   4
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);

static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED;

constexpr byte resetAmountPin = 5;
constexpr byte coinPin = 2;
constexpr byte CoinTypes = 4;
constexpr int coinPulses[CoinTypes] = {1, 2, 5, 10};
constexpr float coinValue[CoinTypes] = {1.0, 2.0, 5.0, 10.0};

volatile int impulseCountISR = 0;
volatile unsigned long lastPulseISR;

constexpr unsigned long WaitAfterLastPulseDetected = 50;
int impulseCount = 0;
unsigned long lastPulse;

int coinType = 0;
float totalAmount = 0.0;

void setup() {
  Serial.begin(115200);
  pinMode(resetAmountPin, INPUT_PULLUP);
  pinMode(coinPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(coinPin), coinInterrupt, FALLING);
  lcd.init();
  lcd.backlight();
  Serial.println("Start");
  printAmount();
}

void loop() {
  // Only required for testing START
  MCAEmulation();
  // Only required for testing END

  if (coinInserted()) {
    totalAmount += lookup(impulseCount);
    printAmount();
  }
  checkResetAmount();
  
}

void printAmount() {
  Serial.print("Total Amount: ");
  Serial.println(totalAmount, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Total Balance: ");
  lcd.setCursor(0, 1);
  lcd.print(totalAmount, 2);
}

int lookup(int pulses) {
  for (int i = 0; i < CoinTypes; i++) {
    if (pulses == coinPulses[i]) {
      return coinValue[i];
    }
  }
  return 0;
}

// The ISR should read
// void IRAM_ATTR coin interrupt()
// That's fixed in the version linked below
void  coinInterrupt() {
  impulseCountISR++;
  lastPulseISR = millis();
}

void getISRData() {
  taskENTER_CRITICAL(&my_spinlock);
  impulseCount = impulseCountISR;
  lastPulse    =  lastPulseISR;
  taskEXIT_CRITICAL(&my_spinlock);
}

void clearISRData() {
  taskENTER_CRITICAL(&my_spinlock);
  impulseCountISR = 0;
  taskEXIT_CRITICAL(&my_spinlock);
}

boolean coinInserted() {
  boolean result = false;
  getISRData();
  if (impulseCount > 0 && millis() - lastPulse > WaitAfterLastPulseDetected ) {
    clearISRData();
    result = true;
  }
  return result;
}

void checkResetAmount() {
  static byte state = HIGH;
  static byte lastState = HIGH;
  static unsigned long lastChange;
  byte actState = digitalRead(resetAmountPin);
  if (actState != lastState) {
    lastChange = millis();
    lastState = actState;
  }
  if (actState != state && millis() - lastChange > 30) {
    state = actState;
    if (!state) {
      totalAmount = 0.0;
      printAmount();
    }
  }
}

and a simple MCA (Multi Coin Acceptor) Emulation

#pragma once

struct coinStruct {
  byte Pin;
  byte State;
  byte lastState;
  unsigned long lastChange;
  int Pulses;
};

constexpr byte noOfMyCoins = 4;
coinStruct myCoins[noOfMyCoins] = {
  {23, HIGH, HIGH, 0,  1},
  {19, HIGH, HIGH, 0,  2},
  {18, HIGH, HIGH, 0,  5},
  {15, HIGH, HIGH, 0, 10}
};

constexpr byte coinOut = 4;
unsigned long lastPulseTime;
int coinOutCount = 0;
boolean pmSet = false;

void doCoinOut() {
  if (coinOutCount > 0 && millis() - lastPulseTime > 2) {
    lastPulseTime = millis();
    coinOutCount--;
    digitalWrite(coinOut, LOW);
    delay(1);
    digitalWrite(coinOut, HIGH);
  }
}


boolean buttonDown(coinStruct & aCoin) {
  byte bState = digitalRead(aCoin.Pin);
  if (bState != aCoin.lastState) {
    aCoin.lastState = bState;
    aCoin.lastChange = millis();
  }
  if (bState != aCoin.State && millis() - aCoin.lastChange > 30) {
    aCoin.State = bState;
    if (bState == HIGH) return true;
  }
  return false;
}

void MCASetup() {
  if (!pmSet) {
    for (int i = 0; i < noOfMyCoins; i++) {
      pinMode(myCoins[i].Pin, INPUT_PULLUP);
    }
    digitalWrite(coinOut,HIGH);
    pinMode(coinOut, OUTPUT);
    pmSet = true;
  }
}

void MCAMachine() {
  for (int i = 0; i < noOfMyCoins; i++) {
    if (buttonDown(myCoins[i]) && coinOutCount == 0) {
      coinOutCount = myCoins[i].Pulses;
      /*
      Serial.print("Coin Type ");
      Serial.print(i+1);
      Serial.print("\tPulses ");
      Serial.println(coinOutCount);
     */ 
    }
  }
  doCoinOut();
}


void MCAEmulation() {
  MCASetup();
  MCAMachine();
}

I

[Edit]: The timing of the MACEmulation and the value of WaitAfterLastPulseDetected may have to be adjusted to those mentioned by @JohnLincoln in post #13!

[Edit 2]: I recognized I forgot the IRAM_ATTR attribute for the ISR and added it now on Wokwi in a new version (for some reason I had to move the ISR in front of setup() as it otherwise throws an error when the attribute is inserted...).

Nice job!

Original loop:

	void loop() {
	  if (impulsCount == coinPulses[coinType]) {
	    coinValue = lookup(impulsCount);
	    if (coinValue > 0) {
	      totalAmount += coinValue;
	    }
	    delay(100);
	    Serial.print("Total Amount: ");
	    Serial.println(totalAmount, 2);
            ///// *****Need a code here to send the impuls to the coffee machine*****
	    impulsCount = 0;
	    lcd.setCursor(0, 0);
	    lcd.print("Total Balance: ");
	    lcd.setCursor(0, 1);
	    lcd.print(totalAmount, 2);
	  }
	}

Let us look at the data:

  • coinType is set to 0 and never changes
  • Therefore coinPulses[coinType] is always returning the first array value = 1, we can substitute it by constant 1.
  • Hence lookup(impulsCount) will always be lookup(1) and will return always 1.0. We can substitute lookup(1) by 1.0.

If we reduce loop() now to the relevant parts and ignore that the value of impulsCount may be changed at any time by the ISR it looks like this:

void loop() {
	  if (impulsCount == 1) {
	    totalAmount += 1.0;
   	    delay(100);
	    impulsCount = 0;
	  }
	}

Functional description:

  • If a first (and only one!) pulse has been detected
    • totalAmount will be increased by 1
    • for 100 msec the sketch will ignore further pulses
    • after the delay impulsCount will be set to zero

This way one may count the number of coins but not the values that are coded in the number of pulses (unless the time between two pulses exceeds 100 msec ...).

That should explain why you got all 50 pulses after removing the delay ... :wink:

An ESP32 Critical Section using spinlocks is kind of a big gun with relatively high overhead. If you're only accessing the ISR data from a single core, the Arduino noInterrupts() function works just as well with lower overhead.

I think you're right, just wanted to be on the (very) safe side ...

BTW I forgot the IRAM_ATTR but fixed that in the newer Wokwi version and have put a comment in the sketch listed above. Wokwi did no longer recognize the function declaration after inserting the attribute when the declaration was behind setup() (where attachInterrupt() is called). Had to move it before setup() is declared.

Thanks for your post and please feel free to improve the sketch!

Here is the answers:

  • Just one pulse or a sequence?
    Any possible way.
  • Duration of the pulse/s:
    let is say 50ms.
  • Voltage required: 3.3V, 5V or anything else?
    Actually I don't know the voltage of the pulse wire that connected between the coin acceptor and coffee machine.
  • Do you need galvanic isolation?
    I'm not sure, but while the coin acceptor connected directly to the coffee machine so I think no need.

Thank you guys for the support.
really appreciated and every single reply is added value for me.
My question for this topic not yet answered :sleepy:

Suppose your code in post #16 is correct, I still need to know what I have to send to the coffee machine.
Anyone can help?