Mbed based laser tag system using IR LEDs and receivers

Dependencies:   mbed fwdcrc16library PinDetect TextLCD

This project was the result of an embedded systems design course held in 2013.

main.cpp

Committer:
Mike951
Date:
2019-08-15
Revision:
5:6e6ab7e5074e
Parent:
4:7294c20b1025

File content as of revision 5:6e6ab7e5074e:

//////////////////////////////////////////
//Embedded Systems Design Final Project //
//Robert Michael Swan                   //
//March 21, 2013                        //
//                                      //
//Compiled Code Size: 17.7KB            //
//////////////////////////////////////////
//Libraries Used
#include "mbed.h"
#include "TextLCD.h"
#include "PinDetect.h"
#include "crc16.h"
//Peripherals 
PinDetect  sw2(P0_11); //switch2 debounced input
PinDetect  sw3(P0_10); //switch3 debounced input
TextLCD lcd(P1_15, P1_16, P1_19, P1_20, P1_21, P1_22); // rs, e, d4-d7 led pins:4,6,11-14
DigitalOut LB(P1_31); //lcd backlight
DigitalOut LP(P1_29); //lcd power
DigitalOut led1(P0_22); //on-board led ds1
DigitalOut led2(P0_23); //on-board led ds2
Serial IR(P1_27,P1_26); //IR transmitter and receiver output and input control P1_27 controls IR diode, P1_26 controls IR reciever
PwmOut Square_Gen(P1_25);       //square wave generator for IR led
//Timers
Timeout stats;   //used for displaying text for limited periods of time
Timeout shot_time; //used to ensure shots take place in a brief period of time
Timeout flash;  //used to flash leds and backlight when shooting or tagging
Timer timer;    //used as seconds for reset timer       
Ticker clock_tick;    //used with timer for minutes of reset timer
//Required global variables
unsigned mins = 0;   //global variable for minutes in game, since ticker interrupt cannot attach functions that have parameters
bool btn2 = false;  //global variable for sw2, since the PinDetect interrupt cannot attach functions that have parameters
bool btn3 = false;  //global variable for sw3, since the PinDetect interrupt cannot attach functions that have parameters
bool done = false;   //global variable for timeout, since TimeOut interrupt cannot attach functions that have parameters
bool taggable = false;  //global variable for whether serial received input will be used or not
volatile bool tagged = false;     //global variable for whether tag is complete
char id_rx[15];         //global variable for received ID. Required to use serial interrupt correctly
unsigned id_pos = 0;    //global variable for recieved ID array location. Also required for serial interrupt.

//Functions
/*Minute Interrupt
    Intended to be called every minute to increment the minute counter.
    Also resets the seconds timer as it reaches 60 seconds (assuming it is called at timer.start();)
*/
void minutes()
{
    mins ++;
    timer.reset();
}

/* Flashing interrupt
    Intended to be used with a timeout object to flash lights when shot or shooting
*/
void flasher()
{
    LB = 0; 
    led1 = !led1;
    led2 = !led2;
}

/*Shot Check/Reset Interrupt
    Intended to be called after a short period of time to start array over from the start
    Should keep data from being received over an excessive amount of time and thus is one
    method of ensuring player is actually tagged
*/
void shot_check()
{
    id_pos = 0;     //resets current id
}

/*Serial Callback interrupt
    Receives IR information using IR receiver
    If taggable = false, received data is thrown away.
    Otherwise, up to 15 characters are collected in a time period of 0.5 seconds
    If less than 15 characters received in 0.5 seconds, function array returns to first bit
*/
void callback() 
{
    // Note: you need to actually read from the serial buffer to clear the RX interrupt
    if  (!taggable)
    { 
        IR.getc(); //ensures no extra buffering left over when player is not taggable
        return; 
    }
    
    id_rx[id_pos] = IR.getc();  //place received character into array
    /*if (id_pos == 0)    //starts a timer for the time in which receiver must receive a full id
    {
        shot_time.attach(&shot_check, 0.5);
    }*/
    if(id_pos >= 14)   
    {
        id_pos = 0; //ensures no array overflows
        shot_time.detach(); //ensures no further action upon array until next time tagged
        tagged = true;  //changes tagged flag such that the main code will trigger after an ID is fully received
        return;
    }
    id_pos ++;  //increment to next array position for id
    return;    
}

