Parquímetro

Autores: Álvaro Sánchez-Paniagua Ríos |Adrián Basagoitia Blázquez | Mario Magaña Agudo

Introducción: ideas y objetivos

En primer lugar se ha de explicar que tuvimos varias ideas para la realización del proyecto, todas ellas eran válidas, pero debido a que no teníamos experiencia ni conocimientos previos sobre sistemas empotrados o sistemas similares a Arduino decidimos escoger un proyecto con el que todos los integrantes hubiésemos interactuado y tuviésemos un conocimiento previo, por lo que escogimos una máquina expendedora de “tickets” asociada a un espacio de estacionamiento de vehículos o parking. El concepto es general y prácticamente la totalidad de las personas ha interactuado con una máquina de este estilo en algún momento de su vida, desde máquinas expendedoras de comida, máquinas para comprar billetes de transporte público, máquinas expendedoras del ticket para saber cuándo es la cita en un centro médico o como es nuestro caso para la expedición de un ticket en un parking o un parquímetro en la vía publica en lugares como en centro de Madrid.


Asimismo nuestro enfoque fue algo parecido al mundo laboral, donde en una empresa en la que te encuentres trabajando recibas por ejemplo, un lunes un proyecto que debes implementar en 3 o 6 meses de un sistema y tu supervisor te diga a ti o al grupo de trabajo en el que te encuentres que el cliente va a venir el miércoles o el viernes y hay que tener algo para enseñarle. En términos generales un primer prototipo básico y con algunas funcionalidades básicas que estén recogidas durante la especificación.
El proyecto en un principio iba a estar orientado de menos a más, es decir implementar lo más básico y después añadir funcionalidades más complejas, pero esto no ha sido del todo un camino fácil y hemos tenido que “cortar” alguna de las funcionalidades que habíamos pensado, pero esto se detallara en los subsiguientes apartados.

Materiales y presupuesto

Los materiales utilizados en el proyecto han sido:

  • 1 x Placa Arduino Uno R3.
  • 1 x Pantalla LCD 16×2.
  • 4 x TRCT5000 (Sensores Infrarrojos).
  • 1 x Placa de prototipado.
  • 2 x Botones.
  • 1 x Modulo de controlador de motos paso a paso ULN2003.
  • 1 x Motor paso a paso. – 4 x Resistencias de 1KΩ. – 1 x Resistencia de 220Ω.
  • Cableado diverso
  • Fragmentos de madera y cartón diversos.

Todo estaba incluido en el kit excepto:

ElementoPrecio (€)Descripción de compra
Infrarrojos10Comprados en Amazon
Deslizadores5Comprados en Aliexpress
Pegamento, elementos de fabricación y otros3Comprado en tiendas varias
TOTAL18
Implementación teórica en tinkercat.

Implementación software

En esta sección se explicará el código implementado para el proyecto, y su funcionamiento, se mostrarán algunos detalles del código, la totalidad del código se puede consultar en el archivo .ino adjuntado con este documento.
Esta sección se dividirá en declaración de variables, configuración y bucle cíclico.

/* ***** Import ***** */
#include <LiquidCrystal.h>
#include <Stepper.h>

/* ***** Variable declaration ***** */
// Buttons
int acceptButtonState = 0;
int rejectButtonState = 0;

// Hours related
const float hourCost = 0.5;
int sliderValue = 0; 
int hours = 0;
float cost = 0;
float totalCost = 0;

// Coin related
int IRbadCoinState  = 0;
int IR50CentCoinState = 0;
int IR1EuroCoinState  = 0;
int IR2EuroCoinState  = 0;

// Timers
int timerHours = 0;
int timerCoins = 0;

// Booleans
bool booleanHoursAccept = false;
bool booleanHoursReject = false;
bool booleanCoinsAccept = false;
bool booleanCoinsReject = false;

/* *** Pin *** */
// Pin IR
const int pinBadCoin    = A2;
const int pin50CentCoin = 10;
const int pin1EuroCoin  = A1;
const int pin2EuroCoin  = 11;

// Pin LCD
const int  rs = 7;
const int  en = 6;
const int  d4 = 2;
const int  d5 = 3;
const int  d6 = 4;
const int  d7 = 5;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

// Slider
const int slider = A0;

// Buttons
const int rejectButton = A4;
const int acceptButton = A5;

// Stepper engine
const int stepsPerRevolution = 300;
const int rolePerMinute = 15;
const int engine1 = 8;
const int engine2 = 9;
const int engine3 = 12;
const int engine4 = 13;
Stepper engine(stepsPerRevolution, 8, 12, 9, 13);

