Торговый автомат Arduino для мониторинга ввода монет в слот во время ожидания ввода пользователя

У меня есть проект под названием «Торговый автомат с луковой шкуркой». Функция должна быть такой: когда монета вставлена, импульсы от монетоприемника (что иногда неточно, поэтому я не использовал AttachInterrupt) записываются Arduino и отображаются пользователю как кредиты. затем, ожидая ввода монет, пользователю будет предложено выбрать продукт.

ЭКС. Сценарий 1: Пользователь бросает монету номиналом 1 цент, затем машина покажет пользователю свой кредит в 1 цент и попросит нажать кнопку. пользователь нажмет кнопку, затем машина включит двигатель на (X) секунд, чтобы выдать продукт, скажет спасибо, а затем снова перезагрузится.

ЭКС. Сценарий 2: Пользователь бросает монету номиналом 5 центов, затем машина покажет пользователю свой кредит в размере 5 центов и попросит нажать кнопку. затем пользователь снова бросит монету достоинством в 1 цент, поэтому машина отобразит свой кредит как 6 центов и снова попросит нажать кнопку. пользователь нажмет кнопку, затем машина включит двигатель на (6X) секунд, чтобы выдать продукт, скажет спасибо, а затем снова перезагрузится.

Вот код, который я написал до сих пор, но проблема в том, что при активации он записывает только первый цент/импульс, а затем продолжает ждать ввода пользователя для нажатия кнопки, даже если я брошу больше монет, он не засчитается вставлены новые монеты, всегда отображается только 1 цент / 1 кредит.

ОТРЕДАКТИРОВАНО: Целевого кредита нет, подойдет 1 цент. но если пользователь сбросит 2 цента, то после выбора пользователем продукта должны быть выданы 2 набора продуктов. Итак, цель заключается в том, что Arduino должен ждать ввода монет в слот и одновременно ожидать ввода пользователя.

#include <Wire.h>  // Поставляется с Arduino IDE, требуется для I2C
#include <LiquidCrystal_I2C.h>  //Вызов библиотеки LCD I2C

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);   // Устанавливаем адрес I2C ЖК-дисплея. Настраиваем передачу данных от микроконтроллера к ЖК-дисплею.

int coinPin = 2;            //Определяется как приемный штифт от монетоприемника.
int coinState = 0;          //Определяем статус coinPin
int credits = 0;            //Определим переменную для отображения кредита.
int delayTime = 0;          //Определить переменную задержки для количества продолжительность подачи листа луковой шкурки
int resetPin = 12;          //Определяем вывод сброса для сброса после завершения функции
int sensorPin = A0; // выбираем входной контакт для LDR
int sensorValue = 0; // переменная для хранения значения, поступающего с датчика
const int laserPin = 6;
const int shortSizeFeedTime = 5000;  //Продолжительность прохождения 2 штук луковой шкурки через устройство подачи
const int longSizeFeedTime = 7500;   //Продолжительность прохождения 3 штук луковой шкурки через устройство подачи
const int ledpin = 13;      //определяем вывод светодиода на выводе 13
const int ButtonS = 8;      //Определить кнопку для короткой луковой шкурки
const int ButtonL = 7;      //Определить кнопку для длинной луковицы
const int shortSize = 9;    //Определяем вывод для вывода двигателя shortSize
const int longSize = 10;    //Определяем вывод для вывода двигателя longSize

void setup()
{
  digitalWrite(resetPin, HIGH);
  delay(200);
  pinMode(coinPin, INPUT);     //Определяем функцию контакта кнопки и входного контакта.
  pinMode(resetPin, OUTPUT);  //Устанавливаем PIN-код сброса как ВЫХОД
  pinMode(shortSize, OUTPUT);
  pinMode(longSize, OUTPUT);
  pinMode(laserPin, OUTPUT);         //Лазерный PIN-код
  /*
    lcd.begin(16, 2);         //Планирование столбцов и строк. ЖК-дисплей
    lcd.setCursor(4,0);         //Устанавливаем начальную позицию курсора в позиции (4,0)
    lcd.print("WELCOME");      //Отображение текста на ЖК-дисплее
    lcd.setCursor(0,1);         //Устанавливаем начальную позицию курсора в позиции (0,1)
    lcd.print("PLZ  INSERT COIN");      //Отображение текста на ЖК-дисплее
  */
  Serial.begin(9600);  // Начинаем последовательное соединение с ПК

  lcd.begin(16, 2);  // инициализируем ЖК на 16 символов 2 строки, включаем подсветку
  pinMode(ButtonS, INPUT_PULLUP);
  pinMode(ButtonL, INPUT_PULLUP);
  digitalWrite(laserPin, HIGH);// используется в качестве лазерного датчика натяжного троса
  start(); //ПЕРЕЙТИ К ЗАПУСКУ ФУНКЦИИ
  //задержка(8000);
  // Подождите, а затем сообщите пользователю, что он может запустить Serial Monitor и ввести символы для
  // Отображать. (Установите для параметра «Последовательный монитор» значение «Без окончания строки»)
  //ЖК.очистить();
  //lcd.setCursor(0,0); //Начинаем с символа 0 в строке 0
}


void start()
{
  //-------- Напишем символы на дисплее ------------------
  // ПРИМЕЧАНИЕ. Позиция курсора: (CHAR, LINE) начинается с 0.
  lcd.clear();
  lcd.setCursor(4, 0); //Начинаем с символа 4 в строке 0
  lcd.print("WELCOME!");
  delay(1000);
  lcd.setCursor(0, 1);
  lcd.print(" ->Insert Coin<-");
  loop();
}


void loop()
{
  sensorValue = analogRead(sensorPin);//считываем статус LDR, подключенного к аналоговому выводу для лазерного растяжного троса
  coinState = digitalRead(coinPin);   //значение coinPin в coinState
  lcd.setCursor(0, 0);        //Устанавливаем начальную позицию курсора в позиции (0,0)
  while (analogRead(sensorPin) < 1000)
  {
    lcd.clear();
    lcd.print("PLS. REMOVE ANY");
    lcd.setCursor(0, 1);
    lcd.print("OBJECT FRM TRAY!");
    delay(2000);
    start();
  }
  if (coinState == LOW)         //Условие
  {
    credits += 1;          //Вычисляем значение переменной. С каждым разом оно будет увеличиваться. 5
    delay(500);                        //Расписание задержки 500мс.
    lcd.clear();            //Очистить весь ЖК-экран команды
    lcd.print("CREDIT:");         //Отображение сообщения на ЖК-дисплее
    lcd.setCursor(8, 0);        //Устанавливаем начальную позицию курсора в позиции (8,0)
    lcd.print(credits);            //Отображает значение переменной «кредит» на ЖК-экране.
    lcd.setCursor(12, 0);        //Устанавливаем начальную позицию курсора в позиции (8,0)
    lcd.print("PHP");          //Отображение текста на ЖК-дисплее
    lcd.setCursor(0, 1);
    lcd.print("PRESS TO PROCEED");

    //Ждем ввода кнопки от пользователя
    while (digitalRead(ButtonS) == HIGH || digitalRead(ButtonL) == HIGH) {

      //---------Сервопривод A----------------
      if (digitalRead(ButtonS) == LOW && sensorValue > 1000)  {
        lcd.clear();
        lcd.setCursor(4, 0);
        lcd.print("VENDING:");
        lcd.setCursor(0, 1);
        lcd.print("LONG ONIONSKIN");
        delayTime = credits * longSizeFeedTime;
        digitalWrite(longSize, HIGH);//Включаем двигатель питателя
        delay(delayTime); //время расчета для 2шт луковой шкурки
        digitalWrite(longSize, LOW);//Выключаем двигатель питателя
        lcd.clear();
        lcd.setCursor(3, 0);
        lcd.print("THANK YOU!");
        lcd.setCursor(0, 1);
        lcd.print("PLS. COME AGAIN!");
        credits = 0;//обнуляем кредиты
        delay(3000);
        digitalWrite(resetPin, LOW);//необязательно для активации PIN-кода сброса оборудования
        start();//Перейти к запуску функции, своего рода сброс
        break;//на самом деле такого никогда не происходит :)
      } // когда предмет выдан, выходим из цикла и возвращаемся к ожиданию монеты

      //---------Сервопривод B-------------
      else if (digitalRead(ButtonL) == LOW && sensorValue > 1000) {
        lcd.clear();
        lcd.setCursor(4, 0);
        lcd.print("VENDING:");
        lcd.setCursor(0, 1);
        lcd.print("SHORT ONIONSKIN");
        delayTime = credits * shortSizeFeedTime;//время расчета для 2шт луковой шкурки
        digitalWrite(shortSize, HIGH);//Включаем двигатель питателя
        delay(delayTime);
        digitalWrite(shortSize, LOW);//Выключаем двигатель питателя
        lcd.clear();
        lcd.setCursor(3, 0);
        lcd.print("THANK YOU!");
        lcd.setCursor(0, 1);
        lcd.print("PLS. COME AGAIN!");
        credits = 0;
        delay(3000);
        digitalWrite(resetPin, LOW);//необязательно для активации PIN-кода сброса оборудования
        start();//Перейти к запуску функции, своего рода сброс
        break;//Опять же, такого никогда не бывает :)
      }
    }//Завершить цикл while
  }//Конец цикла обнаружения монет
}//Завершить цикл void()