/*Button 2 Interrupt
    Sets button variable to true for use in code when button is pressed
*/
void btn2Pressed()
{
    led1 = !led1;
    btn2 = true;
}

/*Button 3 Interrupt
    Sets button variable to true for use in code when button is pressed
*/
void btn3Pressed()
{
    led2 = !led2;
    btn3 = true;
    
}

/*Statistics Display Timeout
    Sets display "done" flag to true
    Intended to be used with timeout interrupt and statistics function to jump out of function
    if it has been 5 seconds.
*/
void finish_disp ()
{
    done = true;
    return;
}

/*Error Routine.
    Displays "ERROR" on LCD screen and flashes leds
    for an indefinite period of time.
*/
void error_custom ()
{
    lcd.cls();
    lcd.printf("ERROR");
    led1 = 0;
    led2 = 1;
    while(1)
    {
      led1 = !led1;
      led2 = !led2;
      wait(0.5);
    } 
}
/*Alphanumeric selector
    Used with char_sel function in order to select a number 0-9 or letters A-Z through user input.
    Returns a char.
*/
char alpha_num_sel (bool number)
{
    char n0 = '0';
    char n_tmp = '0';
    unsigned incr = 0;   //used to increment characters between 0-9 or A-Z 
    unsigned mod;
    if (number)
    {
        lcd.cls();
        lcd.printf("Pick\nNumber:%c",n_tmp);
        n0 = '0';
        n_tmp = n0;
        mod = 11;   //modulus for character range
    }
    else
    {
        lcd.cls();
        n0 = 'A';
        n_tmp = n0;
        lcd.printf("Pick\nLetter:%c",n_tmp);
        mod = 26;
    }
    while(1)
    {
        while(!btn2 && !btn3)   //waits until button input received
        {
            wait(0.05);
        }
        if(btn3)
        {
            btn3 = false;
            btn2 = false;       //if both butons pressed, it just selects the current character
            return (n_tmp);
        }
        else if(btn2)
        {
            btn2 = false;
            incr ++;
            incr = incr % mod; //ensures that numbers stay within the 0-9/A-Z range
            n_tmp = n0 + incr;    //increment current character selected
            if (incr == 10 && number) //feature addition to allow for space entry
            {
                n_tmp = ' ';
            }
            lcd.cls();
            if(number)
            {
                lcd.printf("Pick\nNumber:%c",n_tmp);    //displays current number
            }
            else
            {
                lcd.printf("Pick\nLetter:%c",n_tmp);     //displays current letter
            }
        }
        else
        {
            error_custom();
        }
    
    }
}