/* ***** Configuration ***** */
void setup() {  
  // Serial port initialization
  Serial.begin(9600);

  // Engine
  engine.setSpeed(rolePerMinute);
  
  // Pin declaration
  pinMode(rs, OUTPUT);
  pinMode(en, OUTPUT);
  pinMode(d4, OUTPUT);
  pinMode(d5, OUTPUT);
  pinMode(d6, OUTPUT);
  pinMode(d7, OUTPUT); 
  pinMode(engine1, OUTPUT); 
  pinMode(engine2, OUTPUT); 
  pinMode(engine3, OUTPUT); 
  pinMode(engine4, OUTPUT); 

  pinMode(pinBadCoin, INPUT);
  pinMode(pin50CentCoin, INPUT);
  pinMode(pin1EuroCoin, INPUT);
  pinMode(pin2EuroCoin, INPUT);
  pinMode(slider, INPUT);
  pinMode(rejectButton, INPUT); 
  pinMode(acceptButton, INPUT);


  // Screen initialization
  lcd.begin(16, 2);

} // End Setup

/* *****  Loop ***** */
void loop() {
  // Clean variable values
  sliderValue = 0; 
  acceptButtonState = 0;
  rejectButtonState = 0;
  IRbadCoinState  = 0;
  IR50CentCoinState = 0;
  IR1EuroCoinState  = 0;
  IR2EuroCoinState  = 0;
  hours = 0;
  cost = 0;
  totalCost = 0;

  timerHours = 0;
  timerCoins = 0;

  booleanHoursAccept = false;
  booleanHoursReject = false;
  booleanCoinsAccept = false;
  booleanCoinsReject = false;


  // Clear LCD text and turn off the LCD.
  lcd.clear();
  lcd.noDisplay();
  
  /* ***** "Sleep Mode" ***** */
  while(acceptButtonState <= 500 && rejectButtonState <= 500){
    acceptButtonState = analogRead(acceptButton);
    rejectButtonState = analogRead(rejectButton);
    delay(500);
  }
  
  /* ***** Welcome Message ***** */
  // Turn On LCD and clear variables of buttons
  lcd.display();
  acceptButtonState = 0;
  rejectButtonState = 0;
  

  /* Welcome Message */
  lcd.setCursor(0, 0);
  lcd.print("Bienvenido");

  delay(2000);


  /* ***** Hours ***** */
  // Hours Welcome Message
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Elija num horas");
  lcd.setCursor(0, 1);
  lcd.print("con deslizador,");
  
  delay(3000);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("y pulse boton");
  lcd.setCursor(0, 1);
  lcd.print("aceptar.");
  delay(3000);

  /* Select Hours */
  while(booleanHoursAccept != true && booleanHoursReject != true){
    while(acceptButtonState <= 500 && rejectButtonState <= 500 && timerHours <= 200){
      hours = 0;
      sliderValue = analogRead(slider);
      lcd.clear();
      lcd.setCursor(0, 0);

      if(sliderValue <= 163){
        hours = 1;
        lcd.print("Horas: 1");
      }else if(sliderValue > 163 && sliderValue <= 346){
        hours = 2;
        lcd.print("Horas: 2");
      }else if(sliderValue > 346 && sliderValue <= 509){
        hours = 3;
        lcd.print("Horas: 3");
      }else if(sliderValue > 509 && sliderValue <= 693){
        hours = 4;
        lcd.print("Horas: 4");
      }else if(sliderValue > 693 && sliderValue <= 856){
        hours = 5;
        lcd.print("Horas: 5");
      }else{// > 856 && <= 1023
        hours = 6;
        lcd.print("Horas: 6");
      }
     
      // Read state of the buttons
      acceptButtonState = analogRead(acceptButton);
      rejectButtonState = analogRead(rejectButton);
      
      delay(100);
      timerHours += 1;
    } // End Inner while

    // Accept operation
    if(acceptButtonState > 500 && rejectButtonState <= 500){
      booleanHoursAccept = true;
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Horas: ");
      lcd.print(hours);
      lcd.setCursor(0, 1);
      lcd.print("Precio: ");
      cost = hourCost*hours;
      totalCost = hourCost*hours;
      lcd.print(cost);

    // Reject Operation
    }else if((acceptButtonState < 500 && rejectButtonState >= 500) || (timerHours > 200)){
      booleanHoursReject = true;
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Operacion");
      lcd.setCursor(0, 1);
      lcd.print("Cancelada");

      engine.step(-stepsPerRevolution);
      engine.step(stepsPerRevolution);
      delay(4000);

    // Both buttons pressed
    }else if(acceptButtonState > 500 && rejectButtonState > 500){
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Pulse solo");
      lcd.setCursor(0, 1);
      lcd.print("un botón");
      timerHours = 0;
    }

    delay(5000);
    acceptButtonState = 0;
    rejectButtonState = 0;
  } // End outer While
    

  /* ***** Coins ***** */
  // Welcome Message
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Introduzca");
  lcd.setCursor(0, 1);
  lcd.print("Monedas");

  while(booleanCoinsAccept != true && booleanCoinsReject != true && booleanHoursAccept){
    while(rejectButtonState == 0 && cost > 0 && timerCoins <= 200){
      // Clean value of variables
      IRbadCoinState  = 1;
      IR50CentCoinState = 1;
      IR1EuroCoinState  = 1;
      IR2EuroCoinState  = 1;
      rejectButtonState = 0;

      // Read new values
      IRbadCoinState    = analogRead(pinBadCoin);
      IR50CentCoinState = digitalRead(pin50CentCoin);
      IR1EuroCoinState  = analogRead(pin1EuroCoin);
      IR2EuroCoinState  = digitalRead(pin2EuroCoin);
      
      if(IRbadCoinState <= 100){
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Moneda no");
        lcd.setCursor(0, 1);
        lcd.print("Reconocida");
        delay(2000);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Total: ");
        lcd.print(totalCost);
        lcd.setCursor(0, 1);
        lcd.print("Faltante: ");
        lcd.print(cost);
        delay(2000);
        timerCoins = 0;

      }else if(IR50CentCoinState == LOW){
        cost = cost - 0.50;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Total: ");
        lcd.print(totalCost);
        lcd.setCursor(0, 1);
        lcd.print("Faltante: ");
        lcd.print(cost);
        delay(2000);
        timerCoins = 0;

      }else if(IR1EuroCoinState <= 100){
        cost = cost - 1;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Total: ");
        lcd.print(totalCost);
        lcd.setCursor(0, 1);
        lcd.print("Faltante: ");
        lcd.print(cost);
        delay(2000);
        timerCoins = 0;

      }else if(IR2EuroCoinState == LOW){
        cost = cost - 2;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Total: ");
        lcd.print(totalCost);
        lcd.setCursor(0, 1);
        lcd.print("Faltante: ");
        lcd.print(cost);
        delay(2000);
        timerCoins = 0;

      }else{
        // No coin introduced
        timerCoins += 1;
      }

      delay(100);
      rejectButtonState = analogRead(rejectButton);
    } // End inner while coins

    // Reject Operation
    if(rejectButtonState >= 500 || timerCoins > 200){
        booleanCoinsReject = true;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Operacion");
        lcd.setCursor(0, 1);
        lcd.print("Cancelada");
    
    // Accept operation
    }else if(cost <= 0){
        booleanCoinsAccept = true;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Pago");
        lcd.setCursor(0, 1);
        lcd.print("Completado");
    }
    delay(2000);
  }// End outer while coins

  /* Move Engine & Drop token */
  if(booleanCoinsAccept){
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Expidiendo");
    lcd.setCursor(0, 1);
    lcd.print("ficha");
    delay(2000);

    engine.step(stepsPerRevolution);
    delay(2000);
    engine.step(-stepsPerRevolution);
    delay(2000);
    engine.step(-stepsPerRevolution);
    delay(2000);
    engine.step(stepsPerRevolution);
    delay(4000);

  }else if(booleanCoinsReject){
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Devolviendo");
    lcd.setCursor(0, 1);
    lcd.print("Dinero");

    engine.step(-stepsPerRevolution);
    delay(400);
    engine.step(stepsPerRevolution);
    delay(4000);
  }
} // End Loop

