Browse Source

Initial commit

master
Stephan Brunner 7 years ago
commit
55194a74ac
No known key found for this signature in database
GPG Key ID: D3CD7237AFB7EF6B
  1. 7
      LICENSE.md
  2. 28
      README.md
  3. 22
      main.c
  4. 133
      os.c
  5. 12
      os.h
  6. 189
      os_asm.s
  7. 7
      os_config.h

7
LICENSE.md

@ -0,0 +1,7 @@
Copyright 2019 Stephan Brunner
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

28
README.md

@ -0,0 +1,28 @@
# Simple operating system for the ATMega328P
This operating system was creating during a class assignment. The code is experimental and highly untested, but it should work.
## How it works
The OS uses a very simple round robin scheduler and does a context switch every 262144 clock cycles. This behaviour is controllable by setting OCR0A and the prescalers of Timer0. You cannot use Timer0 in your application, it is exclusively used by the scheduler.
## How to use
Simply put the files starting with `os` in your project. Edit `os_config.h` to fit your needs. From your main, call `os_init()` and create your tasks with `os_task_add(task_func, task_data)`. To start the scheduler, call `os_run()`. Note that this call never returns, so subsequent code will not be executed.
Please note that tasks currently may never terminate and **must** be terminated using `os_current_task_kill()`. To access the void-pointer given to `os_task_add`, call `os_current_task_get_data()`.
Please do not try to use recursion or deep call stacks, as you only have a very limited stack.
**You may not use Timer0, because it is used for the scheduler! Do not try to reenable interrupts when using custom interrupt service routines!**
## Configuration
The operating system can be configured at compile time using the following define-macros in `os_config.h`:
- `OS_STACK_SIZE` controls the stack size for _one_ task.
- `OS_TASK_COUNT` controls how many tasks _may_ be created simultaneously.
## License
Copyright 2019 Stephan Brunner
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

22
main.c

@ -0,0 +1,22 @@
#include "os.h"
static void single_shot_task(void* data) {
/* Do stuff */
os_current_task_kill();
}
static void looping_task(void* data) {
while (1) {
/* Do stuff */
}
}
int main(void) {
os_init();
os_task_add(single_shot_task, 0);
os_task_add(looping_task, 0);
os_run();
}

133
os.c

@ -0,0 +1,133 @@
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <string.h>
#include "os.h"
#include "os_config.h"
#ifndef ARRAYSIZE
#define ARRAYSIZE(x) (sizeof(x)/sizeof(*(x)))
#endif
// Assemble tasks
void os_asm_switch_to_task(void);
// Temporary buffers for context switch
volatile uint16_t ins_tmp;
volatile uint8_t sreg_tmp;
volatile uint16_t stack_tmp;
volatile uint8_t reg_tmp[32];
struct os_task {
uint8_t valid;
os_task_func code_ptr;
void* data_ptr;
uint8_t* stack_ptr;
uint8_t status;
uint8_t registers[32];
uint8_t stack[OS_STACK_SIZE];
} tasks[OS_TASK_COUNT];
typedef size_t os_task_id_t;
#define TASK_IDX_INVALID -1
static os_task_id_t task_current_idx = TASK_IDX_INVALID;
void os_init(void) {
for (size_t i = 0; i < ARRAYSIZE(tasks); i++) {
tasks[i].valid = 0;
}
}
void os_run(void) {
TCCR0A = 0;
OCR0A = 0xFF;
TCCR0B = _BV(WGM12) | _BV(CS10);
TIMSK0 |= _BV(OCIE1A);
// Wait fo fist task switch
sei();
TCNT0 = 0xFF;
while (1);
}
static void os_do_switch_to_task(os_task_id_t task_idx) {
task_current_idx = task_idx;
struct os_task* task = tasks + task_idx;
for (size_t i = 0; i < ARRAYSIZE(task->registers); i++) {
reg_tmp[i] = task->registers[i];
}
ins_tmp = (uint16_t) task->code_ptr;
sreg_tmp = task->status;
stack_tmp = (uint16_t) task->stack_ptr;
// Reset Timer and do context switch
TCNT0 = 0;
TIFR0 &= ~_BV(OCF0A);
os_asm_switch_to_task();
}
void os_interrupt_saved(void) {
os_task_id_t next_task_idx = TASK_IDX_INVALID;
if (task_current_idx != TASK_IDX_INVALID) {
struct os_task* task_current = tasks + task_current_idx;
for (size_t i = 0; i < ARRAYSIZE(task_current->registers); i++) {
task_current->registers[i] = reg_tmp[i];
}
task_current->code_ptr = (void*) ins_tmp;
task_current->status = sreg_tmp;
task_current->stack_ptr = (uint8_t*) stack_tmp;
next_task_idx = task_current_idx + 1;
next_task_idx %= ARRAYSIZE(tasks);
} else {
if (tasks[0].valid) {
next_task_idx = 0;
}
}
if (next_task_idx == TASK_IDX_INVALID) {
while (1);
}
os_do_switch_to_task(next_task_idx);
}
void os_task_add(os_task_func func, void* data) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
for (size_t i = 0; i < ARRAYSIZE(tasks); i++) {
struct os_task* t = tasks + i;
if (t->valid) {
continue;
}
t->valid = 1;
t->code_ptr = func;
t->stack_ptr = t->stack + sizeof (t->stack) - 1;
t->status = 0;
t->data_ptr = data;
break;
}
}
}
void* os_current_task_get_data(void) {
return tasks[task_current_idx].data_ptr;
}
void os_current_task_kill(void) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
tasks[task_current_idx].valid = 0;
}
// Wait fo context switch.
while (1);
}
// Linker magic to add interrupt vector
void TIMER0_COMPA_vect(void) __attribute__((signal, naked, __INTR_ATTRS));

