Hi, i will try to make a "short" introduction first and I will try to explain what is all about (my native language is Romanian).
My journey in the microcontroller world started with PICAXE08M microcontroller, thanks to an australian friend and, obviously, I migrated later to true Microchip microcontrollers, where I learned to program them using the JAL language. We have there, inside jallib library collection, a library named timer0_isr_interval where you can define your own non-blocking delays.
Some how, is working like millis() function but in a different way. First, you must set the size of an array corresponding to the number of total non-blocking delays you need in your application. Then, in every cell of that array, you put a number representing the required delay in milliseconds. An interrupt will trigger at every millisecond and instead of a variable increment as in millis, it will decrement the values of every cell until those will equal zero. Periodically, in your program, you will check if your delay has become zero and that means that that delay has just expired and you do the task assigned to that delay. Then, you set the delay again at the initial value.
It is useful in simulating multitasking.
After a while, I decided that is the time to see what are those AVRs, what are they doing, and if their parents knows that! Obviously, as a good PIC programmer, I bricked my first ATmega32 and returned for awhile back to PICs. But I don't like to lose battles so I tried again with an ATmega644P (util I will make an AVR Doctor, my ATmega32 remains bricked). This time I was careful and I succeeded in transforming it into a Sanguino microcontroller. I started exploring Wiring language and I can recognize the educational value and the project completion speed it offers. But i don't like how it waste the microcontroller resources. Because of this, I moved to avr-gcc toolchain and a big help was the project of Mike, libarduino, hosted on googlecode.com.
I don't know yet too much about the interrupts and timers of AVRs but I succeeded in porting the slotted, non-blocking delays from JAL to avr-gcc looking at the millis() example. I decided to compare them and for that I took a project theme where you must simultaneously blink 3 LEDs at different speeds: 250, 500 and 750 milliseconds. So here are the two avr-gcc C examples:
Using millis():
#ifndef F_CPU
#define F_CPU 16000000U // required by Atmel Studio 6
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
//#include <util/delay.h>
#define sbi(PORT, BIT) (_SFR_BYTE(PORT) |= _BV(BIT)) // set bit
#define tbi(PORT, BIT) (_SFR_BYTE(PORT) ^= _BV(BIT)) // toggle bit
#define FALSE 0
#define TRUE 1
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)
volatile uint32_t timer0_overflow_count = 0;
volatile uint32_t timer0_millis = 0;
static uint8_t timer0_fract = 0;
//#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
//ISR(TIM0_OVF_vect)
//#else
ISR(TIMER0_OVF_vect)
//#endif
{
// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
uint32_t m = timer0_millis;
uint8_t f = timer0_fract;
m += MILLIS_INC;
f += FRACT_INC;
if (f >= FRACT_MAX) {
f -= FRACT_MAX;
m += 1;
}
timer0_fract = f;
timer0_millis = m;
timer0_overflow_count++;
}
void millis_init(void) {
#if defined(TCCR0A) && defined(WGM01)
sbi(TCCR0A, WGM01);
sbi(TCCR0A, WGM00);
#endif
// set timer 0 prescale factor to 64
#if defined(__AVR_ATmega128__)
// CPU specific: different values for the ATmega128
sbi(TCCR0, CS02);
#elif defined(TCCR0) && defined(CS01) && defined(CS00)
// this combination is for the standard atmega8
sbi(TCCR0, CS01);
sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
// this combination is for the standard 168/328/1280/2560
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);
#elif defined(TCCR0A) && defined(CS01) && defined(CS00)
// this combination is for the __AVR_ATmega645__ series
sbi(TCCR0A, CS01);
sbi(TCCR0A, CS00);
#else
#error Timer 0 prescale factor 64 not set correctly
#endif
// enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
sbi(TIMSK0, TOIE0);
#else
#error Timer 0 overflow interrupt not set correctly
#endif
}
uint32_t millis()
{
uint32_t m;
uint8_t oldSREG = SREG;
// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli();
m = timer0_millis;
SREG = oldSREG;
return m;
}
uint32_t currentMillis, pLED1Millis = 0, pLED2Millis = 0, pLED3Millis = 0;
void main(void) __attribute__((noreturn));
void main(void) {
sbi(DDRD, 5);
sbi(DDRC, 6);
sbi(DDRC, 7);
millis_init();
sei();
while (1) {
//
currentMillis = millis();
if (currentMillis - pLED1Millis > 250) {
pLED1Millis = currentMillis;
tbi(PORTD, 5);
}
if (currentMillis - pLED2Millis > 500) {
pLED2Millis = currentMillis;
tbi(PORTC, 6);
}
if (currentMillis - pLED3Millis > 750) {
pLED3Millis = currentMillis;
tbi(PORTC, 7);
}
}
}