Appendix A: Code Listing
#include <90s8515.h>

/* speed and direction output pins */
#define speed           OCR1B                   //use PWM for speed control
#define drivedir        PORTA.0
#define turndir         PORTA.1
#define turn            PORTA.2

/* direction signal values */
#define fwd             1
#define rev             0
#define dirleft         1
#define dirright        0

/* state definitions */
#define center          0
#define left            1
#define farleft         2
#define right           3
#define farright        4
#define start           5

/* photosensor values */
#define LEDS            ~PINC   //inverted since "on" is for a black line

#define LLL             (0x01 & LEDS) == 0x01   //far left sensor
#define LL              (0x02 & LEDS) == 0x02   //slightly left sensor
#define CC              (0x04 & LEDS) == 0x04   //center sensor
#define RR              (0x08 & LEDS) == 0x08   //slightly right sensor
#define RRR             (0x10 & LEDS) == 0x10   //far right sensor

/* other definitions */
#define t0reload        256-62          //1 ms timebase @ 4MHz
#define deltaT          10              //state change time


unsigned char state;
unsigned int watchdog;                  //stops car if no line is found after time
unsigned int farcount;                  //after backing up, car steers and waits before going forward
unsigned int t_statemachine;            //task timer
void initialize(void);
void steer_off(void);
void steer_left(void);
void steer_right(void);
void reset(void);
void statemachine(void);

void steer_off(void)
{
        turn = 0;
}

void steer_left(void)
{
        turndir = dirleft;
        turn = 1;
}

void steer_right(void)
{
        turndir = dirright;
        turn = 1;
}

void reset(void)                        //turn off steering and stop car; return to start state
{
        steer_off();
        speed = 0;
        state = start;
}

void statemachine(void)
{
        t_statemachine = deltaT;        //reset machine timer

        if(LEDS == 0x1f)                //all LEDS off (car has been picked up in the air)
        {
        reset();
        }

        /* We want to reset the watchdog timer to 0 as long as
         * at least one sensor is on the line. */
        if(LEDS != 0)
        {
                watchdog = 0;
        }
        /* If the watchdog counter reaches 1.5 seconds, the car should be stopped.  However,
         * if we're backing up to find the line again, let it go until 3 seconds have passed. */
        if((watchdog++ == 1500/deltaT) && (drivedir != rev))
        {
        reset();
        }
        else if(watchdog == 3000/deltaT)
        {
        reset();
        }
        switch(state)
        {
                case start:                             //initial state
                        if(LEDS == 0x04)                //do nothing until only center sensor is on
                        {
                                drivedir = fwd;
                                speed = 200;
                                state = center;
                        }
                        break;
                case center:                            //center of car is on the line
                        if(LL)                          //near left sensor on, steer left to correct
                        {
                                steer_left();
                                state = left;
                        }
                        else if(RR)                     //near right sensor on, steer right to correct
                        {
                                steer_right();
                                state = right;
                        }
                        break;
                case left:
                        if(CC)
                        {
                                steer_off();            //we're back on track, go straight
                                state = center;
                        }
                        else if(LLL)                    //we're way off track, back up and turn the
                        {                               //wheels in the opposite direction to correct
                                drivedir = rev;
                                speed = 255;
                                steer_right();
                                state = farleft;
                        }
                        break;
                case right:
                        if(CC)
                        {
                                steer_off();
                                state = center;
                        }
                        if(RRR)
                        {
                                drivedir = rev;
                                speed = 255;
                                steer_left();
                                state = farright;
                        }
                        break;
                case farleft:                           //we're currently backing up to find the line
                        if(LL)                          //the inner left sensor is back on the line, so
                        {                               //go forward again & flip the steering 
                                steer_left();
                                drivedir = fwd;
                                state = left;
                        }
                        break;                          //otherwise, continue backing up
                case farright:
                        if(RR)
                        {
                                steer_right();
                                drivedir = fwd;
                                state = right;
                        }
                        break;
        }
}

interrupt [TIM0_OVF] void t0(void)                      //establishes 1msec time base
{
        TCNT0 = t0reload;
        if(t_statemachine > 0) t_statemachine--;
}

void main(void)
{
        initialize();

        while(1)
        {
                if(t_statemachine == 0) statemachine(); //fire off state machine if it's time
        }
}

void initialize(void)
{
        DDRA = 0xff;                    //PORTA is an output
        DDRC = 0x00;                    //PORTC is an input
        PORTC = 0xe0;                   //internal pullups off, except for the top 3 unused bits

        /* set up PWM to cycle every ~32 msec */
        TCCR1A = 0x20 + 0x01;           //non-inverted, 8-bit PWM
        TCCR1B = 0x04;                  //prescale to 256

        /* set up timer 0 to interrupt every 1 msec */
        TIMSK = 0x02;                   //timer 0 overflow interrupt
        TCCR0 = 0x03;                   //prescale to 64
        TCNT0 = t0reload;

        watchdog = 0;
        t_statemachine = deltaT;        //initialize task timer

        state = start;                  //initialize state variable
        #asm("sei")                     //fire up the interrupts
}