Finished RTOS similar to FreeRTOS

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

lundeen_rtos.tar.gz

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.

playing a scale with the atmega16

Specification

The purpose of this assignment is to get further practice using interrupts, particularly concurrent interrupts. A musical “note” is a waveform that has a certain fundamental frequency. For example, the “A” above middle C on a piano has a frequency of 440 Hz, and middle C itself is approximately 261 Hz. On an equally tempered scale, each of the twelve notes is related to each other by a factor of 2 12 . A note that is an octave above another note is double the frequency of the lower note. A perfect sine wave consists only of the fundamental frequency, and sounds very “pure” or mellow, like a flute. Most waveforms consist of a fundamental and harmonics or overtones, which are additional frequencies above the fundamental. For instance, a square wave consists of the fundamental and multiples of the fundamental in decreasing amplitude. The distinctive sound of a violin is mostly a sawtooth wave (created as the rosin on the bow catches the string, then releases it), with other overtones (called dissonant overtones, due to the fact that they are not multiples of the fundamental) created by resonances in the wood.

For this assignment, you are to use interrupts to generate the notes of a musical scale, while at the same time using another interrupt to update a count. We will generate square waves by toggling a bit within a port on and off. Specifically, your program should play the notes from middle C to the next higher C, each of approximately one second in duration. These notes should be playing at the same time that an 8-bit binary count is being displayed in the LED’s, updating at approximately 1/4 second. As before, determine the code sizes, and estimate the percentages of time your ISR’s and “main” program takes of the total execution time. Also, for each note, estimate its accuracy – that is, how close is the note your program plays to the calculated frequency for the note.

Download the source here

Compiling

This code requires avrdude, avr-gcc, and objcopy are installed

To compile, type:

make

After it’s compiled, to install to a connected and powered on atmega16 board, type:

make install

Note Makefile may require some modification depending on your environment.

This program includes a demo called homework3_demo.avi.

Program Size

The ATmega16 provides 16K bytes of Programmable Flash Program Memory, 512 bytes eeprom, and 1K byte SRAM

make gives a 3982 byte executable. It contains the following headers:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000074 000248 00 AX 0 0 2
[ 2] .data PROGBITS 00800060 0002bc 000020 00 WA 0 0 1
[ 3] .bss NOBITS 00800080 0002dc 000002 00 WA 0 0 1
[ 4] .stab PROGBITS 00000000 0002dc 000378 0c 5 0 4
[ 5] .stabstr STRTAB 00000000 000654 000054 00 0 0 1
[ 6] .shstrtab STRTAB 00000000 0006a8 00003b 00 0 0 1
[ 7] .symtab SYMTAB 00000000 00084c 000450 10 8 17 4
[ 8] .strtab STRTAB 00000000 000c9c 0002f2 00 0 0 1

#without space optimization
Program: .text + .data + .bootloader == 618 bytes (3.8% Full)
Data: .data + .bss + .noinit == 34 bytes (3.3% Full)

Here is most the code

/* http://www.avrfreaks.net/index.php?name=PNphpBB2&amp;file=viewtopic&amp;t=50106
 * was a big help understanding the timer syntax
 *
 * also relied on the atmega16 manual and the #defines located in /usr/avr/include/avr */

#include <avr/io.h>
#include <avr/interrupt.h>

/*counter keeps track of how many overflows in timer0 = about a second */
int counter = 0;

/*number of ticks to make a note at 1 MHz*/

/*
At 1MHz, these are the number of ticks it takes to equal a given note

const int C_l = 3817/2;
const int D   = 3401/2;
const int E   = 3030/2;
const int F   = 2865/2;
const int G   = 2551/2;
const int A   = 2272/2;
const int B   = 2024/2;
const int C_h = 1912/2; */

//const long int notes[8] = {3817, 3401, 3030, 2865, 2551, 2272, 2024, 1912};
const long int notes[8] = {1908, 1700, 1515, 1432, 1275, 1136, 1012, 956};

