I often do 1ms ticks, the ISR simply incrementing the current time. In main, I then have a scheduler queue (simple) which contains the target time and either a function pointer or index for a switch block to do the work, sorted by target time. Each loop pass just needs to check if the target time of the queue head is reached or not, and pop/execute it if so. For delays shorter than 1ms like satisfying setup times, busy wait is usually fine.
Timing precision isn't guaranteed (worst case is probably if your 1ms turns into 0ms, I think), but execution order is, it's pretty easy to extend (e.g. add priority) and manage, and you're not blocking interrupts for very long. Likewise, I usually try to have my ISRs just collect data or set pre-processed outputs/settings and submit an event to the queue for processing if necessary.