Casos de uso

Los casos de usos que hemos contemplado son:

  • Operación correcta: El usuario “despertará” al sistema, elegirá un número de horas, introducirá el dinero y recibirá un “ticket”.
  • Operación cancelada: El usuario cancelará la operación seleccionando el número de horas o introduciendo el dinero. En cualquiera de los casos el sistema devolverá cualquier dinero introducido.
  • Tiempo límite: Un usuario puede decidir abandonar la operación e irse físicamente sin cancelar la operación en el sistema. El sistema tiene un tiempo límite después del que cancelará automáticamente la operación y devolverá el dinero introducido.

Problemas encontrados

Durante la implementación del proyecto hemos tenido diversos problemas y/o dificultades:

  • Numero de pines: Debido a la naturaleza de la placa Arduino UNO los pines son limitados y algunas de las cosas que teníamos pensadas como la introducción de una segunda pantalla LCD o un display de 7 segmentos para mostrar las horas, o un segundo motor tuvimos que descartarlo.
  • Clasificador de monedas: En un primer momento no sabíamos cómo clasificar las monedas, buscando por internet comprobamos que había distintos modos de hacerlo, por diámetro, por peso, etc… y de distintas maneras, por caída vertical, deslizando por una rampa, etc…
    La opción que elegimos fue clasificar las monedas a través de una rampa por diámetro, en un principio queríamos haber podido clasificar todas y cada una de las monedas, de céntimo o de euros pero tuvimos que desistir debido a que se necesitaban demasiados pines, como solución restringimos el uso de monedas 50 céntimos, 1 euro y 2 euros, además de poder saber si se ha introducido una moneda que no es de cualquiera de los tipos anteriores.
    En cuanto a la implementación física en un principio pensamos en hacer el clasificador con algún tipo de herramienta CAD en 3D y posteriormente imprimirlo, pero el coste de la impresión era demasiado alto para nuestro presupuesto y decidimos desistir de esta idea e implementar físicamente el clasificador con cartón.
  • Motor paso a paso y pines digitales 0 y 1: Durante el proceso de pruebas del software no se encontró ningún problema con el uso de los pines digitales 0 y 1, pero cuando se procedió a probar el software con el hardware encontramos una serie de problemas, en concreto relacionados con el motor paso a paso, que solo giraba en un sentido, concretamente hacia la derecha. Después de revisar el código varias veces y revisar el cableado no encontramos solución alguna y pensamos que el motor podría haber dejado de funcionar, por lo que probamos el motor de forma separada con un código de prueba y funcionaba. Tras esto probamos a poner el motor en los pines 0 y 1 nuevamente con el mismo resultado. Como solución dejamos de usar los pines digitales 0 y 1 y pasamos dos sensores infrarrojos a los pines analógicos.

