Uitleg code versie 1
Initialisatie
In dit stuk code worden de in- en uitgangen geconfigureerd, de bibliotheken geïnitialiseerd en een welkomstbericht afgespeeld door de DFPlayer-mini module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ANSEL = 0x00; //no analog function ANSELH = 0x00; //no analog function TRISA = 0x00; //PORTA als input TRISB |= 0b00001110; //RB1, RB2, RB3 input TRISB &=~0b00100000; //RB5 output TRISD = 0x10; //PORTD output (RD4 input) PORTD = 0x00; //PORTD laag LCD_Start(); //start LCD LCD_Clear(); //clear LCD IR_INIT(); //initialise IR receive and send initLED(); //initialise smartled configUART(1); //configure UART @ 9600bps __delay_ms(2000); setVOL(20); //set volume to 20 playNUM(1); //welkom message waitForStop(); //wait for playing to stop |
Setup
Het systeem kan op 2 manieren geconfigureerd worden: manueel en automatisch. Als automatisch wordt gekozen, zullen de instellingen gedownload worden van de webserver. Bij manueel moet de gebruiker deze zelf instellen. Na het instellen van het spel moet de 'fire-mode' worden ingesteld. Hierna volgt een countdown van 10 seconden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | setVOL(20); //set volume to 20 playNUM(2); //play setup select waitForStop(); //wait for playing to stop setupSelect(); //select setup mode if(!setup){ //manueel selectTeam(); //set team in output IR data selectPlayer(); //set player in output IR data } else{ //automatisch startup(); //startup message and connect to wifi } setFireMode(); //set fire mode for rifle countDown(); //10s countdown unsigned char inTeam = 0; //inkomend teamnummer unsigned char inPlayer = 0; //id van inkomende speler unsigned char inWeapon = 0; //damage van inkomende speler unsigned char ammo = myAmmo;//momentele ammo is mijn maximale ammo unsigned char life = myLife;//momentele levens is mijn maximale levens updateLED(ammo,life); //zet ammo en life op leds |
Main loop
In de main-loop wordt het indrukken van drukknoppen en binnengekomen data geïnterpreteerd. Het ontvangen van IR-data gebeurt interrupt based. Als een volledige 16bits code werd ontvangen zal IRIF hoog worden. Zo weet de main loop dat er data behandeld moet worden.
Wanneer de speler schiet of wanneer deze geraakt wordt, zullen de smartLED's aangepast worden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | while(1){ //eeuwige loop if(RB1 && life){ //schieten kan als je niet dood bent if(!semi && ammo){ //single shot IR_Send(); //ir schot beep(); //beep sound ammo--; //decrement ammo } else if(ammo >= 3 && semi){ //als ammo groter is dan 3 en semi is geselecteerd for(char c=0; c<3;c++){ //semi shot IR_Send(); //ir schot beep(); //beep sound ammo--; //decrement ammo __delay_ms(2); //short delay } } else if (ammo < 3 && semi){ //als ammo lager is dan 3 en simi is geselecteerd for(char c=0; c<ammo;c++){ //semi shot IR_Send(); //ir schot __delay_ms(2); //short delay beep(); //beep sound ammo--; //decrement ammo } } __delay_ms(50); //wachttijd tussen shots updateLED(ammo,life); //update ammo en life } if(trigger && !ammo){ //proberen schieten zonder kogels configUART(1); //9600bps setVOL(20); //set volume to 20 playNUM(15); //play no ammo error waitForStop(); //wait for play to stop configUART(0); //115200bps } if(reload){ //herladen ammo = myAmmo; //momentele ammo is max ammo beeps(); //beep soung 1s updateLED(ammo,life); //update ammo en life configUART(1); //9600bps setVOL(20); //set volume to 20 playNUM(14); //play reloading waitForStop(); //wait for play to stop configUART(0); //115200bps if(!life){ //for testing fase life = myLife; //momentele levens is max levens } } else if(mode){ //als mode log(myId,myTeam,'K',2); //test log een kill door speler 2 __delay_ms(50); //wacht 50ms } if(IRIF){ //nieuwe ontvangen data /* * indata 16bits * XPPPPTTT DDDDDDDD */ inPlayer = (inData>>11)&0x0F; //krijg id van inkomende speler inTeam = (inData>>8)&0x07; //krijg team van inkomende speler inWeapon = inData&0xFF; //krijg damage van inkomende speler /* * met gamemode wordt momenteel geen rekening meer gehouden * damage wordt geregistreerd waneer inkomend signaal niet van zelfde team komt */ if(inTeam != myTeam){ //als teams niet hetzelfde zijn if(inWeapon >= life){ //als er meer damage is dan levenspunten -> dood life = 0; //je bent dood if(setup){ //als auto setup was geselecteerd configUART(0); //baud 115200bps for esp01 log(myId,myTeam,'K',inPlayer); //log kill __delay_ms(50); configUART(1); //baud 9600bps for dfplayer } configUART(1); //baud 9600bps for dfplayer beep(); //beep sound setVOL(20); //set volume to 20 playNUM(13); //play dead waitForStop(); //wait for play to stop } else{ //damage is kleiner dan levens life -= inWeapon; //trek damage af van levens beep(); //beep sound setVOL(20); //set volume to 20 playNUM(12); //play hit waitForStop(); //wait for play to stop } updateLED(ammo,life);//update ammo en life } /* flash HIT LED*/ hit = 1; __delay_ms(50); hit = 0; __delay_ms(50); IRIF = 0; //clear 'interrupt' flag } } |
Speler wordt geraakt
Volgende code wordt uitgevoerd wanneer een speler geraakt wordt. Eerst kijkt het het programma of de binnengekomen data niet van een ander teamlid komt (gamemode's zijn nog niet geïmplementeerd). Daarna worden de levens van de speler verminderd en indien nodig wordt er een 'kill' gelogd.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | if(inTeam != myTeam){ //als teams niet hetzelfde zijn if(inWeapon >= life){ //als er meer damage is dan levenspunten -> dood life = 0; //je bent dood if(setup){ //als auto setup was geselecteerd configUART(0); //baud 115200bps for esp01 log(myId,myTeam,'K',inPlayer); //log kill __delay_ms(50); configUART(1); //baud 9600bps for dfplayer } configUART(1); //baud 9600bps for dfplayer beep(); //beep sound setVOL(20); //set volume to 20 playNUM(13); //play dead waitForStop(); //wait for play to stop } else{ //damage is kleiner dan levens life -= inWeapon; //trek damage af van levens beep(); //beep sound setVOL(20); //set volume to 20 playNUM(12); //play hit waitForStop(); //wait for play to stop } updateLED(ammo,life);//update ammo en life } /* flash HIT LED*/ hit = 1; __delay_ms(50); hit = 0; __delay_ms(50); IRIF = 0; //clear 'interrupt' flag } |
Interrupt routine
In de interrupt routine wordt de IR_Handler en de millis timer geplaatst. De IR_Handler zorgt voor het ontvangen van de IR-data. De millis timer wordt momenteel allen gebruikt in de timeOut van de waitFor functie van de ESP8266 bibliotheek.
1 2 3 4 | void interrupt isr(void){ //interrupt routine IR_Handler(); //interpreteren van IR pulsen millisTimer(); //incrementen van miller counter } |
UART config
Omdat er twee modules gebruik maken van het RS232 protocol (TTL) en dus de UART van de µC moet er gewisseld kunnen worden tussen de modules. De ESP-module werkt met een baud rate van 115200bps en de audio-module met een baud rate van 9600bps. De Tx lijn wordt gewisseld met een multiplexer. De select lijn is verbonden met RD2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void configUART(char speed){ if(speed == 0){ /* UART configs */ // Baud rate 38400 w/ xtal 19660800: ((19660800/115200)/4)-1 = 42 SPBRGH = 42 >> 8; // Baudrate high register -> 8 MSB of SPBRG SPBRG = 42 & 0xFF; // Baudrate low register -> 8 LSB of SPBRG TRISC6 = 0; // RC6 is TX -> output TRISC7 = 1; // RC7 is RX -> input BAUDCTL = 0x08; // Non inverted TX data, 16bit baud gen RCSTA = 0x90; // Enable serial, enable receiver TXSTA = 0x24; // Send 8bits, enable transmitter, high speed, asynchronous mode RD2 = 0; // Select MUX to 0 __delay_ms(2); } else{ /* UART configs */ // Baud rate 9600 w/ xtal 19660800: ((19660800/9600)/4)-1 = 511 SPBRGH = 511 >> 8; // Baudrate high register -> 8 MSB of SPBRG SPBRG = 511 & 0xFF; // Baudrate low register -> 8 LSB of SPBRG TRISC6 = 0; // RC6 is TX -> output TRISC7 = 1; // RC7 is RX -> input BAUDCTL = 0x08; // Non inverted TX data, 16bit baud gen RCSTA = 0x90; // Enable serial, enable receiver TXSTA = 0x24; // Send 8bits, enable transmitter, high speed, asynchronous mode RD2 = 1; // Select MUX to 1 __delay_ms(2); } } |
Buzzer
Om met de buzzer geluid te maken zijn er twee functies. De beeps functie genereert een 1000Hz blokgolf voor 0.5 seconden. De beep functie genereert een 714Hz blokgolf voor 70ms.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void beep(void){ //short beep for(char i=0; i<50; i++){ //loop 50 times /* square wave of 714Hz*/ RB5 = 1; __delay_us(700); RB5 = 0; __delay_us(700); } } void beeps(void){ //beep for 0.5s for(char i=0; i<250; i++){ //loop 250 times /* square wave of 1000 Hz*/ RB5 = 1; __delay_ms(1); RB5 = 0; __delay_ms(1); } } |
Setup selecteren
Om te kiezen tussen automatische configuratie en manuele configuratie wordt de functie setupSelect gebruikt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void setupSelect(void){ while(!reload && !mode); //wait for button to be pressed if(mode){ //pressed on mode setup = 0; //manual mode setVOL(20); //set volume to 20 playNUM(3); //play manual selected } else{ setup = 1; //auto mode setVOL(20); //set volume to 20 playNUM(4); //play auto selected } beep(); //beep sound waitForStop(); //wait for play to stop __delay_ms(250);//no doubble press } |
Automatische configuratie
Wanneer er gekozen wordt voor de auto setup modus wordt deze functie aangeroepen. Eerst wordt de ESP-module ingesteld als station, daarna wordt er verbinding gemaakt met het netwerk en wordt er een GET request gestuurd naar een lokale server op 192.168.0.117/esp/getData.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | void startup(void){ //auto startup (get data from server) beep(); //start with beep /* * WDT werkt op 31kHz interne klok * Met prescaler op 65536 -> 2.11s timeout * Als die tijd bereikt wordt, reset deze de microcontroler */ /* WDT telt van 0 tot 65535 */ WDTPS3 = 1; WDTPS2 = 0; WDTPS1 = 1; WDTPS0 = 1; configUART(0); //UART @ 115200bps SWDTEN = 1; //start WDT __delay_ms(1500); //wait 1.5s, ESP8266-01 startup writeString("AT+CWMODE=1\r\n"); //Station mode waitFor("OK\r\n",2000); //wait for OK asm("clrwdt"); //clear wdt //writeString("AT+CWJAP_CUR=\"telenet-99FEDF4\",\"yM8ak4khxywZ\"\r\n");//connect to AP writeString("AT+CWJAP_CUR=\"LaboEA-ICT\",\"SJS2900Schoten153\"\r\n");//connect to AP waitFor("OK\r\n",10000); //wait for response OK asm("clrwdt"); //clear wdt writeString("AT+CIPSTART=\"TCP\",\"192.168.0.117\",80\r\n");//connect to server waitFor("OK\r\n",2000); //wait for response OK asm("clrwdt"); //clear wdt writeString("AT+CIPSEND=54\r\n");//setup send to server waitFor("> ",2000); //wait for response asm("clrwdt"); //clear wdt writeString(request); //send request writeString("\r\n"); //einde van request waitFor("+IPD",2000); //wait for +IPD waitFor("\r\n\r\n",2000); //wait for response asm("clrwdt"); //clear wdt getSettings(); //receive gamedata function SWDTEN = 0; //disable wdt configUART(1); //baud 9600bps for dfplayer setVOL(20); //set volume to 20 playNUM(5); //play settings received waitForStop(); //wait for audio to end configUART(0); //baud 115200bps for ESP01 } |
Ontvangen van instellingen
In deze functie worden de instellingen in string formaat omgezet naar decimale waardes en opgeslagen in de juiste variabelen. Op de smartLED's wordt de spelerID (ook te vinden in game viewer) weergegeven in de teamkleur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | void getSettings(void){ /* data from server format: ID,TEAM,AMMO,HEALTH,GAMEMODE */ char temp = '\0'; //temp char is null temp = getByte(); //get byte(char) | cijfer 1 myId = temp-'0'; //ascii naar decimaal temp = getByte(); //get byte(char) | comma temp = getByte(); //get byte(char) | cijfer 1 myTeam = temp-'0'; //ascii naar decimaal temp = getByte(); //get byte(char) | comma temp = getByte(); //get byte(char) | cijfer 1 myAmmo = temp-'0'; //ascii naar decimaal temp = getByte(); //get byte(char) | cijfer 2/comma if(temp != ','){ //als karakter geen comma is -> nog een digit myAmmo = (10*myAmmo)+(temp-'0'); //ascii naar decimaal temp = getByte(); //get byte(char) | cijfer 3/comma if(temp != ','){ //als karakter geen comma is -> nog een digit myAmmo = (10*myAmmo)+(temp-'0'); //ascii naar decimaal } } temp = getByte(); //get byte(char) | cijfer 1 myLife = temp - '0';//ascii naar decimaal temp = getByte(); //get byte(char) | cijfer 2/comma if(temp != ','){ //als karakter geen comma is -> nog een digit myLife = (10*myLife)+(temp-'0'); //ascii naar decimaal temp = getByte(); //get byte(char) | cijfer 3/comma if(temp != ','){ //als karakter geen comma is -> nog een digit myLife = (10*myLife)+(temp-'0'); //ascii naar decimaal } } temp = getByte(); //get byte(char) | cijfer 1 gameMode = temp-'0'; //ascii naar decimaal outData |= (myId << 11);//zet id in ir code outData |= (myTeam << 8); //zet team in ir code outData |= 0x000F; //15 damage /* give leds team color * num leds = player */ switch(myTeam){ //check team case 1: //rood for(unsigned char i = 0; i<myId; i++){ setLED(i,20,0,0); //red } break; case 2: //blauw for(unsigned char i = 0; i<myId; i++){ setLED(0,0,0,20); //blue } break; default: //fout setLED(0,20,20,20); //licht wit break; } writeLED(); //stuur data naar leds } |
Manuele configuratie
Wanneer er gekozen werd voor de manuele modus worden onderstaande functies achter elkaar uitgevoerd. Eerst worden de instructies afgespeeld door de audio module. Daarna kan de gebruiker kiezen welke instelling hij neemt. Na het selecteren zal een korte "beep" te horen zijn.
Team selecteren
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | void selectTeam(void){ configUART(1); //baud 9600bps for dfplayer setVOL(20); //set volume to 20 playNUM(6); //play team select waitForStop(); //wait for play to stop while(!reload && !mode); //wait for button to be pressed if(reload){ //pressed on reload myTeam = 1; //set local team outData |= 0b0000000100000000; //set team in ir data setVOL(20); //set volume to 20 playNUM(8); //play red selected setLED(0,20,0,0); //first led red } else{ //pressed on mode myTeam = 2; //set local team outData |= 0b0000001000000000; //set team in ir data setVOL(20); //set volume to 20 playNUM(7); //play blue selected setLED(0,0,0,20); //first led blue } beep(); //beep sound writeLED(); //give color to leds waitForStop(); //wait for audio to stop playing __delay_ms(250); //no doubble press } |
Speler selecteren
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void selectPlayer(void){ setVOL(20); //set volume to 20 playNUM(9); //play select player waitForStop(); //wait for audio to stop while(!reload && !mode); //wait for button to be pressed if(reload){ //pressed on reload myId = 1; //set local id outData |= 0b0000100000000000; //set player in ir data } else{ //pressed on mode myId = 2; //set local id outData |= 0b0001000000000000; //set player in ir data } beep(); //beep sound __delay_ms(250);//no doubble press } |
'Fire-mode' en countdown
Na de automatische- of manuele instellingen moet er altijd een fire mode gekozen worden. Bij 'single' wordt er telkens 1 'kogel' afgevuurd, bij 'semi-auto' zijn dat er 3 per keer. Als de 'fire-mode' ingesteld is, wordt er -na het indrukken van de trigger- afgeteld van 10 seconden afgeteld.
fire-mode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void setFireMode(void){ configUART(1); //baud9600bps for dfplayer setVOL(20); //set volume to 20 playNUM(16); //play fire mode select waitForStop(); //wait for playing to stop while(!reload && !mode); //wait for button to be pressed if(reload){ //pressed on reload semi = 0; //single fire mode setVOL(20); //set volume to 20 playNUM(18);//play selected single } else{ //pressed on mode semi = 1; //semi fire mode setVOL(20); //set volume to 20 playNUM(17);//play selected semi-auto } beep(); //beep sound waitForStop(); //wait for audio to stop __delay_ms(250);//no doubble press } |
countdown
1 2 3 4 5 6 7 8 9 10 11 12 | void countDown(void){ setVOL(20); //set volume to 20 playNUM(11); //play trigger to start waitForStop(); //wait for audio to stop configUART(0); //baud 115200bps for ESP01 while(!trigger);//wait for trigger press /* wait 10s with beeps */ for(char i=0; i<10; i++){ beeps(); //beep 0.5s __delay_ms(500); } } |
Logging
Om een scoresysteem te kunnen maken wordt er data naar de server gestuurd. Deze verwerkt de ontvangen data en zet deze om naar tabellen en grafieken. Om data te versturen met de ESP-module wordt de functie log gebruikt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void log(unsigned char id,unsigned char team,char type,unsigned char kby){ configUART(0); //baud 115200bps for ESP01 char buf[85]; //request buffer char bufb[4]; //request length buffer sprintf(buf,"GET /esp/regDead.php?N=%d&T=%d&Y=%c&K=%d HTTP/1.0\r\nHost: 192.168.0.117\r\n\r\n",id,team,type,kby); sprintf(bufb,"%d",strlen(buf));//create string writeString("AT+CIPSTART=\"TCP\",\"192.168.0.117\",80\r\n");//connect to server //waitFor("OK\r\n",2000); //wait for response OK __delay_ms(20); writeString("AT+CIPSEND=");//setup send to server writeString(bufb); //send request string length writeString("\r\n");//end command //waitFor("> ",2000); //wait for response __delay_ms(20); writeString(buf); //send request string writeString("\r\n");//end request string } |
smartLED's
Het aantal resterende kogels en levens wordt weergegeven op de smartLED's. Om de data op de LED's aan te passen wordt de functie updateLED gebruikt. Hier worden de variabelen ammo en life geschaald naar 20 LED's.
updateLED
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | void updateLED(unsigned char am,unsigned char li){ if(am == 0){ //if no ammo /* 10 first LEDs purple */ for(unsigned char i = 0; i<9; i++){ setLED(i,10,0,10); } } else{ //ammo available /* scale ammo to 10 leds*/ unsigned char x = map(am,1,myAmmo,0,NUMLEDS/2); /* if led is in range -> red */ for(unsigned char i = 0; i<x; i++){ setLED(i,20,0,0); } /* if led is not in range -> off*/ for(unsigned char i = 9; i>x; i--){ setLED(i,0,0,0); } } if(li == 0){ //if no lives /* 10 last LEDs purple */ for(unsigned char i = 10; i<NUMLEDS; i++){ setLED(i,20,0,20); } } else{ //ammo available /* scale life to 10 leds */ unsigned char y = map(li,1,myLife,10,NUMLEDS); /* if led is in range -> green */ for(unsigned char i = 10; i<y; i++){ setLED(i,0,20,0); } /* if led is not in range -> off*/ for(unsigned char i = 19; i>y; i--){ setLED(i,0,0,0); } } writeLED(); //write data to LEDs } |
map functie
Het schalen van variabelen gebeurt met de map functie. Bij het aanroepen van de functie kan een integer geschaald worden tussen een minimale en maximale waarde.
1 2 3 4 5 | /* scaling function like arduino*/ unsigned int map(unsigned int in, unsigned int inm, unsigned int inM, unsigned int outm, unsigned int outM) { return (in - inm) * (outM - outm) / (inM - inm) + outm; } |