12
os.h

@ -0,0 +1,12 @@
#ifndef _OS_H_
#define _OS_H_
typedef void (*os_task_func)(void*);
void os_init(void);
void os_run(void);
void os_task_add(os_task_func func, void* data);
void os_current_task_kill(void);
void* os_current_task_get_data(void);
#endif /* _OS_H_ */

189
os_asm.s

@ -0,0 +1,189 @@
#include <avr/io.h>
.section .text
// Task switch interrupt
.global TIMER0_COMPA_vect
TIMER0_COMPA_vect:
push r26
push r27
push r28
push r29
push r30
push r31
// Save SREG
in r26, 0x3F
ldi r31, hi8(sreg_tmp)
ldi r30, lo8(sreg_tmp)
st Z, r26
// Save Instruction + Stack
in r29, 0x3E // SPH
in r28, 0x3D // SPL
adiw YL, 6+2
// Save Stack
ldi r31, hi8(stack_tmp)
ldi r30, lo8(stack_tmp)
st Z+, r28
st Z+, r29
sbiw YL, 1
// Save instruction
ld r26, Y+
ld r27, Y+
ldi r31, hi8(ins_tmp)
ldi r30, lo8(ins_tmp)
st Z+, r27
st Z+, r26
// r26 = i
// r27 = tmp
// Z = register file
// Y = reg_tmp
// Z = 0
ldi r31, 0
ldi r30, 0
ldi r29, hi8(reg_tmp)
ldi r28, lo8(reg_tmp)
ldi r26, 0
_os_interrupt_cpy_next:
ld r27, Z+
st Y+, r27
inc r26
// We can only save the first 26 registers, the rest is used here
cpi r26, 26
breq _os_interrupt_regsaved
rjmp _os_interrupt_cpy_next
_os_interrupt_regsaved:
pop r31
pop r30
pop r29
pop r28
pop r27
pop r26
mov r16, r26
mov r17, r27
mov r18, r28
mov r19, r29
mov r20, r30
mov r21, r31
// Z = register file
// Y = reg_tmp
ldi ZH, 0
ldi ZL, 16
ldi YH, hi8(reg_tmp + 26)
ldi YL, lo8(reg_tmp + 26)
mov r26, 0
// r26 = i
// r27 = tmp
_os_interrupt_cpy_next_2:
ld r27, Z+
st Y+, r27
inc r26
cpi r26, 6
breq _os_interrupt_regsaved_2
rjmp _os_interrupt_cpy_next_2
// Pop last instruction addr
pop r31
_os_interrupt_regsaved_2:
jmp os_interrupt_saved
// Restore task context and jump to task
.global os_asm_switch_to_task
os_asm_switch_to_task:
// Stack
ldi YL, lo8(stack_tmp)
ldi YH, hi8(stack_tmp)
ld ZL, Y+
ld ZH, Y
out 0x3E, ZH // SPH
out 0x3D, ZL // SPL
// Old Instruction
ldi YH, hi8(ins_tmp)
ldi YL, lo8(ins_tmp)
ld ZL, Y+
ld ZH, Y+
push ZL
push ZH
// R30-31
ldi YH, hi8(reg_tmp+30)
ldi YL, lo8(reg_tmp+30)
ld ZH, Y+
push ZH
ld ZH, Y+
push ZH
// SREG
ldi YL, lo8(sreg_tmp)
ldi YH, hi8(sreg_tmp)
ld ZL, Y
push ZL
// R26-R29
ldi YL, lo8(reg_tmp+26)
ldi YH, hi8(reg_tmp+26)
ldi ZL, 0
_reg_copy1_next:
cpi ZL, 4
breq _reg_copy1_finished
ld ZH, Y+
push ZH
inc ZL
jmp _reg_copy1_next
_reg_copy1_finished:
// R0-R25
ldi YL, lo8(reg_tmp)
ldi YH, hi8(reg_tmp)
ldi ZL, 0
ldi ZH, 0
_reg_copy2_next:
cpi ZL, 26
brge _reg_copy2_finished
ld r26, Y+
st Z+, r26
rjmp _reg_copy2_next
_reg_copy2_finished:
pop r29
pop r28
pop r27
pop r26
pop r31
out 0x3F, r31 // SREG
pop r31
pop r30
reti

7
os_config.h

@ -0,0 +1,7 @@
#ifndef _OS_CONFIG_H
#define _OS_CONFIG_H
#define OS_STACK_SIZE 128
#define OS_TASK_COUNT 8
#endif /* _OS_CONFIG_H */
Loading…
Cancel
Save