Simple XOpen System
Indice
X-Open System
Il sistema di marcatura IR X-Open si pone nel mondo del lasertag italiano come un sistema entry level ad alte prestazioni.
Vediamone le caratteristiche tecniche:
- Dual core: due microcontrollori comunicanti via UART che gestiscono in modo autonomo ricezione e invio IR per le migliori prestazioni possibili.
- Attivazione del gearbox mediante relay.
- Audio mediante speaker amplificato
- Compatibile con i proiettori e con i sensori X-TaG
- Low Cost
Ricordiamo comunque a tutti i costruttori che si cimenteranno nell'esperienza che X-Open nasce con lo scopo di fare avvicinare i giocatori al mondo lasertag nel modo più economico possibile. Il sistema per essere programmato (cambio punti ferita, cambio numero caricatori e colpi, ecc...) deve essere connesso ad un PC e riprogrammato.
Proprio per queste caratteristiche difficilmente sarà un sistema che verrà accettato a tornei o gare ufficiali in quanto le impostazioni non possono essere controllate e non possono essere bloccate. E' un ottimo sistema, molto performante e stabile, ma nasce per fare avvicinare nuovi giocatori al mondo del lasertag in maniera economica, per consentire a quelle persone che sono dubbiose di provare a giocare a lasertag con il proprio club in modo MILES compatibile spendendo pochissimo o per i più smanettoni di creare proprie realizzazioni partendo dal sistema base pubblicato di seguito.
Elenco componenti
Il progetto si compone dei seguenti componenti:
- 5x Resistenze 100 Ohm 1/4W
- 5x Resistenze 10K Ohm 1/4W
- 1x IRLD024 Mosfet N-Channel
- 4x BS170 Mosfet N-Channel
- 1x Speaker
- 1x Relay bobina 5V
- 1x Resistenza 1,2 Ohm 1/4W
- 1x LED IR 940nm
- 5x LED Rosso
- 4x TSOP4840
Schematico
Breadboard
Pinout componenti
- BS170, mosfet-N ad uso generico.
- IRLD024, Mosfet-N ad alte prestazioni
- Relay, bobina a 5V, contatto singolo 250VAC 10A
Spiegazioni tecniche
- MOSFET N: o transistor a effetto di campo è un componente a 3 piedini, Gate, Drain e Source.
Il mosfet di base è un amplificatore comandato in tensione tramite il pin Gate, infatti quando la tensione tra Gate e Source (Vgs > Vth) supera una certa soglia (variabile in base al modello di mosfet) tra Drain e Source si crea un canale di silicio fortemente drogato che permette alla corrente elettrica di fluire quasi liberamente. Quasi poichè è sempre presente una certa resistenza tra Drain e Source anch'essa caratteristica di ogni modello di mosfet. Usiamo mosfet di tipo N perchè si accendono portando a valore logico alto il pin del microcontrollore.
- Resistenze di gate: le resistenze di gate sono così chiamate perchè si posizionano tra pin di uscita del microcontrollore e il gate del mosfet.
Idealmente il gate dovrebbe avere resistenza infinita e quindi non permettere scorrimento di corrente, ma in pratica, sono presenti capacità parassite che durante i fronti di salita del segnale di pilotaggio diventano dei corto circuiti. Grazie a queste resistenze si evita di stressare il microcontrollore.
- Resistenze di pull-down: sono molto grandi e servono a "tirare" verso massa il segnale presente sul Gate dei mosfet stabilizzandolo e mantengono spento il mosfet (Vgs = 0).
Librerie necessarie
- Arduino TimerObject: [1]
- z3t0 IR Remote Library: [2]
Una volta scaricata la libreria IRRemote è bene cancellare la libreria RobotIrRemote inclusa nella IDE di Arduino per evitare conflitti.
Aprite la cartella IRRemote ed effettuate una modifica al file IRRemoteInt.h, alla riga 204:
// Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, Nano, etc
#else
//#define IR_USE_TIMER1 // tx = pin 9
#define IR_USE_TIMER2 // tx = pin 3
#endif
Si deve modificare in:
// Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, Nano, etc
#else
#define IR_USE_TIMER1 // tx = pin 9
//#define IR_USE_TIMER2 // tx = pin 3
#endif
- Arduino Timer: [3]
Prima di programmare
Quando si programmano i due Arduino sarà necessario interrompere la connessione seriale tra i due.
E' buona norma sfilarli dalla breadboard durante la programmazione per evitare che in caso di errori firmware o hardware la porta USB del PC possa rompersi a causa di un corto circuito o una richiesta di corrente troppo alta.
Meglio bruciare un 7805 che il controller USB del computer! ;)
Firmware Weapon
/*
* (C) Copyright 2016 X-Tag IR (http://www.xtag-ir.com/).
*
* Licensed under the GNU GPL license version 3.
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/gpl-3.0.en.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "TimerObject.h"
#include "IRremote.h"
// Weapon configuration
#define WEAPON_RATE 600 //rateo di fuoco colpi al minuto valori compresi tra [1-1200]
#define WEAPON_DAMAGE 34 //danno dell'arma
#define WEAPON_MAGAZINES 10 //numero di caricatori
#define WEAPON_AMMO 30 // proiettili in un singolo caricatore
#define WEAPON_RELOAD_TIME_MS 5000 // tempo di ricarica espresso in millisecondi
// Hardware settings
#define PIN_MUZZLE 4
#define PIN_GB 5
#define PIN_TRIGGER 11
#define PIN_RELOAD 12
#define PIN_STATUS 13
#define SERIAL_BAUD_RATE 115200
#define MAX_SERIAL_BUFFER_SIZE 30
#define MIN_IR_SEND_TS 33
// Runtime variables
uint8_t playerTeam;
uint8_t playerNo;
uint16_t ammo;
uint8_t magazines;
uint16_t fireDelay;
bool isActive = false;
bool isReloading = false;
bool isFiring = false;
IRsend irSend;
TimerObject *tmrReload = new TimerObject(WEAPON_RELOAD_TIME_MS);
TimerObject *tmrFire;
char serialBuffer[MAX_SERIAL_BUFFER_SIZE];
uint8_t serialBufferPos = 0;
bool getParityBit(uint32_t data, unsigned int len)
{
unsigned int bits1count = 0;
for (int i=0; i<len; i++)
{
if ((data >> i) & 0b1) {
bits1count++;
}
}
return (bits1count % 2);
}
void fireShot() {
if (ammo == 0) {
stopFire();
return;
}
uint32_t packet = 0x0;
boolean parityBit;
packet |= playerTeam & 0b111;
packet <<= 5;
packet |= playerNo & 0b11111;
packet <<= 8;
packet |= WEAPON_DAMAGE & 0b11111111;
parityBit = getParityBit(packet, 16);
packet <<= 1;
packet |= parityBit;
digitalWrite(PIN_MUZZLE, HIGH);
irSend.sendSony(packet, 17);
digitalWrite(PIN_MUZZLE, LOW);
ammo--;
}
void hardwareSetup() {
pinMode(PIN_MUZZLE, OUTPUT);
pinMode(PIN_GB, OUTPUT);
pinMode(PIN_STATUS, OUTPUT);
pinMode(PIN_TRIGGER, INPUT_PULLUP);
pinMode(PIN_RELOAD, INPUT_PULLUP);
digitalWrite(PIN_MUZZLE, LOW);
digitalWrite(PIN_GB, LOW);
digitalWrite(PIN_STATUS, LOW);
Serial.begin(SERIAL_BAUD_RATE);
fireDelay = (60000 / WEAPON_RATE);
if (fireDelay < MIN_IR_SEND_TS*2) fireDelay = MIN_IR_SEND_TS*2;
fireDelay -= MIN_IR_SEND_TS;
tmrFire = new TimerObject(fireDelay);
tmrFire->setOnTimer(&fireShot);
}
void setup() {
hardwareSetup();
}
void stopOps() {
tmrReload->Stop();
stopFire();
}
void ledStatus(bool s) {
digitalWrite(PIN_STATUS, s);
}
void activate() {
ledStatus(true);
isActive = true;
}
void deactivate() {
ledStatus(false);
isActive = false;
stopOps();
}
void reammo() {
ammo = 0;
magazines = WEAPON_MAGAZINES;
}
void _reloadMagazine() {
ledStatus(true);
magazines--;
ammo = WEAPON_AMMO;
tmrReload->Stop();
}
void reloadMagazine() {
if (!isActive) return;
if (magazines == 0) return;
ledStatus(false);
tmrReload->setOnTimer(&_reloadMagazine);
tmrReload->Start();
}
void onSerialMessage(char *message) {
uint8_t n;
if (strcmp_P(message, PSTR("#activate")) == 0) {
activate();
} else if (strcmp_P(message, PSTR("#deactivate")) == 0) {
deactivate();
} else if (strcmp_P(message, PSTR("#reammo")) == 0) {
reammo();
} else if (sscanf(message, "#team %d", &n) == 1) {
playerTeam = n;
} else if (sscanf(message, "#player %d", &n) == 1) {
playerNo = n;
}
}
void startFire() {
if (ammo == 0) return;
if (!isActive) return;
if (isReloading) return;
if (isFiring) return;
fireShot();
digitalWrite(PIN_GB, HIGH);
isFiring = true;
tmrFire->Start();
}
void stopFire() {
if (!isFiring) return;
digitalWrite(PIN_GB, LOW);
isFiring = false;
tmrFire->Stop();
}
void loopTrigger() {
if (digitalRead(PIN_TRIGGER) == LOW) {
if (!isFiring) startFire();
} else {
if (isFiring) stopFire();
}
}
void loopReload() {
if (digitalRead(PIN_RELOAD) == LOW) {
if (!isReloading) reloadMagazine();
}
}
void loopSerial() {
char c;
while (Serial.available()) {
c = Serial.read();
if ((c == '\n') || (c == '\r')) {
serialBuffer[serialBufferPos++] = '\0';
if (serialBufferPos > 1) {
onSerialMessage(serialBuffer);
}
serialBufferPos = 0;
} else {
serialBuffer[serialBufferPos++] = c;
}
}
}
void loop() {
loopSerial();
loopTrigger();
loopReload();
tmrReload->Update();
tmrFire->Update();
}
Firmware Player
/*
* (C) Copyright 2016 X-Tag IR (http://www.xtag-ir.com/).
*
* Licensed under the GNU GPL license version 3.
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/gpl-3.0.en.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Timer.h"
#include "IRremote.h"
// Miles settings
#define TEAM_RED 1
#define TEAM_BLUE 2
#define TEAM_YELLOW 3
#define TEAM_GREEN 4
#define TEAM_BLACK 5
#define TEAM_WHITE 6
#define TEAM_PURPLE 7
#define PLAYER_IMMUNE_MODE_REAL 0
#define PLAYER_IMMUNE_MODE_TEAM 1
#define PLAYER_IMMUNE_MODE_SELF 2
#define XOPEN_SYS_ADMIN 0x09
#define XOPEN_SYS_ADMIN_KILL_PLAYER 0x00
#define XOPEN_SYS_ADMIN_RESPAWN 0x04
#define XOPEN_SYS_ADMIN_SENSOR_TEST 0x15
#define XOPEN_SYS_ADMIN_ENDGAME 0x07
#define XOPEN_SYS_ADMIN_STARTGAME 0x03
//////////////////////////////////////////////////////////////////////
// Player confiurable parameters
#define PLAYER_LIFE 100 // vita del giocatore
#define PLAYER_ARMOR 0 // piastre antiproiettile del giocatore
#define PLAYER_ARMOR_FACTOR 2 // fattore di riduzione del danno
#define PLAYER_TEAM TEAM_RED // squadra del giocatore
#define PLAYER_NO 15 // numero del giocatore
// Game configuration
#define PLAYER_IMMUNE_MODE PLAYER_IMMUNE_MODE_TEAM // immunità colpi propri/squadra/realistico
//////////////////////////////////////////////////////////////////////
// Hardware settings
#define PIN_HITLED A1
#define PIN_TSOP 9
#define PIN_SOUND 10
#define PIN_STATUS 13
//#define DEBUG_PLAYER
#define SERIAL_BAUD_RATE 115200
// Runtime variables
uint16_t playerLife;
uint8_t playerArmor;
uint8_t playerArmorFactor;
uint8_t playerTeam;
uint8_t playerNo;
bool playerIsDead;
Timer tmr;
IRrecv irRecv(PIN_TSOP);
decode_results irRes;
void hardwareSetup() {
pinMode(PIN_HITLED, OUTPUT);
pinMode(PIN_SOUND, OUTPUT);
pinMode(PIN_STATUS, OUTPUT);
digitalWrite(PIN_HITLED, HIGH);
digitalWrite(PIN_SOUND, HIGH);
digitalWrite(PIN_STATUS, LOW);
irRecv.enableIRIn();
Serial.begin(SERIAL_BAUD_RATE);
}
void hitled(bool s) {
digitalWrite(PIN_HITLED, s);
}
void buzzer(uint16_t frequency, uint16_t duration) {
tone(PIN_SOUND, frequency, duration);
}
void ledInitPlayer() {
digitalWrite(PIN_STATUS, HIGH);
for (uint8_t i=0; i<3; i++) {
hitled(!(i % 2));
if (i % 2 == 0) buzzer(1000, 500);
delay(500);
}
hitled(false);
}
void ledPlayerDead() {
digitalWrite(PIN_STATUS, LOW);
for (uint8_t i=0; i<4; i++) {
hitled(!(i % 2));
if (i % 2 == 0) buzzer(500, 500);
delay(500);
}
hitled(true);
buzzer(500, 2000);
}
void ledSensorTest() {
hitled(true);
buzzer(1000, 1000);
delay(1000);
hitled(false);
}
void ledShot() {
tmr.oscillate(PIN_HITLED, 100, LOW, 1);
buzzer(500, 200);
}
void initPlayer() {
ledInitPlayer();
playerLife = PLAYER_LIFE;
playerArmor = PLAYER_ARMOR;
playerArmorFactor = PLAYER_ARMOR_FACTOR;
playerTeam = PLAYER_TEAM;
playerNo = PLAYER_NO;
playerIsDead = false;
#ifdef DEBUG_PLAYER
Serial.println("Player respawn");
#endif
Serial.print("#team ");
Serial.println(playerTeam);
Serial.print("#player ");
Serial.println(playerNo);
Serial.println("#activate");
Serial.println("#reammo");
}
bool getParityBit(uint32_t data, unsigned int len)
{
unsigned int bits1count = 0;
for (int i=0; i<len; i++)
{
if ((data >> i) & 0b1) {
bits1count++;
}
}
return (bits1count % 2);
}
void playerDead() {
if (playerIsDead)
return;
Serial.println("#deactivate");
playerLife = 0;
playerIsDead = true;
ledPlayerDead();
#ifdef DEBUG_PLAYER
Serial.println("Player dead");
#endif
}
void onInvalidMessage() {
// TODO: Nearmiss shot
}
void onSystemAdminMessage(uint8_t lsb) {
switch (lsb) {
case XOPEN_SYS_ADMIN_ENDGAME:
case XOPEN_SYS_ADMIN_KILL_PLAYER:
playerDead();
break;
case XOPEN_SYS_ADMIN_RESPAWN:
case XOPEN_SYS_ADMIN_STARTGAME:
initPlayer();
break;
case XOPEN_SYS_ADMIN_SENSOR_TEST:
ledSensorTest();
break;
}
}
void onSystemMessage(uint8_t command, uint8_t lsb) {
if (command = XOPEN_SYS_ADMIN) {
return onSystemAdminMessage(lsb);
}
}
void onShotMessage(uint8_t teamId, uint8_t playerId, uint8_t damage) {
if (playerIsDead)
return;
if (
((PLAYER_IMMUNE_MODE == PLAYER_IMMUNE_MODE_TEAM) && (teamId == PLAYER_TEAM)) ||
((PLAYER_IMMUNE_MODE == PLAYER_IMMUNE_MODE_SELF) && (teamId == PLAYER_TEAM) && (playerId == PLAYER_NO))
) {
return;
}
ledShot();
// Decrease player's armor
if (playerArmor > 0) {
damage /= playerArmorFactor;
playerArmor--;
}
// Decrease player's life
if (damage > playerLife) {
playerLife = 0;
} else {
playerLife-=damage;
}
if (playerLife == 0) {
playerDead();
}
#ifdef DEBUG_PLAYER
Serial.print("Player life: ");
Serial.print(playerLife);
Serial.println("");
#endif
}
void onValidMessage(uint32_t message) {
if (!message)
return;
uint8_t msb = (message >> 8) & 0xff;
uint8_t lsb = message & 0xff;
uint8_t teamId = (msb >> 5) & 0b111;
uint8_t command = msb & 0b11111;
if (teamId == 0x00)
{
onSystemMessage(command, lsb);
}
else
{
if (lsb > 0)
{
onShotMessage(teamId, command, lsb);
}
else
{
onInvalidMessage();
}
}
}
void onIrMessage(decode_results res) {
if (res.decode_type != SONY)
return;
uint32_t rawMessage = res.value & 0x1ffff;
uint32_t message = (rawMessage >> 1) & 0xffff;
boolean parityBit = rawMessage & 0b1;
if ((getParityBit(message, 16) != parityBit) || !message)
{
onInvalidMessage();
return;
}
onValidMessage(message);
}
void loopIr() {
if (irRecv.decode(&irRes))
{
onIrMessage(irRes);
irRecv.resume();
}
}
void setup() {
delay(500);
hardwareSetup();
initPlayer();
}
void loop() {
loopIr();
tmr.update();
if (playerIsDead) {
digitalWrite(PIN_HITLED, HIGH);
}
}