Joseph Tarango
Department of Computer Science and Engineering
Bourns College of Engineering
University of California, Riverside

Home
CS120B | Syllabus
Lab 1 | Lab 2 | Lab 3 | Lab 4 | Lab 5 | Lab 6 | Lab 7 | Game Project

lab5

Lab 7: Scheduler and Producer-Consumer I/O

In this lab we will introduce a structure for designing a real-time operating system using state machines. We will build a simple kernel that will process state machines according to the period specified by each state machine. We will use the scheduler to implement a producer-consumer problem where we input via a keypad, and use the LCD to output the characters pressed on the keypad.

In our design, the producer/consumer problem will be introduced due to having peripherals that operate at different period lengths. For this lab it is required that:
  • Producer (keypad) Period = 50 ms.
  • Consumer (LCD) Period = 1000 ms.
From examining the above periods, it is clear to see that the keypad can detect button presses much faster than the LCD can output them. Therefore we need to have a structure to capture the inputs and save them, such that the slower LCD can output them at it's own rate. You will need to use the below code template and fill in the declared functions to build a queue structure.
  • Use the same circuit as in Lab 6 and IO for the LCD.
  • Use a button toggle in your circuit to halt the consumption of items from the queue (ie. pause the LCD). The producer(keypad) should continue to place items on the queue while the pause button is toggled. When the pause button is toggled again, the LCD will continue to output items in the queue, including any items added while the LCD was paused.

Building the Scheduler:

Remember that we went over the scheduler in lecture. You can find all of the information in PES Chapter 7. We will define a structure called a task that represents a process in our operating system. The task structure should contain all of the information that represents that process, such as period, state, etc... The heart of each task is the function that it will be executing. Each of these functions will be defined as a global function, and we use function pointers in the task struct to point to the appropriate function. Function pointers work just like a pointer to a char or integer, but they have some specific syntax on how they must be called and defined. For more information on function pointers see: http://www.newty.de/fpt/fpt.html

Download the following files.
io.h
io.c

SAMPLE CODE:

#include <avr/io.h>

#include <avr/interrupt.h>

#include <stdio.h>

#include "io.h"

 

#define NIL 0x00 //Absolute Zero

#define QUEUE_SIZE (32) //Max Queue Size

 

//These are macros used to set, clear, or get a single bit from an 8-bit variable.

//For example SET_BIT(temp,3); sets temp = 0x08.

#define SET_BIT(p,i) ((p) |= (1 << (i)))

#define CLR_BIT(p,i) ((p) &= ~(1 << (i)))

#define GET_BIT(p,i) ((p) & (1 << (i)))

 

//Interrupt service routine Settings

volatile unsigned int timerFlag = 0;//Timer flag

 

//Interrupt service routine

//We enter this function ~4 times per millisecond,

//We can set a flag that signals a period has passed every period*4 times the function is entered.

//If our period is 1000 ms, then we would enter the function 1000*4 = 4000 times before transitioning to next state.

ISR(TIMER0_OVF_vect) { //Timer0 overflow interrupt service routine

      //Set flag, use "timerFlag"

      timerFlag = 1;

}

 

//Configure ATMega32 Timer control registers. Correct values can be found in ATMega32 datasheet.

void InitTimer() {

      //Set prescaler.

      TCCR0 |= (1<<CS01);

      //Enable Overflow Interrupt Enable on Timer.

      TIMSK |= (1<<TOIE0);

      //Initialize starting value of timer

      TCNT0 = 0 ;

}

 

//Conversion table from keypad input to corresponding character.

unsigned char conv_table[] = {

      '1',  '2',  '3',  'A',

      '4',  '5',  '6',  'B',

      '7',  '8',  '9',  'C',

      '*',  '0',  '#',  'D'

};

 

//Variables to implement our circular queue.

const unsigned int MAX_QUEUE_SIZE = 32;

unsigned char queue[QUEUE_SIZE] = {0}; //Queue of characters to output.

unsigned int queue_front = 0; //Queue front index.

unsigned int queue_back = 0; //Queue back index.

unsigned int num_objects = 0; //Number of objects in queue.

 

//Functionality - Push a character onto back of queue

//Parameter: takes in a single unsigned char.

//Returns: One if full else zero.

 

unsigned int push_queue(unsigned char c) {

      //If queue is not full.

      //Increment back counter, modulate according to the max queue size, and increase number of objects.

      //Put data into correct location.

      //Return not full.

      //Else Return queue is full.

      return 1;

}

 

//Functionality - Pop first character from top of queue.

//Parameter: None

//Returns: unsigned char from queue else null character.

unsigned char pop_queue() {

      //If queue is not empty.

      //Retrieve data in correct location.

      //Clear location with null character.

      //Increment front counter and modulate according to the max queue size.

      //Return data.

      //Else return null character to indicate empty.

      return '\0';

}

 

//Struct for Tasks represent a running process in our simple real-time operating system.

typedef struct _task {

      //Tasks should have members that include: state, period, a measurement of elapsed time, and a function pointer.

      int state; //Task's current state

      unsigned long int period; //Task period

      unsigned long int elapsedTime; //Time elapsed since last task tick

      int (*TickFct)(int); //Task tick function

} task;

 