PS: Я где-то читал, что использование миллиса может помочь решить проблему двойного выполнения или использование правильного цикла while или switch-case. Я мало что знаю о том, как работают циклы while, do- while, for, switch.

, 👍1

Обсуждение

эти два сценария одинаковы, @jsotola

@jsotola для каждого сценария выпадала разная монета. расчет продукта также отличался из-за вставленной суммы, @EngrAbbas

Аббас и ребята, мне интересно, можно ли удалить код кнопки, затем заставить код ждать несколько секунд, затем он проверит, сбросил ли клиент 1 цент или 2 цента, а затем запустит конкретную команду для каждого значения? я только начал изучать программирование на ардуино. надеюсь скоро услышать от вас, ребята, @polu


2 ответа


Лучший ответ:

0

После нескольких исследований здесь представлен окончательный рабочий код и решение проблемы.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Константы

bool userSelect = false;
const int coinPin = 2;      // Определяется как приемный штифт от монетоприемника.
int coinState = 0;          // Определяем статус coinPin
volatile int credits = 0;   // Определим переменную для отображения кредита.
int delayTime = 0;          // Определить переменную задержки для количества продолжительности подачи луковой шкурки
int resetPin = 12;          // Определить вывод сброса для сброса после завершения функции
int sensorPin = A0;         // выбираем входной контакт для LDR
int sensorValue = 0;        // переменная для хранения значения, поступающего с датчика
const int laserPin = 6;
const int shortSizeFeedTime = 5000;  // Длительность прохождения 2pc OnionSkin через фидер
const int longSizeFeedTime = 7500;   // Длительность прохождения 3pc OnionSkin через фидер
const int ledpin = 13;      // Определить вывод светодиода на выводе 13
const int ButtonS = 4;      // Определить кнопку для короткого размера OnionSkin
const int ButtonL = 5;      // Определить кнопку для длинного размера OnionSkin
const int shortSize = 6;    // Определить вывод для вывода двигателя shortSize
const int longSize = 7;    // Определить вывод для вывода двигателя longSize

//Объявление контактов I2C для ЖК-дисплея
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

