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

Lab 5: Synchronous State Machines - Reflex Timer Game

In this lab you will be developing a simple reflex timer game.  You will be introduced to using the timer on the AVR, as well as implementation of synchronous state machines.

Circuit Design:


Part I: Timers

In this section you are going to learn how the timers work on the ATMega32 microcontroller.  The ATMega32 has 3 timers, known as Timer0, Timer1, and Timer2.  Timer0 and Timer2 are 8-bit timers, and Timer1 is a 16-bit timer.  This means that Timers 0 and 2 will count from 0-256 before overflowing, and Timer1 can count from 0-65535.

Keeping Track of Time & Interrupt Service Routines

Remember that an interrupt service routine is a function that is called when a certain event is triggered in the hardware.  When a certain event happens that triggers an interrupt, all processing is stopped and the code automatically begins to execute the corresponding interrupt service routine.
Each timer has two methods of triggering an interrupt: when it overflows (wraps from its maximum value to 0), or if it matches a value that you give it (known as a compare interrupt.We will be using only overflow interrupts in this lab. 

OK, so now we know what an interrupt does, but how do we use it to keep track of time?  What we really want to know is how many times an overflow interrupt is triggered each second.  Luckily, we know that our ATMega32 microcontroller has an internal clock that is operating at a frequency of 8MHz.  This means that every second, the ATMega32 is performing 8,000,000 cycles, or 8e6 cycles/second.

Each timer has some internal registers that control its behaviour.  Each timer has what is known as a prescaler.  A prescalar is simply a value to divide the clockspeed by, which effectively slows down the clock.  So if the ATMega32 is performing 8e6 cycles/second, we can use the prescalar to make the timer run at 8e6 / 8 = 1e6 cycles/second.  This gives us a nice even number of 1,000,000 cycles/second to work with.

Now we have a timer running at 1,000,000 cycles/second.  We can take this further if we recognize that our timers (Timer0 or Timer2) are both 8-bit timers.  This means that every 256 cycles, an interrupt is generated by the timer.  So we can do a little math here:

(1e6 cycles per second) / (256 cycles per interrupt) = 3906 interrupts per second =  ~4 interrupts per millisecond.

The above equation should tell us that every millisecond, about 4 interrupts are generated by the timer.  Therefore, in our interrupt service routine we can keep a global variable counter that tracks how many times the function has been entered.  Every time the counter reaches 4, a millisecond has passed.  Voila, we now have a method for keeping track of time and of executing synchronous state machines.

Fill in the following code, using your newfound knowledge of timers.  Write a simple 2-state (plus a once-visited init state) state machine that toggles the lights on port A once per second.

SAMPLE CODE:

#include <avr/io.h>

#include <avr/interrupt.h>

 

const unsigned int ONE_MS = 4;

 

const unsigned int RT_Period = 1000;

unsigned int RT_Clk = 0;

unsigned int RT_Counter = 0;

 

enum RT_States { INIT, S0, S1 } RT_State;

 

//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

       //Put code in me!

       //Count up to period and set flag

}

 

void RT_ClkTick() {

      //Put code in me!

       //wait until ISR sets flag

}

 

void RT_Tick() {

      switch(RT_State) { //transitions

      case INIT:

             //...

      }

      switch(RT_State) { //actions

      case S0:

             //...

      }

}

 

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

void InitTimer() {

      // Set prescaler to 8.  Since the ATMega32 runs at 8Mhz, our timer clock will run at 8MHz/8 = 1MHz.

      //TCCR0 |= ?

 

      //Enable Overflow Interrupt Enable on Timer0. 

      //TIMSK |= ?

 

      //Initialize starting value of timer

      TCNT0=0;

}

 

int main()

{

      DDRA = 0xFF;

 

      //Enable Global Interrupts

      SREG |= (1<<7);

 

      InitTimer();

 

      //init state

      RT_State = S0;

 

      //init output

      PORTA = 0x00;

 

      while(1) {

             RT_Tick();

            RT_ClkTick();

      }

}

 

Part 2: Reflex Timer Game

Well done ladies and gentlemen, you've made it this far.  Your final task for this lab is to use the timers above to implement a reflex game.  The procedure below details how the game should work.

1.  Output instructions on the LCD screen.  The instructions should tell the user that they can start the game by pressing '1' on the keypad.
2.  Once the user begins the game, the game should wait for a random amount of time.  A good way of generating a psuedo random number is to increment a counter while waiting for the user to start the game.
3.  Once the random amount of time has expired, the lights on port A should flash and the LCD screen should direct the user to press '1' again as fast as possible.
4.  Once the user has pressed '1' again, the time it took for the user to press the button should be displayed on the LCD screen.
5.  If the user does not press the button within 3000 milliseconds, the LCD screen should display a message that the user was too slow.  If the user pressed the button before the random amount of time expired, the LCD screen should admonish the user for being a cheater.
6.  The game should return to step 1 after the user presses '2'.


In order to implement the game, you may want to set up an additional timer.  Remember that the ATMega32 has 3 timers.  The first timer can be in charge of transitioning between states, while the second timer keeps track of the time in the game (such as a 3000 ms limit for the user being too slow).  Alternatively you could use a single timer with multiple counters -  the implementation is up to you.

You must follow the state machine format detailed to you in previous labs and in lecture.  You will be graded on how closely you follow the format. 

Using the LCD screen

Download the following files and include them in your project.  They include necessary functions to interface to the LCD screen.  Don't forget to do a #include in your main file to include them in the compilation process.

io.h
io.c

You must first call LCD_init() to initialize the LCD screen.  Then use LCD_ClearScreen().  You can write to the LCD screen using LCD_DisplayString(unsigned char column, const unsigned char* str) to write the string str to the LCD, starting at column.

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 5 Part 1
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:
lab5_part1.c
lab5_part2.c
postlab5.pdf
postlab5.txt