As long as you use a timer counter, say the internal CPU cycle counter (ARM_DWT_CYCCNT) to determine the intervals related to button presses and so on, I do not see any problem in having the FSM loop repeat that often.
For example, let's say you have an "up" button. How exactly do you determine it is time to perform the "up" action? You surely don't want to do it each iteration of the FSM, because that'd lead right now to ~250,000 up goings per second. What I recommend, is using an unsigned integer counter to implement the delay, or "dead time". Whenever you detect that "up" button is being pressed, you check the counter. If it is zero, you do the action, and set it to the inital press delay (in timer units; CPU cycles if using ARM_DWT_CYCCNT). If it is nonzero, you decrement the counter by the duration of the last iteration, and only do the action if it brings the counter to zero.
Example:
/* Interval, in milliseconds, after initial button press */
#define DEADTIME_FIRST_MS 300
/* Autorepeat interval, in milliseconds */
#define DEADTIME_REPEAT_MS 200
/* Deadtime intervals in CPU cycles, using ARM_DWT_CYCCNT */
#define DEADTIME_FIRST ((uint32_t)(FCPU * DEADTIME_FIRST_MS / 1000.0))
#define DEADTIME_REPEAT ((uint32_t)(FCPU * DEADTIME_REPEAT_MS / 1000.0))
struct button {
void (*event)(struct button *); /* Parameter is a pointer to this structure */
int gpio; /* GPIO pin, perhaps? */
uint32_t deadtime; /* Current deadtime */
};
struct button ui_button[] = {
{ .event = FUNCTION_TO_QUEUE_THE_EVENT, .gpio = GPIO_TO_CHECK, .deadtime = 0 },
/* All UI buttons listed here like above */
};
#define UI_BUTTONS (sizeof ui_button / sizeof ui_button[0])
void main(void)
{
uint32_t last_cyccnt, curr_cyccnt, duration;
init_sys();
filters_init();
user_events_init();
lcd_init();
curr_cyccnt = ARM_DWT_CYCCNT;
while (1) {
last_cyccnt = curr_cyccnt;
curr_cyccnt = ARM_DWT_CYCCNT;
duration = (uint32_t)(curr_cyccnt - last_cyccnt); /* Reminder: wraparound math! */
filters_process();
user_events_process(duration);
lcd_process();
}
}
and within user_events_process(),
for (int i = 0; i < UI_BUTTONS; i++) {
struct button *const b = ui_button + i; /* == &(ui_button[i]) */
if (!b->deadtime) {
if (gpio_button_pressed(b->gpio)) {
/* Individual button press. */
b->deadtime = DEADTIME_FIRST;
b->event(b);
}
} else
if (b->deadtime <= duration) {
if (gpio_button_pressed(b->gpio)) {
/* Autorepeat. */
b->deadtime = DEADTIME_REPEAT;
b->event(b);
} else {
/* Button no longer pressed. */
b->deadtime -= duration;
}
} else {
/* Button is in deadtime after an event. */
b->deadtime -= duration;
}
}
This gives you immediate button response, but fixed initial delay and autorepeat intervals. It will react to spurious noise on the GPIO, but given sufficient dead time, is immune to switch bounce. There are other patterns as well, but all the good ones rely on some sort of a reliable counter to determine exactly when to act on the current button state.
Note how the button loop doesn't even check the GPIO state unless the dead time has elapsed? This way you don't waste checking the GPIO state, unless it is something we can act upon. Technically, only the deadtime member needs to be in RAM, and the rest could be left in Flash/ROM, but meh, this is just an example.
Written this way, button response is not at all dependent on the FSM cycle interval; it is dictated by the timer counter, here the cycle counter, ARM_DWT_CYCCNT, with the duration of the previous iteration used for the dead time counters. It is not cycle-accurate, but it does generate the events in the correct iteration of the loop, plus or minus one iteration, and should definitely be precise enough for UI stuff. Other things might wish to keep track of the cycle counter on their own, but given thousands of iterations per second, I'd just use the same mechanism; that's why I did it in main(), above.