Maquina expendedora con Arduino

 Maquina expendedora con Arduino

Componentes y esquema de conexion: 


Los componentes Hardware que usaremos para la siguiente practica serán:
- Arduino UNO 
- LCD 
- Joystick 
- Sensor temperatura/Humedad DHT11(Ojo el modelo del esquema no corresponde con el utilizado)
- Sensor Ultrasonido 
- Botón
- 2 LEDS Normales (LED1, LED2)
- Resistencia 220ohms

El esquema de conexiones puede ser visualizado a continuación:

Leyenda de colores: 
- Rojo: Vcc
- Negro: Gnd
- Azul: Lcd 
- Blanco: Leds
- Naranja: Sensor ultrasonidos
- Amarillo: Sensor Dht
- Verde: Joystick
- Morado: Botón


Desarrollo del código:

Para estructurar de una manera ordenada nuestro programa nos apoyaremos en las denominadas maquinas de estados las cuales resultan de mucha utilidad en este tipo de comportamientos. 

Primero tendremos una maquina de estados general que costara de tres estados:

Start status:

Este inicia la ejecución de la maquina indicándolo por la pantalla lcd y el led1, el cual parpadeara durante los seis segundos que la maquina se encuentra en este estado. La maquina no volverá a este estado a no ser que sea apagada y encendida de nuevo.

Service status:

Este sera el estado pensado para el trato con el cliente general y esta compuesto de otros 5 sub-estados los cuales dominaran su lógica:

- Waiting client
- Showing temperature and humidity
- Menu
- Preparing coffe
- Take off drink 

Admin status:

Este modo esta pensado para que el administrador pueda comprobar el buen funcionamiento de la maquina y modificar el precio de los productos, al igual que el anterior esta dividido en una serie de 6 sub-estados

- Admin Menu
- Showing temperature and humidity
- Showing us distance
- Showing time counter
- Price mod menu
- Modifiying price

Ahora comentaremos la división del código:

Loop principal:

Para conseguir una buena estructura de código una de las decisiones de diseño tomadas es que el loop principal solo contenga la lógica de los cambios de estado. Es decir, en este solo analizaran los "time stamps" o "user inputs" que generan cambios en los estados y se modificaran las variables necesarias para estos cambios.

Concluimos que las únicas tareas del loop principal son:
- Comprobación y actualización de "time stamps"
- Captura de "inputs" introducidos por el usuario
- Cambios de estado
- Reinicio del Watchdog

Tratamiento de "inputs":

Tendremos dos elementos a tratar: 
- El joystick: 
Este esta compuesto por la parte móvil que actúa en el eje "x" e "y" y por un botón central:
 
La captura del movimiento de "x" e "y" se realiza de una forma muy sencilla. Se crean dos funciones "joystick_ver_movement" y "joystick_hor_movement" las cuales capturan este movimiento y lo devuelven al loop principal, el cual asigna este valor a una variable global para que pueda ser tratado por los correspondientes hilos(de los cuales hablaremos más adelante). Se decide su captura en el bucle principal ya que si establecemos un hilo que capture cada x tiempo se podrían perder muchas medidas y empeorar el comportamiento de este. 

- El botón:
Tanto el botón del joystick como el botón adicional serán capturados de la misma manera, cada uno ira conectado a uno de los pines de interrupción del arduino(en el caso de esta placa pin2 y 3). Cuando se pulsa uno de estos botones se llama a su correspondiente manejador de interrupción el cual coloca a valor "true" el flag que indica que ha sido pulsado para que el loop principal comience a llamar a la función encargada de en el caso del botón de switch evitar el bounce time y en el caso del botón adicional comprobar el tipo de pulsación de la que se trata en función al tiempo que se mantiene pulsado.

Para ver el funcionamiento más en detalle observar las funciones que se encuentran dentro del código en la sección "Input interface".


Tratamiento de pantalla Lcd:

Todo el tratamiento de la pantalla se realiza desde los threads de los que hablaremos al final de este "post". En esta sección mencionaremos las funciones en las que nos apoyaremos para mostrar los diferentes menús y valores por esta pantalla. 

Cuando se trata de texto estático como sucede en el estado "Start" con el "CARGANDO..." o en los sub-estados finales de "Service" llamamos a la función lcd.print() desde el thread correspondiente.

