1066 lines
35 KiB
C
1066 lines
35 KiB
C
// *************os.c**************
|
|
// ECE445M Labs 1, 2, 3, 4, 5 and 6
|
|
// Starter to labs 1,2,3,4,5,6
|
|
// high level OS functions
|
|
// Students will implement these functions as part of Lab
|
|
// Runs on MSPM0
|
|
// Jonathan W. Valvano
|
|
// January 10, 2026, valvano@mail.utexas.edu
|
|
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <ti/devices/msp/msp.h>
|
|
#include "file.h"
|
|
#include "../inc/Clock.h"
|
|
#include "../inc/LaunchPad.h"
|
|
#include "../inc/Timer.h"
|
|
#include "../RTOS_Labs_common/OS.h"
|
|
#include "../RTOS_Labs_common/RTOS_UART.h"
|
|
#include "../RTOS_Labs_common/SPI.h"
|
|
#include "../RTOS_Labs_common/ST7735_SDC.h"
|
|
#include "../RTOS_Labs_common/eFile.h"
|
|
#include "../RTOS_Labs_common/heap.h"
|
|
#include "bump.h"
|
|
|
|
// Hardware interrupt priorities
|
|
// Priority 0: Periodic threads
|
|
// Priority 1: Input/output interrupts like UART and edge triggered
|
|
// Priority 2: 1000 Hz periodic event to implement OS_MsTime and sleep using TimerG8
|
|
// Priority 2: SysTick for running the scheduler
|
|
// Priority 3: PendSV for context switch
|
|
|
|
// *****************Timers******************
|
|
// SysTick for running the scheduler
|
|
// Use TimerG0 is used for SDC timeout
|
|
// Use TimerG7 for background periodic threads
|
|
// Use TimerG8 is interrupts at 1000Hz to implement OS_MsTime, and sleeping
|
|
// Use TimerG12 for 32-bit OS_Time, free running (no interrupts)
|
|
// Use TimerA0 for PWM outputs to motors
|
|
// Use TimerA1 for PWM outputs to motors
|
|
// Use TimerG6 for Lab 1 and then for PWM to servo steering
|
|
|
|
/* Improvements to make (ask Devin)
|
|
* Re-write all simple C functions in assembly
|
|
* Create "kernel requests" that will handle critical sections in PendSV
|
|
* Maybe look into using SVC?
|
|
*/
|
|
|
|
void OSDisableInterrupts(void);
|
|
void OSEnableInterrupts(void);
|
|
long StartCritical(void);
|
|
void EndCritical(long);
|
|
#define OSCRITICAL_ENTER() { sr=StartCritical(); }
|
|
#define OSCRITICAL_EXIT() { EndCritical(sr); }
|
|
|
|
uint32_t TimeMs; // in ms
|
|
uint32_t SysTickStart,SysTickElapsed; // execution time of SysTick_Handler in 12.5ns units
|
|
|
|
TCB_t tcbs[MAXNUMTHREADS];
|
|
int32_t Stacks[MAXNUMTHREADS][STACKSIZE];
|
|
TCB_t* RunPt; // Can't be static since osasm.s needs it
|
|
TCB_t* ReadyLists[NUMPRIORITIES]; // Linked list heads for each priority
|
|
TCB_t* RunPts[NUMPRIORITIES]; // Keeping track of last run thread for each priority
|
|
TCB_t* Sleeping; // Linked list of all sleeping threads
|
|
TCB_t* Free; // Linked list of all free TCBs (makes addThread faster)
|
|
Event_t PeriodicTasks[MAXEVENTS];
|
|
Mailbox_t OS_Mailbox;
|
|
Fifo_t OS_Fifo;
|
|
|
|
uint8_t crashed;
|
|
|
|
void OS_ClearMsTime(void); // implemented in osasm.s
|
|
|
|
uint32_t OS_MsTime(void); // implemented in osasm.s
|
|
|
|
void StartOS(void); // implemented in osasm.s
|
|
|
|
#define bump_right_pin (1<<31)
|
|
#define bump_left_pin (1<<27)
|
|
#define bump_center_pin (1<<28)
|
|
#define bump_pins (bump_right_pin | bump_center_pin | bump_left_pin) // for bump swithces
|
|
|
|
#define TogglePB20() (GPIOB->DOUTTGL31_0 = (1<<20))
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Systick Interrupt Handler
|
|
// SysTick interrupt happens every 2 ms
|
|
// used for preemptive foreground thread switch
|
|
// ------------------------------------------------------------------------------
|
|
void SysTick_Handler(void) {
|
|
SysTickStart = OS_Time();
|
|
SCB->ICSR = 0x10000000; // Invoke PendSV for context switch
|
|
SysTickElapsed = OS_Time() - SysTickStart;
|
|
} // end SysTick_Handler
|
|
|
|
// Scheduler handles the algorithm for switching threads
|
|
void Scheduler(void); // implemented in osasm.s
|
|
|
|
uint32_t OS_LockScheduler(void){
|
|
uint32_t old = SysTick->CTRL;
|
|
SysTick->CTRL= 5;
|
|
return old;
|
|
}
|
|
void OS_UnLockScheduler(uint32_t previous){
|
|
SysTick->CTRL = previous;
|
|
}
|
|
|
|
|
|
//
|
|
//@details Initialize operating system, disable interrupts until OS_Launch.
|
|
//Initialize OS controlled I/O: serial, ADC, systick, LaunchPad I/O and timers.
|
|
// Interrupts not yet enabled.
|
|
// @param none
|
|
// @return none
|
|
//@brief Initialize OS
|
|
//
|
|
void OS_Init(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
// Initialize all tcbs as free
|
|
for (int threadID = 0; threadID < MAXNUMTHREADS; threadID++){
|
|
tcbs[threadID].state = FREE;
|
|
if (threadID == MAXNUMTHREADS-1){
|
|
tcbs[threadID].next = NULL;
|
|
}
|
|
else{
|
|
tcbs[threadID].next = &tcbs[threadID+1];
|
|
}
|
|
// set thread ID
|
|
tcbs[threadID].id = threadID;
|
|
}
|
|
|
|
// Start Free linked list
|
|
Free = &tcbs[0];
|
|
|
|
// Initialize all ready lists as empty and last ran as NULL
|
|
for (int priority = 0; priority < NUMPRIORITIES; priority++){
|
|
ReadyLists[priority] = NULL;
|
|
RunPts[priority] = NULL;
|
|
}
|
|
|
|
// Initialize RunPt to NULL, indicating no threads yet
|
|
RunPt = NULL;
|
|
|
|
// Initialize sleeping to NULL (blocked lists are per-semaphore, initialized in OS_InitSemaphore)
|
|
Sleeping = NULL;
|
|
|
|
// Initialize periodic threads to NULL
|
|
for (int i = 0; i < MAXEVENTS; i++){
|
|
PeriodicTasks[i].task = NULL;
|
|
}
|
|
|
|
// Initalize MsTime
|
|
OS_ClearMsTime();
|
|
|
|
// Initialize UART
|
|
UART_Init(1);
|
|
|
|
// Initialize time
|
|
TimerG12_Init();
|
|
|
|
//Enable Interrupts occurs at OS_LaunchOS
|
|
}
|
|
|
|
// ******** OS_InitSemaphore ************
|
|
// initialize semaphore
|
|
// input: pointer to a semaphore
|
|
// output: none
|
|
void OS_InitSemaphore(Sema4_t *semaPt, int32_t value){
|
|
semaPt->Value = value;
|
|
semaPt->blockedList = NULL;
|
|
semaPt->blockedTail = NULL;
|
|
}
|
|
|
|
|
|
// Instead of completely disabling interrupts, maybe just prevent scheduler (PRIMASK)
|
|
|
|
// ******** OS_Wait ************
|
|
// decrement semaphore
|
|
// Lab2 spinlock
|
|
// Lab3 block if less than zero
|
|
// input: pointer to a counting semaphore
|
|
// output: none
|
|
void OS_Wait(Sema4_t *semaPt){long sr;
|
|
sr = StartCritical();
|
|
--(semaPt->Value);
|
|
if (semaPt->Value < 0){ // Block this thread
|
|
TCB_t* blockedThread = RunPt;
|
|
blockedThread->blocked = semaPt;
|
|
|
|
// Unlink from ready list
|
|
if (blockedThread->next == blockedThread){ // only thread in its priority
|
|
RunPts[blockedThread->priority] = NULL;
|
|
ReadyLists[blockedThread->priority] = NULL;
|
|
} else{
|
|
RunPts[blockedThread->priority] = blockedThread->prev;
|
|
ReadyLists[blockedThread->priority] = blockedThread->next;
|
|
}
|
|
blockedThread->prev->next = blockedThread->next;
|
|
blockedThread->next->prev = blockedThread->prev;
|
|
|
|
// Enqueue at tail of this semaphore's blocked list
|
|
blockedThread->next = NULL;
|
|
if (semaPt->blockedList == NULL){ // First waiter on this semaphore
|
|
blockedThread->prev = NULL;
|
|
semaPt->blockedList = blockedThread;
|
|
} else {
|
|
semaPt->blockedTail->next = blockedThread;
|
|
blockedThread->prev = semaPt->blockedTail;
|
|
}
|
|
semaPt->blockedTail = blockedThread;
|
|
|
|
OS_Suspend();
|
|
}
|
|
EndCritical(sr);
|
|
}
|
|
|
|
|
|
// ******** OS_Signal ************
|
|
// increment semaphore
|
|
// Lab2 spinlock
|
|
// Lab3 wakeup blocked thread if appropriate
|
|
// input: pointer to a counting semaphore
|
|
// output: none
|
|
void OS_Signal(Sema4_t *semaPt){long sr;
|
|
sr = StartCritical();
|
|
++(semaPt->Value);
|
|
if (semaPt->Value <= 0){ // wakeup oldest waiting thread
|
|
TCB_t* wakeupThread = semaPt->blockedList;
|
|
|
|
// Dequeue from head of this semaphore's blocked list
|
|
semaPt->blockedList = wakeupThread->next;
|
|
if (semaPt->blockedList != NULL){
|
|
semaPt->blockedList->prev = NULL;
|
|
} else {
|
|
semaPt->blockedTail = NULL;
|
|
}
|
|
wakeupThread->blocked = NULL;
|
|
|
|
// Link to ready list
|
|
if (ReadyLists[wakeupThread->priority] == NULL){ // Handle empty list
|
|
ReadyLists[wakeupThread->priority] = wakeupThread;
|
|
wakeupThread->next = wakeupThread;
|
|
wakeupThread->prev = wakeupThread;
|
|
RunPts[wakeupThread->priority] = wakeupThread;
|
|
} else { // Normal reinsertion: insert just before RunPts (runs last in this round)
|
|
RunPts[wakeupThread->priority]->prev->next = wakeupThread;
|
|
wakeupThread->prev = RunPts[wakeupThread->priority]->prev;
|
|
RunPts[wakeupThread->priority]->prev = wakeupThread;
|
|
wakeupThread->next = RunPts[wakeupThread->priority];
|
|
}
|
|
|
|
if (RunPt->priority > wakeupThread->priority){
|
|
OS_Suspend(); // Context switch if we're waking up a higher priority thread
|
|
}
|
|
}
|
|
EndCritical(sr);
|
|
}
|
|
|
|
// ******** OS_bWait ************ Could be done in assembly (store only)
|
|
// Lab2 spinlock, set to 0
|
|
// Lab3 block if less than zero
|
|
// input: pointer to a binary semaphore
|
|
// output: none
|
|
void OS_bWait(Sema4_t *semaPt){
|
|
long sr = StartCritical();
|
|
if (semaPt->Value == 0){ // Resource taken, block this thread
|
|
TCB_t* blockedThread = RunPt;
|
|
blockedThread->blocked = semaPt;
|
|
|
|
// Unlink from ready list
|
|
if (blockedThread->next == blockedThread){ // only thread in its priority
|
|
RunPts[blockedThread->priority] = NULL;
|
|
ReadyLists[blockedThread->priority] = NULL;
|
|
}
|
|
else{
|
|
RunPts[blockedThread->priority] = blockedThread->prev;
|
|
ReadyLists[blockedThread->priority] = blockedThread->next;
|
|
}
|
|
blockedThread->prev->next = blockedThread->next;
|
|
blockedThread->next->prev = blockedThread->prev;
|
|
|
|
// Enqueue at tail of this semaphore's blocked list
|
|
blockedThread->next = NULL;
|
|
if (semaPt->blockedList == NULL){ // First waiter on this semaphore
|
|
blockedThread->prev = NULL;
|
|
semaPt->blockedList = blockedThread;
|
|
}
|
|
else{
|
|
semaPt->blockedTail->next = blockedThread;
|
|
blockedThread->prev = semaPt->blockedTail;
|
|
}
|
|
semaPt->blockedTail = blockedThread;
|
|
|
|
OS_Suspend(); // Returns here when OS_bSignal wakes this thread
|
|
}
|
|
semaPt->Value = 0; // Acquire: mark semaphore as taken
|
|
EndCritical(sr);
|
|
}
|
|
|
|
// ******** OS_bSignal ************ Could be done in assembly (store only)
|
|
// Lab2 spinlock, set to 1
|
|
// Lab3 wakeup blocked thread if appropriate
|
|
// input: pointer to a binary semaphore
|
|
// output: none
|
|
void OS_bSignal(Sema4_t *semaPt){
|
|
long sr = StartCritical();
|
|
if (semaPt->blockedList != NULL){ // Someone is waiting on this semaphore
|
|
TCB_t* wakeupThread = semaPt->blockedList; // oldest waiter
|
|
|
|
// Dequeue from head of this semaphore's blocked list
|
|
semaPt->blockedList = wakeupThread->next;
|
|
if (semaPt->blockedList != NULL){
|
|
semaPt->blockedList->prev = NULL;
|
|
}
|
|
else{
|
|
semaPt->blockedTail = NULL;
|
|
}
|
|
wakeupThread->blocked = NULL;
|
|
|
|
// Link to ready list
|
|
if (ReadyLists[wakeupThread->priority] == NULL){ // Handle empty list
|
|
ReadyLists[wakeupThread->priority] = wakeupThread;
|
|
wakeupThread->next = wakeupThread;
|
|
wakeupThread->prev = wakeupThread;
|
|
RunPts[wakeupThread->priority] = wakeupThread;
|
|
}
|
|
else{ // Normal reinsertion
|
|
RunPts[wakeupThread->priority]->prev->next = wakeupThread;
|
|
wakeupThread->prev = RunPts[wakeupThread->priority]->prev;
|
|
RunPts[wakeupThread->priority]->prev = wakeupThread;
|
|
wakeupThread->next = RunPts[wakeupThread->priority];
|
|
}
|
|
|
|
if (RunPt->priority > wakeupThread->priority){
|
|
OS_Suspend(); // Context switch if we're waking up a higher priority thread
|
|
}
|
|
}
|
|
else{
|
|
semaPt->Value = 1; //release the resource
|
|
}
|
|
EndCritical(sr);
|
|
}
|
|
|
|
|
|
|
|
// ******** OS_AddThread ***************
|
|
// add a foreground thread to the scheduler
|
|
// Inputs: pointer to a void/void foreground task
|
|
// number of bytes allocated for its stack
|
|
// priority, 0 is highest, 255 is the lowest
|
|
// Priorities are relative to other foreground threads
|
|
// Outputs: 1 if successful, 0 if this thread can not be added
|
|
// stack size must be divisable by 8 (aligned to double word boundary)
|
|
// In Lab 2, you can ignore both the stackSize and priority fields
|
|
// In Lab 3, you can ignore the stackSize fields
|
|
// In Lab 4, the stackSize can be 128, 256, or 512 bytes
|
|
|
|
int OS_AddProcessThread(void(*task)(void),
|
|
uint32_t stackSize, uint32_t priority, uint32_t pid){
|
|
return 0;
|
|
}
|
|
|
|
static void inline setInitialStack(int threadID){
|
|
tcbs[threadID].sp = &Stacks[threadID][STACKSIZE-12]; // Thread stack pointer
|
|
Stacks[threadID][STACKSIZE-1] = 0x01000000; // Thumb bit
|
|
Stacks[threadID][STACKSIZE-3] = 0x14141414; // R14
|
|
Stacks[threadID][STACKSIZE-4] = 0x12121212; // R12
|
|
Stacks[threadID][STACKSIZE-5] = 0x33333333; // R3
|
|
Stacks[threadID][STACKSIZE-6] = 0x22222222; // R2
|
|
Stacks[threadID][STACKSIZE-7] = 0x11111111; // R1
|
|
Stacks[threadID][STACKSIZE-8] = 0x00000000; // R0
|
|
Stacks[threadID][STACKSIZE-9] = 0x77777777; // R7
|
|
Stacks[threadID][STACKSIZE-10] = 0x66666666; // R6
|
|
Stacks[threadID][STACKSIZE-11] = 0x55555555; // R5
|
|
Stacks[threadID][STACKSIZE-12] = 0x44444444; // R4
|
|
}
|
|
|
|
int OS_AddThread(void(*task)(void),
|
|
uint32_t stackSize, uint32_t priority){
|
|
|
|
// put Lab 2 (and beyond) solution here
|
|
if (priority >= NUMPRIORITIES){
|
|
return 0; // Not a valid priority
|
|
}
|
|
|
|
int threadID = 0;
|
|
long sr = StartCritical();
|
|
// Find the first free tcb (critical section)
|
|
if (Free != NULL){
|
|
threadID = Free->id;
|
|
Free = Free->next;
|
|
}
|
|
else{
|
|
return 0; // No free tcb
|
|
}
|
|
EndCritical(sr);
|
|
|
|
// Mark thread as active
|
|
tcbs[threadID].state = ACTIVE;
|
|
|
|
// Mark thread as not sleeping
|
|
tcbs[threadID].sleep = 0;
|
|
|
|
// Mark thread as not blocked
|
|
tcbs[threadID].blocked = NULL;
|
|
|
|
// set thread priority
|
|
tcbs[threadID].priority = priority;
|
|
|
|
// Initialize the stack for this thread
|
|
setInitialStack(threadID);
|
|
|
|
// Set PC to point to task
|
|
Stacks[threadID][STACKSIZE-2] = (int32_t)task;
|
|
|
|
// Add to ready list (critical section)
|
|
sr = StartCritical();
|
|
if (ReadyLists[priority] == NULL){ // Handle case of empty ready list for that priority
|
|
ReadyLists[priority] = &tcbs[threadID];
|
|
tcbs[threadID].next = &tcbs[threadID];
|
|
tcbs[threadID].prev = &tcbs[threadID];
|
|
RunPts[priority] = &tcbs[threadID];
|
|
}
|
|
else{ // Link new tcb into its proper linked list
|
|
tcbs[threadID].next = RunPts[priority];
|
|
tcbs[threadID].prev = RunPts[priority]->prev;
|
|
tcbs[threadID].prev->next = &tcbs[threadID];
|
|
RunPts[priority]->prev = &tcbs[threadID];
|
|
}
|
|
|
|
if (RunPt == NULL){ // Handle case of first ever thread
|
|
RunPt = &tcbs[threadID];
|
|
}
|
|
|
|
EndCritical(sr);
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ******** OS_AddProcess ***************
|
|
// add a process with foregound thread to the scheduler
|
|
// Inputs: pointer to process text (code) segment, entry point at top
|
|
// pointer to process data segment
|
|
// number of bytes allocated for its stack
|
|
// priority (0 is highest)
|
|
// Outputs: 1 if successful, 0 if this process can not be added
|
|
// This function will be needed for Lab 5
|
|
// In Labs 2-4, this function can be ignored
|
|
int OS_AddProcess(void *text, void *data, uint32_t stackSize, uint32_t priority){
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int OS_LoadProgram(char *name, uint32_t priority){
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// ******** OS_Id ***************
|
|
// returns the thread ID for the currently running thread
|
|
// Inputs: none
|
|
// Outputs: Thread ID, number greater than zero
|
|
uint32_t OS_Id(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
return RunPt->id;
|
|
}
|
|
|
|
|
|
|
|
uint32_t lcm2(uint32_t n1,uint32_t n2){
|
|
uint32_t n;
|
|
if(n1 > n2){
|
|
n = n1;
|
|
}else{
|
|
n = n2;
|
|
}
|
|
while( ((n % n1) != 0) || ((n % n2) != 0) ){
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
uint32_t lcm3(uint32_t n1,uint32_t n2,uint32_t n3){
|
|
return lcm2(lcm2(n1,n2),n3);
|
|
}
|
|
uint32_t lcm4(uint32_t n1,uint32_t n2,uint32_t n3,uint32_t n4){
|
|
return lcm2(lcm2(n1,n2),lcm2(n3,n4));
|
|
}
|
|
uint32_t lcm5(uint32_t n1,uint32_t n2,uint32_t n3,uint32_t n4,uint32_t n5){
|
|
return lcm2(lcm2(n1,n2),lcm3(n3,n4,n5));
|
|
}
|
|
|
|
//******** OS_AddPeriodicThread ***************
|
|
// Add a background periodic thread
|
|
// typically this function receives the highest priority
|
|
// Inputs: task is pointer to a void/void background function
|
|
// period in ms
|
|
// priority 0 is the highest, 3 is the lowest
|
|
// Priorities are relative to other background periodic threads
|
|
// Outputs: 1 if successful, 0 if this thread can not be added
|
|
// You are free to select the resolution of period
|
|
// It is assumed that the user task will run to completion and return
|
|
// This task can not spin, block, loop, sleep, or kill
|
|
// This task can call OS_Signal OS_bSignal OS_AddThread
|
|
// This task does not have a Thread ID
|
|
// In lab 2, this command will be called 0 or 1 times
|
|
// In lab 3, this command will be called 0 to 4 times
|
|
// In labs 3-7, there will be 0 to 4 background periodic threads, and this priority field
|
|
// determines the relative priority of these threads
|
|
// For Lab 3, it ok to make reasonable limits to reduce the complexity. E.g.,
|
|
// - You can assume there are 0 to 4 background periodic threads
|
|
// - You can assume the priorities are sequential 0,1,2,3,4
|
|
// - You can assume a maximum thread execution time, e.g., 50us
|
|
// - You can assume a slowest period, e.g., 50ms
|
|
// - You can limit possible periods, e.g., 1,2,4,5,10,20,25,50ms
|
|
// - You can assume (E0/T0)+(E1/T1)+(E2/T2)+(E3/T3) is much less than 1
|
|
|
|
static uint8_t numRegistered = 0; // unique tasks added by user
|
|
uint8_t numPeriodic = 0; // total schedule entries (set by builder)
|
|
uint8_t ThisTask = 0;
|
|
|
|
// ******** OS_BuildTimeline ************
|
|
// Rebuild the aperiodic schedule from registered periodic tasks.
|
|
// Each task gets a sub-ms offset = priority * 100us within its ms slot.
|
|
// With max 4 tasks at offsets 0, 100, 200, 300us, no two tasks ever
|
|
// collide, so jitter is limited to ISR entry latency (~30 cycles).
|
|
// Fills PeriodicTasks[] with the expanded timeline and sets numPeriodic.
|
|
// This routine was written by Claude Opus 4.6
|
|
#define TASK_SPACING_US 200
|
|
static void OS_BuildTimeline(void){
|
|
if (numRegistered == 0) return;
|
|
|
|
// Save registration data to stack before overwriting the array
|
|
Event_t reg[4];
|
|
for (int i = 0; i < numRegistered; i++){
|
|
reg[i] = PeriodicTasks[i];
|
|
}
|
|
|
|
// Compute LCM of all registered periods (in ms)
|
|
uint32_t lcm = reg[0].period;
|
|
for (int i = 1; i < numRegistered; i++){
|
|
lcm = lcm2(lcm, reg[i].period);
|
|
}
|
|
|
|
uint32_t count = 0;
|
|
uint32_t prevTime = 0;
|
|
uint32_t firstTime = 0;
|
|
|
|
// Generate schedule entries in sorted time order:
|
|
// outer loop over ms ticks ensures ascending time
|
|
// inner loop over priority 0..3 ensures higher priority fires first
|
|
for (uint32_t ms = 0; ms < lcm; ms++){
|
|
for (uint32_t p = 0; p <= 3; p++){
|
|
// Find the registered task at this priority
|
|
int idx = -1;
|
|
for (int i = 0; i < numRegistered; i++){
|
|
if (reg[i].priority == p){ idx = i; break; }
|
|
}
|
|
if (idx < 0) continue;
|
|
|
|
// Does this task fire on this ms tick?
|
|
if (ms % reg[idx].period != 0) continue;
|
|
|
|
if (count >= MAXEVENTS) return; // overflow guard
|
|
|
|
uint32_t time_us = ms * 1000 + p * TASK_SPACING_US;
|
|
|
|
PeriodicTasks[count].task = reg[idx].task;
|
|
PeriodicTasks[count].priority = reg[idx].priority;
|
|
PeriodicTasks[count].period = reg[idx].period;
|
|
|
|
// Set timeToNext for the previous entry (delay from prev to this)
|
|
if (count > 0){
|
|
PeriodicTasks[count - 1].timeToNext = time_us - prevTime;
|
|
} else {
|
|
firstTime = time_us;
|
|
}
|
|
|
|
prevTime = time_us;
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// Last entry wraps to first entry of the next LCM cycle
|
|
if (count > 0){
|
|
uint32_t lcm_us = lcm * 1000;
|
|
PeriodicTasks[count - 1].timeToNext = lcm_us - prevTime + firstTime;
|
|
}
|
|
|
|
numPeriodic = count;
|
|
ThisTask = 0;
|
|
}
|
|
|
|
int OS_AddPeriodicThread(void(*task)(void),
|
|
uint32_t period, uint32_t priority){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
if (priority > 3 || period == 0 || numRegistered >= 4){
|
|
return 0;
|
|
}
|
|
|
|
if (numRegistered == 0){
|
|
// Intialize Timer G7 for periodic thread management
|
|
TimerG7_IntArm(10000, 80, 0); // High priority
|
|
}
|
|
|
|
// Store registration info (builder reads from first numRegistered entries)
|
|
PeriodicTasks[numRegistered].task = task;
|
|
PeriodicTasks[numRegistered].period = period;
|
|
PeriodicTasks[numRegistered].priority = priority;
|
|
numRegistered++;
|
|
|
|
// Rebuild schedule with the new task included
|
|
OS_BuildTimeline();
|
|
return 1;
|
|
}
|
|
|
|
void TIMG7_IRQHandler(void){
|
|
if((TIMG7->CPU_INT.IIDX) == 1){ // this will acknowledge
|
|
(*PeriodicTasks[ThisTask].task)(); // execute user task
|
|
ThisTask = ThisTask+1;
|
|
if (ThisTask == numPeriodic){
|
|
ThisTask = 0; // wrap around
|
|
}
|
|
TIMG7->COUNTERREGS.LOAD = PeriodicTasks[ThisTask].timeToNext-1; // set reload register
|
|
}
|
|
}
|
|
|
|
void TIMG8_IRQHandler(void){
|
|
if((TIMG8->CPU_INT.IIDX) == 1){ // this will acknowledge
|
|
++TimeMs;
|
|
// Decrement all TCB sleep counters
|
|
TCB_t* thread = Sleeping;
|
|
while (thread != NULL){
|
|
TCB_t* nextSleeper = thread->next; // Need to save in case of re-linkage
|
|
--(thread->sleep);
|
|
|
|
if (thread->sleep == 0){ // Time to wake up
|
|
// Unlink from sleeping list
|
|
if (thread->prev != NULL){
|
|
thread->prev->next = thread->next;
|
|
}
|
|
if (thread->next != NULL){
|
|
thread->next->prev = thread->prev;
|
|
}
|
|
|
|
// Link to ready list
|
|
if (ReadyLists[thread->priority] == NULL){ // Handle empty list
|
|
ReadyLists[thread->priority] = thread;
|
|
thread->next = thread;
|
|
thread->prev = thread;
|
|
RunPts[thread->priority] = thread;
|
|
}
|
|
else{ // Normal reinsertion
|
|
RunPts[thread->priority]->prev->next = thread;
|
|
thread->prev = RunPts[thread->priority]->prev;
|
|
RunPts[thread->priority]->prev = thread;
|
|
thread->next = RunPts[thread->priority];
|
|
}
|
|
|
|
if (thread == Sleeping){
|
|
Sleeping = nextSleeper;
|
|
}
|
|
|
|
if (RunPt->priority > thread->priority){
|
|
OS_Suspend(); // Context switch if we're waking up a higher priority thread
|
|
}
|
|
}
|
|
thread = nextSleeper;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Edge triggered Interrupt Handler
|
|
// Rising edge of PA18 (S1)
|
|
// Falling edge of PB21 (S2)
|
|
// Falling edge of PA27 (bump)
|
|
// Falling edge of PA28 (bump)
|
|
// Falling edge of PA31 (bump)
|
|
//----------------------------------------------------------------------------
|
|
void (*S2Task)(void) = NULL;
|
|
void (*PA28Task)(void) = NULL;
|
|
void (*BumpTask)(bump_status) = NULL;
|
|
uint32_t BumpStatus; // WiFi logging global in RTOS_MotorBoard.c
|
|
void GROUP1_IRQHandler(void){
|
|
// write this
|
|
TogglePB20();
|
|
if(GPIOA->CPU_INT.RIS&(1<<18)){ // PA18
|
|
GPIOA->CPU_INT.ICLR = 1<<18;
|
|
|
|
}
|
|
uint32_t flags = GPIOA->CPU_INT.RIS;
|
|
if(flags & bump_pins){
|
|
bump_status status = bump_none;
|
|
if(flags & bump_left_pin) status |= bump_left;
|
|
if(flags & bump_center_pin) status |= bump_center;
|
|
if(flags & bump_right_pin) status |= bump_right;
|
|
|
|
GPIOA->CPU_INT.ICLR = (flags & bump_pins);
|
|
BumpStatus = (uint32_t)status;
|
|
|
|
if(BumpTask != NULL){
|
|
BumpTask(status);
|
|
} else {
|
|
crashed =1;
|
|
bump_collision();
|
|
|
|
}
|
|
}
|
|
//if(GPIOA->CPU_INT.RIS&(1<<28)){ // PA28
|
|
//GPIOA->CPU_INT.ICLR = 1<<28;
|
|
// PA28Task();
|
|
|
|
//}
|
|
if(GPIOB->CPU_INT.RIS&(1<<21)){ // PB21
|
|
GPIOB->CPU_INT.ICLR = 1<<21;
|
|
//GPIOB->DOUTTGL31_0 = (1<<20); // For profiling
|
|
S2Task(); // Assume task WILL be initialized
|
|
}
|
|
}
|
|
// ******** OS_AddS1Task ***************
|
|
// add a background task to run whenever the S1 (PA18) button is pushed
|
|
// Inputs: pointer to a void/void background function
|
|
// priority 0 is the highest, 1 is the lowest
|
|
// Outputs: 1 if successful, 0 if this thread can not be added
|
|
// It is assumed that the user task will run to completion and return
|
|
// This task can not spin, block, loop, sleep, or kill
|
|
// This task can call OS_Signal OS_bSignal OS_AddThread
|
|
// This task does not have a Thread ID
|
|
// Because of the pin conflict with TFLuna, this command will not be called
|
|
int OS_AddS1Task(void(*task)(void), uint32_t priority){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
return 0; // replace this line with solution
|
|
};
|
|
|
|
|
|
// ******** OS_AddS2Task ***************
|
|
// add a background task to run whenever the S2 (PB21) button is pushed
|
|
// Inputs: pointer to a void/void background function
|
|
// priority 0 is highest, 1 is lowest
|
|
// Outputs: 1 if successful, 0 if this thread can not be added
|
|
// It is assumed user task will run to completion and return
|
|
// This task can not spin block loop sleep or kill
|
|
// This task can call issue OS_Signal, it can call OS_AddThread
|
|
// This task does not have a Thread ID
|
|
// In lab 2, this function will be called 0 or 1 times
|
|
// In lab 3, this function will be called will be called 0 or 1 times
|
|
// In lab 3, there will be many background threads, and this priority field
|
|
// determines the relative priority of these four threads
|
|
int OS_AddS2Task(void(*task)(void), uint32_t priority){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
if (priority > 1){
|
|
return 0;
|
|
}
|
|
|
|
// From EdgeTriggered.c
|
|
GPIOB->POLARITY31_16 |= 0x00000800; // falling
|
|
GPIOB->CPU_INT.ICLR = 0x00200000; // clear bit 21
|
|
GPIOB->CPU_INT.IMASK = 0x00200000; // arm PB21
|
|
NVIC->IP[0] = (NVIC->IP[0]&(~0x0000FF00))|priority<<14; // set priority (bits 15,14) IRQ 1
|
|
NVIC->ISER[0] = 1 << 1; // Group1 interrupt
|
|
|
|
S2Task = task;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ******** OS_AddPA28Task ***************
|
|
// add a background task to run whenever the bump (PA28) button is pushed
|
|
// Inputs: pointer to a void/void background function
|
|
// priority 0 is the highest, 1 is the lowest
|
|
// Outputs: 1 if successful, 0 if this thread can not be added
|
|
// It is assumed that the user task will run to completion and return
|
|
// This task can not spin, block, loop, sleep, or kill
|
|
// This task can call OS_Signal OS_bSignal OS_AddThread
|
|
// This task does not have a Thread ID
|
|
// In lab 3, this command will be called 0 or 1 times
|
|
// In lab 2, not implemented
|
|
// In lab 3, there will be many background threads, and this priority field
|
|
// determines the relative priority of these four threads
|
|
int OS_AddPA28Task(void(*task)(void), uint32_t priority){
|
|
// put Lab 3 (and beyond) solution here
|
|
if (priority > 1){
|
|
return 0;
|
|
}
|
|
|
|
// Initialize PA28
|
|
IOMUX->SECCFG.PINCM[PA28INDEX] = (uint32_t) 0x00060081; // input, pull up
|
|
|
|
// From EdgeTriggered.c
|
|
GPIOA->POLARITY31_16 |= (1<<25); // falling
|
|
GPIOA->CPU_INT.ICLR = (1 << 28); // clear bit 28
|
|
GPIOA->CPU_INT.IMASK = (1<< 28); // Arm PA28
|
|
// These were probably already done by S2 task but we'll just do it again
|
|
NVIC->IP[0] = (NVIC->IP[0]&(~0x0000FF00))|priority<<14; // set priority (bits 15,14) IRQ 1
|
|
NVIC->ISER[0] = 1 << 1; // Group1 interrupt
|
|
|
|
PA28Task = task;
|
|
|
|
return 1;
|
|
};
|
|
|
|
|
|
|
|
// ******** OS_Sleep ************
|
|
// place this thread into a dormant state
|
|
// input: number of msec to sleep
|
|
// output: none
|
|
// You are free to select the time resolution for this function
|
|
// OS_Sleep(0) implements cooperative multitasking
|
|
void OS_Sleep(uint32_t sleepTime){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
TCB_t* thread = RunPt;
|
|
|
|
thread->sleep = sleepTime; //set time (in ms) to sleep for
|
|
if (sleepTime == 0){ // just suspend
|
|
OS_Suspend();
|
|
}
|
|
else{
|
|
long sr = StartCritical();
|
|
// Handle case where this is the only thread in that priority
|
|
if (thread->next == thread){
|
|
RunPts[thread->priority] = NULL;
|
|
ReadyLists[thread->priority] = NULL;
|
|
}
|
|
else{
|
|
RunPts[thread->priority] = thread->prev;
|
|
ReadyLists[thread->priority] = thread->next;
|
|
}
|
|
|
|
// Unlink from ready list
|
|
thread->prev->next = thread->next;
|
|
thread->next->prev = thread->prev;
|
|
|
|
// Link to sleeping list
|
|
if (Sleeping != NULL){
|
|
Sleeping->prev = thread;
|
|
}
|
|
|
|
thread->next = Sleeping;
|
|
thread->prev = NULL;
|
|
Sleeping = thread;
|
|
|
|
OS_Suspend(); //suspend current thread
|
|
EndCritical(sr);
|
|
}
|
|
}
|
|
|
|
// ******** OS_Kill ************
|
|
// kill the currently running thread, release its TCB and stack
|
|
// input: none
|
|
// output: none
|
|
void OS_Kill(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
long sr = StartCritical();
|
|
TCB_t* threadKilled = RunPt;
|
|
// Handle case where this is the only thread in that priority
|
|
if (threadKilled->next == threadKilled){
|
|
RunPts[threadKilled->priority] = NULL;
|
|
ReadyLists[threadKilled->priority] = NULL;
|
|
}
|
|
else{
|
|
RunPts[threadKilled->priority] = threadKilled->prev; // Set this for scheduler
|
|
ReadyLists[threadKilled->priority] = threadKilled->next; // Doesn't really matter as long as it doesn't point to killed thread
|
|
}
|
|
|
|
// Unlink from ready list
|
|
threadKilled->prev->next = threadKilled->next;
|
|
threadKilled->next->prev = threadKilled->prev;
|
|
|
|
threadKilled->state = FREE; //label killed thread as free
|
|
// Add to free list
|
|
threadKilled->next = Free;
|
|
Free = threadKilled;
|
|
|
|
OS_Suspend(); // Will clear SysTick and pend PendSV
|
|
|
|
EndCritical(sr);
|
|
|
|
|
|
for(;;){}; // can not return (must return in Lab 5 since called from SVC_hander)
|
|
|
|
};
|
|
|
|
|
|
|
|
// ******** OS_Suspend ************
|
|
// suspend execution of currently running thread
|
|
// scheduler will choose another thread to execute
|
|
// Can be used to implement cooperative multitasking
|
|
// Same function as OS_Sleep(0)
|
|
// input: none
|
|
// output: none
|
|
void OS_Suspend(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
SysTick->VAL = 0; // Clear Systick
|
|
SCB->ICSR = 0x10000000; // Invoke PendSV
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ******** OS_Fifo_Init ************
|
|
// Initialize the Fifo to be empty
|
|
// Inputs: size
|
|
// Outputs: none
|
|
// In Lab 2, you can ignore the size field
|
|
// In Lab 3, you should implement the user-defined fifo size
|
|
// In Lab 3, you can put whatever restrictions you want on size
|
|
// e.g., 4 to 64 elements
|
|
// e.g., must be a power of 2,4,8,16,32,64,128,256
|
|
void OS_Fifo_Init(uint32_t size){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
OS_Fifo.PutI = OS_Fifo.GetI = 0; // Initialize put and get indices
|
|
OS_InitSemaphore(&OS_Fifo.CurrentSize, 0); // Semaphore used to synchronize threads
|
|
OS_Fifo.MaxSize = size;
|
|
OS_Fifo.LostData = 0;
|
|
}
|
|
|
|
// ******** OS_Fifo_Put ************
|
|
// Enter one data sample into the Fifo
|
|
// Called from the background, so no waiting
|
|
// Inputs: data
|
|
// Outputs: true if data is properly saved,
|
|
// false if data not saved, because it was full
|
|
// Since this is called by interrupt handlers
|
|
// this function can not disable or enable interrupts
|
|
int OS_Fifo_Put(uint32_t data){
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
if (OS_Fifo.CurrentSize.Value == OS_Fifo.MaxSize){
|
|
OS_Fifo.LostData++;
|
|
return 0; // Not saved, full
|
|
}
|
|
else{
|
|
OS_Fifo.data[OS_Fifo.PutI] = data; // Save data
|
|
OS_Fifo.PutI = (OS_Fifo.PutI + 1) % OS_Fifo.MaxSize; // PutI wraps around
|
|
OS_Signal(&OS_Fifo.CurrentSize);
|
|
return 1; // Success
|
|
}
|
|
|
|
}
|
|
|
|
// ******** OS_Fifo_Get ************
|
|
// Remove one data sample from the Fifo
|
|
// Called in foreground, will spin/block if empty
|
|
// Inputs: none
|
|
// Outputs: data
|
|
uint32_t OS_Fifo_Get(void){ uint32_t data;
|
|
// put Lab 2 (and beyond) solution here
|
|
|
|
OS_Wait(&OS_Fifo.CurrentSize); // Spin if empty
|
|
data = OS_Fifo.data[OS_Fifo.GetI]; // Get data
|
|
OS_Fifo.GetI = (OS_Fifo.GetI + 1) % OS_Fifo.MaxSize; // Wraps around
|
|
return data;
|
|
}
|
|
|
|
// ******** OS_Fifo_Size ************
|
|
// Check the status of the Fifo
|
|
// Inputs: none
|
|
// Outputs: returns the number of elements in the Fifo
|
|
// greater than zero if a call to OS_Fifo_Get will return right away
|
|
// zero or less than zero if the Fifo is empty
|
|
// zero or less than zero if a call to OS_Fifo_Get will spin or block
|
|
int32_t OS_Fifo_Size(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
return OS_Fifo.CurrentSize.Value;
|
|
}
|
|
// ******** OS_MailBox_Init ************
|
|
// Initialize communication channel
|
|
// Inputs: none
|
|
// Outputs: none
|
|
void OS_MailBox_Init(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
OS_Mailbox.data = 0;
|
|
OS_Mailbox.free.Value = 1;
|
|
OS_Mailbox.dataValid.Value = 0;
|
|
}
|
|
|
|
// ******** OS_MailBox_Send ************
|
|
// enter mail into the MailBox
|
|
// Inputs: data to be sent
|
|
// Outputs: none
|
|
// This function will be called from a foreground thread
|
|
// It will spin/block if the MailBox contains data not yet received
|
|
void OS_MailBox_Send(uint32_t data){
|
|
// put Lab 2 (and beyond) solution here
|
|
OS_bWait(&OS_Mailbox.free);
|
|
OS_Mailbox.data = data;
|
|
OS_bSignal(&OS_Mailbox.dataValid);
|
|
};
|
|
|
|
// ******** OS_MailBox_Recv ************
|
|
// remove mail from the MailBox
|
|
// Inputs: none
|
|
// Outputs: data received
|
|
// This function will be called from a foreground thread
|
|
// It will spin/block if the MailBox is empty
|
|
uint32_t OS_MailBox_Recv(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
OS_bWait(&OS_Mailbox.dataValid);
|
|
uint32_t data = OS_Mailbox.data;
|
|
OS_bSignal(&OS_Mailbox.free);
|
|
return data;
|
|
};
|
|
|
|
// ******** OS_Time ************
|
|
// return the system time, counting up
|
|
// Inputs: none
|
|
// Outputs: time in 12.5ns units, 0 to 4294967295
|
|
// The time resolution should be less than or equal to 1us, and the precision 32 bits
|
|
// It is ok to change the resolution and precision of this function as long as
|
|
// this function and OS_TimeDifference have the same resolution and precision
|
|
uint32_t OS_Time(void){
|
|
// put Lab 2 (and beyond) solution here
|
|
return ~(TIMG12->COUNTERREGS.CTR);
|
|
};
|
|
|
|
// ******** OS_TimeDifference ************
|
|
// Calculates difference between two times
|
|
// Inputs: two times measured with OS_Time
|
|
// Outputs: time difference in 12.5ns units
|
|
// The time resolution should be less than or equal to 1us, and the precision at least 12 bits
|
|
// It is ok to change the resolution and precision of this function as long as
|
|
// this function and OS_Time have the same resolution and precision
|
|
uint32_t OS_TimeDifference(uint32_t start, uint32_t stop){
|
|
// put Lab 2 (and beyond) solution here
|
|
return stop - start;
|
|
};
|
|
|
|
|
|
|
|
// ******** OS_Launch ***************
|
|
// start the scheduler, enable interrupts
|
|
// Inputs: number of 12.5ns clock cycles for each time slice
|
|
// you may select the units of this parameter
|
|
// Outputs: none (does not return)
|
|
// In Lab 2, you can ignore the theTimeSlice field
|
|
// In Lab 3, you should implement the user-defined TimeSlice field
|
|
// It is ok to limit the range of theTimeSlice to match the 24-bit SysTick
|
|
// make PendSV priority 3, and SysTick priority 2
|
|
void OS_Launch(uint32_t theTimeSlice){
|
|
// put Lab 2 (and beyond) solution here
|
|
SysTick->CTRL = 0; // Disable SysTick during setup
|
|
SysTick->VAL = 0; // Clear
|
|
SCB->SHP[1] = (SCB->SHP[1] & (~0xC0000000)) | (2<<30) | (3<<22); // Priority 2 for SysTick, 3 for PendSV
|
|
SysTick->LOAD = theTimeSlice - 1; // reload value
|
|
SysTick->CTRL = 0x7; // Re-enable, core clock and interrupt arm
|
|
StartOS();
|
|
}
|