blackbox/main.c

Thu, 08 Dec 2011 17:14:02 +0100

author
Malte Bayer <mbayer@neo-soft.org>
date
Thu, 08 Dec 2011 17:14:02 +0100
changeset 48
5bba01aad0a6
parent 47
34ac9f92bc1e
child 49
3957ff8e86c6
permissions
-rw-r--r--

memsave and implemented minimum slot speed for virtual cars

#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <stdlib.h>
#include <stdint.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#include "driver/rs232.h"
#include "driver/adc.h"

#include "main.h"
#include "lowlevel.h"

char ok[]               PROGMEM="OK\n";
char busy[]             PROGMEM="BUSY\n";
char prepare[]          PROGMEM="!RACE PREPARE\n";
char countdownstart[]   PROGMEM="!COUNTDOWN\n";
char racestart[]        PROGMEM="!RACE START\n";

typedef union {
    uint32_t value;
    uint16_t word[2]; // high, low word
    uint8_t  byte[4]; // all four bytes
} u32;

#define MAX_SLOTS       6
typedef struct {
    uint8_t speedlimit; // 4bits speedlimit
    uint8_t speedminimum; // 4bits speedminimum
    uint8_t fuel;
    uint16_t jumpstart_time, laps;
    u32 lap_time_start, lap_time;
    uint8_t trackswitch; // 1bit bool
} cardata;

static unsigned char s[8];
static uint8_t  countdown, countdown_loops;
uint8_t  mode = 0;
// valid race modes:
// 0: free drive / idle
// 1: waiting for countdown start
// 2: race countdown initiated
// 3: Race start condition

volatile u32 sysclk;
volatile uint8_t sysclk_packettimer = 0;
volatile cardata slot[MAX_SLOTS];

volatile uint8_t car0, car1;
volatile uint16_t car0_new, car0_old;
volatile uint16_t car1_new, car1_old;
uint8_t car0_state, car1_state;

volatile uint8_t program_count = 0;
volatile uint8_t program_id;
volatile uint8_t program_command;
volatile uint8_t program_parameter;

volatile uint8_t datalen = 0;
char data[10]; // 8 bytes data buffer + string termination

static char buffer[RS232_BUFSIZE+1];
static uint8_t buffer_len;

volatile uint16_t transmit_buffer;
volatile uint16_t transmit_buffer_queue;
volatile uint8_t  transmit_len;
volatile uint8_t  transmit_len_next;
volatile uint8_t  transmit_len_queue;

volatile uint16_t response;
volatile uint8_t response_len;
volatile uint8_t timer0_delay;


int insert_queue(uint16_t tmp, uint8_t len) {
    if (transmit_buffer_queue == 0) {
        transmit_buffer_queue = tmp;
        transmit_len_queue = len;
        return 1;
    }
    return 0;
}