/*Character Selection
    Used with setup() function in order to pick a character A-Z and number 0-9
    for use as part of user id and/or team. Passed a 0 if selecting team letter (A-Z),
    otherwise will select player id letter
*/
char char_sel (bool player)
{
    if (player)
    {
        lcd.cls();
        lcd.printf("A-Z? or\n0-9?");
        while(!btn2 && !btn3)   //waits until button input received
        {
            wait(0.05);
        }
        if(btn2)    //if select button pressed, goes through numerical selection
        {
            btn2 = false;
            btn3 = false;   //if both buttons pressed at the same time, it just does numerical selection
            
            return(alpha_num_sel(1));
        }
        else if(btn3)
        {
            btn3 = false;   //pressing the option button in this case is the same as selecting a team
        }
        else
        {
            error_custom(); //if you somehow make it here without pressing buttons, it will throw an error
        }    
    }
return(alpha_num_sel(0)); //returns capital alpha character for id or team by calling selection function
}
        
    
/*Setup Routine.
    Sets the user id for use in-game.
    Function must be passed an array of characters of size 15
    in order to work correctly.
*/
void setup (char* ID_array)
{
    unsigned size = 1;
    
    lcd.cls();
    lcd.printf("ID Size?\n%u",size);
    
    while(1)
    {
        while(!btn2 && !btn3)   //waits until button input received
        {
            wait(0.05);     //ensures buttons cannot be pressed extremely fast
        }
        if(btn2 && btn3)    //routine to set size of player id in bytes
        {
            btn2 = false;   //handling for simultaneous button pressing
            btn3 = false;   
            size++;
            if (size > 12) 
            {
                size = 1;    //ensures size cannot be greater than 12 or less than 1
            }
            break;          //sets size to whatever current size is +1 and moves on
        }
        else if(btn2)   //increases size or loops back to 1, displays current size
        {
            btn2 = false;
            size++;
            if (size > 12) 
            {
                size = 1;   //ensures size cannot be greater than 12 or less than 1
            }
            lcd.cls();
            lcd.printf("ID Size?\n%u",size);    //displays current size
        }
        else if(btn3)
        {
            btn3 = false;   //selects current size
            break;
        }
        else
        {
            error_custom();    //error called if unexpected output
        }
    }
        
    for (unsigned j = 0; j < size; j ++)    //sets player id
    {
        ID_array[j] = char_sel(1);
    }
    for (unsigned j = size; j < 12; j ++)
    {
        ID_array[j] = ' ';      //fill unused characters with spaces
    }
    
    lcd.cls();
    lcd.printf("On team?\n Y or N");
    
    while(!btn2 && !btn3)   //waits until button input received
    {
        wait(0.05);     //ensures buttons cannot be pressed extremely fast
    }
    if(btn3)
    {
        btn3 = false;
        btn2 = false;   //you're on a team if you press both buttons
        ID_array[12] = char_sel(0);   //sets team letter
    }
    else if (btn2)
    {
        btn2 = false;
        ID_array[12] = '0';    //sets team to 'zero', indicating no team
    }
    else
    {
        error_custom(); //throws an error if you somehow get here)
    }
    
    lcd.cls();
    lcd.printf("Your ID\n is:");
    wait(1);
    lcd.cls();
    for(unsigned j = 0; j <= 12; j++)  //prints out player id and team
    {
        lcd.printf("%c",ID_array[j]);
        if (j == 7)
        {
            lcd.printf("\n"); //puts in a new line after eight characters
        }
    }
    crc16_attach(ID_array,13);   //attaches crc bits to last two bits of 15 character array
    wait(3);
    return;
}

/*ID Checking Routine
   Checks received IDs to see if it is a valid shot
   player_array is the 15 character id array of the player that constains a 16-bit crc in the last two bytes
   Returns a boolean true if shot is valid, otherwise false
*/
bool check_id(char* player_array)
{
    if (id_rx[12] == player_array[12] && id_rx[12] >= 'A')
    {
        return false;   //if ids are on the same team or are the same person, they cannot be hit by each other/themselves
    }
    else if(id_rx[13] == player_array[13] && id_rx[14] == player_array[14])
    {
        return false;
    }
    return (crc16_match(id_rx, 13));  //compares the crc tag in received id to a calculated one. Returns false if different.
}

/*Respawn Display
    Displays appropriate information for who player was hit by while they are "respawning"
    Returns nothing
*/
void respawn()
{
    lcd.cls();
    lcd.printf("Hit by:");
    wait(1);
    lcd.cls();
    for(unsigned j = 0; j <= 12; j++)  //prints out id and team of person who tagged player
    {
        lcd.printf("%c",id_rx[j]);
        if (j == 7)
        {
            lcd.printf("\n"); //puts in a new line after eight characters
        }
    }
    wait(4);
}
/*Firing function
  Sends ID information through IR
  Parameters: player_id
  player_id = 15 character array that contains player id and crc hash
  returns nothing 
*/
void fire(char* player_id)
{
    for (unsigned i = 0; i < 15; i ++)
    {
        IR.putc(player_id[i]);
    }
}   

