I've been looking into the use of input capture on a timer/counter to do this. I think it might work, but there's one loose end that I can't quite figure out how to handle.
I just use a pin change interrupt and ARM_DWT_CYCCNT or whatever other timer I'd be using, with a hook to an "idle" function that gets called at irregular intervals, but at least once per quarter of the timer wraparound interval. One thing the idle function does, is check if the latest transition on the signal pattern state machine has expired, setting a suitable flag in that case. Such "idle" or timeout checks are very common, so it is generally a good idea to make sure you can do them efficiently. I like the "at least once per quarter timer wraparound" (which is not exactly the same as at least four times per timer wraparound, because the former says that the interval between consecutive calls is at most a quarter of the expiry interval, whereas the latter technically allows almost a full wraparound interval between calls).
So, to recap: you have a simple state machine, with a state indicator (number or pointer or whatever), and at least one timestamp, plus an expiry flag on that timestamp, and some kind of idle/cleanup/yield function that is called often enough. In the simplest case, you set that timestamp to now plus the time after which the current state expires. The idle function only compares the current time to that timestamp, and sets the expiry flag when the timer exceeds that timestamp. The state machine examines the expiry flag.
If your timer and timestamp are 32-bit, then using
uint32_t and unsigned integer arithmetic (
uint32_t timestamp, timer), the check is trivial (but odd-looking):
if ((int32_t)(timestamp-timer) <= 0) expired_flag = 1;This works, because
uintN_t arithmetic is modular (wraps around), and
intN_t uses two's complement format. It is
NOT the same as
if (timestamp <= timer) expired_flag = 1;, because this latter fails when
timestamp is very large and
timer very small (i.e., when the wraparound happens between the two); the former works even in that case.
In the state machine code, the expiry flag is another datum you use to determine the proper transition. Also, you clear the
expired_flag exactly after setting the
timestamp, always, and only then. Both should be
volatile, so the compiler knows their values can be changed unexpectedly by e.g. code running in an interrupt.
In other words, we're very much along the same lines, just tiny differences in the practical implementation details.
There are many different ways to ensure the idle function (or function set, since you probably will have more than one such timer expiry handlers) is called often enough, varying from timer interrupts to rate-limited "very often called" functions – similar to
yield() in the Arduino environment (which is also used in various OSes to yield running time to other tasks/processes), so I haven't bothered to talk about that, since something like it is always needed whenever you have timers that can wrap around.