//Функция счетчика CoinInterrupt
void coinInterrupt() {

  // Каждый раз, когда монетоприемник посылает импульс, прерываем основной цикл, чтобы добавить 1 цент, и включаем светодиод
  credits += 1;
  digitalWrite(ledpin, HIGH);
  digitalWrite(ledpin, LOW);
}

//Функция Long OnionSkin Feeder
void longSkinServe() {
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("VENDING:");
  lcd.setCursor(0, 1);
  lcd.print("LONG ONION SKIN");
  delayTime = credits * longSizeFeedTime;
  digitalWrite(longSize, HIGH);
  delay(delayTime);
  digitalWrite(longSize, LOW);
  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("THANK YOU!");
  lcd.setCursor(0, 1);
  lcd.print("PLS. COME AGAIN!");
  delay(3000);
  digitalWrite(resetPin, LOW);
  credits = 0;
  userSelect = false;
}

//Короткая функция OnionSkin Feeder
void shortSkinServe() {
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("VENDING:");
  lcd.setCursor(0, 1);
  lcd.print("SHORT ONION SKIN");
  delayTime = credits * shortSizeFeedTime;
  digitalWrite(shortSize, HIGH);
  delay(delayTime);
  digitalWrite(shortSize, LOW);
  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("THANK YOU!");
  lcd.setCursor(0, 1);
  lcd.print("PLS. COME AGAIN!");
  delay(3000);
  digitalWrite(resetPin, LOW);
  credits = 0;
  userSelect = false;
}

void setup() {
  Serial.begin(9600);  // Начинаем последовательное соединение с ПК
  // устанавливаем количество столбцов и строк ЖК-дисплея:
  lcd.begin(16, 2);  // инициализируем ЖК на 16 символов 2 строки, включаем подсветку
  lcd.print("STARTING UP...");
  attachInterrupt(digitalPinToInterrupt(coinPin), coinInterrupt, RISING);
  pinMode(ledpin, OUTPUT);
  pinMode(resetPin, OUTPUT);  //Устанавливаем PIN-код сброса как ВЫХОД
  pinMode(shortSize, OUTPUT);
  pinMode(longSize, OUTPUT);
  // pinMode(laserPin, OUTPUT); //Лазерный PIN-код
  pinMode(ButtonS, INPUT_PULLUP);
  pinMode(ButtonL, INPUT_PULLUP);
  delay(2000);

}

// Основной цикл
void loop() {
  if (credits == 0) {//еще не запущено, кредитов еще нет
    lcd.clear();
    lcd.print("ONION SKIN VENDO");
    lcd.setCursor(0, 1);
    lcd.write("-[INSERT COINS]-");
    delay(3000);
    lcd.clear();
    lcd.print("-[INSERT COINS]-");
    lcd.setCursor(1, 1);
    lcd.write("P1 or P5 ONLY");
    Serial.println("No Coin Inserted Yet...Waiting");
    delay (3000);

    //---Ждем монеты/кредита-----------
    while (credits > 0 && userSelect == false) { //если монета вставлена, остается в цикле включения сервопривода

      lcd.clear();            //Очистить весь ЖК-экран команды
      lcd.print("CREDITS:");         //Отображение сообщения на ЖК-дисплее
      lcd.setCursor(9, 0);        //Устанавливаем начальную позицию курсора в позиции (8,0)
      lcd.print(credits);            //Отображает значение переменной «кредит» на ЖК-экране.
      lcd.setCursor(12, 0);        //Устанавливаем начальную позицию курсора в позиции (8,0)
      lcd.print("PHP");          //Отображение текста на ЖК-дисплее
      lcd.setCursor(0, 1);
      lcd.print("PRESS TO PROCEED");
      Serial.println("Credits:");
      Serial.print(credits);
      if (digitalRead(ButtonS) == HIGH || digitalRead(ButtonL) == HIGH) {

        if (digitalRead(ButtonS) == LOW)  {
          //выполняем двигатель короткого размера
          userSelect = true;
          shortSkinServe();
        }
        else if (digitalRead(ButtonL) == LOW)
        {
          userSelect = true;
          longSkinServe();
        }
      }
      delay (80); // для цикла while управление скоростью обновления ЖК-дисплея
    }

  }


}