ISR ( USART_RXC_vect ) {
    uint8_t tmp;
    char c = UDR;

    // check for buffer overflow
    if (buffer_len==sizeof(buffer)) {
        buffer_len=0;
    }  else {
        // collect characters until end of line
        if ( (c=='\n') ) {
            buffer[buffer_len]=0;

            // packet end received, parse the received packet
            switch (buffer[0]) {
                case 'P': // inject a program data word to the rails
                    if (program_count == 0) {
                        program_id = buffer[3]-'0';
                        program_command = buffer[1]-'0';
                        if (program_command > 9)
                            program_command = buffer[1]-'A'+10;
                        program_parameter = buffer[2]-'0';
                        if (program_parameter > 9)
                            program_parameter = buffer[2]-'A'+10;
                        if (program_command < 4)
                            program_count = 0x02; // send commands twice (fuel, speed, brake)
                            else
                            program_count = 0x01;
                        RS232_puts_p(ok);
                    } else RS232_puts_p(busy);
                    break;

                case 'L': // Limit maximum speed for a car
                    tmp = buffer[2]-'0';
                    if (tmp > 9)
                        tmp = buffer[2]-'A'+10;
                    slot[buffer[1]-'0'].speedlimit = tmp;
                    RS232_puts_p(ok);
                    break;

                case 'S': // set minimum speed for a car
                    tmp = buffer[2]-'0';
                    if (tmp > 9)
                        tmp = buffer[2]-'A'+10;
                    slot[buffer[1]-'0'].speedminimum = tmp;
                    RS232_puts_p(ok);
                    break;

                case 'I': // get Information data (incl. important global parameter dump)
                    RS232_puts(VERSION);
                    RS232_putc(':');
                    for (tmp=0;tmp<MAX_SLOTS;tmp++) RS232_putc(slot[tmp].speedlimit); // output speed limits
                    RS232_putc(':');
                    for (tmp=0;tmp<MAX_SLOTS;tmp++) RS232_putc(slot[tmp].speedminimum); // output minimum speed
                    RS232_putc(':');
                    for (tmp=0;tmp<MAX_SLOTS;tmp++) {
                        itoa(slot[tmp].fuel, s, 16);
                        RS232_putc(s); // output fuel levels (0=empty, 100=full, 0xff=no fuel option)
                        RS232_putc(',');
                    }
                    RS232_putc(':');
                    for (tmp=0;tmp<MAX_SLOTS;tmp++) {
                        itoa(slot[tmp].jumpstart_time, s, 16);
                        RS232_puts(s); // output jumpstart times
                        RS232_putc(',');
                    }
                    RS232_putc(':');
                    RS232_putc('\n');
                    break;

            }

            // wait for the next packet
            buffer_len=0;
        } else {
            buffer[buffer_len++]=c;
        }
    }
}


void jumpstart(uint8_t controller) {
    if (slot[controller].jumpstart_time == 0) {
        slot[controller].jumpstart_time = sysclk.word[0]; // low word
        RS232_putc('~');
        RS232_putc('0'-controller);
        RS232_putc(':');
        itoa(slot[controller].jumpstart_time, s, 16);
        RS232_puts(s);
        RS232_putc('\n');
    }

}


