Таблица переходов
Развивая идею обратных вызовов функций, от условных операторов можно практически уйти. Адрес — не что иное, как число. Наши перечисления — это тоже, по сути, числа. Что нам мешает создать двухмерный массив из этих чисел и осуществлять переходы от одного состояния к другому, опираясь исключительно на него? Такой массив еще называется таблицей переходов (англ. transition table).
STATES / EVENTS | NONE | BUTTON_PRESSED | TIME_OUT |
---|---|---|---|
SHOW_TIME | SHOW_TIME | SHOW_TEMPERATURE | — |
SHOW_TEMPERATURE | SHOW_TEMPERATURE | ADJUST_HOURS | SHOW_TIME |
ADJUST_HOURS | ADJUST_HOURS | ADJUST_MINUTES | — |
ADJUST_MINUTES | ADJUST_MINUTES | SHOW_TIME | — |
Так как некоторые события не должны происходить в некоторых состояниях (например, таймер должен быть отключен, когда мы показываем время), то для отладки было бы неплохо ввести дополнительную функцию error()
с вечным циклом. Как только устройство перестанет реагировать, вы поймете, что осуществился переход именно в эту функцию (скорее всего).
void error(void) {
while(1);
}
Перечисления автоматически заполняются числами начиная с нуля. Соответственно, добавив в перечисление, в конец, еще одно «состояние» / «событие», мы получим размер нужного нам массива.
typedef enum {
STATE_SHOW_TIME, // 0
STATE_SHOW_TEMPERATURE, // 1
STATE_ADJUST_HOURS, // 2
STATE_ADJUST_MINUTES, // 3
STATE_MAX, // 4
} STATE_t;
typedef enum {
EVENT_NONE, // 0
EVENT_BUTTON_PRESSED, // 1
EVENT_TIME_OUT, // 2
EVENT_MAX, // 3
} EVENT_t;
Эти ненастоящие событие и состояние, но они позволят не создавать дополнительных макросов для таблицы переходов. Перепишем таблицу в соответствии с тем, как мы зарисовали ее выше, но только на языке Си.
typedef void (*TRANSITION_FUNC_PTR_t)(void);
TRANSITION_FUNC_PTR_t transition_table[STATE_MAX][EVENT_MAX] = {
[STATE_SHOW_TIME] [EVENT_NONE] = show_time,
[STATE_SHOW_TIME] [EVENT_BUTTON_PRESSED] = show_temperature,
[STATE_SHOW_TIME] [EVENT_TIME_OUT] = error,
[STATE_SHOW_TEMPERATURE][EVENT_NONE] = show_temperature,
[STATE_SHOW_TEMPERATURE][EVENT_BUTTON_PRESSED] = show_hours,
[STATE_SHOW_TEMPERATURE][EVENT_TIME_OUT] = show_time,
[STATE_ADJUST_HOURS] [EVENT_NONE] = show_hours,
[STATE_ADJUST_HOURS] [EVENT_BUTTON_PRESSED] = show_minutes,
[STATE_ADJUST_HOURS] [EVENT_TIME_OUT] = error,
[STATE_ADJUST_MINUTES] [EVENT_NONE] = show_minutes,
[STATE_ADJUST_MINUTES] [EVENT_BUTTON_PRESSED] = show_time,
[STATE_ADJUST_MINUTES] [EVENT_TIME_OUT] = error,
};
Главный цикл, как и в прошлый раз, будет весьма лаконичным.
int main(void) {
mcu_init();
// …
while(1) {
transition_table[state][event]();
}
}
Пожалуй, на этом мы выжали из машины состояний всё, что могли. В некоторых случаях переход может зависеть от предыдущего состояния, тогда придется добавить еще одну глобальную переменную и, вероятно, обернуть все переменные связанные с режимом работы, в одну структуруcap. Например, так:
Интересная реализация машины состояний применена в сенсорной библиотеке от ST.
typedef struct {
ST_t current_state = STATE_SHOW_TIME;
ST_t previous_state = STATE_SHOW_TIME;
EV_t event = EVENT_NONE;
} STATE_MACHINE_t;
Однако оставим это вам на откуп, а сейчас самое время перейти к операционной системе и реализовать ту же функциональность средствами FreeRTOS.