Controladores Lógicos Programables Usos y Ventajas
Arduino: Maquinas de estado con Arduino
1. Máquinas de estado con Arduino
Máquinas de estado: blink.ino nos enseña la función de reposo (sleep)
Daremos una mirada al esquema blink.ino de Arduino como una máquina de estado explícita.
Posteriormente exploraremoslasmáquinasde estadodonde se elaboraráy pondrá en práctica un
diagrama de reloj despertador.
Casi todos los esquemas de Arduino terminan usualmente con al menos una máquina de estado
intencional ytambiénunmontónde máquinasde estadonointencionales.Aquí,introduciremosel
concepto de máquinas de estado y las aplicaremos a una rutina blink “Hello World” en un
microcontrolador.Luegode estoexploraremosalgunasmáquinasde estadomáscomplicadas,tales
como un reloj despertador.
Que es una máquina de estado
Un ‘estado’ es la condición de una cosa en un tiempo determinado. Algunos que pueden realizar
tareasyque utilizanestadoscomosunúcleosonmáquinasde estado.Tambiénsonconocidascomo
máquinasde estadofinitas,loque significaque sabemostodoslosposiblesestadosde ella.Laclave
para la máquina de estado es el concepto del tiempo y la historia. El estado de la máquina es
evaluadaperiódicamente.Cadavezque esevaluada,unnuevoestadoeselegido(el que podríaser
el mismo estado nuevamente) y el resultado es presentado.
Una máquina de estado genérica:
Un diagrama de una máquina de estado genérica. El elemento de memoria contiene el nuevo
estado conocido como el estado variable.Cuando la máquina deestado cuenta con los servicios,el
estado variablees actualizado con el valor de la próxima etapa.Acá la nueva etapa es una función
de ambos;elestado actualy algunosinputs.La nubedela lógica es un sistema que decidecual será
el próximo estado, o la próxima lógica de estado.
Una máquina de estado simple: El contador.
Un clásicoejemplode unamáquinadeestadoesuncontador.Porejemplo,un‘forloop’ouncircuito
integrado lógico de 74×4040 trabajan como una máquina de estado. Cada vez que la máquina
cambia de estado, ya sea por la línea del reloj o por el comportamiento de bucle, el estado de la
memoria cambia a un nuevo estado igualando el estado anterior más uno. El set finito de estados
que puede tener es la cantidad de números que puede contar.
2. Un contador básico expresado como máquina de estado
Partes de la máquina de estado del contador:
El estado actual es el valor que ha sido contado.
El resultado es el estado actual.
La próxima etapa lógica es lo que sea el estado actual + 1.
No hay entradas.En un diagramamás avanzado,una entradao una función de reseteodel
contador será establecido como entrada.
La máquina de estado avanza a cualquier ritmo que sea atendida.
Máquinas de Estado Moore y Mealy
Los profesoresde lógicadigital amanpreguntarenlaspruebassobre Moore vs. Mealyy es por eso
que tenemos que mencionarlo. La distinción entre ellas muchas veces no hace sentido, mientras
escribasenunamáquinade estadoenC;esmás como una:distinciónde “cómohacerunhardware
lógico”.Para nosotros la lecciónde Moore y Mealyes que existiógente que pensósobre este tipo
de cosas e inventóformasde notarlo.Mientras ellosse centrabanen el hardware lógico,nosotros
nos centraremos en el software c.
Máquina de estado Moore
Edward Moore escribió un ensayo en 1956 (Gedanken-experiments on Sequential Machines) y por
lo tantoel estilode lamáquinallevasunombre. Él dice que la salidadepende solodel estado,yel
próximo estado es dependiente del estado actual (o salida), y la entrada.
Nuestro diagrama previo
3. Puedes notar que no importa cuál será el estado de la entrada, la salida solo depende el estado
actual contenido dentro del elemento de la memoria.
Máquina de estado Mealy
George Mealy escribió un ensayo un año antes que Moore, titulado “A Method for Synthesizing
Sequential Circuits”, en el cual entra en profundidad acerca de crear máquinas de estado desde
funcionesmatemáticas,ydescribeesassalidasde máquinasde estadoentérminosde susentradas.
Para diagramar la máquinaMealy,lasalidaestáhecha para dependerde ambos:el estado actual y
la entrada. Aquí la nube de la lógica de la próxima etapa contiene la lógica de salida también:
Una forma de dibujar la máquina Mealy
Tambiénpuede serdibujadoseparandolanube enlalógicadel próximo estado o lógica de salida.
Otra forma de dibujar una máquina Mealy
Diagramas de Estado Abstractos
Hasta ahora, hemosvistoque lasmáquinasde estadotienenunamemoriaque almacenael estado
actual,que debe provenirde algúnestado,yque irá al siguiente estado.Aunque nuestrodiagrama
actual no muestracómose mueve atravésde estados,el propósitodebiese serdibujarunmapade
movimientoentre losestadosparaque podamosescribiruncódigoque emulenuestrodiseño.Esto
se llama diagrama de estado.
Este es un ejemplo de cómo se pueden diagramar los estados de una máquina de estado, usando
ideas de Moore y Mealy.
4. Un diagrama de estado abstracto
Partes del diagrama:
El círculo es el estado
El nombre coloquial del estado está dado en la mitad superior del circulo
La salidadel estadoestádadaenlamitadinferiordelcírculo.A vecesestoesexplicito,como
“X=1,” y el estado se comporta como Moore, aunque a veces puede ser “SALIDA =
ENTRADA”donde durante ese estado,hace la salidaigual a loque haya sidola entrada.En
este sentido, la máquina es muy Mealy. Normalmente se engloban los dos para escribir
máquinas de estado C porque realmente no importa la construcción C de la máquina.
La flecha es el recorrido de la transición de estado.
Las Xs,arriba de la línea,sonlascondicionespara que esatransiciónde estadoocurra.Si se
dan las condiciones, ese será el recorrido de transición.
Las Ys, bajo la línea, son cosas que pasan durante la transición. Este concepto se desvía
estrictamente del mundo lógico digital de Moore y Mealy y se origina de la naturaleza de
ejecución consecutiva de C. Mientras utilizamos un programa, es fácil chequear las
condicionesde transiciónyrealizaralgunasaccionesde una solavez mientrascambianlos
estados, entonces se deben poner las acciones bajo las condiciones.
Siempre tenenmenteque estassonrepresentacionesabstractasde códigosysonunaherramienta
para crear niveles más altos de operación. Agrega cuantos detalles necesites en el diagrama.
Considera un estado básico que se pueda mover hacia un nuevo estado.
5. Estado simple con un set de transiciones de estado incompleto
¿Qué pasa si no se cumplen las condiciones de salida? El diagrama está incompleto y no se
han definidotodaslasposiblesaccionesdelestado.Usualmentenosabríamoslasolución completa
del programa hasta que estén hechos los diagramas, y los vayamos completando. Con este
diagrama,podemosasumirque si lascondicionesnosonconcretadas,el estadovolveráasímismo.
Estado especificado completamente
Acá, se definen todas las posibles transiciones de estado.
Diagrama de estado del contador
Revisa el comportamiento del contador como se dibuja con un diagrama de estado.
6. Diagrama de estado de un contador simple
Acá todos los estados posibles son representados con un círculo único. La acción del estado es
agregar uno. Para determinarel próximoestado,esfácil ver que sólo tenemosunaopción,que es
regresar al estado en que estábamos.
El Sketch de blink.ino
blink_fsm.ino
Salgamos del mundo teórico al re-escribir el familiar sketch de blink.ino con el comportamiento y
sintaxisde unamáquinade estado,usandoel caso de un interruptor.El ejemploesiluminarel LED
por 1 segundo,apagarlo1segundoy repetir el proceso. Acá hay dos estados, LED_ON y LED_OFF.
Parpadeo básico de un LED
En cada estado, el valor del LED se muestra bajo la línea. Las fechas están cada una etiquetadas
como VERDADERO porque no importa qué, nos moveremosal próximo estado de todas maneras.
Esta máquina de estado no tiene ninguna entrada. Cada segundo, evaluaremos la máquina de
estado. Si el estado es 1, la salida es 1 y se mueve al estado 0. Aquí está la implementación C.
Fuente archivo completo (github): blink_fsm.ino
7. Para realizar la máquina de estado en C, debes crear una variable que sostiene el valor actual del
estado, define las palabras para cada valor numérico en que puede estar el estado y escribe un
estado del interruptor para evaluar el próximo estado.
Definirlosestados:
//Define the states of the machine
#define LED_OFF 0
#define LED_ON 1
Crear el estado variable (global):
uint8_t fsm_state = LED_OFF;
Hasta ahora, el estado puede tener 1 o 2 opciones, entonces se selecciona el tipo de datos de
tamaño más pequeño
La máquinade estadoesconstruidaenloop():
void loop(){ //state machine
switch (fsm_state) {
case LED_OFF: //Statements to execute every time LED_OFF is
reached
digitalWrite(13, LOW);
fsm_state = LED_ON;
break;
case LED_ON: //Statements to execute every time LED_ON is reached
digitalWrite(13, HIGH);
fsm_state = LED_OFF;
break;
default:
break;
}
delay(1000); // wait for a second
}
Cada vez que la función loop es ejecutada, se evalúa la máquina de estado. El estado variable
(fsm_state) es global, entonces retiene el estado.
En este casose puede notarque encadasegundoesperamos1segundoyevaluamoslamáquinade
estado.El códigoextraasociadoal procesode lamáquinade estacausaráque el ciclode tiemposea
8. mucho mayor a un segundo y correrá un poco lento. Esto podría ser interrumpido con el fin de
obtener mayor precisión.
blink_fsm_ms.ino
No quiero tener que esperar un segundo completo. Durante ese tiempo podría estar realizando
otras cosas! Preferiría que el proceso de la máquina de estado fuera a un intervalo más rápido.
Como 1 microsegundo y se quede en un mismo estado 1000 veces con el fin de crear retrasar.
Un programa básico de cambio de estado que funciona más rápido de lo que fue programado
Con este diseño,nodejaré el estadoamenosque el msCountsalcance 1000. El loopse retrasa por
1 microsegundo en vez de 1000 microsegundos. Cuando la condición es verdadera para que una
transición de estado ocurra, el estado del LED es escrita y el contador se resetea.
Fuente de archivo completo (github): blink_fsm_ms.ino
Como antes, los mismos estados y la variable del estado es utilizada. La máquina de estado se
expande para proveer funcionalidad si y solo si la transición del estado va a ocurrir.
switch (fsm_state)
{
case LED_OFF:
//Statements to execute every time LED_OFF is reached
//Statements to execute when it's time to move to the next state
if(msCounts >= 1000) {
digitalWrite(13, HIGH); //Process the next state's function
fsm_state = LED_ON;
msCounts = 0;
9. }
break; case LED_ON: //Statements to execute every time LED_ON is
reached
//Statements to execute when it's time to move to the next state
if(msCounts >= 1000) {
digitalWrite(13, LOW); //Process the next state's function
fsm_state = LED_OFF;
msCounts = 0; }
break; default:
break;
}
Ahora, cada estado solo se mueve si la transición lógica de estado es verdadero usando un
enunciado “IF”. Aquí es donde es obvio cuán fácil es agregar tareas de 1 tiempo a la acción de
estadosde transición.Ellossonsoloagregadosal enunciado“IF”,yseránejecutadossolocuandoel
estado se mueve.
La máquina de estado funciona pero no como quisiéramos. Noten que el estado LED está en LOW
durante el estadoLED_ON yHIGH durante el estadoLED_OFF.Esfácil ejecutarel códigode unasola
vezdejandounestado,peronodurante la entradadel estado. Es contablemente intuitivoypuede
ser realizado claramente al agregar dos estados más, cada uno sólo espera.
blink_fsm_final
10. Un programa de cambio de estado que utilice un estado adicional de espera.
Acá los estados LED_ON y LED_OFF escriben al LED, limpian el contador y continúan adelante.
Notalateral de sincronizaciónyexactitud: Elcontadorha sido modificado a una cuenta de999 para
tener en cuenta el estado extra, pero no ayuda mucho. Llegamos más lejos mientas más rápido
hacemoscorrerel reloj.Esto es debido a queel tiempo quetoma evaluarla máquina deestado está
comenzando a alcanzar el total de tiempo para ejecutar el loop() INCLUYENDO el retraso(1);
enunciado.
Fuente de archivo completo (github): blink_fsm_final.ino
Primero, los dos estados extra son agregados a la lista de #defines.
blink_fsm_final
//Define the states of the machine
#define LED_OFF 0
#define LED_ON_WAIT 1
#define LED_ON 2
#define LED_OFF_WAIT 3
El estadovariable esel mismoentoncesnosmoveremosalaimplementaciónde lamáquinade
estadoactual.
//state machine
switch (fsm_state){
11. case LED_OFF: //Statements to execute every time LED_OFF is reached
digitalWrite(13, LOW);
fsm_state = LED_ON_WAIT;
break;
case LED_ON_WAIT: //Statements to execute every time LED_OFF_WAIT is
reached
if(msCounts >= 1000) {
fsm_state = LED_ON;
msCounts = 0;
}
break;
case LED_ON: //Statements to execute every time LED_ON is reached
digitalWrite(13, HIGH);
fsm_state = LED_OFF_WAIT;
break;
case LED_OFF_WAIT: //Statements to execute every time LED_ON_WAIT is
reached
if(msCounts >= 1000) {
fsm_state = LED_OFF;
msCounts = 0;
}
break;
default:
break;
}
Vemosque losestadosextrase han convertidoencasosextrasen la sentenciaSwitch.Losestados
que siempre se muevenhaciadelantede formasimple asignanelpróximoestadoal estadovariable.
El estado de retraso chequea las cuentas antes de asignar un nuevo estado, además retiene el
estado en el que estaban.
Más notas sobre sincronización:Ejecutar el incrementado msCounts con una Rutina de Servicio de
Interruptorde 1 microsegundo (ISR).Mientrastanto utilizar el loop en el FSMtan rápido como sea
posible.Esto corregirá la sincronización.Debemosteneren cuenta que si el tiempo de ejecución del
12. código entre las llamadasISER (el tiempo de proceso de la máquina deestado) es más largo que el
intervalo de la llamada ISER, el programa probablemente se bloqueará
Si ¿Y qué? ¡El LED estaba parpadeando para empezar!
Por favor consideralasfuncionesde alarmade un reloj despertador.¿Cómose veríasu modelode
comportamiento?El relojdespertadortienevariosestadosquepuedenexistirdentroytieneunpar
de entradas que pueden ser utilizadas para controlarlo.
Estados:
Mantener el tiempo agradablemente
Ejecución de alarma
Espera con impaciencia el final del ciclo de repetición de alarma por lo que puede que la
alarma se active nuevamente
Entradas
Interruptor de brazo (Arm switch)
Botón de reposo (sleep)
Hay algunasmás, como entradaspara establecerhora y estableceralarma,peroeste ejemploestá
limitado a las operaciones de alarma solamente.
Descrito como máquina de estado:
Las funciones de alarma del reloj despertador.
13. Si intentamoscaminarmentalmente atravésde laoperaciónde lamáquinade estadoveremosque
podemosobtenerdel‘idle’al sonarsi elreloj despertadornoestáarmado.Tambiénpodemosvolver
a ‘idle’ al presionar el botón de reposo (sleep). Tenemos que desarmar la alarma, esto cumple
nuestra definición interna de cómo actúa un reloj despertador, entonces proseguimos.
No existe unamaneraenque realmente demosseguimientoal tiempo,inclusosi solofueraparael
experimento.Envezde darle seguimientoalos días, horas, etc.,soloqueremosdarle seguimiento
a los segundos. Entonces necesitaremos contar los segundos. La máquina de estado de parpadeo
de LED ya hace eso!Sololocambiaremosparamantenerel seguimientoyagregaratodalamáquina
de estado dentro.
Re-usando la máquina de estado de parpadeo para contar segundos.
Comoun beneficio,se puede bloquearel parpadeodelLEDa menosque laalarmase apague y usar
eso como resultado de depuración.
Acá está el archivo fuente completo (github): alarm_clock.ino
Conexiones de Hardware:
Conecta un botón de apertura normal entre el pin 7 y el GND de un Arduino. Esto servirá
como “REPOSO” (sleep).
Conecta el switch SPST entre el pin 9 y la tierra. Esto servirá como “ARM” (brazo).
14. El código es muy similar a los ejemplosprevios,pero tiene dos máquinas de estado construidas
sobre él.
Primero, los estados y las variables de la máquina de estado del temporizador:
//Timer FSM numbers
uint16_t msCounts = 0;
uint16_t sCounts = 0;
#define LED_OFF 0
#define LED_ON_WAIT 1
#define LED_ON 2
#define LED_OFF_WAIT 3
uint8_t timer_fsm_state = LED_OFF;
Luego, los estados y las variables de la máquina de estado de alarma. La hora de la alarma se
establece en 15 segundos del reinicio (de hecho cerca de 20 con error), y el ciclo de reposo se
establece por 5 segundos.
//Alarm FSM numbers
#define ALARM_SECONDS 15
#define SNOOZE_SECONDS 5
uint8_t alarmActive = 0;
uint16_t nextAlarmTime = 65535;
#define ALARM_IDLE 0
#define ALARM_RINGING 1
#define ALARM_SNOOZING 2
uint8_t alarm_fsm_state = ALARM_IDLE;
Loop() evalúa ambas máquinas de estado cada vez que funciona.
//timer state machine
switch (timer_fsm_state)
{
case LED_OFF: //Statements to execute every time LED_OFF is reached
digitalWrite(13, LOW);
timer_fsm_state = LED_ON_WAIT;
break;
15. case LED_ON_WAIT: //Statements to execute every time LED_OFF_WAIT is
reached
if(msCounts >= 1000) {
timer_fsm_state = LED_ON;
msCounts = 0; }
break;
case LED_ON: //Statements to execute every time LED_ON is reached
if(alarmActive == 1) {
digitalWrite(13, HIGH);
}
timer_fsm_state = LED_OFF_WAIT;
break;
case LED_OFF_WAIT: //Statements to execute every time LED_ON_WAIT is
reached
if(msCounts >= 1000) {
timer_fsm_state = LED_OFF;
msCounts = 0;
if( sCounts < 0xFFFF ) {
sCounts++;
}
}
break;
default:
break;
}
uint8_t buttonValue = digitalRead(7);
uint8_t switchValue = digitalRead(9); //alarm state machine
switch (alarm_fsm_state){
case ALARM_IDLE:
if((sCounts >= nextAlarmTime)&&(switchValue == 0)) { //Goto next
state
16. alarm_fsm_state = ALARM_RINGING;
alarmActive = 1; }
break;
case ALARM_RINGING:
if(buttonValue == 0) {
nextAlarmTime = sCounts + SNOOZE_SECONDS;
alarm_fsm_state = ALARM_SNOOZING;
alarmActive = 0; }
if(digitalRead(9) == 1)//If switch went off, goto idle instead
{
alarm_fsm_state = ALARM_IDLE; // this overwrites the snooze button
option
alarmActive = 0; }
break;
case ALARM_SNOOZING:
if(sCounts >= nextAlarmTime) { //Goto next state
alarm_fsm_state = ALARM_RINGING;
alarmActive = 1; }
if(switchValue == 0) { //Goto alarm idle
alarm_fsm_state = ALARM_IDLE; }
break;
default:
break;
}
Para operar, cierra el switch “ARM” (brazo) y carga el programa.
Conclusión
En nuestra experiencia, cualquier programador creador de códigos que puede diagramar un
programa antes de escribirlo tendrá éxito con el programa. Se han escritomuchos programas que
han terminado como un montón de “enunciados if” anidados porque no nos adelantamos a los
hechos. Inevitablemente, se necesita agregar una cosa pequeña que corrompe enormemente el
programa y somosforzadosa re-evaluarlasdecisiones.El usarherramientastalescomodiagramas
de máquinasde estado,diagramasde flujo,diagramasde clase ytestsde unidadque nospermiten
17. escribir códigos más complejospero mantenibles(mientras permanece relativamente cuerdo). La
máquina de estado es solo otra idea para tener en la caja de herramientas y esperamos que les
ayude en el futuro.