Также вот принципиальная схема.

Некоторые могут сказать, что кодирование неэффективно по сравнению с использованием нескольких операторов if else..if, но это решает проблему. Ура!

,

2

Сделайте это конечным автоматом:

Исходное состояние: режим ожидания, в лотке ничего нет, кредитов нет

Если вводится монета, то она подсчитывает кредиты и отображает ее. (Всегда должно быть правдой)

Если нажата кнопка, то что-то будет продано (и кредит вычтен) только в том случае, если на балансе достаточно средств и лоток пуст. В противном случае в течение нескольких секунд будет отображаться ошибка.

Отображение ошибки можно выполнить, установив глобальное значение, а также время начала и продолжительность:

long credit;
enum{noError, insufficientCredit, trayError, thankyou};
int error;
unsigned long messageTimestamp;
unsigned long messageDuration;


void updateLCD(){
    if(millis() - messageTimestamp > messageDuration) 
        error=noError;
    switch(error){
    case noError: break;

    case insufficientCredit: 

        //показать ошибку недостаточного кредита
        break;
    case trayError: 

        //показать ошибку лотка
        break;
    case thankyou: 

        //показываем сообщение с благодарностью
        break;
    }

    if(credit == 0){
        //показать режим привлечения
    }else{
        //показать кредит
    }
}

Вы можете сделать что-то подобное для двигателя:

bool motorOn;
unsigned long motorTimestamp;
unsigned long motorDuration;

if(motorOn && millis() - motorTimestamp> motorDuration){ 
    motorOn = false;
    digitalWrite(shortSize, LOW);//Выключаем двигатель питателя
}

Таким образом вы также можете предотвратить повторный запуск торгового автомата при работающем двигателе, выполнив дополнительную проверку motorOn.

,

Я согласен с идеей государственной машины. Нарисуйте сетку, показывающую все возможные состояния (например, нет денег, есть деньги, ожидание кнопки, выдача товара и т. д.). Затем все возможные действия (например, добавление денег, нажатие кнопки) и определение того, что нужно сделать в каждом состоянии для каждого возможного действия., @Nick Gammon

На самом деле суммы целевого кредита нет, базовый кредит составляет 1 цент. 1 цента достаточно для запуска двигателя подачи/выдачи продукта. если вы добавите 2 цента, то у вас будет 2 кредита и получится 2 набора продуктов. проблема в коде, который я сделал, распознается/подсчитывается только 1 цент, это похоже на то, когда он обнаруживает импульс на coinPin, он подсчитывает первый кредит, а затем переходит к следующей функции, чтобы дождаться ввода пользователя. мне нужно, чтобы он все еще ждал обнаружения импульса монеты, одновременно ожидая ввода пользователя., @EngrAbbas

@ratchet Если вы также любезно включите модификацию/исправление в основной код. Как я уже сказал, я мало знаком с программированием на C++. Я также не знаю, в какой части кода вызывать функцию updateLCD(), а также где вставить/заменить опубликованное вами решение., @EngrAbbas

@ratchet Freak Привет, есть новости? Я все еще жду и любопытствую :) Спасибо., @EngrAbbas

@EngrAbbas, если вы хотите масштабировать количество выдаваемого продукта, вы можете соответствующим образом масштабировать продолжительность двигателя., @ratchet freak

@ratchetfreak Я изменил код, но он все равно не работает. вот это я сделал. https://pastebin.com/YPR48f7s Я думаю, проблема в том, что я не знаю, куда вставить ваш код. У меня нет никаких знаний о том, как работает millis(), я только что прочитал в Интернете, что это то, что вы используете для многозадачности/делания двух разных вещей одновременно. пожалуйста помоги, @EngrAbbas