Mejoras

En este apartado se expondrán algunas de las mejoras que hemos pensado o que no hemos podido implementar:

  • Una segunda pantalla LCD: En esta pantalla se mostraría la hora actual y la fecha, cuando se seleccionasen las horas con el deslizador, se pondría la hora actual en la línea superior y la hora de salida en la inferior. Se necesitaría otra placa con más pines además de un módulo con conexión a internet para poder consultar la hora actual a un servicio online como el de Microsoft.
  • Más monedas: Una mejora es poder detectar todas las monedas, desde 1 céntimo hasta 2 euros. Se necesitaría otra placa con más pines.
  • Autentificación de las monedas: Nuestro sistema asume que el cliente va a introducir monedas del sistema monetario europeo. Pero en el mundo real no es así y se podrían introducir monedas que fuesen del mismo diámetro y que puede que tengan un valor inferior como las liras turcas. Para solucionar esto se debería usar un clasificador de monedas de tipo comercial como los que se encuentran en máquinas expendedoras de tabaco, billetes de transporte público, o máquinas expendedoras generalistas.
  • Expedición de ticket: Hemos elegido expedir una ficha, pero en la vida real deberíamos expedir un ticket o mandar algún tipo de notificación digital vía NFC o correo electrónico. Para ello se necesitaría otra placa con más pines, conexión a internet y en caso del NFC, un lector de NFC.
  • Pago con tarjeta: Es el medio de pago más común, y por lo tanto se debería poder pagar con este método. Para ello se necesitaría otra placa con más pines, conexión a internet y un lector de tarjetas.
  • Devolución de cambio: En nuestro caso no se devuelve cambio en caso de que se haya introducido dinero de más, es necesario devolver el dinero. Para implementarlo se necesitaría otra placa, diversa circuitería y un lugar donde almacenar los distintos tipos de monedas para devolver además de un algoritmo que devuelva las monedas utilizando la combinación óptima.
  • Conocimiento por parte del sistema de cuantas plazas dispone el aparcamiento y cuantas plazas están libres. Asimismo esto se podría mejorar a su vez si el sistema tiene conocimiento de que plazas están libres y pueda decir al usuario donde puede aparcar. Por ejemplo, durante la expedición del “ticket” el sistema podría decir: “Espacio libre en Planta 2, Sección A”.

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *