blackbox/main.c

Mon, 05 Dec 2011 17:14:32 +0100

author
Malte Bayer <mbayer@neo-soft.org>
date
Mon, 05 Dec 2011 17:14:32 +0100
changeset 38
ff76255904c4
parent 37
136a79772098
child 41
9857c18c5e64
permissions
-rw-r--r--

implemented response reader to carrerashark

#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";


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


#define MAX_SLOTS       6
volatile uint8_t speedlimit[MAX_SLOTS];
volatile uint8_t fuel[MAX_SLOTS];
volatile uint8_t jumpstart_time[MAX_SLOTS];



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;
                    speedlimit[buffer[1]-'0'] = 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(speedlimit[tmp]); // output speed limits
                    RS232_putc(':');
                    for (tmp=0;tmp<MAX_SLOTS;tmp++) RS232_putc(fuel[tmp]); // output fuel levels (0=empty, 100=full, 0xff=no fuel option)
                    RS232_putc(':');
                    for (tmp=0;tmp<MAX_SLOTS;tmp++) RS232_putc(jumpstart_time[tmp]); // output jumpstart times
                    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 (jumpstart_time[controller] == 0) {
        jumpstart_time[controller] = 1;
        // todo: later the timestamp should written instead of just "1"
        RS232_putc('~');
        RS232_putc('0'-controller);
        RS232_putc('\n');
    }

}


int do_controller(uint8_t controller) {
    // read controller X speed & encode controller data packet
    uint16_t tmp = 0;
    switch (controller) {
        case 0:
            if (mode!=1) tmp = ((getADC(CONTROLLER1_SPEED) / CONTROLLER_DIVISOR) & 0x0F);
            if ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > speedlimit[controller]) tmp = speedlimit[controller];
            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 ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > speedlimit[controller]) tmp = speedlimit[controller];
            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 ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > speedlimit[controller]) tmp = speedlimit[controller];
            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 ((mode == 2) && (tmp != 0)) { jumpstart(controller); tmp = 0; }
            if (tmp > speedlimit[controller]) tmp = speedlimit[controller];
            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: tmp = (1<<5); break; // todo regler 5
        case 5: tmp = (1<<5); break; // todo regler 6
    }
    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 ((getADC(CONTROLLER1_SPEED) / CONTROLLER_DIVISOR) > 0) tmp |= 0b11000001;
    if ((getADC(CONTROLLER2_SPEED) / CONTROLLER_DIVISOR) > 0) tmp |= 0b10100001;
    if ((getADC(CONTROLLER3_SPEED) / CONTROLLER_DIVISOR) > 0) tmp |= 0b10010001;
    if ((getADC(CONTROLLER4_SPEED) / CONTROLLER_DIVISOR) > 0) tmp |= 0b10001001;
    // todo: regler 5 und 6
    // 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 ) {
    // trigger packet transfer:
    transmit_len = transmit_len_next;
    // here is some more time to do something else...
}

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 (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++) {
        speedlimit[i] = 15;
        fuel[i] = 100;
        jumpstart_time[i] = 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!
            LEDS_OFF();
            LED(3, 1);
            mode = 3;
            } break;
    }
}


int main(void)
{

    unsigned char s[10];
    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();

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