Finished RTOS similar to FreeRTOS
July 25, 2009 2 Comments
This is an RTOS developed by myself, with some code taken from FreeRTOS. This is some of the most difficult code I have ever written, although it really doesn’t do anything useful. Man, context switching in C can be a bear! It’s like a programatic buffer overflow! (seriously, you do a function call and just pop off enough stuff to overwrite the instruction pointer).
Framework
The purpose of this assignment is to begin implementing our RTOS.
You are to implement the first several functions and data items listed in the UIKAPI documentation: UIKInitialize, UIKAddTask, UIKRun, UIKDelay, and the internals UIKDispatcher, UIKIdle, UIKTickHandler, UIKTicknum, UIKTickLen, and UIKIntDepth. This will also involve the implementation of the context-switching mechanism you will use inside the process of switching tasks.
In order to demonstrate your scheduler and the use of the API, you can modify some of your earlier assignments to use the API, or write new tasks. You should be able to demonstrate the current execution of at least three tasks at once, all being scheduled by your system.
The code will follow at the end, but first, a video demonstration.
Semaphores
The purpose of this assignment is to add counting semaphores to theUIKAPI. This assignment
can be done in groups of up to two people.You are to implement the following counting ssemaphore functions:
+ UIKSem* UIKSemCreate(); – will create and initialize a new counting semaphore. The
new semaphore should be initialized to 1. The function should return a pointer to the
new semaphore, or a value of -1 if there was problem creating the semaphore.
+ void UIKSemPend(UIKSem* sem); – will perform the P(s) (wait or test or acquire) operation
on the semaphore. This function should decrement the semaphore value, and if
the result is negative, should block the requesting task.
+ void UIKSemPost(UIKSem* sem); – will perfrmthe V (s) (signal or release) operation on
the semophore. This function is the one that should be called when a task is leaving a
critical section. It should increment the semaphore value, and if the result is less than or
equal to zero, should unblock the highest priority task that is waiting on the semaphore
by changing its state to ready.
+ INT8 UIKSemValue(UIKSem* sem); – will return the current value of the semaphore.In addition to implementing the semaphore functions, design a set of tasks that demonstrates
the proper operation of your semaphore functions.
The critical sections are implemented by disabling interrupts. In this assignment, there is a task that delays in the middle of a semaphore lock to demonstrate the trace when two tasks are fighting for the same resource.
Alrighty, so here is a video demonstrating this:
Event Queues
The purpose of this assignment is to add event queues and the associated support routines to
the UIKAPI. This is the last piece of the UIK we will implement – yeah!A common requirement is for a task to wait until an event occurs. An event is usually some
sort of externally-triggered operation, such as an I/O device requiring service or a timer going
off. One possibility is for an interrupt service routine to set an event flag, then allow tasks to
wait until that flag is set. Once the flag is set, the task can become ready.An event flag group is an O/S global variable (byte) that represents events by bits. For the
purpose of this exercise, only one event flag will be defined: Bit 0 represents a timer event.
This flag should be set whenever an auxilliary timer goes off. For our implementation, this
timer can be implemented using one of the timers built into the Atmel AVR, spearate from the
timer you are using for the tick. You should write in ISR that handles an interrupt from this
timer by setting bit 0 in the event flags variable.You are to implement the following event functions:
+ void UIKAssocEvent(int Event); – will cause the task to become ¿associated¿ with
the flag(s) specified by Event. Event is a value that specifies the bits representing one or
more events that the task should be associated with. The task should then block until the
event occurs.
+ void UIKDisassocEvent(int Event); – causes the task to no longer be associated with
the event. In other words, the task will not be scheduled when the event occurs.In addition to implementing the semaphore functions, design an application that demonstrates
the proper operation of your event functions.
Events are fairly straightforward. project6 adds a timer0 event. taskblinky3 (in tasks.h/tasks.c) is associated with a timer0 overflow via a call to UIKAssocEvent (which sets the task to a wait state and associates the function with the flag). It uses a global flag, EVENT, to keep track of when an interupt occurs. Then, during the tick, if this EVENT flag is set then the function associated with this event will be set to ready.
Here is a video of this:
Code
I guess I’ll post most of it inline here too, because why not?
//main.c /* * This is a simple demonstration of the uik api. * It schedules 3 simple tasks and lets the scheduler take over execution */ #include "uik.h" #include "tasks.h" /* As per the specs, TICKLEN is the number of 10^-9 seconds on a 1MHz chip */ /* as set below it 1 TICKLEN == 1/100 seconds */ const int TICKLEN = 10000; /*declare vars in tasks.h */ extern uint8_t taskblinky_stack[64]; extern void taskblinky(); extern uint8_t taskblinky2_stack[64]; extern void taskblinky2(); extern uint8_t taskblinky3_stack[64]; extern void taskblinky3(); extern uint8_t taskcount_stack[80]; extern void taskcount(); extern UIKSem* portbsem; void init_timer0() { /*prescaler/64*/ TCCR0 |= ((1 << CS01) | (1 << CS00)); /*overflow mode - about every 1/61 seconds */ TIMSK |= (1 << TOIE0); } void main(void) { int id; /*Initialize the ports */ DDRB = 0xff; DDRD = 0x00; /*semaphore to protect portb */ portbsem = UIKSemCreate(); PORTB = 0xff; /*Initialize the RTOS - this sets up the timer */ UIKInitialize(TICKLEN); /*Tell the RTOS about our tasks */ id = UIKADDTask(&taskblinky, 42, taskblinky_stack, 64); UIKRun(id); id = UIKADDTask(&taskblinky2, 35, taskblinky2_stack, 64); UIKRun(id); id = UIKADDTask(&taskblinky3, 99, taskblinky3_stack, 64); UIKRun(id); id = UIKADDTask(&taskcount, 30, taskcount_stack, 80); UIKRun(id); /*this is an external event we make use of */ init_timer0(); /*this enables interupts, so doesn't need to be done in main */ startrtos(); }
//tasks.h /* * This is a simple demonstration of the uik api. * It schedules 3 simple tasks and lets the scheduler take over execution */ #include "uik.h" #include "tasks.h" /* As per the specs, TICKLEN is the number of 10^-9 seconds on a 1MHz chip */ /* as set below it 1 TICKLEN == 1/100 seconds */ const int TICKLEN = 10000; /*declare vars in tasks.h */ extern uint8_t taskblinky_stack[64]; extern void taskblinky(); extern uint8_t taskblinky2_stack[64]; extern void taskblinky2(); extern uint8_t taskblinky3_stack[64]; extern void taskblinky3(); extern uint8_t taskcount_stack[80]; extern void taskcount(); extern UIKSem* portbsem; void init_timer0() { /*prescaler/64*/ TCCR0 |= ((1 << CS01) | (1 << CS00)); /*overflow mode - about every 1/61 seconds */ TIMSK |= (1 << TOIE0); } void main(void) { int id; /*Initialize the ports */ DDRB = 0xff; DDRD = 0x00; /*semaphore to protect portb */ portbsem = UIKSemCreate(); PORTB = 0xff; /*Initialize the RTOS - this sets up the timer */ UIKInitialize(TICKLEN); /*Tell the RTOS about our tasks */ id = UIKADDTask(&taskblinky, 42, taskblinky_stack, 64); UIKRun(id); id = UIKADDTask(&taskblinky2, 35, taskblinky2_stack, 64); UIKRun(id); id = UIKADDTask(&taskblinky3, 99, taskblinky3_stack, 64); UIKRun(id); id = UIKADDTask(&taskcount, 30, taskcount_stack, 80); UIKRun(id); /*this is an external event we make use of */ init_timer0(); /*this enables interupts, so doesn't need to be done in main */ startrtos(); } lundeen@AnnieWilkes:~/classes/rtos/project6/project6$ ls default main.c project6.aps project6.avi project6.aws README sourceLineMap.txt tasks.c tasks.h uik.c uik.h lundeen@AnnieWilkes:~/classes/rtos/project6/project6$ cat tasks.h /*This file contains prototypes of the tasks that will be added */ #ifndef TASKS_H #define TASKS_H #include "uik.h" #include /*semaphore to protect portb */ UIKSem* portbsem; /*declare places to save contexts */ uint8_t taskblinky_stack[64] __attribute__((weak)); /*flash PORTB (tested with LEDs) */ void taskblinky(); /*declare places to save contexts - this has a forced conflict with blinky*/ uint8_t taskblinky2_stack[64] __attribute__((weak)); /*flash PORTB (tested with LEDs) */ void taskblinky2(); /*declare places to save contexts */ uint8_t taskblinky3_stack[64] __attribute__((weak)); /*flash PORTB (tested with LEDs) */ void taskblinky3(); /*count the lower bytes of PORTB up from 0 to 8 */ uint8_t taskcount_stack[80] __attribute__((weak)); void taskcount(); #endif
//tasks.h /*This file contains prototypes of the tasks that will be added */ #ifndef TASKS_H #define TASKS_H #include "uik.h" #include /*semaphore to protect portb */ UIKSem* portbsem; /*declare places to save contexts */ uint8_t taskblinky_stack[64] __attribute__((weak)); /*flash PORTB (tested with LEDs) */ void taskblinky(); /*declare places to save contexts - this has a forced conflict with blinky*/ uint8_t taskblinky2_stack[64] __attribute__((weak)); /*flash PORTB (tested with LEDs) */ void taskblinky2(); /*declare places to save contexts */ uint8_t taskblinky3_stack[64] __attribute__((weak)); /*flash PORTB (tested with LEDs) */ void taskblinky3(); /*count the lower bytes of PORTB up from 0 to 8 */ uint8_t taskcount_stack[80] __attribute__((weak)); void taskcount(); #endif
//uik.h /* * uik.h contains the header for the uik kernel */ #ifndef UIK_H #define UIK_H #include #include #include #include #define max_numtasks 16 typedef enum { wait, ready, run, initialized } task_state; typedef struct task_context { volatile task_state state; uint8_t priority; //stack_ptr includes registers, sreg, ip, - eg context uint8_t* stack_ptr; //delay is the amount of time the process must wait uint32_t delay; } task_context; /* for now, just make a static array for tcb TODO: make tcb into something better, like a linked list */ volatile task_context tcb[max_numtasks]; /*saves the current context */ void savecontext( ) __attribute__((naked)); /*restores the context of the taskid */ void restorecontext(uint8_t id) __attribute__((naked)); void UIKInitialize(uint16_t ticklen); uint8_t UIKADDTask(void* task_ptr, uint8_t priority, uint8_t* buffer_ptr, uint16_t stack_size); void UIKRun(uint8_t taskid); void SIG_OUTPUT_COMPARE1A(void) __attribute__ ((signal, naked)); void tick(void); void ticknodelay(void); void startrtos(void) __attribute__ ((naked)); void UIKDelay(uint32_t ticks) __attribute__ ((naked)); /*the idle task should always be part of the tasklist */ uint8_t idletask_stack[64] __attribute__((weak)); void idletask(); /*semaphores */ typedef struct _UIKSem { uint8_t value; } UIKSem; UIKSem* UIKSemCreate(); unsigned char semtest(UIKSem* sem); void UIKSemPend(UIKSem* sem); void UIKSemPost(UIKSem* sem); uint8_t UIKSemValue(UIKSem* sem); /*Events */ /*MAX_EVENTS cannot be larger than the size of EVENT_FLAG*/ #define MAX_EVENTS 8 #define PORT0_OVERFLOW 0 /*bit 0 of event_flag is associated with a timer0 interrupt */ unsigned char EVENT_FLAG; uint8_t ASS_BLOCK[MAX_EVENTS]; void UIKAssocEvent(uint8_t Event); void UIKDisassocEvent(uint8_t Event); #endif
/* * uik.c contains the source for the uik kernel */ #include "uik.h" int numtasks = 0; /*0 is always the idle task, which is the default curr_task */ int curr_task = 0; /*pointer to current task */ volatile uint8_t* currTCB; /* * macro for saving the context: first saves sreg, disables interrupts * pushes all general purpose registers on the stack and saves where the * stack is at */ #define savecontext() asm volatile ( "push r0 nt" "in r0, __SREG__ nt" "cli nt" "push r0 nt" "push r1 nt" "clr r1 nt" "push r2 nt" "push r3 nt" "push r4 nt" "push r5 nt" "push r6 nt" "push r7 nt" "push r8 nt" "push r9 nt" "push r10 nt" "push r11 nt" "push r12 nt" "push r13 nt" "push r14 nt" "push r15 nt" "push r16 nt" "push r17 nt" "push r18 nt" "push r19 nt" "push r20 nt" "push r21 nt" "push r22 nt" "push r23 nt" "push r24 nt" "push r25 nt" "push r26 nt" "push r27 nt" "push r28 nt" "push r29 nt" "push r30 nt" "push r31 nt" "lds r26, currTCB nt" "lds r27, currTCB+1 nt" "in r0, 0x3d nt" "st x+, r0 nt" "in r0, 0x3e nt" "st x+, r0 nt" ); /* * Opposite to savecontext(). Interrupts will have been disabled during * the context save so we can write to the stack pointer. */ #define restorecontext() asm volatile ( "lds r26, currTCB nt" "lds r27, currTCB+1 nt" "ld r28, x+ nt" "out __SP_L__, r28 nt" "ld r29, x+ nt" "out __SP_H__, r29 nt" "pop r31 nt" "pop r30 nt" "pop r29 nt" "pop r28 nt" "pop r27 nt" "pop r26 nt" "pop r25 nt" "pop r24 nt" "pop r23 nt" "pop r22 nt" "pop r21 nt" "pop r20 nt" "pop r19 nt" "pop r18 nt" "pop r17 nt" "pop r16 nt" "pop r15 nt" "pop r14 nt" "pop r13 nt" "pop r12 nt" "pop r11 nt" "pop r10 nt" "pop r9 nt" "pop r8 nt" "pop r7 nt" "pop r6 nt" "pop r5 nt" "pop r4 nt" "pop r3 nt" "pop r2 nt" "pop r1 nt" "pop r0 nt" "out __SREG__, r0 nt" "pop r0 nt" ); /* * UIKInitialize sets up the timer, and adds the idle task to tcb */ void UIKInitialize(uint16_t ticklen) { int i; // setup timer - timer1 is used so we can use 16 bits TCCR1B |= (1 << WGM12); //enable CTC interrupt TIMSK |= (1 << OCIE1A); OCR1A = ticklen; //not default:timer runs with /64 resolution //TCCR1B |= ((1 << CS10) | (1 << CS11)); //default: timer1 runs at 1MHz on the atmega16 so ticklen can be specified in microsecs TCCR1B |= (1 << CS10); // add the idle task as the task with lowest priority */ UIKADDTask(&idletask, 255, idletask_stack, 64); //initialize the ASS_BLOCK so that there are no events associated //with anything - note interupts are not enabled yet for(i=0; i max_numtasks) { //error, tcb is not big enough, need to increase max_numtasks in uik.h return (-1); } //setup parameters //"initialized" could be changed to ready without much effect, but UIKRun would //not have to be called tcb[numtasks].state = initialized; tcb[numtasks].priority = priority; tcb[numtasks].delay = 0; // place a few known bytes on the bottom - useful for debugging // tcb[numtasks].stack_lptr = stack_ptr; tcb[numtasks].stack_ptr = stack_ptr - 1; // will be location of most significant part of stack address *stack_ptr = 0x11; stack_ptr--; // least significant byte of stack address *stack_ptr = 0x22; stack_ptr--; *stack_ptr = 0x33; stack_ptr--; // address of the executing function this_address = task_ptr; *stack_ptr = this_address & 0x00ff; stack_ptr--; this_address >>= 8; *stack_ptr = this_address & 0x00ff; stack_ptr--; //simulate stack after a call to savecontext *stack_ptr = 0x00; //necessary for reti to line up stack_ptr--; *stack_ptr = 0x00; //r0 stack_ptr--; *stack_ptr = 0x00; //r1 wants to always be 0 stack_ptr--; *stack_ptr = 0x02; //r2 stack_ptr--; *stack_ptr = 0x03; //r3 stack_ptr--; *stack_ptr = 0x04; //r4 stack_ptr--; *stack_ptr = 0x05; //r5 stack_ptr--; *stack_ptr = 0x06; //r6 stack_ptr--; *stack_ptr = 0x07; //r7 stack_ptr--; *stack_ptr = 0x08; //r8 stack_ptr--; *stack_ptr = 0x09; //r9 stack_ptr--; *stack_ptr = 0x10; //r10 stack_ptr--; *stack_ptr = 0x11; //r11 stack_ptr--; *stack_ptr = 0x12; //r12 stack_ptr--; *stack_ptr = 0x13; //r13 stack_ptr--; *stack_ptr = 0x14; //r14 stack_ptr--; *stack_ptr = 0x15; //r15 stack_ptr--; *stack_ptr = 0x16; //r16 stack_ptr--; *stack_ptr = 0x17; //r17 stack_ptr--; *stack_ptr = 0x18; //r18 stack_ptr--; *stack_ptr = 0x19; //r19 stack_ptr--; *stack_ptr = 0x20; //r20 stack_ptr--; *stack_ptr = 0x21; //r21 stack_ptr--; *stack_ptr = 0x22; //r22 stack_ptr--; *stack_ptr = 0x23; //r23 stack_ptr--; *stack_ptr = 0x24; //r24 stack_ptr--; *stack_ptr = 0x25; //r25 stack_ptr--; *stack_ptr = 0x26; //r26 stack_ptr--; *stack_ptr = 0x27; //r27 stack_ptr--; *stack_ptr = 0x28; //r28 stack_ptr--; *stack_ptr = 0x29; //r29 stack_ptr--; *stack_ptr = 0x30; //r30 stack_ptr--; *stack_ptr = 0x31; //r31 stack_ptr--; //store the address of the stack this_address = stack_ptr; *(tcb[numtasks].stack_ptr) = (this_address & 0xff); this_address >>= 8; *(tcb[numtasks].stack_ptr + 1) = (this_address & 0xff); numtasks++; //return the task id return(numtasks - 1); } /* * UIKRun is sort of unnecessary, but since it's in the specification API we will * force the call here, inventing a distinction between the "initialized" and * "ready" states. */ void UIKRun(uint8_t taskid) { //the scheduler will handle if/when it actually runs based on priority tcb[taskid].state = ready; } /*this is the scheduler - handled in an interrupt (other interupts are disabled) */ void SIG_OUTPUT_COMPARE1A(void) { //save the execution context - this disables interupts savecontext(); tick(); restorecontext(); //reti re-enables interrupts asm volatile ("reti"); } /* * tick increments through tcb and does 2 tasks: it increments the delays and * finds the maximum ready task. One constraint is the tick should be long enough * that this has a chance to complete. */ void tick(void) { int i; //start with the high being the idle task, which is always ready //and has lowest priority int high = 0; for(i=0; i 0) { tcb[i].delay--; if(tcb[i].delay == 0) { //the state should have been waiting tcb[i].state = ready; } } //if an event needs to be dealt with by marking the task as ready //and updating "Event" if(EVENT_FLAG != 0) { //if tcb[i] in ASS_BLOCK, mark tcb[i] as ready //if xxxxxxx1 if((EVENT_FLAG & (unsigned char)0x01) != 0) { tcb[ASS_BLOCK[0]].state = ready; //reset the bit to 0 EVENT_FLAG &= 0xFE; } /*TODO, additional comparisons as events are added see UIKAssocEvent */ } //find highest priority task that's ready if(tcb[i].state == ready && tcb[i].priority < tcb[high].priority) { high = i; } } //EVENT_FLAGS should all be dealt with now and their processes marked as ready curr_task = high; currTCB = tcb[high].stack_ptr; } /* * ticknodelay is the tick without decrementing delays * this is kind of a band-aid */ void ticknodelay(void) { int i; //start with the high being the idle task, which is always ready //and has lowest priority int high = 0; for(i=0; i<numtasks; i++) { //if an event needs to be dealt with by marking the task as ready //and updating "Event" if(EVENT_FLAG != 0) { //if tcb[i] in ASS_BLOCK, mark tcb[i] as ready //if xxxxxxx1 if((EVENT_FLAG & (unsigned char)0x01) != 0) { tcb[ASS_BLOCK[0]].state = ready; //reset the bit to 0 EVENT_FLAG &= 0xFE; } /*TODO, additional comparisons as events are added see UIKAssocEvent */ } //find highest priority task that's ready if(tcb[i].state == ready && tcb[i].priority < tcb[high].priority) { high = i; } } //EVENT_FLAGS should all be dealt with now and their processes marked as ready curr_task = high; currTCB = tcb[high].stack_ptr; } /* * startrtos sets up the idletask, enables interrupts, and begins the idletask executing * it will be interupted if any other tasks exist on the first tick */ void startrtos(void) { //start with the idle process tcb[0].state = ready; currTCB = tcb[0].stack_ptr; //this is the idle process, it will be swapped out soon enough restorecontext(); //enable interupts with the return asm volatile ("reti"); } /* * This is the interupt that is in charge of scheduling */ void UIKDelay(uint32_t ticks) { //save the execution context - this disables interupts savecontext(); //if a -1 is passed it implies no time passes tick-wise if (ticks value = 1; return thissem; } /* * This is for UIKSemPend logic */ unsigned char semtest(UIKSem* sem) { if(sem->value > 0) { //critical secion - must retest cli(); if(sem->value > 0) { sem->value--; sei(); //end critical section return 1; } } return 0; } /* * This is the semaphore acquire */ void UIKSemPend(UIKSem* sem) { //wait until sem > 0 while (semtest(sem) == 0) { //delay current task, which is the one trying to access semaphore //implies highest priority task gets semaphore first UIKDelay(1); } } /* * This is the semaphore release */ void UIKSemPost(UIKSem* sem) { //critical section cli(); sem->value++; sei(); //end critical section //theoretically shouldn't have to do anything //but just as to not have to wait for the next tick UIKDelay(0); } /* * This returns the current value of a semaphore */ uint8_t UIKSemValue(UIKSem* sem) { return sem->value; } /* * UIKAssocEvent associates an external event with the process that calls it * the events are defined in uik.h */ void UIKAssocEvent(uint8_t Event) { //one function can be associated with multiple events //but an event should only be associated with one function at a time //error checking does not exist to save space, but be wary //if you would like to error check, just verify if(ASS_BLOCK[i] != 255) for every bit //before setting it. also you would need to reenable interupts before returning if(Event == 0) { //succesfuly give them the nothing they ask for return; } //ASS_BLOCK and curr_task must be protected //unless we want a semaphore for curr_task, the best way seems to just be to disable //interupts cli(); //set the state for waiting, since it's waiting for the event to occur tcb[curr_task].state = wait; //if xxxxxxx1 if((Event & 0x01) != 0) { ASS_BLOCK[0] = curr_task; } //if xxxxxx1x if((Event & 0x02) != 0) { ASS_BLOCK[1] = curr_task; } //if xxxxx1xx if((Event & 0x04) != 0) { ASS_BLOCK[2] = curr_task; } //if xxxx1xxx if((Event & 0x08) != 0) { ASS_BLOCK[3] = curr_task; } //if xxx1xxxx if((Event & 0x10) != 0) { ASS_BLOCK[4] = curr_task; } //if xx1xxxxx if((Event & 0x20) != 0) { ASS_BLOCK[5] = curr_task; } //if x1xxxxxx if((Event & 0x40) != 0) { ASS_BLOCK[6] = curr_task; } //if 1xxxxxxx if((Event & 0x80) != 0) { ASS_BLOCK[7] = curr_task; } sei(); //give up execution - a -1 allows the other processes not to tick UIKDelay(-1); } /* * UIKDissasocEvent disassociates a task with an external event */ void UIKDisassocEvent(uint8_t Event) { //a semaphore could easily be used in place of disabling interupts cli(); //if xxxxxxx1 if((Event & 0x01) != 0) { ASS_BLOCK[0] = 255; } //if xxxxxx1x if((Event & 0x02) != 0) { ASS_BLOCK[1] = 255; } //if xxxxx1xx if((Event & 0x04) != 0) { ASS_BLOCK[2] = 255; } //if xxxx1xxx if((Event & 0x08) != 0) { ASS_BLOCK[3] = 255; } //if xxx1xxxx if((Event & 0x10) != 0) { ASS_BLOCK[4] = 255; } //if xx1xxxxx if((Event & 0x20) != 0) { ASS_BLOCK[5] = 255; } //if x1xxxxxx if((Event & 0x40) != 0) { ASS_BLOCK[6] = 255; } //if 1xxxxxxx if((Event & 0x80) != 0) { ASS_BLOCK[7] = 255; } sei(); } /* * timer0 overflowing is a hardware event that our event functionality handles */ ISR(TIMER0_OVF_vect) { //interupts are disabled since this is an interrupt so locking event_flag not necessary //the 0th bit is associated with timer0 EVENT_FLAG |= (1 << PORT0_OVERFLOW); }
Installation/Compilation
Due to the complex nature of this program, it was compiled with AVR Studio on Windows XP. Although I certainly miss my usual development tools on Linux, the AVR studio debugger was much better in my opinion.
To compile, open project6.aps, select Build->Rebuild All. To copy to the Atmega16, click on the “AVR” sprite. Under Program ->Flash, click the “Program” button and select the homework4_*.hex file.
Of course, everything should work under linux as well.
This reminds me of that time I made a compiler (https://webstersprodigy.net/2007/06/25/ramel/) for a class. Totally useless, like this, but I guess I can say that I have build both a functional Operating System and a compiler now.
Also, the STK500 I was using for this, I had to give back – so development is done for now. But hey, hopefully this is still useful to someone.