int do_controller(uint8_t controller) {
    // read controller X speed & encode controller data packet
    uint16_t tmp = 0;
    if ( (PIN(SW_PACECAR_PORT) & _BV(SW_PACECAR)) == 0 ) {
        // map controller 1+2 to 5+6
    }

    switch (controller) {
        case 0:
            if (mode!=1) tmp = ((getADC(CONTROLLER1_SPEED) / CONTROLLER_DIVISOR) & 0x0F);
            if (tmp < slot[controller].speedminimum) tmp = slot[controller].speedminimum;
            if ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > slot[controller].speedlimit) tmp = slot[controller].speedlimit;
            tmp = tmp << 1;
            if ( (PIN(CONTROLLER_PORT) & _BV(CONTROLLER1_SW)) != 0) {
                tmp |= (1<<5);
                if (mode == 0) LED(1,0);
            } else if (mode == 0) LED(1,1);
            break;
        case 1:
            if (mode!=1) tmp = ((getADC(CONTROLLER2_SPEED) / CONTROLLER_DIVISOR) & 0x0F);
            if (tmp < slot[controller].speedminimum) tmp = slot[controller].speedminimum;
            if ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > slot[controller].speedlimit) tmp = slot[controller].speedlimit;
            tmp = tmp << 1;
            if ( (PIN(CONTROLLER_PORT) & _BV(CONTROLLER2_SW)) != 0) {
                tmp |= (1<<5);
                if (mode == 0) LED(2,0);
            } else if (mode == 0) LED(2,1);
            break;
        case 2:
            if (mode!=1) tmp = ((getADC(CONTROLLER3_SPEED) / CONTROLLER_DIVISOR) & 0x0F);
            if (tmp < slot[controller].speedminimum) tmp = slot[controller].speedminimum;
            if ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > slot[controller].speedlimit) tmp = slot[controller].speedlimit;
            tmp = tmp << 1;
            if ( (PIN(CONTROLLER_PORT) & _BV(CONTROLLER3_SW)) != 0) {
                tmp |= (1<<5);
                if (mode == 0) LED(4,0);
            } else if (mode == 0) LED(4,1);
            break;
        case 3:
            if (mode!=1) tmp = ((getADC(CONTROLLER4_SPEED) / CONTROLLER_DIVISOR) & 0x0F);
            if (tmp < slot[controller].speedminimum) tmp = slot[controller].speedminimum;
            if ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > slot[controller].speedlimit) tmp = slot[controller].speedlimit;
            tmp = tmp << 1;
            if ( (PIN(CONTROLLER_PORT) & _BV(CONTROLLER4_SW)) != 0) {
                tmp |= (1<<5);
                if (mode == 0) LED(5,0);
            } else if (mode == 0) LED(5,1);
            break;
        case 4: // virtual car #1
            if ((mode == 0) && (tmp < slot[controller].speedminimum)) tmp = slot[controller].speedminimum;
            if (tmp > slot[controller].speedlimit) tmp = slot[controller].speedlimit;
            tmp = tmp << 1;
            if (slot[controller].trackswitch) tmp |= (1<<5);
            break;
        case 5: // virtual car #2
            if ((mode == 0) && (tmp < slot[controller].speedminimum)) tmp = slot[controller].speedminimum;
            if (tmp > slot[controller].speedlimit) tmp = slot[controller].speedlimit;
            tmp = tmp << 1;
            if (slot[controller].trackswitch) tmp |= (1<<5);
            break;

    }
    tmp |=  (0b1000000000 | (controller << 6));
    if ( (PIN(SW_FUEL_PORT) & _BV(SW_FUEL)) != 0) tmp |= 1; // benzinstand aktiv - tankmodusschalter
    return insert_queue(tmp, 9);
}

uint8_t mirror( uint8_t n ) {
    n = ((n >> 1) & 0x55) | ((n << 1) & 0xaa);
    n = ((n >> 2) & 0x33) | ((n << 2) & 0xcc);
    n = ((n >> 4) & 0x0f) | ((n << 4) & 0xf0);
    return n;
}

int do_program(uint8_t controller, uint8_t command, uint8_t parameter) {
    // send program data packet
    uint16_t tmp;
    parameter = mirror(parameter);
    controller = mirror(controller);
    command = mirror(command);
    tmp = 0b1000000000000 | (parameter << 4) | command | (controller >> 5);
    return insert_queue(tmp, 12);
}

int do_active(void) {
    // send controller active data packet
    uint16_t tmp = 0b10000000;
    if ((slot[0].speedminimum != 0) || ((getADC(CONTROLLER1_SPEED) / CONTROLLER_DIVISOR) > 0)) tmp |= 0b11000001;
    if ((slot[1].speedminimum != 0) || ((getADC(CONTROLLER2_SPEED) / CONTROLLER_DIVISOR) > 0)) tmp |= 0b10100001;
    if ((slot[2].speedminimum != 0) || ((getADC(CONTROLLER3_SPEED) / CONTROLLER_DIVISOR) > 0)) tmp |= 0b10010001;
    if ((slot[3].speedminimum != 0) || ((getADC(CONTROLLER4_SPEED) / CONTROLLER_DIVISOR) > 0)) tmp |= 0b10001001;
    if (slot[4].speedminimum != 0) tmp |= 0b10000101;
    if (slot[5].speedminimum != 0) tmp |= 0b10000011;
    // todo: wenn Daten enpfangen wurden hier eine Quittierung senden anstatt dem Active Word

    return insert_queue(tmp, 7);
}

