#include <LiquidCrystal.h>
/*
* Boost Gauge project for my Nissan Stagea
* To Do:
* 1. Fix the boost bar graph scaling to re-read the values after its been changed in the menu - DONE
* 2. Move the alarm code and all the calculation code out of the display printing routine. - DONE
* - Perhaps move the display printing into a suborutine? - LATER?
* 3. Read/Write EEPROM for saving etc variable. Make sure to handle the case where there is no data in EEPROM.
* - http://arduino.cc/playground/Code/EEPROMWriteAnything
* 3. Think of a better way to do the menus and tidy things up. Code is pretty nasty right now.
* 4. Add a peak value feature with adjustable timeout.
* 5. Fix display errors when graph scale is set low and boost significantly higher causes it to wrap to the first line.
*/
/*
*LCD Pinout Cable Pin header Arduino Pin
*1 - GND 1
*2 - +5V 2
*3 - Contrast 3
*4 - RS 4 2
*5 - RW 5 3
*6 - E(Enable) 6 4
*7 - DB0
*8 - DB1
*9 - DB2
*10 - DB3
*11 - DB4 7 5
*12 - DB5 8 6
*13 - DB6 9 7
*14 - DB7 10 8
*15 - Backlight +5V 11 NC
*16 - Backlight GND 12 NC
*/
// LiquidCrystal lcd(rs, rw, enable, d4, d5, d6, d7) - 4 bit mode
LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8);
// LiquidCrystal lcd(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7) - 8 bit mode
//LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
// Analog 0-5v Sensor in
word Analog1pin = A5;
int Analog1Raw = 0;
int Analog1mV = 0;
float Analog1AFR = 0;
// Shift Regsiter pins for Button Input
#define ShiftData 9
#define ShiftClock 10
#define ShiftLatch 11
// Other pins
#define BuzzerPin 12
// Stored config
#define CONFIG_START 0
#define CONFIG_VERSION stageav1
/* -- This shit doesnt work....
struct ConfigStruct {
int BoostMin; // BoostMin for graph Scaling
int BoostMax; // BoostMax for graph Scaling
int BoostAlarm;// Alarm for audible/visual boost warning
};
ConfigStruct Config = { 0 , 0 , 0 };
Config.BoostMin = 0;
Config.BootMax = 500;
Config.BoostAlarm = 300;
*/
// Variable for buttons
unsigned int buttonScanInterval = 100; // Interval between checking for button press
unsigned long buttonScanLastms = 0; // Last time we looked
byte ButtonBits = 255; // B11111111
unsigned int ButtonRepeatDelay = 200; // 300ms delay for button repeat
unsigned long ButtonRepeatLastms = 0; // Last time a button was pressed
// Variables for tracking menu states :: 0 - Run (no menu), 1 - Display Menu
byte MenuState = 0;
byte SubMenu = 0; // Used to track what SubMenu we're in
unsigned int MenuTimeout = 10000; // 10 second timeout for menus
// Text display timer variables
unsigned int timerTextInterval = 100; // interval between sensor data updates
unsigned long timerTextLastms = 0; // Last time we looked
// mV for boost min and max. This is used for the bar graph
int BoostMin = 0;
int BoostMax = 500; // 17 psi
int BoostAlarm = 300; // 10 psi
// Bar Graph magic
unsigned int timerBarGraphInterval = 10; // interval between bar graph updates
unsigned long timerBarGraphLastms = 0; // Last time we looked
int LCDWidth = 16; // LCD Display width in chars
int LCDPixelWidth = 5; // Each char is 5 pixels wide
int LCDBars = LCDWidth * LCDPixelWidth; // This is the number of pixels we have to use
int mVPerBar = BoostMax / LCDBars; // Bar graph magic calculations
int mVPerChar = mVPerBar * LCDPixelWidth; //mV per full char
int BarGraphValue = 0; // This variable is used further down when printing the bar graph
int BarGraphWritten = 0; // This variable is used to track how much whitespace we need to print after the bar to clear the display.
const float kpa2psi = 0.145037738;
void setup()
{
// lcd.begin(24, 4);
lcd.begin(16, 2);
// Setup our custom chars for the bar graph
byte bar1[8] = {
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
};
lcd.createChar(1, bar1);
byte bar2[8] = {
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
};
lcd.createChar(2, bar2);
byte bar3[8] = {
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
};
lcd.createChar(3, bar3);
byte bar4[8] = {
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
};
lcd.createChar(4, bar4);
byte bar5[8] = {
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
};
lcd.createChar(5, bar5);
byte leftArrow[8] = {
B00010,
B00110,
B01111,
B11111,
B01111,
B00110,
B00010,
};
lcd.createChar(6, leftArrow);
byte rightArrow[8] = {
B01000,
B01100,
B11110,
B11111,
B11110,
B01100,
B01000,
};
lcd.createChar(7, rightArrow);
// Smiley - just for fun
byte smile[8] = {
B10001,
B00000,
B00100,
B01100,
B00000,
B10001,
B01110,
};
lcd.createChar(8, smile);
lcd.begin(16, 2);
Serial.begin(9600);
// Set Pin modes
pinMode(BuzzerPin, OUTPUT); // alarm buzzer
digitalWrite(BuzzerPin, LOW); // Initially, off.
pinMode (Analog1pin, INPUT); // boost sensor in
// Set Shift Register Pin Modes
pinMode(ShiftData, INPUT);
pinMode(ShiftClock, OUTPUT);
pinMode(ShiftLatch, OUTPUT);
lcd.clear();
lcd.setCursor(0,0);
lcd.print ("Boost Gauge...");
delay(500);
lcd.clear();
}
void loop()
{
// Get the current ms value to compare timers with.
unsigned long currentMillis = millis();
//////////////////////////
// Button Input/Menus //
//////////////////////////
// Scan for button presses at the defined interval
if (currentMillis - buttonScanLastms > buttonScanInterval)
{
buttonScanLastms = currentMillis;
// Reset to "all-on"
// ButtonBits = 255; // B11111111
// Reset to "all-off"
ButtonBits = 0;
// Delay for key repeat - Delay the rescan if we've had a keypress detected within the ButtonRepeat time.
if (currentMillis - ButtonRepeatLastms > ButtonRepeatDelay)
{
/*
Using shift register example from http://arduino.cc/en/Tutorial/ShftIn12
*/
//Pulse the latch pin high to collect parallel data.
digitalWrite(ShiftLatch,1);
delayMicroseconds(20);
//Drop the latch low to start shifting data into the MCU serially.
digitalWrite(ShiftLatch,0);
// Collect the data into a byte var.
ButtonBits = shiftIn(ShiftData, ShiftClock, MSBFIRST);
// Serial.println(ButtonBits, BIN);
}
if (ButtonBits == B00000000) // No buttons pressed
{
// We can use this section for timeout code if necessary later.
}
else if (ButtonBits == B10000000) // Escape (Top Left)
{
ButtonRepeatLastms = currentMillis;
// If we're not in a Menu, set the MenuState, and then print the main menu.
if (MenuState == 0)
{
MenuState = 1; // Set this flag to say we're in a menu
SubMenu = 0; // Reset this to be main menu
WriteMenu(SubMenu);
}
// If we're in a menu, set MenuState to 0 and clear the display in preperation for sensor view.
else if (MenuState > 0)
{
MenuState = 0; // Set this flag to say we're not in a menu
lcd.clear();
}
}
else if (ButtonBits == B01000000) // Up (Top Middle)
{
ButtonRepeatLastms = currentMillis;
// If We're in a Menu
if (MenuState > 0)
{
// And we're in Menu 1 (Graph MaxBoost)
if (SubMenu == 1)
{
BoostMax = BoostMax + 5;
mVPerBar = BoostMax / LCDBars; // Bar graph magic calculations
mVPerChar = mVPerBar * LCDPixelWidth; //mV per full char
}
// And we're in Menu 2 (Boost Alarm)
else if (SubMenu == 2)
{
BoostAlarm = BoostAlarm + 5;
}
WriteMenu(SubMenu);
}
}
else if (ButtonBits == B00100000) // Enter (Top Right)
{
ButtonRepeatLastms = currentMillis;
}
else if (ButtonBits == B00010000) // Not used
{
ButtonRepeatLastms = currentMillis;
}
else if (ButtonBits == B00001000) // Right (Bottom Right)
{
ButtonRepeatLastms = currentMillis;
// If we're in a menu, Increment the SubMenu variable and print the menu.
if (MenuState > 0)
{
SubMenu++;
WriteMenu(SubMenu);
}
}
else if (ButtonBits == B00000100) // Down (Bottom Middle)
{
ButtonRepeatLastms = currentMillis;
// If We're in a Menu
if (MenuState > 0)
{
// And we're in Menu 1 (Graph MaxBoost)
if (SubMenu == 1)
{
BoostMax = BoostMax - 5;
mVPerBar = BoostMax / LCDBars; // Bar graph magic calculations
mVPerChar = mVPerBar * LCDPixelWidth; //mV per full char
}
// And we're in Menu 2 (Boost Alarm)
else if (SubMenu == 2)
{
BoostAlarm = BoostAlarm - 5;
}
WriteMenu(SubMenu);
}
}
else if (ButtonBits == B00000010) //Left (Bottom Left)
{
ButtonRepeatLastms = currentMillis;
// If we're in a menu, Decrement the SubMenu variable and print the menu.
if (MenuState > 0)
{
SubMenu--;
WriteMenu(SubMenu);
}
}
else if (ButtonBits == B00000001) // Not Used
{
ButtonRepeatLastms = currentMillis;
}
else
{
// Do Nothing if some idiot presses more than one button at a time.
}
}
//////////////////////////////////////
// Bar Graph / Alarm //
/////////////////////////////////////
if (currentMillis - timerBarGraphLastms > timerBarGraphInterval)
{
timerBarGraphLastms = currentMillis;
//Read our sensor "Analog1"
Analog1Raw = analogRead(Analog1pin);
// Turn the alarm on or off.
if (Analog1Raw < BoostAlarm) digitalWrite(12, LOW); // Everything is fine...
if (Analog1Raw >= BoostAlarm) digitalWrite(12, HIGH); // Alarm!!
// We only want to print the bar graph stuff if we're not in a menu.
if (MenuState == 0) {
lcd.setCursor (0,1);
BarGraphValue = Analog1Raw;
BarGraphWritten = 0;
// To speed bar graph drawing, each full char is equivelent to 1/16th of the scale
// Print a full char for every full 1/16th of the scale and then print out a single part char for the remainder.
while (BarGraphValue > mVPerChar)
{
lcd.write(5); // Write full blocks each representing 1x mvPerChar.
BarGraphValue = BarGraphValue - mVPerChar; // Drop the remaining BarGraphValue by what we've printed
BarGraphWritten++; // Increment the char printed counter
}
// Print the appropriate 'part char' representing the remainder BarGraphValue.
if (BarGraphValue > ( 3 * mVPerBar))
{
lcd.write(4);
}
else if (BarGraphValue > ( 2 * mVPerBar))
{
lcd.write(3);
}
else if (BarGraphValue > ( 1 * mVPerBar))
{
lcd.write(2);
}
else if (BarGraphValue > ( 0 * mVPerBar))
{
lcd.write(1);
}
BarGraphWritten++; //Increment the chars printed counter.
// Print whitespace to the end of the line. Always prints over whats there. May be inefficient on LCD IO.
while ((BarGraphWritten - LCDWidth) < 0)
{
lcd.write(" ");
BarGraphWritten++;
}
}
} //if (currentMillis - timerBarGraphLastms > timerBarGraphInterval)
/////////////////////////////
// Text Display //
/////////////////////////////
if (currentMillis - timerTextLastms > timerTextInterval && MenuState == 0 )
{
timerTextLastms = currentMillis;
//Debugging - Print the pin Voltage. 0-5000
/*
lcd.setCursor(12,0);
if (Analog1mV<1000) lcd.print(' ');
if (Analog1mV<100) lcd.print(' ');
if (Analog1mV<10) lcd.print(' ');
lcd.print(Analog1mV);
*/
// Map it to mV.
//Analog1mV = map(Analog1Raw, 0, 1023, 0, 5000);
//psi=(0.04+((Analog1mV/1000)/(0.004*5)))*kpa2psi
// float Analog1mVx = Analog1mV;
//float Analog1Psi = (0.04+((Analog1mVx/1000)/(0.004*5)))*kpa2psi;
float Analog1Psi = Raw2PSI(Analog1Raw);
lcd.setCursor(0,0);
if (Analog1Psi<10) lcd.print(' ');
lcd.print (Analog1Psi);
lcd.print ("psi");
} // if (currentMillis - timerTextLastms > timerTextInterval)
}
// Self explanitory
void WriteMenu (int menuID) {
// Clear the screen ready for menus
lcd.clear();
if (menuID == 0)
{
lcd.setCursor(0,0);
lcd.print("< Main Menu >");
lcd.setCursor(0,1);
lcd.print("Esc | Enter=Save");
}
else if (menuID == 1)
{
lcd.setCursor(0,0);
lcd.print("<Graph MaxBoost>");
lcd.setCursor(0,1);
lcd.print(Raw2PSI(BoostMax));
lcd.print ("psi");
}
else if (menuID == 2)
{
lcd.setCursor(0,0);
lcd.print("< Boost Alarm >");
lcd.setCursor(0,1);
lcd.print(Raw2PSI(BoostAlarm));
lcd.print ("psi");
}
// Unknown Menu ID.. oh noes!
else
{
lcd.setCursor(0,0);
lcd.print("< Undef Menu >");
lcd.setCursor(0,1);
lcd.print("menuID: ");
lcd.print(menuID);
}
}
float Raw2PSI(int value){
float result;
result = map(value, 0, 1023, 0, 5000);
result = (0.04+((result/1000)/(0.004*5)))*kpa2psi;
return result;
}
/* void padding( int number, byte width ) {
int currentMax = 10;
char returnString[width];
for (byte i = 0; i < width-1; i++){
if (number < currentMax) {
returnString[i]=("0");
}
currentMax *= 10;
}
// ToDo: Loop over the rest of the string, add to returnString and return
Serial.print(number);
}
*/