Spockie-Tech
Site Admin
Joined: 31 May 2004
Posts: 3160
Location: Melbourne, Australia
|
Here it is..
You will see the main loop is very simple, just runs around repeatedly calling the 6 main checks (states). The play-timer, weapon_timer etc values are used as state-mode-controls through using nested if's, rather than explicit declaration of modes, which is more common in multi-modem state-machines.
The code reads nicer if you set your tabs so that the //'s line up nicely, and if you are using an editor that supports code-folding (Very useful for hiding clutter), the //{ and //} comment marks define code-folding points to roll-up blocks of subroutines, like the I2C Port Expander I/O (used to make 16x2 LCD comms only take up 2 pins)
Fold (or delete) the LCD I/O functions, Take out all the declares, and definitions, attract messages and comments, and you can see the core execution is really just the 6 state-checks running off the millis() timer at the end.
As Jake said, Many State-Machines are really just a heap of nested if-then statements (or use case-switch to the same effect). Normally you would use a few semaphore/flags for linked-functions to signal each other about what theyre up to, but this didnt need that.
Note the version number is 0.11 though. it all worked perfectly *first time at alpha version 0.1, and the +0.01 was just to adjust the play timers a bit, so it most follow some clean-ish logic
I hope it helps. sometimes other peoples code can help light a bulb, other times you read it and go "Why the hell would anyone do it *that way !?!"
code:
// TechnoMagic SideTracked RentaBot Controller v0.11
// update 0.11 - change weapon timer to 4 on 3 off
// - change credit time to 90 seconds per coin (down from 180)
/* ------------------------------------------------
By Brett Paulin
May 2009
Version 0.1
This is a library of routines to drive a HD44780 LCD Connected via I2C MCP23017 as per the the I2C-SPI LCD
"back-pack" from SpikenzieLabs.com .
HOOK-UP:
SIGNAL I2C LCD BOARD - ARDUINO
Ground GND GND
VCC 5V 5V
SDA** SDA Analog 4
SCL** SCL Analog 5
The buttons (if used) are wired with a normally open (NO) switch to ground. (Pressing the button will contact the
ground signal) J3 on the PCB has an extra ground and pins for buttons 1 to 5.
** I2C communications require a pull-up resistor on both the SCL and SDA lines. There must be a 4.7k through
10kohm resistor on both the SDA and SCL pulling them up to VCC. Even if you are using other I2C devices
use only one set of pull-up resistors.
Pull-up resistors are not included on the I2C-SPI LCD board, for the following reasons; they are not
required when the board is used with the SPI interface, only one set of resisters is required for the I2C
bus (if you where to use more them one board there would be too many), and it gives you more control.
USAGE:
1. Disconnect the power and hook-up the Arduino and the I2C Board as described above.
2. Set your I2C address. (Up to 7 I2C Boards and be used on the same I2C bus.)
See below in the define section for this line
#define MCP23017 B00100xxx
the "xxx" is your board's changeable I2C Address
Use the dip switch on the I2C board to modify the address. Switch position 1 is the LSB, rightmost "x" above.
3. Power the board and upload the sketch.
4. You should see a message and a counter on your LCD. If you hooked up buttons and are pressing one, you should
also see that message. You may need to adjust the contrast POT (blue square on PCB) to see the display properly.
LEGAL:
This code is provided as is. No guaranties or warranties are given in any form. It is up to you to determine
this codes suitability for your application.
*/
//{ Value, Control and I/O Definitions
//{ Port Expander
#define MCP23017 B00100000 // MCP23017 I2C Address was B01000000 - last 3 are address bits
#define IOCON 0x0A // MCP23017 Config Reg.
#define IODIRA 0x00 // MCP23017 address of I/O direction
#define IODIRB 0x01 // MCP23017 1=input
#define IPOLA 0x02 // MCP23017 address of I/O Polarity
#define IPOLB 0x03 // MCP23017 1= Inverted
#define GPIOA 0x12 // MCP23017 address of GP Value
#define GPIOB 0x13 // MCP23017 address of GP Value
#define GPINTENA 0x04 // MCP23017 IOC Enable
#define GPINTENB 0x05 // MCP23017 IOC Enable
#define INTCONA 0x08 // MCP23017 Interrupt Cont
#define INTCONB 0x09 // MCP23017 1= compair to DEFVAL(A or B) 0= change
#define DEFVALA 0x06 // MCP23017 IOC Default value
#define DEFVALB 0x07 // MCP23017 if INTCONA set then INT. if diff.
#define GPPUA 0x0C // MCP23017 Weak Pull-Ups
#define GPPUB 0x0D // MCP23017 1= Pulled HIgh via internal 100k
//}
//{ LCD Control
#define LCDClr 0x01 // clear the LCD
#define LCDHome 0x02 // move cursor to home position
#define LCDCsLf 0x10 // move cursor left
#define LCDCSRt 0x14 // move cursor right
#define LCDShftL 0x18 // shift displayed chars left
#define LCDShftR 0x1C // shift displayed chars right
#define LCDDDRam 0x80 // Display Data RAM control (Address in Lower 7 bits)
#define LCDddram2 0xC0 // 9th position of display ??
#define RS_pin B00000100
#define RW_pin B00000010
#define E_pin B00000001
//}
//{ IO Pins
#define weapon1_ready 8 // Weapon 1 Ready Indicator Light
#define weapon2_ready 9 // Weapon 1 Ready Indicator Light
#define credit1_inPin 7 // Input - credit slot 1
#define credit2_inPin 6 // Input - credit slot 2
#define weapon1_inPin 8 // Input - Bot Weapon Trigger 1
#define weapon2_inPin 9 // Input - Bot Weapon Trigger 2
#define weapon1_outPin 5 // Output - Bot Weapon Enable 1
#define weapon2_outPin 4 // Output - Bot Weapon Enable 2
#define bot1_en_outPin 3 // Output - Bot 1 Enable
#define bot2_en_outPin 2 // Output - Bot 2 Enable
//}
#define attract_msg_change_delay 2500 // 2500 = 2.5 seconds // display change speed
#define credit_time 90000 //90,000 = 90 seconds - 180,000 milliseconds = 180 seconds = 3 minutes
//}
//{ Variable Initialisation
byte LCDCONT = 0; // REQUIRED
byte button = 0; // REQUIRED
int i = 0; // REQUIRED
int j = 0;
int buttonPress = 0;
int count = 0;
char print_buffer[16];
char attract_msg [17]=" ";
char NUMB[4] = "000";
unsigned long play_timer_1 = 0; // Player 1 millis play end time
unsigned long countdown_pt_1 = 0; // remaining countdown time
unsigned long play_timer_2 = 0; // Player 2 millis end time
unsigned long countdown_pt_2 = 0; // remaining countdown time
unsigned long weapon_timer_1 = 0; // Player 1 millis weapon timer
unsigned long countdown_wt_1 = 0; // weapon countdown timer
int weapon_1_active = 0; // Weapon 1 Active
int weapon_1_ready = 0; // Weapon 1 Ready to Fire
unsigned long weapon_timer_2 = 0; // Player 2 millis weapon timer
unsigned long countdown_wt_2 = 0; // weapon countdown timer
int weapon_2_active = 0; // Weapon 2 Active
int weapon_2_ready = 0; // Weapon 2 Ready to Fire
unsigned long attract_msg_change_time ; // Attract message change timer
int attract_msg_num =0; // Counter for which attract message showing
unsigned long weapon_cycle_time = 2000; // 4 second cycle time (millis)
unsigned long weapon_active_time = 0700; // 2 second active, 2 second recharge time
int credit1_change; // Coin Input 1 change detect
int credit2_change; // Coin Input 1 change detect
int weapon1_change; // Weapon Fire 1 Input Change Detect
int weapon2_change; // Weapon Fire 1 Input Change Detect
// ** Attract Messages **
char* attract_msg_array[]={
// 34567890123456" < -16chr Sizer
"* Robot Wars ! *",
"RC Fighting Bots",
" 2 CREDITS EACH ",
" FOR 3 MINUTES ",
" Remote Control ",
"Fighting Robots!",
"Insert 2 credits",
"to begin Battle ",
"Insert 1 credit ",
"for extra 90 sec",
"Smash the Robots",
"into submission!",
"Unlimited Battle",
"add credit +time",
"Two Robots Enter",
"One Robot Leaves",
"Insert 2 credits",
"to begin Battle ",
"Insert 1 credit ",
"for extra 90 sec",
};
int attract_msg_count=19;
//}
//{ Add-In Library Functions
#include <Wire.h> // Used for the I2C Communications
#include <PString.h> // Used to render Values to Strings for Printing
#include <Debounce.h> // Used to detect switch inputs with debounce time
//}
//------------------------------------------------------
//{ Setup
Debounce credit1_in = Debounce(20, credit1_inPin);
Debounce credit2_in = Debounce(20, credit2_inPin);
Debounce weapon1_in = Debounce(20, weapon1_inPin);
Debounce weapon2_in = Debounce(20, weapon2_inPin);
void setup()
{
Wire.begin(); // join i2c bus (address optional for master)
portexpanderinit();
LCDinit();
LCDcmd(LCDClr);
pinMode(credit1_inPin, INPUT); // Set Credit1 Input Pin to be an input
pinMode(credit2_inPin, INPUT); // Set Credit2 Input Pin to be an input
pinMode(weapon1_inPin, INPUT); // Set weapon1 Input Pin to be an input
pinMode(weapon2_inPin, INPUT); // Set weapon2 Input Pin to be an input
pinMode(weapon_1_ready, OUTPUT); // Set weapon1 Ready Pin to be an output
pinMode(weapon_2_ready, OUTPUT); // Set weapon2 Ready Pin to be an output
pinMode(weapon1_outPin, OUTPUT); // Set weapon1 Output Enable Pin to be an output
pinMode(weapon2_outPin, OUTPUT); // Set weapon2 Output Enable Pin to be an output
pinMode(bot1_en_outPin, OUTPUT); // Set weapon2 Output Enable Pin to be an output
pinMode(bot2_en_outPin, OUTPUT); // Set weapon2 Output Enable Pin to be an output
// *********************************************
Serial.begin(9600); // ** Debugging output on
// *********************************************
} // end of setup loop
//}
//------------------------------------------------------
//{ Main Loop
void loop()
{
attract_mode_display(); // Call Attract Mode to display messages if neither Bot active
coin_1_check(); // coin check and inc play timers if sensed
coin_2_check();
// Add to cumulative run timer for each bot ??
bot_1_active_check(); // see if bot is active and enable if req
bot_2_active_check();
bot_1_weapon_check(); // timer control and inhibit of bot weapons
bot_2_weapon_check();
} // End of Main Loop
//}
//------------------------------------------------------
//{ Functions and Subroutines
//{ I2C Bus Control Functions
void I2C_TX(byte device, byte regadd, byte tx_data) // Transmit I2C Data
{
Wire.beginTransmission(device);
Wire.send(regadd);
Wire.send(tx_data);
Wire.endTransmission();
}
void I2C_RX(byte devicerx, byte regaddrx) // Receive I2C Data
{
Wire.beginTransmission(devicerx);
Wire.send(regaddrx);
Wire.endTransmission();
Wire.requestFrom(int(devicerx), 1);
byte c = 0;
if(Wire.available())
{
byte c = Wire.receive();
button = c >>3;
}
}
void portexpanderinit() // Setup PortIO Chip
{
// --- Set I/O Direction
I2C_TX(MCP23017,IODIRB,B11111000);
I2C_TX(MCP23017,IODIRA,B00000000);
// --- Set I/O Polarity
I2C_TX(MCP23017,IPOLA,B00000000);
I2C_TX(MCP23017,IPOLB,B11111000);
// --- Set ALL Bits of GPIOA
I2C_TX(MCP23017,GPIOA,B00000000);
// --- Set Weak Pull-Up on Bits 7 of GPIOB
I2C_TX(MCP23017,GPPUB,B11111000);
// --- Set Default on Bits 7 of GPIOB
I2C_TX(MCP23017,DEFVALB,B00000000);
// --- Set Use Default on Bits 7 of GPIOB
I2C_TX(MCP23017,INTCONB,B10000000);
// --- Set IOC on Bits 7 of GPIOB
I2C_TX(MCP23017,GPINTENB,B10011000);
// --- Set active low of int pin
I2C_TX(MCP23017,IOCON,B00110000);
}
void checkbutton() // Red if Buttons are being Pressed
{
// Use serial monitor at 9600bps to see buttons that are pressed
I2C_RX(MCP23017,GPIOB);
buttonPress = int(button);
switch (buttonPress)
{
case 1:
Serial.println("Button-One");
break;
case 2:
Serial.println("Button-Two");
break;
case 4:
Serial.println("Button-Three");
break;
case 8:
Serial.println("Button-Four");
break;
case 16:
Serial.println("Button-Five");
break;
}
}
//}
//{ LCD Display Control Functions
void LCDinit() // Initialise LCD
{
// Only used with port expander
LCDCONT = RS_pin | E_pin;
I2C_TX(MCP23017,GPIOB,LCDCONT);
// Standard Hitachi initialization for 8-bit mode form spec sheets
// Instruction Timings Tweaked by Brett.
delay(20); // 15ms Power Up Delay
LCDcmd(B00110000); // Set 8 bit Interface #1
delay(5); // 5ms command delay
LCDcmd(B00110000); // Set 8 bit Interface #2
delay(1); // 1ms (>100us) Delay
LCDcmd(B00110000); // Set 8 bit Interface #3
delay(1); // 1ms (>37us) Delay
LCDcmd(B00111000); // Function Set - 8bit, 2lines, 5x8Font
delay(1); // 1ms (>37us) Delay
LCDcmd(B00001000); // Display Off, Cursor Off, Blink Off
delay(1); // 1ms (>37us) Delay
LCDcmd(LCDClr); // Clear Display
delay(20); // 20ms (Time Unspec?)
LCDcmd(B00000110); // Entry mode = Increment, Move Cursor (not shift display)
delay(1); // 1ms (>37us) Delay
LCDcmd(B0001100); // Display On, Cursor Off, Blink Off
delay(1); // 1ms (>37us) Delay
}
void LCDsetadd (byte LCDadd) // Set Cursor Address
{
LCDcmd (LCDadd | LCDDDRam); // OR Mix Set Ram cmd with Address and send
}
void LCDcmd(byte cmdlcd) // Send Command to LCD
{
LCDCONT =0; // Register Select = Command
I2C_TX(MCP23017,GPIOB,LCDCONT);
LCDwr(cmdlcd);
// delay(02);
}
void LCDwr(byte lcdChar) // Send Data to LCD
{
I2C_TX(MCP23017,GPIOA,lcdChar);
LCDCONT = LCDCONT | E_pin; // If RS is set then it stays set
I2C_TX(MCP23017,GPIOB,LCDCONT);
LCDCONT = RS_pin;
I2C_TX(MCP23017,GPIOB,LCDCONT);
}
void dispnumb(int numbvar) // Send Decimal Number to Display
{
// int THUS= abs(numbvar/1000);
// int HUNDS= abs(numbvar/100);
// int TENS = abs((numbvar - (HUNDS*100))/10);
// int ONES = abs(numbvar-(HUNDS*100)-(TENS*10));
int TENS = abs((numbvar)/10);
int ONES = abs(numbvar-(TENS*10));
// NUMB[0] = HUNDS+48; // +48 for ASCII conversion
NUMB[1] = TENS+48;
NUMB[2] = ONES+48;
for(i=1;i<3;i++)
{
LCDwr(NUMB[i]); // Print-Out 3 digit number at cursor
}
}
void LCDmessage (char message[]) // Send String Message to Display @ current cursor pos
{
int j;
do
{
LCDwr(message[j]);
j = ++j;
} while (message[j] != 0);
}
//}
//{ Bot Control Functions
void attract_mode_display()
{
if ( ( (play_timer_1 <= millis() ) && (play_timer_2 <= millis() ) ) ) // check to ensure both bots off
{
if ( attract_msg_change_time < millis() ) // is it time to change the message ?
{
LCDsetadd (0); // if so, set the cursor home
// attract_msg = attract_msg_array[attract_msg_num]; // select the next message from the group
LCDmessage(attract_msg_array[attract_msg_num]); // and display it
attract_msg_num++ ; // now the 2nd line
LCDsetadd (64);
// attract_msg = attract_msg_array[attract_msg_num];
LCDmessage(attract_msg_array[attract_msg_num]); // and display it
attract_msg_num++ ; // pointer to next msg for next cycle
if (attract_msg_num > attract_msg_count) // check for overflow and reset
attract_msg_num = 0;
attract_msg_change_time = millis() + attract_msg_change_delay; // set how long to delay before changing msg
}
}
}
void coin_1_check()
{
credit1_change = credit1_in.update();
if (credit1_in.read() && credit1_change)
{
// check to see if timer is running or not and start it if not, else add credit time to current
if (play_timer_1 < millis())
{
play_timer_1 = millis() + credit_time;
}
else
{
play_timer_1 = play_timer_1 + credit_time;
}
}
}
void coin_2_check ()
{
credit2_change = credit2_in.update();
if (credit2_in.read() && credit2_change)
{
// check to see if timer is running or not and start it if not, else add credit time to current
if (play_timer_2 < millis() )
{
play_timer_2 = millis() + credit_time;
}
else
{
play_timer_2 = play_timer_2 + credit_time;
}
}
}
void bot_1_active_check()
{
if ( play_timer_1 > millis() )
{
digitalWrite (bot1_en_outPin, HIGH); // enable bot 1 relay output
countdown_pt_1 = ((play_timer_1 - millis())/1000); // convert millis to secs for display
if (!(weapon_1_active && (countdown_wt_1 <= (weapon_active_time/1000) ) ) ) // only diplay power if weapon_recharge is not being displayed
{
// set display address and send message
LCDsetadd (0);
LCDmessage ("Robot1 Power ");
LCDsetadd (12);
//{ Display Formating according to counter size
if (countdown_pt_1 < 10000) // only add this code if >9999 Seconds required.
// { LCDmessage(" "); } // causes last chr of Power message to flicker
if (countdown_pt_1 < 1000 )
{ LCDmessage(" "); }
if (countdown_pt_1 < 100 )
{ LCDmessage(" "); }
if (countdown_pt_1 < 10 )
{ LCDmessage(" "); }
//}
PString (print_buffer, 6, countdown_pt_1);
LCDmessage (print_buffer);
}
if (play_timer_2 < millis() )
{
LCDsetadd (64);
LCDmessage ("Bot2 Add Coins !"); // display other player attract - insert coin to join in - message}
}
}
else
{ digitalWrite (bot1_en_outPin, LOW); } // ## Disable Bot 1 output
}
void bot_2_active_check()
{
if ( play_timer_2 > millis() )
{
digitalWrite (bot2_en_outPin, HIGH); // enable bot 1 relay output
countdown_pt_2 = ((play_timer_2 - millis())/1000); // convert millis to secs for display
if (!(weapon_2_active && (countdown_wt_2 <= (weapon_active_time/1000) ) ) ) // only diplay power if weapon_recharge is not being displayed
{
// set display address and send message
LCDsetadd (64+0);
LCDmessage ("Robot2 Power ");
LCDsetadd (64+12);
//{ Display Formating according to counter size
if (countdown_pt_2 < 10000) // only add this code if >9999 Seconds required.
// { LCDmessage(" "); } // causes last chr of Power message to flicker
if (countdown_pt_2 < 1000 )
{ LCDmessage(" "); }
if (countdown_pt_2 < 100 )
{ LCDmessage(" "); }
if (countdown_pt_2 < 10 )
{ LCDmessage(" "); }
//}
PString (print_buffer, 6, countdown_pt_2);
LCDmessage (print_buffer);
}
if (play_timer_1 < millis() )
{
LCDsetadd (0);
LCDmessage ("Bot1 Add Coins !"); // display other player attract - insert coin to join in - message}
}
}
else
{ digitalWrite (bot2_en_outPin, LOW); } // ## Disable Bot 2 output
}
void bot_1_weapon_check()
{
if (millis() < weapon_timer_1)
{countdown_wt_1 = ( ( weapon_timer_1 - millis() ) /1000 ); // calculate current weapon timer
weapon_1_active = true;
}
else
{weapon_1_active = false;}
if (weapon_1_active)
{
if (countdown_wt_1 > (weapon_active_time/1000))
{ digitalWrite (weapon1_outPin,HIGH); } // activate the weapon relay if time not expired
else
{
digitalWrite (weapon1_outPin,LOW); // LED drive from relay led ?
LCDsetadd (0); // ** This block of code might be disabled if display conflict not desireable
LCDmessage ("WeaponRecharge");
if (countdown_wt_1 < 10 )
{ LCDmessage(" "); } // Insert a space if <10seconds left to pad position
PString (print_buffer, 2, countdown_wt_1); // turn weapon timer value into string
LCDmessage (print_buffer); // and send it
}
}
else // only do this if weapon not active
if ( play_timer_1 > millis() ) // AND bot is active - no weapons after play expired
{
weapon1_change = weapon1_in.update(); // then check to see if weapon button pushed
if (weapon1_in.read() && weapon1_change) // if debounced button is pushed
{weapon_timer_1 = millis() + weapon_cycle_time;} // then start the weapon timer
}
}
void bot_2_weapon_check()
{
if (millis() < weapon_timer_2)
{countdown_wt_2 = ( ( weapon_timer_2 - millis() ) /1000 ); // calculate current weapon timer
weapon_2_active = true;
}
else
{weapon_2_active = false;}
if (weapon_2_active)
{
if (countdown_wt_2 > (weapon_active_time/1000))
{ digitalWrite (weapon2_outPin,HIGH); } // activate the weapon relay if time not expired
else
{
digitalWrite (weapon2_outPin,LOW); // LED drive from relay led ?
LCDsetadd (64); // ** This block of code might be disabled if display conflict not desireable
LCDmessage ("WeaponRecharge");
if (countdown_wt_2 < 10 )
{ LCDmessage(" "); } // Insert a space if <10seconds left to pad position
PString (print_buffer, 2, countdown_wt_2); // turn weapon timer value into string
LCDmessage (print_buffer); // and send it
}
}
else // only do this if weapon not active
if ( play_timer_2 > millis() ) // AND bot is active - no weapons after play expired
{
weapon2_change = weapon2_in.update(); // then check to see if weapon button pushed
if (weapon2_in.read() && weapon2_change) // if debounced button is pushed
{weapon_timer_2 = millis() + weapon_cycle_time;} // then start the weapon timer
}
}
//}
//}
_________________ Great minds discuss ideas. Average minds discuss events. Small minds discuss people
|