Pero para el caso de los menús y la representación de datos(que se realizan de forma dinámica) creamos funciones que nos ayuden a diseñar y simplificar el código. 

Las funciones usadas para representar la información de los sensores y el tiempo son muy simples ya que se basaran en limpiar el lcd y escribir la información obtenida de los sensores a través del thread encargado de esta labor.

Para el caso de mostrar el menú las funciones se complican un poco mas. Comenzamos creando una estructura de datos que represente un menú para simplificar el paso por parámetros y permitir el funcionamiento de estas tanto para el menú de servicio como para el del administrador.
La estructura creada costa de los siguientes campos:
- menu_size(int): Cantidad de valores que contiene el menú
- first_lcd_position(int): Posición del valor que se esta mostrando actualmente en la primera linea del lcd
- second_lcd_position(int): Posición del valor que se esta mostrando actualmente en la segunda linea del lcd
- main_menu_list(char**): Array de strings que forman el menú principal(obligatoria)
- opt_menu_list(float*): Array de números para representar los precios en el caso del menú de servicio(opcional), en caso de estar compuesta de valores "NULL" no se mostrará. 

Una vez inicializados los menús contamos con la función "update_menu_lcd" la cual se encarga de comprobar si es necesario desplazar la lista aprovechándose de los movimientos del joystick capturados en el loop principal y de asegurarse de que el menú se muestra con un movimiento circular, es decir si vamos hacia arriba encontrándonos en la primera posición, en la primera linea pasara a mostrarse la ultima y lo mismo en caso contrario. Por ultimo llama a la función  "show_menu_lcd" la cual se encarga de mostrar las posiciones del menú actuales. 
Además esta ultima función realizara un movimiento de scrolling para que los precios y los menús puedan ser visualizados de forma clara. Para esto toma la longitud de la linea y en caso de no estar navegando por ella desplaza el contenido actual hacia la izquierda cada ciertos ticks. Cuando llega al final o nos estamos moviendo por la lista vuelve al principio y empieza el scroll con un retraso de 2 * "TICKS_TO_SCROLL" para que la visualización para el usuario sea más cómoda.

Threads:

En los threads es donde realmente se encuentra la "magia" del programa ya que serán los encargados de utilizar todo lo anteriormente mencionado para lograr la funcionalidad deseada. 

Dividiremos la explicación de estos en función de los controladores usados:

- Sensors controller: Este contiene 3 threads que se ejecutarán durante todo el programa y serán los encargados de actualizar el valor de los sensores(temperatura/humedad y ultrasonidos) cada 1000ms y otro que actualiza el contador de segundos cada 300ms(se apoya en millis() y una "time stamp" interna para asegurarse que el valor es correcto, ya que si usasemos millis() sin mas el valor se reiniciaria de forma repetida durante la ejecucción). 

- Leds controller(seguridad): Junto con el anterior serán los únicos controladores que se ejecutaran durante todo el programa, este contiene un único thread que se asegurara de que los leds se mantengan en lo esperado por si hubiese algún tipo de falla temporal que por ejemplo no permitiese al led rojo apagarse después del start...

- Start controller: Maneja el estado con su mismo nombre, contiene dos threads, el primero encargado del parpadeo del led1 y el segundo encargado de manejar el lcd. 

- Service cotroller: Maneja el estado con su mismo nombre, contiene dos threads, el primero encargado del gradiente del led2 en el sub-estado "preparing coffe" y el segundo encargado de manejar el lcd. 

- Admin controller: Maneja el estado con su mismo nombre, contiene un thread, el cual se encargara de manejar el lcd(el manejo de los leds en este caso se realiza en el leds controller). 

- Mod price controller: Contiene un solo thread y se encarga tanto de actualizar el precio del producto que estamos modificando, como de mostrar en el lcd.


Watchdog

Para asegurarnos que nuestra placa no se queda bloqueada utilizaremos un watchdog. Inicializaremos este en nuestro setup con valor de 1 segundo(WDTO_1S) y cada vez que termine nuestro loop principal reiniciamos el contador de este, ya que el loop estará constantemente ejecutando y en ningún caso podría superar el valor de tiempo establecido.


Resultados finales:

A continuación se puede visualizar un vídeo del comportamiento final:























Comentarios