He estado trabajando principalmente con MCU de 8 bits, donde la mayoría de los RTOS tienen demasiada sobrecarga.
La mayoría de las aplicaciones en las que he trabajado solo han sido una interrupción periódica con cadenas if/else para toda la lógica de procesamiento, y luego la MCU vuelve a dormir.
Esto ha funcionado bien para muchas cosas y tiene una sobrecarga realmente mínima. Pero para un sistema, estoy llegando al punto en que hay tantas banderas de control que estoy listo para llamar a mi propio sistema "espagueti". Sería horrible que alguien nuevo tomara este sistema e implementara alguna funcionalidad nueva.
(Tengo un LED de dos colores, que debe tener como 8 estados diferentes y patrones de parpadeo dependientes del tiempo según el estado en el que se encuentre el resto del sistema. Es un ejercicio horrible, por lo que debería ser tan simple...)
Estaba viendo tal vez hacer una máquina de estado finito y tratar de eliminar tantas banderas de control.
Un problema conceptual que veo es el uso de temporizadores en una máquina de estado. Actualmente, tengo un temporizador de hardware y luego un montón de contadores de temporizadores definidos por variables que aumentan / disminuyen, una variable de indicador de control va a 0/1, y así pasamos por la cadena if / else.
En mi etapa de planificación para una máquina de estado más estricta, ¿usaría más temporizadores de hardware y activaría las interrupciones externas como eventos para volver a la máquina de estado?
Mi reacción instintiva (ya sea correcta o no) es usar tantas interrupciones externas como sea posible para la máquina de estado ¿eres tú? 1) presentar todo tipo de posibles problemas de prioridad de interrupción que traen su propio conjunto de problemas, donde actualmente el tiempo es muy determinista pero el la lógica de control es simplemente confusa y 2) está utilizando más corriente ejecutando un montón de temporizadores en lugar de simplemente manejar la lógica del temporizador como variables.
Veo cómo aún podría incrementar/decrementar los temporizadores variables en su máquina de estado, pero ¿no es eso antiético para el patrón de la máquina de estado?
Me siento bastante cómodo con el debate de los punteros de función frente a la declaración de cambio sobre cómo codifica la máquina de estado, o si desea usar una tabla de transición, etc.
Me pregunto específicamente cómo la gente ha manejado el aspecto de gestión del temporizador de sus máquinas de estado de una manera elegante.
Una forma común de hacer esto sería establecer un tiempo de ejecución máximo para cada estado y luego comparar cada uno de ellos (con una cobertura de código del 100 %) y asegurarse de que nunca excedan el tiempo de ejecución máximo. Con un poco de suerte, incluso puede usar el perro guardián en el chip para garantizar esto, si puede ejecutarse con tiempos de espera lo suficientemente bajos.
Ahora lo que probablemente esté buscando no es una, sino varias máquinas de estado. Es decir, puede tener una máquina de estado universal como
STATE_MACHINE[state++]();
if(state == STATES_N)
{ /* reset state machine */
}
que no hace más que pasar por los distintos módulos de software, dándoles a cada uno una "fracción de tiempo". Puede ejecutar todos los controladores de hardware de una sola vez y volver a dormir, o puede elegir ejecutar solo uno de ellos. Por supuesto, esto depende de los requisitos en tiempo real.
Uno de esos estados podría ser led_execute()
, que sería la rutina de LED que realiza un seguimiento de lo que está sucediendo en los LED en este momento. Esta rutina reside dentro del controlador de LED y, a su vez, puede realizar un seguimiento de cada estado de LED, de modo que se vea así:
typedef enum
{
LED_OFF,
LED_RED_LIT, // whatever names make sense
LED_RED_BLUE_LIT,
...
LED_DONE,
LED_N
} led_state_t;
...
static led_state_t led_state = LED_OFF;
...
void led_execute (void)
{
led_state = LED_STATE_MACHINE[led_state]();
}
Si los estados dependen de una entrada externa, entonces tal vez omita la parte del estado de retorno y haga que el estado se actualice solo a través de setters/getters.
Esto debería eliminar por completo la necesidad de banderas, en particular banderas no relacionadas ubicadas en el mismo ámbito, lo que puede ser una pesadilla. La parte más importante aquí es no mezclar la complejidad del LED con la complejidad de algún otro hardware.
Digamos que simultáneamente estás eliminando el rebote de un botón. Digamos que debe terminar de eliminar el rebote antes de que se enciendan los LED; eso no significa que los botones tengan que saber sobre los LED o que los LED tengan que saber sobre los botones. El código de la persona que llama debe realizar un seguimiento de estas cosas. Lo que significa que es posible que necesite alguna capa de abstracción entre la máquina de estado más externa y los propios controladores. Si el controlador LED solo recibe una entrada "¡haz esto!" de la persona que llama, entonces no podría importarle menos las razones detrás de esto.
Si desea una "multitarea" de tecnología súper baja, su interrupción del temporizador puede verse así:
timer_isr()
{
process1();
process2();
process3();
}
Entonces, si su temporizador dispara, digamos, 100x por segundo, entonces cada vez que se llama a cada función process(). Estas funciones son FSM que implementan sus diferentes tareas "multitareas". Si son todos independientes, entonces es fácil.
Ahora, si sus tareas son dependientes, puede hacer algo como:
timer_isr()
{
check_buttons();
blink_red();
blink_blue();
}
En este caso, las tareas se comunicarán a través de feas variables globales (no vamos a hacer cuadros de mensajes y FIFO en 8 bits, ¿verdad?). Por ejemplo, check_buttons() rebotaría, etc., y establecería algunas banderas y/o influiría directamente en el estado de los otros dos FSM que hacen parpadear los LED.
Incluso podemos usar esta tecnología de última generación llamada C++:
timer_isr()
{
check_buttons();
red.blink();
blue.blink();
}
En este caso, check_buttons() llamaría a "red.setBlinkMode (algún valor)" cuando se presiona el botón apropiado, por ejemplo. "rojo" y "azul" son objetos globales. En este caso, esta pequeña parte de OO le permite implementar el mismo algoritmo para ambos sin tener que meterse con toneladas de globales o punteros a estructuras, etc.
Es bueno manejar sus botones en un solo lugar en su código. Especialmente si los botones controlan varias cosas, por ejemplo un botón para seleccionar el LED y otro botón para modificar el patrón de parpadeo del LED seleccionado.
El método .blink(), por ejemplo, incrementaría un contador específico de LED hasta que alcance el período de parpadeo, o ajustaría un PWM de forma dependiente del tiempo para que parpadee elegantemente, ese tipo de cosas.
Por ejemplo, si su tiempo se llama cada milisegundo, su método de parpadeo () podría ser:
LED::blink()
{
if( led_on) {
if( counter++ > period ) {
counter=0;
led_pin = !led_pin;
}
} else { led_pin = 0; counter=0; }
}
...algo como eso. Todos son llamados en el mismo período de tiempo, por lo que todas estas pequeñas máquinas de estado conocen el tiempo contando la cantidad de veces que son llamados. En este caso el estado del FSM es led_on y contador.
En general, he descubierto que es mejor elegir un pequeño incremento de tiempo (tal vez un par de milisegundos hasta tal vez 250 useg para un micro de 8 bits) y usarlo para la mayor parte o la totalidad del tiempo. Esto es comparable a la granularidad en un RTOS.
Es más fácil si tiene un micro con una arquitectura decente que permita interrupciones anidadas.
Andy alias
leroy105
keith
leroy105
keith
AlfaGoku