int do_pace_ghost(void) {
    // send ghost and pacecar data packet
    // todo: at the moment, both disabled!
    uint16_t tmp = 0b1111100000;
    if ( (PIN(SW_FUEL_PORT) & _BV(SW_FUEL)) != 0) tmp |= 1; // benzinstand aktiv - tankmodusschalter
    // todo: PC, NH, TK, (KFR, FR)

    return insert_queue(tmp, 9);
}

ISR ( TIMER1_COMPA_vect ) {
    //PORTC ^= _BV(PC0); // DEBUG OUTPUT SYSTEM CLOCK

    // trigger packet transfer:
    if (sysclk_packettimer == 14) { // 15*500 = 7500 NS
        transmit_len = transmit_len_next;
        sysclk_packettimer = 0;
    } else sysclk_packettimer++;
    // here is some more time to do something else...

    // reset both car counters to overflow
    car0_old = TIMER1_500NS;
    car1_old = TIMER1_500NS;

    sysclk.value++; // increment 500ns timer
}

ISR ( TIMER2_COMP_vect ) {
    //OCR2 = TIMER2_50US; // make sure that timer2 is 50µs !!!
    // data packet timer 100µs pro bit...
    if (transmit_len >= 0xFE) {
        if (transmit_len != 0xFF) {
            RAIL_POWER_PORT |= _BV(RAIL_POWER); // end of transmission
            transmit_len = 0xFF;
            transmit_buffer = transmit_buffer_queue;
            transmit_buffer_queue = 0;
            transmit_len_next = transmit_len_queue;

            // start the response receiver timer
            // TODO: only on 8 timeslots, not on every transmission
            // TODO: give slot number to timer - then store the transmission to 8 slots array
            TCNT0 = TIMER0_250US;
            timer0_delay = TIMER0_2300NS;
            response = 0;
            response_len = 0;
            TIMSK |= _BV(TOIE0);

        }
    } else {
        uint16_t bit = (1<<(transmit_len & 0b01111111));
        uint16_t clock;
        if ((transmit_len & 0b10000000) == 0) clock = 0; else clock = 0xffff;
        if ( ((transmit_buffer ^ clock) & bit) != 0 )
            RAIL_POWER_PORT |= _BV(RAIL_POWER); else
            RAIL_POWER_PORT &= ~_BV(RAIL_POWER);
        if ( (transmit_len & 0b10000000) == 0 ) {
            // block 0
            //if (transmit_len == 0) transmit_len = 0xFF; else transmit_len |= 0b10000000; // set clock
            transmit_len |= 0b10000000; // set clock
        } else {
            // block 1, output the current bit
            transmit_len &= 0b01111111; // reset clock
            //if (transmit_len != 0) transmit_len--; // next bit
            if (transmit_len == 0) transmit_len = 0xFE; else transmit_len--; // next bit
        }
    }
}


ISR ( TIMER0_OVF_vect ) {
// TODO: last bit should be set by the sender, not from us!
    TCNT0 = TIMER0_250US;
    if (timer0_delay == 0) {
        RAIL_POWER_PORT &= ~_BV(RAIL_POWER); // pull rails low
        _delay_us(28); // wait some cycles
        if ((PIN(RAIL_DETECT_PORT) & _BV(RAIL_DETECT)) != 0) { // check for logic zero
            if (response == 0) {
                // there is no start bit, so stop the timer and cancel response receiving
                TIMSK &= ~_BV(TOIE0);
            } else {
                // we received a bit (logic low)
                response = response << 1;
                response_len++;
            }
        } else {
            // okay, we have logic high
            response = response << 1;
            response |= 1;
            response_len++;
        }
        if (response_len == 15) { // maximum response length reached
            RAIL_POWER_PORT |= _BV(RAIL_POWER); // restore rails power
            TIMSK &= ~_BV(TOIE0);
        } else {
            _delay_us(20); // wait some cycles
            RAIL_POWER_PORT |= _BV(RAIL_POWER); // restore rails power
        }
    } else timer0_delay--; // 2.3 ms delay not reached yet
}