void port_init() {
  /*PORTC is connected to the amplifier */
  /*PORTB is connected to the LEDs */
  DDRB  = 0xff;  /*11111111 for all output */
  DDRC  = 0xff;
  PORTC = 0xff;  /*all lights turned off   */
  PORTB = 0xff;  /*all lights turned off   */
}

void init_timer1() {
  /*setup timer1, this will measure the notes */
  TCCR1B |= (1 << WGM12);
  /*Enable CTC interrupt */
  TIMSK |= (1 << OCIE1A);
  /* OCR1A = 15625;*/
  OCR1A = notes[0]; /* Set CTC value to middle C to begin with */
  /*timer runs with a 1MHz resolution */
  TCCR1B |= (1 << CS10);

}

void init_timer0() {
  /*prescaler/64*/
  TCCR0 |= ((1 << CS01) | (1 << CS00));
  /*overflow mode - about every 1/61 seconds */
  TIMSK |= (1 << TOIE0);
}

void main (void) {
  port_init();
  init_timer0();
  init_timer1();

  /*Enable global interrupts */
  sei();

  while(1) {
    /*most logic is handled by the interrupts */
  }
}

/* toggle the LED for now, this is the musical note */
ISR(TIMER1_COMPA_vect) {
  /*toggle the output */
  PORTC = ~PORTC;
}

ISR(TIMER0_OVF_vect) {
  counter += 1;
  if (counter == 61) {
    /*count up to 8 for all 8 notes*/
    if (PORTB == 0xf8) {
      PORTB = 0xff;
    }
    else {
      PORTB--;
    }
    //OCR1A = notes[~PORTB];
    OCR1A = notes[~PORTB];
    counter = 0;
  }
}

Here is a short video demonstration:

avr interrupts

This was also for homework..

From the specifications:

The purpose of this assignment is to give you experience with the AVR’s timers and the use of interrupts. You are to repeat assignment #1, this time performing the timing using an interrupt-driven timer. You are to write an interrupt service routine (ISR) for one of the hardware timers on the AVR. Each time the timer interrupts, you should update a counter variable. Then, when determining the amount of time that the switch is held down, use the counter value. Then, use the counter again to determine how long to turn on the LED. As before, in addition to writing the code, determine the size of the code in your program, including main and the ISR.

I found this site hugely helpful:   http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106

For reference, all the first assignment did was to time how long you held down a button, wait a second, then to light up the lights for the same duration. The difference is this assignment uses timers and interrupts wheras the other just looped.

Here is a link to the source.

stk500 avr atmega16 linux gcc hello, world

Does my title sound like buz-word central? You bet it does. That’s because it was a bit difficult to find any good introductory material on this. Maybe that’s because there’s so much information out there…

Anyway, here is some. The README:

Rich Lundeen
lundrich@isu.edu

The Specification:
———————————

The purpose of this assignment is to introduce you to the software tools we will use for the
AVR microprocessor, and to give you a better understanding of the run-time environment that
a C program operates under.
For this assignment, you will write a C main program and two subroutines – one in AVR
assembly, and the other in C. The subroutines should each do the same thing:
1. Monitor a port (your choice) that is connected to a switch. Wait until the switch is
pressed.
2. Once pressed, the routine should keep track of how long the switch is held down.
3. Wait for approximately one second.
4. Using another port (your choice) connected to a LED, turn on the LED for the same
amount of time that the switch was pressed earlier.
In addition to writing the code, determine the size of the code (main and both subroutines) in
your program.

———————————-

This code requires avrdude, avr-gcc, and objcopy are installed.

To compile the C version, type:

make

To compile the asm version, type:

make asm

After it’s compiled, to install to a connected and powered on atmega16 board, type:

make install

homework1.avi contains a short demonstration of the C version – however, both do the same thing. It
will play with vlc or mplayer.

Here is a demo of the program.. oooh blinky lights. Yeah, and anyway, the hardest part is just to find a hello world. This includes one in both C and assembly, so good hunting!