//Declare an array of tasks and an integer containing the number of tasks in our system

task task1, task2; //Add more tasks if necessary

task *tasks[] = { &task1, &task2 };

const int numTasks = sizeof(tasks)/sizeof(task*);

 

//Enumeration of states.

enum Producer_States { Producer_S0 /*... Add More ...*/ };

 

//State machine functions take in their current state as an argument and return the next state.

int Producer_SMTick(int state) {

      //State machine 1 transitions

      switch (state) {

      case -1:

            state = Producer_S0;

            break;

 

      case Producer_S0:

            state = Producer_S0;

            break;

            //...Add More

 

      default:

            state = -1;

            break;

      }

 

      //State machine 1 actions

      switch(state) {

      case -1:

            break;

 

      case Producer_S0:

            break;

            //...Add More

 

      default:

            break;

      }

 

      return state;

}

 

//Enumeration of states.

enum Consumer_States { Consumer_S0/*...*/ };

 

//State machine functions take in their current state as an argument and return the next state.

int Consumer_SMTick(int state) {

      //State machine 2 code

      return state;

}

 

//Add more state machines if necessary.

 

//Constants used for key scaning.

const unsigned int FOUR = 4;

const unsigned int EIGHT = 8;

//Scan the keypad to see if a button is pressed.

unsigned char key_scan() {

      unsigned int i,j;

      for( i = FOUR; i < EIGHT; i++ ) { // Set one column at a time to Zero Voltage.

            PORTC = (~NIL & ~(1<<i)) & 0xF0; //Keep only four most significant bits.

            for ( j = 0; j < FOUR; j++ ) { //Scan each row checking if a button was pressed.

                  /* Use i and j to index into conversion table */

                  if( GET_BIT(PINC,j) == 0 ){return conv_table[(i-FOUR)+(j*FOUR)];}

            }

      }

      return '\0';

}

 

//Implement scheduler code from PES Chapter 7.

int main()

{

      //Set Data Direction Registers

      DDRA = 0xFF;//LCD Data Signals(PORTA[0-7])

      DDRB = 0xC0;//LCD Control Signals(PORTB[0-1]) and AVR Programmer(PINB[5-7] and RESET)

      DDRC = 0xF0;//Keypad(PIN[0-3] and PORTC[4-7])

      DDRD = 0xFC;//Optional Buttons(PIND[0-1]) and LEDs(PORTD[2-3])

      SREG |= (1<<7);//Enable Global Interrupts

 

      InitTimer();//Start timer

 

      LCD_init();//Setup LCD

      LCD_ClearScreen();//Clear the LCD

     

      //Timer based: 4 interrupts per ms * 50 = 50 ms

      const unsigned long int P_period = 4*50; //Period for Producer task 1. (50 ms)

      //Timer based: 4 interrupts per ms * 1000 = 1000 ms

      const unsigned long int C_period = 4*1000; //Period for Consumer task 2.(1000 ms)

      const unsigned long int GCD = 1;//Greatest common divisor for all tasks or smallest time unit for tasks.

 

      task1.state = -1;//Task 1 initial state.

      task1.period = P_period;//Task 1 Period.

      task1.elapsedTime = P_period;//Task 1 current elasped time.

      task1.TickFct = &Producer_SMTick;//Function pointer for the tick 1.

 

      task2.state = -1;//Task 2 initial state.

      task2.period = C_period;//Task 2 Period.

      task2.elapsedTime = C_period;//Task 2 current elasped time.

      task2.TickFct = &Consumer_SMTick;//Function pointer for the tick 2.

 

      unsigned int i; //Scheduler for-loop iterator

      while(1) {

            //Scheduler code

            for ( i = 0; i < numTasks; i++ ) {

                  if ( tasks[i]->elapsedTime == tasks[i]->period ) { //Task is ready to tick

                        tasks[i]->state = tasks[i]->TickFct(tasks[i]->state);//Setting next state for task

                        tasks[i]->elapsedTime = 0;//Reset the elapsed time for next tick.

                  }

                  tasks[i]->elapsedTime += GCD;

            }

            while(!timerFlag);

            timerFlag = 0;

      }

 

      printf("%s \n", "Error: Program should not exit!");

      return 0;

}


POST LAB

INDIVIDUALLY prepare and submit a single-spaced half page report with the following information.

I.     Lab Objective
II.   Personal Contributions
III. Skill learned & knowledge gained.

Turnin

INDIVIDUALLY prepare and submit all lab files into a tar ball. All .c files should be included in lab parts, as well as post lab submitted in pdf and txt format. All files should include a header with name, login, email, lab section, assignment, and group associates; also include: "I acknowledge all content is original."

For Example
Name: John Doe
Login: jdoe
Email: jdoe@cs.ucr.edu
Lab Section: 021
Assignment: Lab 7
Group: John Doe, Jane Doe, and Joe Doe
I acknowledge all content is original.

Tar ball command: tar -cvzf name.tgz *.c *.pdf *.txt

The tar command will compress all files into a .tgz file with all .c .pdf, and .txt files in that directory. Do not include unnecessary files! The .c files be named as follows lab#_part#.c and the postlab#.pdf/postlab#.txt.

For Example:
lab7.c
postlab7.pdf
postlab7.txt