There are several choices for this:
The precisely timed interrupts are possible with timer
interrupts found in several uCs (eg. atmeg128 has 2 8 bit and
2 16 bit timers). With timers a small footprint, efficient
and precise timing is possible. (4usec is a practical target
on the atmega128, but needs to keep other interrupt handlers
small - or maybe allow nested interrupts).
We still need to take care about the mainloop, arrange
it to execute more critical tasks in time (we can use
a scheduler there), but mostly freed from the very time
critical tasks it can focus on the main logic of the
application and precalculate the time of events or
fill in variables that are used from interrupt to calculate
event times (like the phase, rpm and ignition advance and
injector pulsewidth of an engine).
I believe that similar functionality is needed in lots of
applications, so I explained it on this page. Also I'm lazy
to explain the same thing in dozens of emails, I like to send
just pointers instead ;=)
It's a little bit more organized and linkable than a passingby
message in a forum.
You can find the
untested code here
Some explanations (cut from an email message):
> you would like to explain to the rest of the group how your > implementation of a heap based system works First I answer the 'why'. There are several ways to do timing. We can poll in a loop or have a 10kHz interrupt, or we can separate these issues: find out when the events are due and have a mechanism to execute them. This easies programming. Obviously we will have more complex functions (think of ion-sense) which we will not want to clutter with doing a bit of (unrelated tweak) here and there. This can help make the code readable and higher quality, with less overhead and more time for higher level functions. We still need to fit the overall functionality into the clocktics, but not need to care with all little places of the code. The eventqueue works this way: we have the events in a heap semi-sorted according to their scheduled time (type key_t). The heap is a packed binary-tree. The root index is 1 (HEAPROOT), the children have the indexes 2x and 2x+1 of their parent (with index == x) (see heap.c parent() and child() functions). At the root is the smallest key (next to do) event. This is what we need most (the next event to do). The parent's key is always smaller (or equal) than the children. This makes it possible to do insertion and deletion in a very efficient, guaranteed logarithmic (to the number of elements in the heap) time. However, since 16 bit resolution overflows fast (4usec*65536 ~ 1/4sec) we need a small trick: we use 2 heaps. If time is currently 45000, the keys >45000 are inserted into the normal heap (hn), and smaller keys inserted into the overflow-heap (ho). This is necessary because of the incontinuity (47000 is before 2000, although 2000 is smaller). When the timer overflows, we consume the normal heap, and swap the 2 heaps. the draft untested code at http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/megasquirtavr/firmware/ To start with, view eventqueue.c: SIGNAL (SIG_OUTPUT_COMPARE2) and heap.c: void heap_replace1st(uint16_t key, uint16_t val) dispatcher.c: void dispatcher(val_t) If many types of events are to be handled, this should be implemented with a hash or maybe tree. (not a big switch-case)Note, that the dispatcher is configurable, it should fire or stop an injector like 4 sequential LOW-Z (PWM-ed) injbanks on
Any comments, questions, improvements, warnings etc. are welcome.