Download the source here,

/*main.c*/
#include "portstuff.h"

int main (void) {
  DDRB = 0xff;  /*11111111 means all output */
  DDRD = 0x00;  /*00000000 for all input */

  PORTB = 0xff;
  portmon();
  return(0);
}
/*portstuff.h*/
#include
#include
#include

void portmon();
/* portstuff.c */
#include "portstuff.h"

/*this is in clock cycles for my board.
* 30000 seems to be close to about a sec */
const DELAY = 30000;

void portmon() {
  while(1) {
    unsigned long timer = 0;
    unsigned long i;
    /*while a button is pressed */
    while (PIND != 0xff) {
      /*light up the button pressed */
      PORTB = PIND;
      timer += 1;
    }
    PORTB = 0xff;
    /*if the timer is not zero, we need to handle a button pressed */
    if (timer != 0) {
      /*delay for about a second */
      for (i = DELAY; i!=0; i--);
      PORTB = 0x00;
      /*delay for timer roughly equal to how long button was pressed */
      for (i = timer; i!=0; i--);
      timer = 0;
    }
  }
}
#Makefile
#c specific - this is the default

homework1.hex: homework1.out
    objcopy -S -O ihex homework1.out homework1.hex
homework1.out: main.o portstuff.o
    avr-gcc -mmcu=atmega16 main.o portstuff.o -o homework1.out
main.o: portstuff.h main.c
    avr-gcc -c -mmcu=atmega16 main.c
portstuff.o: portstuff.h portstuff.c
    avr-gcc -c -mmcu=atmega16 portstuff.c

#assembly specific rules

asm: howework1asm.out
    objcopy -S -O ihex homework1.out homework1.hex
howework1asm.out: main.o portstuffasm.o
    avr-gcc -mmcu=atmega16 main.o portstuffasm.o -o homework1.out
portstuffasm.o: portstuff.h portstuff.s
    avr-gcc -c -mmcu=atmega16 portstuff.s -o portstuffasm.o

#clean and install

clean:
    rm ./*.o ./*.hex ./homework1.out

install:
    avrdude -y -C /etc/avrdude/avrdude.conf -p atmega16 -P /dev/ttyS0 -c stk500v2 -U flash:w:homework1.hex:i
/*portstuff.s*/
#include "portstuff.h"

.file	"portstuff.c"
PORTB  = 56-0x20
PORTD  = 48-0x20
ALLON  = 31
WAIT   = 0xa
.text
.global	portmon
.type	portmon, @function
portmon:
/*initialize count to zero */
.BEGIN:
/*r18 is our counter*/
ldi     r18,0x00

/*initialize port values*/
in  r24,PORTD
out PORTB, r24

.LEDWAIT:
in  r24,PORTD
nop

/*see if the button is currently pressed - if it is then continue
if not jump to .LEDWAIT */
cpi r24, lo8(-1)
breq .LEDWAIT

.BUTTONPRESSED:

out PORTB, r24

/*measure the number of delays the button is pressed*/
rcall delay
inc r18

/*see if button is still currently pressed - if it is then
jump to Buttonpressed */

in   r24, PORTD
cpi  r24, lo8(-1)
brne .BUTTONPRESSED

out PORTB, r24
ldi r23, WAIT
.DELAYSECOND:
/*theoretically this delays a second */
rcall delay
dec r23
cpi r23,0
brne .DELAYSECOND

.LEDSON:
/*light up all leds for the amount of time the one was pressed*/
out PORTB, ALLON
rcall delay
dec r18
cpi r18,0
breq .BEGIN
rjmp .LEDSON

/*delay function makes time reasonable to deal with */
delay:
ldi r17,0x60
waitouterloop:
ldi r16,0xFF
waitinnerloop:
subi r16,0x01
brne waitinnerloop
subi r17,0x01
brne waitouterloop
ret

.size	portmon, .-portmon