/*-----------------------------------------------------------------------------+ | | | Simple threading implementation for the AVR Microcontroller | | | +------------------------------------------------------------------------------+ | | | Copyright (C) 2009 Harald Freudenberger (haraldfreude@arcor.de) | | | | This program is free software; you can redistribute it and/or modify it | | under the terms of the GNU General Public License as published by the Free | | Software Foundation; either version 3 of the License, or (at your option) | | any later version. | | | | This program is distributed in the hope that it will be useful, but | | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | | for more details. | | | | You should have received a copy of the GNU General Public License along with | | this program; if not, see . | | | +------------------------------------------------------------------------------+ | | | How to use this feature | | | | Add these lines to your C sources: | | | | 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(); | | | | Implement your thread function. An example: | | | | 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"); | | } | | | | Provide stack space for your second thread. Can be done static | | | | static unsigned char mystack[256]; | | | | or dynamic (take care to not free this space before the thread finishes) | | | | unsigned char *mystack = malloc(256); | | | | Call the thread_start() function to start your second thread: | | | | ... | | thread_start(mysecondthread, mystack, sizeof(mystack)); | | ... | | | | Think about how you want to schedule your second thread. With every call to | | thread_switch() the threads are switched between your main thread and the | | second thread in your thread_function. One way to schedule is to establish | | a timer interrupt function and call thread_switch every millisecond. | | Here is some code which is doing this: | | | | 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<= 1000) { | | time_ms = 0; | | time_s++; | | } | | // switch threads (does no harm if second thread is not running) | | thread_switch(); | | } | | | | You don't need to do this via interrupt handler. Maybe for your application | | it is enougth to call thread_swich() every time your main thread is idle. | | The thread_id variable can be used to find out, what's the current active | | thread: 0 - no second thread is running, 1 - main thread is active, 2 - | | second thread is active. | | | | The thread_lock() and thread_unlock() functions provide a simple locking. | | You need this eg. when both threads work on the same hardware. Eg. if both | | threads are printing out some stuff the output will be mixed and it may even | | result in crashes. So if both threads are locked against each other during | | the printf call like this: | | | | ... | | thread_lock(); | | printf("hi out there\r\n"); | | thread_unlock(); | | ... | | | | the output will not mix up characters from both threads any more. | | | | Some notes about the implementation: | | | | - The stack usage is not checked. So starting a second thread with 64 bytes | | stack space into a function with heavy stack usage may end up in | | unexpected overwriting of used memory areas. Make sure the stack space | | is available during the livetime of the second thread and is big enougth. | | - Thread scheduling is expensive. The thread_switch() function needs about | | 160 cycles. With 16 MHz and switching every 1 ms this is 1% of the cpu. | | - The code is too big. Maybe someone can give me an idea about how to | | save/restore all the registers without so many push/pop commands. | | | +-----------------------------------------------------------------------------*/ #ifndef __ASSEMBLY__ #define __ASSEMBLY__ #endif #include /*-----------------------------------------------------------------------------*/ .macro push_registers push r0 push r1 push r2 push r3 push r4 push r5 push r6 push r7 push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 push r16 push r17 push r18 push r19 push r20 push r21 push r22 push r23 push r24 push r25 push r26 push r27 push r28 push r29 push r30 push r31 .endm .macro pop_registers pop r31 pop r30 pop r29 pop r28 pop r27 pop r26 pop r25 pop r24 pop r23 pop r22 pop r21 pop r20 pop r19 pop r18 pop r17 pop r16 pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop r7 pop r6 pop r5 pop r4 pop r3 pop r2 pop r1 pop r0 .endm /*-----------------------------------------------------------------------------*/ .section .data .global thread_id .global thread_stackptrstore .global thread_lockstate thread_id: .byte 0x00 /* the currently active 'thread_id' is stored here with: 0 no second thread active, 1 main thread is currently running 2 second thread is currently running */ thread_stackptrstore: .byte 0x00, 0x00 /* storage for the stack pointer for the currently not active thread. thread_stackptrstore=hi thread_stackptrstore+1=lo */ thread_lockstate: .byte 0x00 /* locking state storage with: 0 no lock, otherwise the thread_id for the thread holding the lock is stored */ /*-----------------------------------------------------------------------------*/ .section .text .global thread_start .global thread_end .global thread_switch .global thread_lock .global thread_unlock thread_start: /* void thread_start(void(*thread_function)(void), unsigned char *stack, unsigned int stacksize) */ /* thread_function pointer is in r25:r24 the stack pointer is in r23:r22 stacksize is in r21:r20 */ in r0, _SFR_IO_ADDR(SREG) cli /* check thread_id value */ push r16 lds r16, thread_id cpi r16, 0x00 pop r16 breq 1f /* thread_id is not zero, second thread is already running */ out _SFR_IO_ADDR(SREG), r0 ret 1: /* thread_id is zero, second thread is not yet started */ /* SREG value is in r0, move SREG value to r1 */ mov r1, r0 /* now all registes onto the current stack (SREG value in r1) */ push_registers /* store the current stack pointer into stackptrstore */ in r27, _SFR_IO_ADDR(SPH) in r26, _SFR_IO_ADDR(SPL) sts thread_stackptrstore, r27 sts thread_stackptrstore+1, r26 /* now calculate the new stack pointer value */ mov r26, r22 mov r27, r23 add r26, r20 adc r27, r21 sbiw r26, 1 /* and establish this as the new stack pointer */ out _SFR_IO_ADDR(SPH), r27 out _SFR_IO_ADDR(SPL), r26 /* setup the thread_id value (1 main thread, 2 alternate thread) */ ldi r16, 0x02 sts thread_id, r16 /* clear thread locking state */ clr r16 sts thread_lockstate, r16 /* now fill the new stack with something usefull */ /* deepest goes the thread_end function address */ ldi r26, lo8(pm(thread_end)) ldi r27, hi8(pm(thread_end)) push r26 push r27 /* next goes the thread_function address */ push r24 push r25 /* that's it, wenn we now return the thread_function is invoked */ /* restore SREG, restore r1 and return... */ rjmp 9f thread_end: /* only used internal when thread_function() returns */ /* only called by the second thread when its thread */ /* function returns */ cli /* restore the main threads stack pointer */ lds r27, thread_stackptrstore lds r26, thread_stackptrstore+1 out _SFR_IO_ADDR(SPH), r27 out _SFR_IO_ADDR(SPL), r26 /* clear the thread id */ sts thread_id, r1 /* clear locking state */ sts thread_lockstate, r1 /* restore registers, restore SREG, ... */ rjmp 8f thread_switch: /* void thread_switch(void) */ push r0 in r0, _SFR_IO_ADDR(SREG) cli /* check thread_id */ push r16 lds r16, thread_id cpi r16, 0x00 pop r16 brne 1f /* thread_id is zero, nothing to switch to */ out _SFR_IO_ADDR(SREG), r0 pop r0 ret 1: /* thread_id is not zero, second thread was started */ /* SREG value is in r0 and r0 is on the stack */ /* move SREG value to r1 and restore r0 value */ mov r1, r0 pop r0 /* now push all registes onto the current stack (SREG value in r1) */ push_registers /* update thread_id (1 main thread, 2 alternate thread) */ lds r16, thread_id dec r16 brne 2f ldi r16, 0x02 2: sts thread_id, r16 /* switch the stack pointer */ in r27, _SFR_IO_ADDR(SPH) in r26, _SFR_IO_ADDR(SPL) lds r29, thread_stackptrstore lds r28, thread_stackptrstore+1 out _SFR_IO_ADDR(SPH), r29 out _SFR_IO_ADDR(SPL), r28 sts thread_stackptrstore, r27 sts thread_stackptrstore+1, r26 8: /* pop the registers from the new stack (r1 holds the SREG value) */ pop_registers 9: /* restore SREG, restore r1 and go back */ out _SFR_IO_ADDR(SREG), r1 clr r1 ret thread_lock: /* void thread_lock(void) */ /* loop until thread_lockstate is zero */ 1: lds r24, thread_lockstate cpi r24, 0x00 brne 1b /* ok, now the same with interrupts disabled */ in r0, _SFR_IO_ADDR(SREG) cli lds r24, thread_lockstate cpi r24, 0x00 breq 2f /* shit, thread_lockstate changed to nonzero */ /* just some cyles ago. try again ... */ out _SFR_IO_ADDR(SREG), r0 rjmp 1b 2: /* thread_lockstate is zero, interrupts are off */ /* now we lock by writing in our thread_id */ lds r24, thread_id sts thread_lockstate, r24 /* done, restore SREG and go back */ out _SFR_IO_ADDR(SREG), r0 ret thread_unlock: /* void thread_unlock(void) */ /* loop until thread_lockstate is the same */ /* as our thread_id */ 1: lds r25, thread_id in r0, _SFR_IO_ADDR(SREG) cli lds r24, thread_lockstate cp r24, r25 breq 2f out _SFR_IO_ADDR(SREG), r0 rjmp 1b 2: /* ok, now clear thread_lockstate */ sts thread_lockstate, r1 /* done, restore SREG and go back */ out _SFR_IO_ADDR(SREG), r0 ret