/*Statistics Display Function
    Displays game statistics without interrupting gameplay significantly
    Requires LCD to be on.
    Function will end if fire button is pressed or 5 seconds have passed
    without the statistics button (btn2) being pressed again.
    Maximum amount of time possible in this function is 15 seconds,
    5 seconds for each of the 3 statistics displays
    Parameters: tag_count = number of times player has been tagged
                shots = number of times player has shot
                mode = level of statistics looking at: tag count(0), shots(1), or time since reset(2) 
    Returns nothing
*/
void statistics(const unsigned &tag_count, const unsigned &shots, short mode)
{
    done = false;   //Used to timeout on statistics display
    lcd.cls();
    if(mode == 0)
    {
        lcd.printf("Tagged:\n%u",tag_count);
    }
    else if(mode == 1)
    {
        lcd.printf("# Shots:\n%u",shots);   //shot count
    }
    else if(mode == 2)
    {
        unsigned secs = 0;  //used to readout numer of seconds on timer
        secs = timer.read();
        lcd.printf("Mins:%u\nSecs:%u", mins,secs);
    }
    else 
    {
    led1 = 1;
    led2 = 1;
    return; //if no valid mode, the function will end. Used to end recursion
    }
    stats.attach(&finish_disp, 5);
    while(!done)
    {
        if(btn3 == true || tagged)
        {
            stats.detach(); //exit if something interrupts stats
            lcd.cls();
            return;
        }
        else if(btn2 == true)
        {
            btn2 = false;
            stats.detach(); //keeps statistics display from timing out for another 5 seconds
            mode ++;
            statistics(tag_count,shots,mode);   //recursive function call of statistics
            return;     //jumps out of function whenever other forms return
        }
        wait(0.05);     //function will get stuck without short wait
    }
    return;
}

int main() 
{
    Square_Gen.period_us(26);       //produces a 38kHz square wave to ensure proper IR communication...
    Square_Gen.pulsewidth_us(13);   //...this signal is sent to one of the IR diode terminals through a transistor
    IR.baud(1200);          //sets baud rate of serial output and input
    IR.attach(&callback);   //attaches interrupt for serial buffer  
    LB = 1; //turn on lcd backlight
    LP = 1; //turn on lcd power
    sw2.mode(PullUp);   //set buttons to a default high voltage for logic low
    sw3.mode(PullUp);   
    sw2.setSampleFrequency(); //set buttons to default sample frequency for debouncing
    sw3.setSampleFrequency();
    sw2.setAssertValue(0);  //set buttons to be logic high when voltage is low.
    sw3.setAssertValue(0);
    sw2.attach_asserted( &btn2Pressed ); //attach button interrupts
    sw3.attach_asserted( &btn3Pressed );
    clock_tick.attach(&minutes, 60);  //ticker interrupts every 60 seconds to add 1 minute and reset timer
    timer.reset();  //ensure timer starts at 0
    timer.start();  //start reset timer
    
    led1 = 1;
    char id[15]; //id array, capable of storing user ids up to 12 bytes with 1 byte for team and 2 bytes for CRC.
    unsigned tag_count = 0;   //number of times player has been tagged in a game
    unsigned shots = 0;       //number of shots fired in a game.
    lcd.cls();      //ensure lcd starts with clear screen
    lcd.printf("Choose \nYour ID.");
    while(btn2 == false && btn3 == false)
    {
        wait(0.2);      //wait to start setup until either button is pressed
    }
    btn2 = false;       //reset button values
    btn3 = false;
    setup(id);      //setup player id and team, passing function the id array
    taggable = true;
    LB = 0; //turn off lcd screen, backlight, and leds to save power during game
    LP = 0;
    led1 = 0;
    led2 = 0;
    while(1) 
    {
        if(tagged)
        {
            flash.attach(&flasher,0.5);
            tagged = false;
            //LB = 1; //turn on backlight and leds for flash
            led1 = 1;
            led2 = 1;
            if (check_id(id))
            {
                taggable = false;    //don't allow new tags if respawning
                flash.detach(); //if tag is valid, no need to flash
                tag_count ++;
                LP = 1; //turn LCD power and backlight on 
                LB = 1;
                respawn();  //prints appropriate respawn info
                taggable = true;
                led2 = 0;
            }
            LB = 0; //turn LCD backlight and power back off
            LP = 0;
            led1 = 0; 
        }
        if(btn3)    //fires IR led if btn3 is pressed
        {
            btn3 = false;
            flash.detach(); //lights will not flash if you fire really fast
            shots ++;   //increment number of times ir has been shot by player
            LB = 1;
            led1 = 1;
            led2 = 1;
            flash.attach(flasher,0.5); //flashes backlight and leds when firing
            fire(id);   //fires player id using IR diode
        }
        if (btn2)   //displays statistics if btn2 is pressed
        {
            flash.detach();
            btn2 = false;
            LP = 1; //turn on display
            LB = 1;
            statistics(tag_count,shots,0);
            LP = 0;
            LB = 0;
        }
        wait(0.01);
    }
}