Hier eine kleine Anleitung auf deutsch (im Code steht's in Englisch):
Diese Zeilen müssen in den C Code mit rein:
extern volatile unsigend char thread_id;
extern void thread_switch(void);
extern void thread_start(void(*thread_function)(void),
unsigned char *stack,
unsigned int stacksize);
extern void thread_lock();
extern void thread_unlock();
Dann wird natürlich noch eine Thread Funktion gebraucht. Z.B so:
void mysecondthread(void)
{
unsigned char counter = 60;
printf("second thread started\r\n");
while(counter--) {
sleep_1_second();
printf("second thread is still active\r\n");
}
printf("second thread terminates\r\n");
}
Dann wird noch ein bischem Memory für den Stack gebraucht.
Z.B. statisch:
static unsigned char mystack[256];
geht aber auch dynamisch, z.B. mit malloc():
unsigned char *mystack = malloc(256);
Wichtig ist nur, daß der stack während der Laufzeit des zweiten
Threads auch vorhanden ist und natürlich groß genug ist.
Der Thread wird dann mit thread_start() gestartet:
...
thread_start(mysecondthread, mystack, sizeof(mystack));
...
jetzt ist der Thread aktiv und läuft solange, bis die Thread
Funktion returned oder mit thread_switch() umgeschaltet wird.
Jeder Aufruf von thread_switch() legt den gerade aktiven Thread
'schlafen' und macht den anderen Thread 'aktiv'. Diese Funktion
macht also z.B. im Timer Interrupt Sinn:
volatile uint16_t time_ms;
volatile uint32_t time_s;
void timer_init()
{
// stop timer
TCCR0 = 0;
// clean time_s, time_ms
time_s = 0;
time_ms = 0;
// set timer 0 value to 0
TCNT0 = 0;
// set compare register to 250
OCR0 = 250;
// set prescaller for timer 0,1 to CK/64
// set clear-timer-on-compare-match on
TCCR0 = (1<<CS01) | (1<<CS00) | (1<<WGM01);
// now we get every 16000000/(250*64)=1000 CK a Compare Match
// this is exact every Millisecond
// enable Timer 0 Compare Match Interrupt
TIMSK |= (1<<OCIE0);
}
ISR(TIMER0_COMP_vect)
{
time_ms++;
if(time_ms >= 1000) {
time_ms = 0;
time_s++;
}
// switch threads (does no harm if second
// thread is not running)
thread_switch();
}
Aber thread_switch() muß nicht zwingend im Interrupt Kontext aufgerufen
werden.
Der ganze Code ist darauf ausgelegt, genau einen 'zweiten' Thread zu
ermöglichen. Der Code ist klein gehalten und braucht nur 3 Bytes für
die interne Verwaltung. thread_start() macht einfach nix, wenn der
zweite Thread bereits läuft. Ein neuer 'zweiter' Thread kann erst
gestarted werden, wenn der vorherige sich beendet hat. Ebenso macht
thread_switch() einfach nichts, wenn kein zweiter Thread läuft.
Mit multithreaded Programmierung muß man sich auch immer ein paar
Gedanken um gemeinsam genutzte Resourcen machen. Bei 16 Bit IO
Operationen oder Zugriff auf globale Variablen muß entsprechend ein
bischen mehr Aufwand getrieben werden. Eine simple Lösung ist schlicht
und einfach mal schnell die Interrupts während des Zugriffs auszumachen.
Intelligentere Lösungen sind Mutexe, Semaphoren. Hier habe ich lediglich
thread_lock() und thread_unlock() implementiert. Die Benutzung ist
einfach. z.B. wird bei einer von beiden Threads genutzten Variablen
jeder Zugriff auf diese Variable von thread_lock/thread_unlock umrundet:
int mycommonvalue = 0;
...
thread_lock();
mycommonvalue++;
thread_unlock();
...
geht natürlich auch mit Funktionen. Z.b. wenn beide Threads
'gleichzeitig' auf serial line was ausgeben, kommt nur Buchstabensalat
raus weil thread_switch() im Timerinterrupt die Threads währenddessen
hin und her schaltet. Werden die printf() Aufrufe mit thread_lock() und
thread_unlock() umrundet, ist die Ausgabe auf UART wieder lesbar.
Falls noch Fragen auftauchen, hier reinposten.