#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_timer.h"
/* Macros para identificar los GPIOS como entrada */
#define INPUT1 GPIO_NUM_18
#define INPUT2 GPIO_NUM_19
#define INPUT3 GPIO_NUM_21
#define INPUT4 GPIO_NUM_22
/* Macros para identificar los GPIOS como salida */
#define OUTPUT1 GPIO_NUM_23
#define OUTPUT2 GPIO_NUM_25
#define OUTPUT3 GPIO_NUM_26
#define OUTPUT4 GPIO_NUM_27
/* Tiempo de cada led prendido y apagado tambien entre cambio de estados */
#define LED_CHANGE_DELAY_MS 1000
/* la duracion de un rebote en el logic analyzer dura 2.6 ms aproximadamente, se deja un poco mas de rango */
#define DEBOUNCER_DELAY_uS 4000
/* El tiempo maximo permitido de inactividad para el cliente, 3 segundos */
#define MAX_USER_INACTIVITY_US 2000000
/* Declaracion manejador de la cola */
QueueHandle_t handlerQueue;
/* Se declara la variable auxiliar de tiempo para el debouncer */
static int64_t past_time = 0;
/* Se usa una variable auxialiar para que ignore un rebote de los 2 rebotes del boton*/
static uint8_t bounce_rectifier = 0xFF;
/* Se declara el saldo del usuario, lo iniciamos en 0 */
static uint8_t balance = 0;
/* Se inicializa una variable para medir la inactividad del usuario*/
static int64_t user_inactivity_us = 0;
/* Variable global para almacenar el tiempo actual cada vez que se repite la tarea */
static int64_t current_time = 0;
/* Inicializacion de los gpios, en este caso utilizamos los botones con pullups activado
para no hacer configuraciones extras, ya que despues de la funcion gpio_reset_pin()
los gpios quedan con pullups activos */
void init_gpio () {
gpio_reset_pin(INPUT1);
gpio_reset_pin(INPUT2);
gpio_reset_pin(INPUT3);
gpio_reset_pin(INPUT4);
gpio_reset_pin(OUTPUT1);
gpio_reset_pin(OUTPUT2);
gpio_reset_pin(OUTPUT3);
gpio_reset_pin(OUTPUT4);
gpio_set_direction(INPUT1, GPIO_MODE_INPUT);
gpio_set_direction(INPUT2, GPIO_MODE_INPUT);
gpio_set_direction(INPUT3, GPIO_MODE_INPUT);
gpio_set_direction(INPUT4, GPIO_MODE_INPUT);
gpio_set_direction(OUTPUT1, GPIO_MODE_OUTPUT);
gpio_set_direction(OUTPUT2, GPIO_MODE_OUTPUT);
gpio_set_direction(OUTPUT3, GPIO_MODE_OUTPUT);
gpio_set_direction(OUTPUT4, GPIO_MODE_OUTPUT);
}
/* Manejo de interrupcion, a este manejador le abjuntamos todos los pines de entrada,
de esta forma con un mismo manejador obtenemos el boton que la activo */
static void IRAM_ATTR gpio_interrupt_handler(void *args) {
int pinNumber = (int)args;
if (((current_time-past_time)>=DEBOUNCER_DELAY_uS)) {
past_time = current_time;
bounce_rectifier++;
/* Ya que el rebote al presionar y soltar activa la interrupcion, cuando se presiona ignora esa
interrupcion y solo deja pasar cuando se suelta */
if (bounce_rectifier & 1) {
xQueueSendFromISR(handlerQueue, &pinNumber, NULL);
}
}
}
void LED_change_activation ( uint8_t coin_value, uint8_t output_pin) {
while ( (balance / coin_value) > 0) {
balance -= coin_value;
printf("Moneda de $%d.\n", coin_value);
gpio_set_level(output_pin, 1);
vTaskDelay(pdMS_TO_TICKS(LED_CHANGE_DELAY_MS));
gpio_set_level(output_pin, 0);
vTaskDelay(pdMS_TO_TICKS(LED_CHANGE_DELAY_MS));
}
}
void return_change ( void ) {
LED_change_activation ( 10 , OUTPUT3);
LED_change_activation ( 5 , OUTPUT2);
LED_change_activation ( 1 , OUTPUT1);
}
/* Esta tarea es la que se encarga de sumar cada entrada de los botones, y tambien en entregar el cambio
una vez que se alcanza o supera el saldo en 15*/
static void pay_parking_task(void *params) {
int pinNumber;
while (1) {
current_time = esp_timer_get_time();
if ( ((current_time - user_inactivity_us ) > MAX_USER_INACTIVITY_US) && (balance > 0)) {
printf("Operacion cancelada, tome su dinero.\n");
return_change();
}
/* Si hay algo en la cola, entonces entra, y lo que tiene en la cola (que debe de ser un numero de gpio)
se coloca en pinNumber, para despues, dependiendo del boton presionada, se le agrega al saldo un valor*/
if (xQueueReceive(handlerQueue, &pinNumber, 0)) {
switch (pinNumber) {
case INPUT1:
balance += 1;
break;
case INPUT2:
balance += 5;
break;
case INPUT3:
balance += 10;
break;
case INPUT4:
balance += 20;
break;
}
printf("Su saldo es: %d.\n", (int)balance);
user_inactivity_us = current_time;
}
/* Si el saldo es mayor o igual a 15, entonces se entrega cambio o ticket*/
if (balance >= 15) {
balance -= 15;
return_change();
printf("Tome su recibo.\n");
gpio_set_level(OUTPUT4, 1);
vTaskDelay(pdMS_TO_TICKS(LED_CHANGE_DELAY_MS));
gpio_set_level(OUTPUT4, 0);
vTaskDelay(pdMS_TO_TICKS(LED_CHANGE_DELAY_MS));
}
}
}
/* Aqui declaramos que tipo de interrupcion tendran los gpios de entrada, se le asigna a la variable
del manejado de cola una cola de 10 items de enteros, se crea la tarea que se utilizara, se activan interrupciones
de los gpios, y se les agrega estas interrupciones al mismo manejador*/
void app_main () {
init_gpio();
gpio_set_intr_type(INPUT1, GPIO_INTR_NEGEDGE);
gpio_set_intr_type(INPUT2, GPIO_INTR_NEGEDGE);
gpio_set_intr_type(INPUT3, GPIO_INTR_NEGEDGE);
gpio_set_intr_type(INPUT4, GPIO_INTR_NEGEDGE);
handlerQueue = xQueueCreate(10, sizeof(int));
xTaskCreate(pay_parking_task, "pay_parking_task", 2048 , NULL , 1 , NULL);
gpio_install_isr_service(0);
gpio_isr_handler_add(INPUT1, gpio_interrupt_handler, (void *) INPUT1);
gpio_isr_handler_add(INPUT2, gpio_interrupt_handler, (void *) INPUT2);
gpio_isr_handler_add(INPUT3, gpio_interrupt_handler, (void *) INPUT3);
gpio_isr_handler_add(INPUT4, gpio_interrupt_handler, (void *) INPUT4);
}