-#include <LiquidCrystal.h>
+// No longer using HD44780-comaptible display,
+// Moving to a brand new world of dot-matrix display tech!
+// Using LCD library from http://code.google.com/p/pcd8544/
+#include <PCD8544.h>
+
#include <TimerOne.h>
+#include <EEPROM.h>
+
+// Undefine this whenever a "release" or "flight-test" build is made.
+// Defining DEBUG sets some crazy values for things like battery warning,
+// and includes a whole bunch of debugging-related code ...
+#define DEBUG 1
#define MAX_INPUTS 8
+// Update this _every_ time a change in datastructures that
+// can/will ber written to EEPROM is done. EEPROM data is
+// read/written torectly into/from the data structures using
+// pointers, so every time a data-set change occurs, the EEPROM
+// format changes as well..
+#define EEPROM_VERSION 7
+
+// Some data is stored in fixed locations, e.g.:
+// * The EEPROM version number for the stored data (loc 0)
+// * The selected model configuration number (loc 1)
+// * (add any other fixed-loc's here for doc-purpose)
+// This means that any pointer-math-operations need a BASE
+// adress to start calc'ing from. This is defined as:
+#define EE_BASE_ADDR 10
+
+// Having to repeat tedious base-address-calculations for the
+// start of model data should be unnessecary. Plus, updating
+// what data is stored before the models will mean that each
+// of those calculations must be updated. A better approach is
+// to define the calculation in a define!
+// NOTE: If new data is added in front of the model data,
+// this define must be updated!
+#define EE_MDL_BASE_ADDR (EE_BASE_ADDR+(sizeof(input_cal_t)+ 10))
+
+// Just as a safety-precaution, update/change this if a chip with
+// a different internal EEPROM size is used. Atmega328p has 1024 bytes.
+#define INT_EEPROM_SIZE 1024
+
+#define MAX_MODELS 4 // Nice and random number..
+
+
// --------------- ADC related stuffs.... --------------------
struct input_cal_t // Struct type for input calibration values
*/
};
volatile model_t model;
+unsigned char current_model; // Using uchar to spend a single byte of mem..
// ----------------- Display related stuffs --------------------
-LiquidCrystal lcd( 12, 11, 10, 6, 7, 8, 9);
-// Parameters are: rs, rw, enable, d4, d5, d6, d7 pin numbers.
+PCD8544 lcd( 8, 9, 10, 11, 12);
+// Param: sclk, sdin, dc, reset, sce
// ----------------- PPM related stuffs ------------------------
// The PPM generation is handled by Timer0 interrupts, and needs
#define framelength 21000 // Max length of frame
#define seplength 300 // Lenght of a channel separator
-#define chmax 1550 // Max lenght of channel pulse
-#define chmin 620 // Min length of channel
+#define chmax 1600 // Max lenght of channel pulse
+#define chmin 495 // Min length of channel
#define chwidht (chmax - chmin)// Useable time of channel pulse
// ----------------- Menu/IU related stuffs --------------------
// Voltage sense pin is connected to a 1/3'd voltage divider.
#define BATTERY_CONV (10 * 3 * (5.0f/1024.0f))
+
+#ifdef DEBUG
+// The following values are for DEBUGGING ONLY!!
#define BATTERY_LOW 92
+#define BATTERY_CRITICAL 0
+#else
+#define BATTERY_LOW 92
+#define BATTERY_CRITICAL 92
+#endif
enum {
VALUES,
BATTERY,
TIMER,
+ CURMODEL,
MENU
}
displaystate;
INVERTS,
DUALRATES,
EXPOS, // Some radios have "drawn curves", i.e. loopup tables stored in external EEPROM ...
- DEBUG,
+ DEBUG_DUMP,
SAVE
}
menu_mainstate;
boolean running;
} clock_timer;
+#ifdef DEBUG
// ----------------- DEBUG-STUFF --------------------
unsigned long prev_loop_time;
unsigned long avg_loop_time;
unsigned long t;
-
+#endif
// ---------- CODE! -----------------------------------
pinMode(4, OUTPUT); // s2
pinMode(5, OUTPUT); // e
- lcd.begin(16,2);
+ lcd.begin(84, 48);
lcd.print("Starting....");
+#ifdef DEBUG
Serial.begin(9600);
Serial.println("Starting....");
+#endif
+
delay(500);
+
+ model_defaults();
read_settings();
- pinMode(A5, OUTPUT); // PPM output pin
- do_channel = false;
- set_timer( seplength );
- Timer1.initialize(framelength);
- Timer1.attachInterrupt(ISR_timer);
-
displaystate = VALUES;
// Arduino believes all pins on Port C are Analog.
if ( !keys[KEY_UP])
calibrate();
+#ifdef DEBUG
// Debugging: how long does the main loop take on avg...
t = micros();
avg_loop_time = t;
prev_loop_time = t;
+#endif
+
+ // Initializing the stopwatch timer/clock values...
+ clock_timer = (clock_timer_t){0, 0, 0, false};
- // Setting this here to be sure I do not forget to init' it....
- // These initializations should be done by read_settings from eeprom,
- // and this "default model values" should probably be moved
- // out to a section of read_settings when handling "new model", or
- // to a separate model_defaults function...
- model.channels = 6;
+ pinMode(A5, OUTPUT); // PPM output pin
+ do_channel = false;
+ set_timer( seplength );
+ Timer1.initialize(framelength);
+ Timer1.attachInterrupt(ISR_timer);
+
+ lcd.clear();
+
+}
+
+void model_defaults( void )
+{
+ // This function provides default values for model data
+ // that is not a result of stick input, or in other words:
+ // provides defautls for all user-configurable model options.
+
+ // Remember to update this when a new option/element is added
+ // to the model_t struct (preferably before implementing the
+ // menu code that sets those options ...)
+
+ // This is used when a user wants a new, blank model, a reset
+ // of a configured model, and (most important) when EEPROM
+ // data format changes.
+ // NOTE: This means that stored model conficuration is reset
+ // to defaults when the EEPROM version/format changes.
+ model.channels = 8;
model.rev[0] = model.rev[1] = model.rev[2] = model.rev[3] =
model.rev[4] = model.rev[5] = model.rev[6] = model.rev[7] = false;
model.dr[0] = model.dr[1] = model.dr[2] = model.dr[3] = 0;
model.dr[4] = model.dr[5] = model.dr[6] = model.dr[7] = 100;
- // Initializing the stopwatch timer/clock values...
- clock_timer = (clock_timer_t){0, 0, 0, false};
}
// ---------- Arduino main loop -----------------------
-void loop () {
-
- // Determine if the UI needs to run...
- boolean disp;
- if ( millis() - last > UI_INTERVAL ) {
- last = millis();
- disp = true;
- }
- else disp = false;
+void loop ()
+{
process_inputs();
battery_val = analogRead(1) * BATTERY_CONV;
if ( battery_val < BATTERY_LOW ) {
digitalWrite(13, 1); // Simulate alarm :P
+ }
+ if ( battery_val < BATTERY_CRITICAL ) {
displaystate = BATTERY;
}
- if ( disp )
- {
+ if ( millis() - last > UI_INTERVAL )
+ {
+ last = millis();
ui_handler();
}
+
+#ifdef DEBUG
if ( displaystate != MENU )
{
// Debugging: how long does the main loop take on avg,
avg_loop_time = ( t - prev_loop_time + avg_loop_time ) / 2;
prev_loop_time = t;
}
+#endif
// Whoa! Slow down partner! Let everything settle down before proceeding.
delay(5);
for (i=0; i<MAX_INPUTS; i++) {
input_cal.min[i] = 1024;
+ input_cal.center[i] = 512;
input_cal.max[i] = 0;
}
+
while ( num_calibrations-- )
{
for (i=0; i<MAX_INPUTS; i++) {
// TODO: WILL need to do center-point calibration after min-max...
lcd.clear();
+ lcd.print("Saving to EEPROM");
+ write_calibration();
+ lcd.setCursor(0 , 1);
lcd.print("Done calibrating");
-
+
Serial.print("Done calibrating");
delay(2000);
}
+void write_calibration(void)
+{
+ int i;
+ unsigned char v;
+ const byte *p;
+
+ // Set p to be a pointer to the start of the input calibration struct.
+ p = (const byte*)(const void*)&input_cal;
+
+ // Iterate through the bytes of the struct...
+ for (i = 0; i < sizeof(input_cal_t); i++)
+ {
+ // Get a byte of data from the struct...
+ v = (unsigned char) *p;
+ // write it to EEPROM
+ EEPROM.write( EE_BASE_ADDR + i, v);
+ // and move the pointer to the next byte in the struct.
+ *p++;
+ }
+}
+
void read_settings(void)
{
- // Dummy. Will be modified to read model settings from EEPROM
- for (int i=0; i<=7; i++) {
- input_cal.min[i] = 0;
- input_cal.center[i] = 512;
- input_cal.max[i] = 1024;
+ int i;
+ unsigned char v;
+ byte *p;
+
+ v = EEPROM.read(0);
+ if ( v != EEPROM_VERSION )
+ {
+ // All models have been reset. Set the current model to 0
+ current_model = 0;
+ EEPROM.write(1, current_model);
+
+ calibrate();
+ model_defaults();
+ // The following does not yet work...
+ for ( i = 0; i < MAX_MODELS; i++)
+ write_model_settings(i);
+
+
+ // After saving calibration data and model defaults,
+ // update the saved version-identifier to the current ver.
+ EEPROM.write(0, EEPROM_VERSION);
+ }
+
+ // Read calibration values from EEPROM.
+ // This uses simple pointer-arithmetic and byte-by-byte
+ // to put bytes read from EEPROM to the data-struct.
+ p = (byte*)(void*)&input_cal;
+ for (i = 0; i < sizeof(input_cal_t); i++)
+ *p++ = EEPROM.read( EE_BASE_ADDR + i);
+
+ // Get the previously selected model from EEPROM.
+ current_model = EEPROM.read(1);
+ read_model_settings( current_model );
+}
+
+void read_model_settings(unsigned char mod_no)
+{
+ int model_address;
+ int i;
+ unsigned char v;
+ byte *p;
+
+ // Calculate the EEPROM start adress for the given model (mod_no)
+ model_address = EE_MDL_BASE_ADDR + (mod_no * sizeof(model_t));
+
+ // Do not try to write the model to EEPROM if it won't fit.
+ if ( INT_EEPROM_SIZE < (model_address + sizeof(model_t)) )
+ {
+ lcd.clear();
+ lcd.print("Aborting READ");
+ lcd.setCursor(0 , 1);
+ lcd.print("Invalid location");
+ delay(2000);
+ return;
+ }
+
+ lcd.clear();
+ lcd.print("Reading model ");
+ lcd.print( (int)mod_no );
+
+ // Pointer to the start of the model_t data struct,
+ // used for byte-by-byte reading of data...
+ p = (byte*)(void*)&model;
+ for (i = 0; i < sizeof(model_t); i++)
+ *p++ = EEPROM.read( model_address++ );
+
+#ifdef DEBUG
+ serial_dump_model();
+#endif
+
+ lcd.setCursor(0 , 1);
+ lcd.print("... Loaded.");
+ delay(1000);
+}
+
+void write_model_settings(unsigned char mod_no)
+{
+ int model_address;
+ int i;
+ unsigned char v;
+ byte *p;
+
+ // Calculate the EEPROM start adress for the given model (mod_no)
+ model_address = EE_MDL_BASE_ADDR + (mod_no * sizeof(model_t));
+
+ // Do not try to write the model to EEPROM if it won't fit.
+ if ( INT_EEPROM_SIZE < (model_address + sizeof(model_t)) )
+ {
+ lcd.clear();
+ lcd.print("Aborting SAVE");
+ lcd.setCursor(0 , 1);
+ lcd.print("No room for data");
+ delay(2000);
+ return;
}
+
+ lcd.clear();
+ lcd.print("Saving model ");
+ lcd.print( (int)mod_no);
+
+ // Pointer to the start of the model_t data struct,
+ // used for byte-by-byte reading of data...
+ p = (byte*)(void*)&model;
+
+ // Write/serialize the model data struct to EEPROM...
+ for (i = 0; i < sizeof(model_t); i++)
+ EEPROM.write( model_address++, *p++);
+
+ lcd.setCursor(0 , 1);
+ lcd.print(".. done saving.");
+ delay(200);
}
-void write_settings(void)
+#ifdef DEBUG
+void serial_dump_model ( void )
{
- // Dummy. Not used anywhere. Will be fleshed out to save settings to EEPROM.
+ int i;
+ int model_address;
+ // Calculate the EEPROM start adress for the given model (mod_no)
+ model_address = EE_MDL_BASE_ADDR + (current_model * sizeof(model_t));
+ Serial.print("Current model: ");
+ Serial.println( (int)current_model );
+ Serial.print("Models base addr: ");
+ Serial.println( EE_MDL_BASE_ADDR );
+ Serial.print("Model no: ");
+ Serial.println( current_model, 10 );
+ Serial.print("Size of struct: ");
+ Serial.println( sizeof( model_t) );
+ Serial.print("Model address: ");
+ Serial.println( model_address );
+ Serial.print("End of model: ");
+ Serial.println( model_address + sizeof(model_t) );
+
+ Serial.println();
+
+ Serial.print("Channel reversions: ");
+ for ( i = 0; i<8; i++)
+ {
+ Serial.print(i);
+ Serial.print("=");
+ Serial.print(model.rev[i], 10);
+ Serial.print(" ");
+ }
+ Serial.println();
+
+ Serial.print("DR1 inp 0: ");
+ Serial.println(model.dr[0]);
+ Serial.print("DR1 inp 1: ");
+ Serial.println(model.dr[1]);
+ Serial.print("DR1 LO val: ");
+ Serial.println(model.dr[4]);
+ Serial.print("DR1 HI val: ");
+ Serial.println(model.dr[5]);
+ Serial.print("DR2 inp 0: ");
+ Serial.println(model.dr[2]);
+ Serial.print("DR2 inp 1: ");
+ Serial.println(model.dr[3]);
+ Serial.print("DR2 LO val: ");
+ Serial.println(model.dr[6]);
+ Serial.print("DR2 HI val: ");
+ Serial.println(model.dr[7]);
+
+ for (i=0; i<MAX_INPUTS; i++) {
+ Serial.print("Input #");
+ Serial.print(i);
+ Serial.print(" pct: ");
+ Serial.print(model.stick[i]);
+ Serial.print(" min: ");
+ Serial.print(input_cal.min[i]);
+ Serial.print(" max: ");
+ Serial.print(input_cal.max[i]);
+ Serial.println();
+ }
}
+#endif
void scan_keys ( void )
{
// Test to see if dualrate-switch #1 applies to channel...
if ( ( current_input == ( model.dr[2]-1) ) || ( current_input == ( model.dr[3]-1) ) )
{
- if ( !keys[KEY_DR1] )
+ if ( !keys[KEY_DR2] )
dr_val = ((float)model.dr[6])/100.0;
else
dr_val = ((float)model.dr[7])/100.0;
}
}
+
+#ifdef DEBUG
void serial_debug()
{
int current_input;
Serial.print("Average loop time:");
Serial.println(avg_loop_time);
+ Serial.print("Free RAM:");
+ Serial.print( FreeRam() );
Serial.println();
}
+#endif
void dr_inputselect( int no, int in )
{
lcd.setCursor(0 , 0);
lcd.print("D/R switch ");
lcd.print( no + 1 );
- lcd.print(" ");
+ //lcd.print(" ");
+
+ lcd.setCursor(0 , 1);
+ lcd.print(" ");
lcd.setCursor(0 , 1);
lcd.print("Input ");
lcd.print(in+1);
lcd.setCursor(0 , 0);
lcd.print("D/R switch ");
lcd.print( menu_substate - 3 );
- lcd.print(" ");
+
+
+ lcd.setCursor(0 , 1);
+ lcd.print(" ");
lcd.setCursor(0 , 1);
lcd.print( state ? "HI" : "LO" );
lcd.print(" Value :");
int col;
scan_keys();
+ if ( check_key( KEY_UP) || check_key(KEY_DOWN))
+ lcd.clear();
+
if ( displaystate != MENU )
{
menu_substate = 0;
return;
}
else if ( check_key(KEY_UP) && displaystate == TIMER ) {
+ displaystate = CURMODEL;
+ return;
+ }
+ else if ( check_key(KEY_UP) && displaystate == CURMODEL ) {
displaystate = VALUES;
return;
}
return;
}
}
-
+
digitalWrite(13, digitalRead(13) ^ 1 );
switch ( displaystate )
{
case VALUES:
- int current_input;
+ int current_input;
+
+
+ row = 0;
+ col = 0;
+
+ lcd.setCursor(col, row);
+ lcd.print(" ");
+
+ lcd.setCursor(col, row);
+ lcd.print("S1:");
+ lcd.print( keys[KEY_DR1] ? "On " : "Off" );
+ lcd.print(" S2:");
+ lcd.print( keys[KEY_DR2] ? "On " : "Off" );
+
+ row = 2;
+ col = 0;
+
for (current_input=0; current_input<MAX_INPUTS; current_input++) {
- // In channel value display, do a simple calc
- // of the LCD row & column location. With 8 channels
- // we can fit eight channels as percentage values on
- // a simple 16x2 display...
- if ( current_input < 4 )
- {
- col = current_input * 4;
- row = 0;
- }
- else
- {
- col = (current_input-4) * 4;
- row = 1;
- }
+ if (row == 6)
+ {
+ row = 2;
+ col = 40;
+ }
+
// Overwriting the needed positions with
// blanks cause less display-flicker than
// actually clearing the display...
lcd.setCursor(col, row);
- lcd.print(" ");
+ lcd.print(" ");
+
lcd.setCursor(col, row);
+
+ char mod_indicator = NULL;
+
+ if (( keys[KEY_DR1] ) && (( model.dr[0] == current_input+1) || ( model.dr[1] == current_input+1)))
+ mod_indicator = '/';
+
+ if (( keys[KEY_DR2] ) && (( model.dr[2] == current_input+1) || ( model.dr[3] == current_input+1)))
+ if (mod_indicator) mod_indicator = '|';
+ else mod_indicator = '\\';
+
+ if ( mod_indicator) lcd.print(mod_indicator);
+ else lcd.print(" ");
+
+ lcd.print( current_input+1);
+ lcd.print(":");
// Display uses percents, while PPM uses ratio....
// New format on stick values
lcd.print( (int)model.stick[current_input] );
+
+ row++;
}
break;
case BATTERY:
- lcd.clear();
+ lcd.setCursor(0 , 0);
lcd.print("Battery level: ");
lcd.setCursor(0 , 1);
+ lcd.print( " ");
+ lcd.setCursor(0 , 1);
lcd.print( (float)battery_val/10);
lcd.print("V");
if ( battery_val < BATTERY_LOW ) lcd.print(" - WARNING");
int minutes;
int seconds;
- lcd.clear();
+ lcd.setCursor(0 , 0);
lcd.print("Timer: ");
lcd.print( clock_timer.running ? "Running" : "Stopped" );
lcd.setCursor(5 , 1);
+ lcd.print(" ");
+ lcd.setCursor(5 , 1);
if ( clock_timer.running )
{
clock_timer.value = millis() - (clock_timer.start + clock_timer.init);
}
break;
+
+
+ case CURMODEL:
+ lcd.setCursor(0 , 0);
+ lcd.print("Model #: ");
+ lcd.print( (int)current_model );
+ lcd.setCursor(0 , 1);
+ lcd.print("NAME (not impl)");
+ break;
+
+
+
case MENU:
- lcd.clear();
+ lcd.setCursor(0 , 0);
switch ( menu_mainstate )
{
case TOP:
+ lcd.setCursor(0 , 0);
lcd.print("In MENU mode!");
lcd.setCursor(0 , 1);
- lcd.print("Esc UP. Scrl DN.");
+ lcd.print("UP to quit.");
+ lcd.setCursor(0 , 2);
+ lcd.print("DOWN to scroll");
+
menu_substate = 0;
if ( check_key(KEY_UP) ) {
displaystate = VALUES;
+ lcd.clear();
return;
}
else if ( check_key(KEY_DOWN) ) {
+ lcd.clear();
menu_mainstate = INVERTS;
return;
}
if ( check_key(KEY_UP) ) {
menu_mainstate = TOP;
+ lcd.clear();
return;
}
else if ( check_key(KEY_DOWN) ) {
menu_mainstate = DUALRATES;
+ lcd.clear();
return;
}
if ( check_key(KEY_UP) ) {
menu_mainstate = INVERTS;
+ lcd.clear();
return;
}
if ( check_key(KEY_DOWN) ) {
menu_mainstate = EXPOS;
+ lcd.clear();
return;
}
if ( check_key(KEY_RIGHT) ) {
// on the time-horizon :P
if ( check_key(KEY_UP ) ) {
menu_mainstate = DUALRATES;
+ lcd.clear();
return;
}
+#ifdef DEBUG
if ( check_key(KEY_DOWN ) ) {
- menu_mainstate = DEBUG;
+ menu_mainstate = DEBUG_DUMP;
+ lcd.clear();
return;
}
+#else
+ if ( check_key(KEY_DOWN ) ) {
+ menu_mainstate = TOP;
+ lcd.clear();
+ return;
+ }
+
+#endif
break;
-
- case DEBUG:
+
+#ifdef DEBUG
+ case DEBUG_DUMP:
lcd.setCursor(0 , 0);
lcd.print("Dumping debug to");
lcd.setCursor(0 , 1);
if ( check_key(KEY_UP ) ) {
// FIXME: Remember to update the "Scroll up" state!
menu_mainstate = EXPOS;
+ lcd.clear();
return;
} else if ( check_key(KEY_DOWN ) ) {
menu_mainstate = SAVE;
+ lcd.clear();
return;
}
break;
-
+#endif
default:
lcd.print("Not implemented");
lcd.setCursor(0 , 1);
return;
}
+#ifdef DEBUG
+/* The following code is taken from the
+ Arduino FAT16 Library by William Greiman
+ The code may or may-not survive in the long run,
+ depending on what licensing-terms we decide on.
+ The license will be open source, but the FAT16lib
+ is GPL v3, and I (fishy) am personally not so sure about that...
+
+ On the other hand... This code is a very "intuitive approach",
+ so contacting the author may give us the option of relicencing just this bit...
+*/
+static int FreeRam(void) {
+ extern int __bss_end;
+ extern int* __brkval;
+ int free_memory;
+ if (reinterpret_cast<int>(__brkval) == 0) {
+ // if no heap use from end of bss section
+ free_memory = reinterpret_cast<int>(&free_memory)
+ - reinterpret_cast<int>(&__bss_end);
+ } else {
+ // use from top of stack to heap
+ free_memory = reinterpret_cast<int>(&free_memory)
+ - reinterpret_cast<int>(__brkval);
+ }
+ return free_memory;
+}
+#endif