ISR (INT0_vect) {
    // car0 detector
    uint16_t tmp = 0;
    car0_new = TCNT1; // get current counter
    if (car0_old < car0_new) {
        // calculate difference
        if (car0 == 0) tmp = car0_new-car0_old;
        if ( (tmp > 54) && (tmp < 74) ) car0 = 1;
        if ( (tmp > 118) && (tmp < 138) ) car0 = 2;
        if ( (tmp > 186) && (tmp < 206) ) car0 = 3;
        if ( (tmp > 246) && (tmp < 266) ) car0 = 4;
    }
    car0_old = car0_new;
}

ISR (INT1_vect) {
    // car1 detector
    uint16_t tmp = 0;
    car1_new = TCNT1; // get current counter
    if (car1_old < car1_new) {
        // calculate difference
        if (car1 == 0) tmp = car1_new-car1_old;
        if ( (tmp > 54) && (tmp < 74) ) car1 = 1;
        if ( (tmp > 118) && (tmp < 138) ) car1 = 2;
        if ( (tmp > 186) && (tmp < 206) ) car1 = 3;
        if ( (tmp > 246) && (tmp < 266) ) car1 = 4;
    }
    car1_old = car1_new;
}


ISR (INT2_vect) {
    // Lap counter Interrupt
    // do not know if this ever occurs ?! this is normally an output pin to trigger the counter start
}

void reset_vars(void) {
    uint8_t i;
    for (i=0; i<MAX_SLOTS; i++) {
        slot[i].speedlimit = 15;
        slot[i].speedminimum = 0;
        slot[i].trackswitch = 0;
        slot[i].fuel = 100;
        slot[i].jumpstart_time = 0;
        slot[i].laps = 0;
    }
    sysclk.value = 0;
}

void countdown_progress(void) {
    // decrement COUNTDOWN_LOOPS
    if (countdown_loops>0) {
        countdown_loops--;
    } else {
        countdown--;
        countdown_loops = COUNTDOWN_DELAY;
    }
    switch (countdown) {
        case 5: LED(1, 1); break;
        case 4: LED(2, 1); break;
        case 3: LED(3, 1); break;
        case 2: LED(4, 1); break;
        case 1: LED(5, 1); break;
        case 0: { // RACE START!
            sysclk.value = 0;
            LEDS_OFF();
            LED(3, 1);
            mode = 3;
            } break;
    }
}

void check_cars(void) {
    u32 clk, diff;
    clk.value = sysclk.value; // freeze system clock time

    if (car0 != car0_state) {
        car0_state = car0;
        if (car0_state != 0) {
            diff.value = clk.value - slot[car0-1].lap_time_start.value;
            if ( diff.value > 3000 ) { // minimum 1.5 second for 1 lap!
                if (slot[car0-1].lap_time_start.value != 0) {
                    slot[car0-1].lap_time.value = diff.value;
                    slot[car0-1].laps++;
                    RS232_putc('L');
                    RS232_putc(':');
                    RS232_putc('A');
                    RS232_putc(':');
                    itoa(slot[car0-1].laps, s, 16);
                    RS232_puts(s);
                    RS232_putc(':');
                    RS232_putc('0'+car0_state);
                    RS232_putc(':');
                    ultoa(diff.value, s, 16);
                    RS232_puts(s);
                    RS232_putc('\n');
                }
                slot[car0-1].lap_time_start.value = clk.value;
            }
        }
    } car0 = 0;

    if (car1 != car1_state) {
        car1_state = car1;
        if (car1_state != 0) {
            diff.value = clk.value - slot[car1-1].lap_time_start.value;
            if ( diff.value > 3000 ) { // minimum 1.5 second for 1 lap!
                if (slot[car1-1].lap_time_start.value != 0) {
                    slot[car1-1].lap_time.value = diff.value;
                    slot[car1-1].laps++;
                    RS232_putc('L');
                    RS232_putc(':');
                    RS232_putc('B');
                    RS232_putc(':');
                    itoa(slot[car1-1].laps, s, 16);
                    RS232_puts(s);
                    RS232_putc(':');
                    RS232_putc('0'+car1_state);
                    RS232_putc(':');
                    ultoa(diff.value, s, 16);
                    RS232_puts(s);
                    RS232_putc('\n');
                }
                slot[car1-1].lap_time_start.value = clk.value;
            }
        }
    } car1 = 0;
}


