Skip to content
Snippets Groups Projects
Commit 800baee9 authored by spacecheese's avatar spacecheese
Browse files

Migrate from sourcekettle.soton.ac.uk

parents
Branches
No related tags found
No related merge requests found
# AVR Build files
*.elf
*.hex
*.a
*.o
*.s
# VSCode
.vscode
*.code-workspace
\ No newline at end of file
////////////////////////////////////////////////////////////////
// File: scheduling.h
// Authors: James Watson
// Date: 11th February 2020
// Description: Header for cooperatively scheduling tasks
// using round robin on an ATmega644p
////////////////////////////////////////////////////////////////
#ifndef SCHEDULING_H
#define SCHEDULING_H
#include <inttypes.h>
#include <stdbool.h>
/**
* @brief The maximum number of tasks that can be handled by the scheduler simultaneously.
*/
#define SCHEDULING_MAX_TASKS 8
/**
* @brief The maximum amount of memory in bytes that can be allocated on the stack before tasks will fail to start.
*/
#define SCHEDULING_MAX_STACK_SIZE 3000
/**
* @brief Generate a block handle that can be used to block and release tasks.
*/
#define SCHEDULING_BLOCK_HANDLE() __COUNTER__ + 1
/**
* @brief The size of the scheduler allocation on the stack in bytes.
*/
#define SCHEDULER_ALLOCATION 512
typedef void Task(uint8_t argCount, char args[]);
/**
* @brief A handle identifying a task to the scheduler.
*/
typedef void* TaskHandle;
/**
* @brief A unique handle identifying the block to the scheduler.
* Use SCHEDULING_BLOCK_HANDLE() to create one.
*/
typedef uint8_t BlockHandle;
/**
* @brief Start the specifed task
*
* @param task A pointer to the task main function.
* @param allocation The size of stack to allocate to the task in bytes. Should be equal to
* the tasks maximum usage + at least 34 bytes to allow space for context saving.
* @param argc The number of arguments provided in args[].
* @param args The arguments to supply to the task on start.
* @return TaskHandle A handle that identifies the task to the scheduler.
*/
TaskHandle SchedulingRunTask(Task* task, uint16_t allocation, uint8_t argc, char args[]);
/**
* @brief The task corresponding to the supplied handle is no longer run. If the task
* calling this is the task to be aborted SchedulingSurrender() should be called
* immediatly after.
*
* @param handle The handle corresponding to the target task.
*/
void SchedulingAbortTask(TaskHandle handle);
/**
* @brief Block the current task until the specified handle is released.
*
* @param handle A unique handle number that identifies the block.
*/
void SchedulingBlockTask(const BlockHandle handle);
/**
* @brief Immediatly release the task blocked using the specified handle.
*
* @param handle A unique handle number that identifies the block.
* @param immediate Indicates if context should be immediatly restored to the first unblocked task using
* this handle. This will not be honoured if not all tasks have started or an immediately released task
* is already being serviced.
*/
void SchedulingReleaseTask(const BlockHandle handle, const bool immediate);
/**
* @brief Surrender the current tasks control to the scheduler until the round robin reaches the task.
* This function uses two stack bytes.
*/
void SchedulingSurrender(void);
/**
* @brief Start the scheduler. Control will not be returned.
*
* @param sleep Boolean indicating if the scheduler should sleep at the end of each round robin cycle.
* In this case an interrupt that wakes the processor from the idle sleep mode should be setup to
* occasionally fire (such as Timer2).
*/
void SchedulingStart(bool sleep);
#endif
\ No newline at end of file
makefile 0 → 100644
COMPILER = avr-gcc
LIBRARIAN = avr-ar
COMPILER_FLAGS = -Os -mmcu=atmega644p -DF_CPU=12000000 -Wall -Wextra -std=c11 -pedantic-errors -Iinc
SOURCE_PATH = src
OUTPUT_PATH = bin
SOURCES = $(wildcard $(SOURCE_PATH)/*.c)
OBJECTS = $(patsubst $(SOURCE_PATH)/%.c, $(OUTPUT_PATH)/%.o, $(SOURCES))
ASM = $(patsubst $(SOURCE_PATH)/%.c, $(OUTPUT_PATH)/%.s, $(SOURCES))
# Library File
$(OUTPUT_PATH)/libscheduling.a: $(OBJECTS)
$(LIBRARIAN) rcs -o $@ $^
# Object Files
$(OUTPUT_PATH)/%.o: $(SOURCE_PATH)/%.c
$(COMPILER) $^ -c $(COMPILER_FLAGS) -o $@
asm: $(ASM)
$(OUTPUT_PATH)/%.s: $(SOURCE_PATH)/%.c
$(COMPILER) $^ -S $(COMPILER_FLAGS) -o $@
.PHONY: clean
clean:
rm $(OUTPUT_PATH)/*
\ No newline at end of file
////////////////////////////////////////////////////////////////
// File: scheduling.c
// Author: James Watson, FreeRTOS Kernal Contributors
// Date: 11th February 2020
// Description: Implementation of cooperative round robin
// scheduler on an ATmega644p
////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <string.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <scheduling.h>
#include "schedulingMacro.h"
typedef struct {
Task* main;
uint16_t allocation;
uint8_t argc;
char* args;
bool running;
BlockHandle blockHandle;
StackPointer context;
} taskStatus;
// Static means definition is not visible outside of this file.
/**
* The current stack pointer for the scheduler context.
*/
static StackPointer schedulerContext;
static StackPointer* newContext;
static taskStatus tasks[SCHEDULING_MAX_TASKS];
static uint8_t tasksCount;
static volatile uint8_t tasksIndex;
static volatile bool allTasksRunning = false;
static volatile uint16_t stackBottom = (uint16_t)RAMEND - SCHEDULER_ALLOCATION;
// Stack bottom pointer used in assembly- not unused.
__attribute__((used)) static volatile uint16_t* stackBottomPointer = &stackBottom;
TaskHandle SchedulingRunTask(Task* task, const uint16_t allocation, uint8_t argc, char* args) {
// Add tasks to list to be started when the scheduler is run.
tasks[tasksCount] = (taskStatus){task, allocation, argc, args, false, 0, 0};
return &tasks[tasksCount++];
}
void SchedulingAbortTask(TaskHandle handle) {
// Update the running state on the target task.
(*(taskStatus*)handle).running = false;
}
__attribute__((naked)) void SchedulingContextSwitch(void) {
// Save the current scheduler context below the program counter.
// Saving context disables interrupts.
SCHEDULING_SAVE_CONTEXT();
// Update the pointer to the stack pointer for RESTORE_CONTEXT.
CurrentContext = newContext;
// Restore the context of the new function.
SCHEDULING_RESTORE_CONTEXT();
// Jump to the address at the bottom of the suspended tasks context.
__asm__ volatile("ret");
}
void SchedulingBlockTask(const BlockHandle handle) {
// Block interrupts whilst modifying the task list non atomically.
cli();
// Can't use tasksIndex here as interrupt may cause this be be wrong.
for (uint8_t i = 0; i < tasksCount; i++) {
if (&(tasks[i].context) == CurrentContext) {
// Update the blocking source for the current task.
tasks[i].blockHandle = handle;
}
}
// Surrender the current task until the block is released.
SchedulingSurrender();
sei();
}
void SchedulingReleaseTask(const BlockHandle handle, const bool immediate) {
uint8_t unblockedTaskI = 0;
bool taskUnblocked = false;
// Don't allow interrupts whilst releasing a task.
cli();
for (uint8_t i = 0; i < tasksCount; i++) {
// Check every task in case there are multiple tasks using the same handle.
if (tasks[i].blockHandle == handle) {
// Clear the blocking handle to resume.
tasks[i].blockHandle = 0;
// Need to unblock all waiting tasks before running the new task if immediate is set.
unblockedTaskI = i;
// Remember that a task was unblocked.
taskUnblocked = true;
}
}
if (!immediate ||
!taskUnblocked ||
!allTasksRunning ||
&(tasks[unblockedTaskI].context) == newContext ||
tasks[tasksIndex].context != *newContext) {
// If the unblocked task should not be immediately run
// Or no task was unblocked
// Or not all tasks are running
// Or the new task is already running (the running task context is the same as what we were about to start)
// Or another immediate task is being serviced (the running task context wasn't scheduled using tasksIndex).
// newContext is equivelant to CurrentContext but is updated before a context switch occurs.
sei();
return;
}
// Immediately give context to the first task associated with blockHandle.
// The current task is going to be interrupted.
// We should run the current (interrupted) task ASAP so it can voluntarily surrender.
// The scheduler will increment tasksIndex so to run the current task again we need to decrement it.
if (newContext != &schedulerContext) {
// Don't re-run the current task the scheduler was interrupted
// Or it was the task that was released.
tasksIndex--;
}
// Update next context for switch to the unblocked task.
newContext = &(tasks[unblockedTaskI].context);
// Context switch will update interrupts to match the task setting.
SchedulingContextSwitch();
sei();
}
__attribute__((naked)) void StartTask(void){
// Save the current context for restoration.
// Saving context disables interrupts.
SCHEDULING_SAVE_CONTEXT();
// Move the stack pointer to the start of the new tasks allocation.
// Modified snippet of SCHEDULING_RESTORE_CONTEXT. See the copyright notice in scheduling.h
// Keep interrupts disabled whilst modifying the stack pointer.
__asm__ volatile( \
"lds r26, stackBottomPointer \n\t" \
"lds r27, stackBottomPointer + 1 \n\t" \
"ld r28, x+ \n\t" \
"out __SP_L__, r28 \n\t" \
"ld r29, x+ \n\t" \
"out __SP_H__, r29 \n\t"
);
// Subsequent task surrenders need to be performed in the new task context.
CurrentContext = &tasks[tasksIndex].context;
// Interrupts should be enabled at the start of a task.
sei();
// Run the task main function in it's new context.
tasks[tasksIndex].main(tasks[tasksIndex].argc, tasks[tasksIndex].args);
}
void StartTasks(void) {
// Set current context so that StartTask saves context in the right place on first iteration.
CurrentContext = &schedulerContext;
while (tasksIndex < tasksCount) {
uint16_t newStackBomttom = stackBottom - tasks[tasksIndex].allocation;
if (newStackBomttom < RAMEND - SCHEDULING_MAX_STACK_SIZE) {
// Not enough memory to start the new task.
exit(EXIT_FAILURE);
}
// Calculate the new number of bytes allocated to the stack.
// The stack pointer counts down from RAMEND.
stackBottom = newStackBomttom;
// Mark the task as running.
tasks[tasksIndex].running = true;
// Context should be restored to the StartTask call when control is surrendered.
StartTask();
// Move to starting the next task.
tasksIndex++;
}
// All tasks have been started.
allTasksRunning = true;
sei();
}
void Sleep(void) {
set_sleep_mode(SLEEP_MODE_IDLE);
// Avoid possible race conditions from manipulation of Sleep Enable bit.
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/group__avr__sleep.html
cli();
sleep_enable();
// Disable brown out detector to save power.
sleep_bod_disable();
// Instruction after sei macro will always run.
sei();
sleep_cpu();
// Microcontroller has been woken from sleep.
sleep_disable();
}
void SchedulingSurrender(void) {
// Switch to the scheduler context.
newContext = &schedulerContext;
SchedulingContextSwitch();
}
void SchedulingStart(const bool sleep) {
if (tasksCount == 0) {
// Don't run if there are no tasks queued.
// Programmer has made some error.
exit(EXIT_FAILURE);
}
StartTasks();
while (1) {
// Main scheduler body.
// Reset the task index to start from the first task in the next round robin cycle.
tasksIndex = 0;
while (tasksIndex < tasksCount) {
// Simple round-robin scheduling.
if (tasks[tasksIndex].running != true || tasks[tasksIndex].blockHandle != 0) {
// Skip this task if it shouldn't be run or is blocked.
tasksIndex++;
continue;
}
// Disable interrupts before setting newContext.
// Interrupts will be restored by the context switch.
cli();
// Set the new context for the incoming switch.
newContext = &tasks[tasksIndex].context;
// Call SchedulingContextSwitch to place current PC on stack for context restoration.
SchedulingContextSwitch();
// Interrupts should be re enabled.
sei();
// Move to the next task.
tasksIndex++;
}
// All tasks have been run in this cycle.
if (sleep) {
Sleep();
}
}
}
\ No newline at end of file
////////////////////////////////////////////////////////////////
// File: schedulingMacro.h
// Authors: James Watson, FreeRTOS Kernal Contributors
// Date: 25th February 2020
// Description: Macros for context switching derrived
// from the FreeRTOS Kernal
////////////////////////////////////////////////////////////////
#ifndef SCHEDULING_MACRO_H
#define SCHEDULING_MACRO_H
#include <inttypes.h>
typedef uint16_t StackPointer;
/**
* @brief DO NOT MODIFY!
* A pointer to a stack address for the currently running task.
*/
volatile StackPointer* CurrentContext;
/*
* FreeRTOS Kernel V10.3.0
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* 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.
*
* The following macros are derived from FreeRTOS ATMega323 port.c and are subject
* to the above notice.
*/
/**
* Save the registers to the stack then store the stack pointer in *CurrentContext.
*/
#ifndef SCHEDULING_SAVE_CONTEXT
#define SCHEDULING_SAVE_CONTEXT() \
__asm__ volatile ( \
"push r0 \n\t" \
"in r0, __SREG__ \n\t" \
"cli \n\t" \
"push r0 \n\t" \
"push r1 \n\t" \
"clr r1 \n\t" \
"push r2 \n\t" \
"push r3 \n\t" \
"push r4 \n\t" \
"push r5 \n\t" \
"push r6 \n\t" \
"push r7 \n\t" \
"push r8 \n\t" \
"push r9 \n\t" \
"push r10 \n\t" \
"push r11 \n\t" \
"push r12 \n\t" \
"push r13 \n\t" \
"push r14 \n\t" \
"push r15 \n\t" \
"push r16 \n\t" \
"push r17 \n\t" \
"push r18 \n\t" \
"push r19 \n\t" \
"push r20 \n\t" \
"push r21 \n\t" \
"push r22 \n\t" \
"push r23 \n\t" \
"push r24 \n\t" \
"push r25 \n\t" \
"push r26 \n\t" \
"push r27 \n\t" \
"push r28 \n\t" \
"push r29 \n\t" \
"push r30 \n\t" \
"push r31 \n\t" \
"lds r26, CurrentContext \n\t" \
"lds r27, CurrentContext + 1 \n\t" \
"in r0, __SP_L__ \n\t" \
"st x+, r0 \n\t" \
"in r0, __SP_H__ \n\t" \
"st x+, r0 \n\t" \
);
#endif
/**
* Restore the stack pointer from *CurrentContext followed by processor registers from the stack.
*/
#ifndef SCHEDULING_RESTORE_CONTEXT
#define SCHEDULING_RESTORE_CONTEXT() \
__asm__ volatile ( \
"lds r26, CurrentContext \n\t" \
"lds r27, CurrentContext + 1 \n\t" \
"ld r28, x+ \n\t" \
"out __SP_L__, r28 \n\t" \
"ld r29, x+ \n\t" \
"out __SP_H__, r29 \n\t" \
"pop r31 \n\t" \
"pop r30 \n\t" \
"pop r29 \n\t" \
"pop r28 \n\t" \
"pop r27 \n\t" \
"pop r26 \n\t" \
"pop r23 \n\t" \
"pop r25 \n\t" \
"pop r22 \n\t" \
"pop r21 \n\t" \
"pop r24 \n\t" \
"pop r20 \n\t" \
"pop r19 \n\t" \
"pop r18 \n\t" \
"pop r17 \n\t" \
"pop r16 \n\t" \
"pop r15 \n\t" \
"pop r14 \n\t" \
"pop r13 \n\t" \
"pop r12 \n\t" \
"pop r11 \n\t" \
"pop r10 \n\t" \
"pop r9 \n\t" \
"pop r8 \n\t" \
"pop r7 \n\t" \
"pop r6 \n\t" \
"pop r5 \n\t" \
"pop r4 \n\t" \
"pop r3 \n\t" \
"pop r2 \n\t" \
"pop r1 \n\t" \
"pop r0 \n\t" \
"out __SREG__, r0 \n\t" \
"pop r0 \n\t" \
);
#endif
#endif
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment