Atmel makes a neat little family of 8-bit microcontrollers called "AVR". They have several KB of RAM, tens of KB of ROM, run on a few milliamps, clock up to several Mhz, and are pretty cheap.
My brother got me started on these back when he was in college using them for projects. He made a few development boards for me, to be able to quickly hook an AVR up to serial and the programmer, and pin headers for easily connecting peripherials.
Since then I found a small company called PJRC who makes a development board called the "Teensy". It's a reasonable price for hobbyists, and nicely compact. They use the USB-enabled versions of the AVR, which can be conveniently programmed over the same USB cable that connects it to the host at runtime, and the programmer software works on all major operating systems. Development iterations can be tested as fast as typing "make" and hitting the button on the microcontroller to re-flash it.
On this page I'm including some resources for writing AVR programs. It is hard to properly wrap the code into libraries, since the device is so small that the little overhead of registering callbacks or allocating instances of objects would quickly eat up the available resources. But, I'll at least try to describe how these bits of code can be rolled into a project.
The AVR libc comes with routines that can pause (busy-loop) for a number of seconds, but if you want to have more precisely timed repeating events you'll want to use one of the hardware timers. It's also just nice to have a general purpose clock reference to look at to find how many ticks/milliseconds have elapsed.
You can actually attach an external crystal to the AVR to drive one of the timer/counters with something like a watch crystal, but as I found out early-on, the Teensy's CPU oscillator is actually more accurate! So, I just drive the timers with a multiplier of the CPU clock. Most AVR projects use a #define macro of "F_CPU" so you can adjust your code to the frequency of the chip at compile time.
I typically use Time/counter 1 at a frequency of F_CPU/8 (2 Mhz on a Teensy) to set up a running "system clock", with a 'ticks' counter and a 'real-time' counter like you would find in a real operating system. I do this by configuring it to count the full 16-bits while setting periodic compare-points where it fires an interrupt, so I can update a millisecond count. Each millisecond I update the compare point so the the interrupt will go off for the next millisecond. (I handle missed interrupts by moving it forward 1ms at a time until it is a time in the future.) I have some nifty math for adjusting the compare-point to sub-tick precision, so most F_CPU values can be handled accurately. In fact, I measured the actual frequency of my board at a wide range of temperatures to determine that my F_CPU was 15999800, so I should have a VERY accurate clock!
This does mean that an interrupt is firing every millisecond, but that is mostly insignificant compared the the number of USB and ADC interrupts that are happening in the DeLorean project. Besides, the 16-bit counter wraps every 33ms and we need to wake up to record that. (a 32-bit counter sure would be nice) I count the wraps of the 16-bit timer in a 16-bit overflow counter, giving a 32-bit tick count, and then also store the milliseconds in a 32-bit counter. With the 2Mhz timer the 32-bit ticks wrap every ~2000 seconds and the 32-bit milliseconds wrap every ~49 days. These are nice ranges for most of my purposes.
Since the avr only has 3 or 4 timers, I wanted to add a task scheduler, where I could queue any number of tasks any number of milliseconds or ticks into the future, and have them activate when it was time. Also, running routines from interrupt handlers can keep interrupts masked for too long, and a scheduler lets the interrupt routines activate tasks and then exit, and then the main loop can come along and run the tasks without worrying about delaying critical interrupts.
I came up with a design where a small "task_t" structure holds a pointer to a function, a callback argument for that function, and the time the task should wake. Then it gets added to either a millisecond-queue, a ticks-queue, or a "now" queue based on what kind of time was specified and whether that time has arrived yet. When the scheduler main loop runs, it periodically sleeps until an interrupt wakes it up, then checks the list for anything ready to run, and then runs the items in the run queue.
The library could use some work still, but it's working for me for now, so I figured I'd share in case anyone wants to help improve it. It's found in sched.h and sched.c in the same github repo as the clock.