int main(void)
{

    uint8_t  packet_index = 1;

    uint8_t  btn_start = _BV(SW_START);
    uint8_t  old_start = btn_start;


    init_hardware();
    reset_vars();
    LED(3, 1); // enable middle led == idle mode

    // switch on rails power
    RAIL_POWER_PORT |= _BV(RAIL_POWER);

    while (1) {
        // check for short circuit on the rails
        check_rails_shortcut();
        check_cars();

        if (response_len > 0) {
            itoa(response, s, 2);
            response_len = 0;
            RS232_puts("ANSWER RX: ");
            RS232_puts(s);
            RS232_putc('\n');
        }


        // read in button presses
        btn_start = (PIN(SW_START_PORT) & _BV(SW_START));
        if (old_start != btn_start) {
            // start button changed
            if (btn_start == 0) {
                // start button press active
                if (mode == 0) {
                    mode = 1; // set wait for race start mode
                    reset_vars();
                    LED(1, 1);
                    LED(2, 1);
                    LED(3, 1);
                    LED(4, 1);
                    LED(5, 1);
                    RS232_puts_p(prepare);
                } else if (mode == 1) {
                    // Initiate race countdown
                    sysclk.value = 0;
                    countdown = 5;
                    countdown_loops = COUNTDOWN_DELAY;
                    mode = 2;
                    LED(1, 0);
                    LED(2, 0);
                    LED(3, 0);
                    LED(4, 0);
                    LED(5, 0);
                    RS232_puts_p(countdownstart);
                }
            }
            old_start = btn_start;
        }

        if (mode==3) {
            // RACE START!
            // issue reset command to lap counter
            mode = 0;
            RS232_puts_p(racestart);
            program_command = 6;
            program_parameter = 9;
            program_id = 0;
            program_count = 1;
            LAP_COUNTER_PORT |= _BV(LAP_COUNTER);
            // TODO: beep long
            _delay_us(50);
            LAP_COUNTER_PORT &= ~_BV(LAP_COUNTER);
        }


        switch (packet_index) {
            case 1:
                if (program_count > 0) {
                    // command in queue
                    if (do_program(program_id, program_command, program_parameter)) {
                        packet_index++;
                        program_count--;
                    }
                } else {
                    // output idle command
                    if (do_program(7, 19, 0)) packet_index++; // reset
                    //if (do_program(7, 20, 15)) packet_index++; // reset / pitstop detect
                    //if (insert_queue(0, 0)) packet_index++; // null command
                }

                if ( (packet_index>1) && (mode == 2) ) countdown_progress();

                break;
            case 2:
                if (do_pace_ghost()) packet_index++;
                break;
            case 3:
                if (do_active()) packet_index++;
                break;
            case 4:
                if (do_controller(0)) packet_index++;
                break;
            case 5:
                if (do_controller(4)) packet_index++;
                break;
            case 6:
                if (do_controller(1)) packet_index++;
                break;
            case 7:
                if (do_controller(5)) packet_index++;
                break;
            case 8:
                if (do_controller(2)) packet_index++;
                break;
            case 9:
                if (do_active()) packet_index++;
                break;
            case 10:
                if (do_controller(3)) packet_index = 1;
                // last packet, so reset packet index
                break;
        }


    } // main loop end
};

mercurial