X-Git-Url: https://git.defcon.no/?a=blobdiff_plain;f=source%2FRCTXDuino%2FRCTXDuino.pde;h=d238b753edeece29ec1549d730be5011a6e29f91;hb=3c5f990b9bde180af15b5755df660aa11f5b7caf;hp=cf711bb4700d7789d8cb07855862cb55e8baa59b;hpb=eb26ecc53cac3453f083fefa643cdcff13ba8a06;p=rctxduino diff --git a/source/RCTXDuino/RCTXDuino.pde b/source/RCTXDuino/RCTXDuino.pde index cf711bb..d238b75 100644 --- a/source/RCTXDuino/RCTXDuino.pde +++ b/source/RCTXDuino/RCTXDuino.pde @@ -1,23 +1,68 @@ #include #include +#include + +#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 3 + +// 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.... -------------------- -int maxval[8]; // Stores maximum values read during calibration, setup() sets 1024 -int minval[8]; // Stores minimum values read during calibration, setup() sets 0 -int rev[8]; -int dr[8]; // The Dual-rate array uses magic numbers :P -/* dr[0] = Input channel #1 of 2 for D/R switch #1. 0 means off, 1-4 valid values. - dr[1] = Input channel #2 of 2 for D/R switch #1. 0 means off, 1-4 valid values. - dr[2] = Input channel #1 of 2 for D/R switch #2. 0 means off, 1-4 valid values. - dr[3] = Input channel #2 of 2 for D/R switch #2. 0 means off, 1-4 valid values. - dr[4] = D/R value for switch # 1 LOW(off). Value -100 to 100 in steps of 5. - dr[5] = D/R value for switch # 1 HIGH(on). Value -100 to 100 in steps of 5. - dr[6] = D/R value for switch # 1 LOW(off). Value -100 to 100 in steps of 5. - dr[7] = D/R value for switch # 1 HIGH(on). Value -100 to 100 in steps of 5. -*/ - -volatile float channel[8]; +struct input_cal_t // Struct type for input calibration values +{ + int min[MAX_INPUTS]; + int max[MAX_INPUTS]; + int center[MAX_INPUTS]; +} ; +input_cal_t input_cal; + +struct model_t +{ + int channels; // How many channels should PPM generate for this model ... + float stick[8]; // The (potentially recalc'ed) value of stick/input channel. + int raw[8]; + boolean rev[8]; + int dr[8]; // The Dual-rate array uses magic numbers :P + /* dr[0] = Input channel #1 of 2 for D/R switch #1. 0 means off, 1-4 valid values. + dr[1] = Input channel #2 of 2 for D/R switch #1. 0 means off, 1-4 valid values. + dr[2] = Input channel #1 of 2 for D/R switch #2. 0 means off, 1-4 valid values. + dr[3] = Input channel #2 of 2 for D/R switch #2. 0 means off, 1-4 valid values. + dr[4] = D/R value for switch # 1 LOW(off). Value -100 to 100 in steps of 5. + dr[5] = D/R value for switch # 1 HIGH(on). Value -100 to 100 in steps of 5. + dr[6] = D/R value for switch # 1 LOW(off). Value -100 to 100 in steps of 5. + dr[7] = D/R value for switch # 1 HIGH(on). Value -100 to 100 in steps of 5. + */ +}; +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); @@ -26,20 +71,22 @@ LiquidCrystal lcd( 12, 11, 10, 6, 7, 8, 9); // ----------------- PPM related stuffs ------------------------ // The PPM generation is handled by Timer0 interrupts, and needs // all modifiable variables to be global and volatile... -int max_channels = 6; // How many channels should PPM generate ... + volatile long sum = 0; // Frame-time spent so far volatile int cchannel = 0; // Current channnel volatile bool do_channel = true; // Is next operation a channel or a separator + // All time values in usecs // TODO: // The timing here (and/or in the ISR) needs to be tweaked to provide valid // RC PPM signals accepted by standard RC RX'es and the Microcopter... -long framelength = 21500; // Max length of frame -long seplength = 400; // Lenght of a channel separator -long chmax = 1700; // Max lenght of channel pulse -long chmin = 600; // Min length of channel -long chwidht = (chmax - chmin); // Useable time of channel pulse (1000) + +#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 chwidht (chmax - chmin)// Useable time of channel pulse // ----------------- Menu/IU related stuffs -------------------- @@ -63,6 +110,7 @@ enum { VALUES, BATTERY, TIMER, + CURMODEL, MENU } displaystate; @@ -88,10 +136,13 @@ int battery_val; #define UI_INTERVAL 250 unsigned long last = 0; -unsigned long timer_start = 0; -unsigned long timer_init = 0; -unsigned long timer_value = 0; -boolean timer_running = false; +struct clock_timer_t +{ + unsigned long start; + unsigned long init; + unsigned long value; + boolean running; +} clock_timer; // ----------------- DEBUG-STUFF -------------------- unsigned long prev_loop_time; @@ -116,10 +167,9 @@ void setup(){ Serial.begin(9600); Serial.println("Starting...."); delay(500); + + model_defaults(); read_settings(); - scan_keys(); - if ( keys[KEY_UP]) - calibrate(); pinMode(A5, OUTPUT); // PPM output pin do_channel = false; @@ -133,14 +183,41 @@ void setup(){ // In reality they are tri-purpose; ADC, Digital, Digital Interrupts // Unfortunately the interrupt mode is unusable in this scenario, but digital I/O works :P pinMode(A2, INPUT); + digitalWrite(A2, HIGH); + scan_keys(); + if ( !keys[KEY_UP]) + calibrate(); // Debugging: how long does the main loop take on avg... t = micros(); avg_loop_time = t; prev_loop_time = t; + + + // Initializing the stopwatch timer/clock values... + clock_timer = (clock_timer_t){0, 0, 0, false}; +} + +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. - dr[0] = dr[1] = dr[2] = dr[3] = 0; - dr[4] = dr[5] = dr[6] = dr[7] = 100; + // 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 = 6; + 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; } @@ -196,7 +273,7 @@ void set_timer(long time) boolean check_key( int key) { - return ( keys[key] && !prev_keys[key] ); + return ( !keys[key] && prev_keys[key] ); } void mplx_select(int pin) @@ -220,7 +297,6 @@ void mplx_select(int pin) void calibrate() { int i, r0, r1, r2, adc_in; - int calcount = 0; int num_calibrations = 200; lcd.clear(); @@ -229,52 +305,186 @@ void calibrate() lcd.print("their extremes.."); Serial.print("Calibration. Move all controls to their extremes."); - for (i=0; i<=7; i++) { - minval[i] = 1024; - maxval[i] = 0; + for (i=0; i maxval[i] ) { - maxval[i] = adc_in; + if ( adc_in > input_cal.max[i] ) { + input_cal.max[i] = adc_in; } delay(10); } - - calcount++; } // 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"); + + 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++) { - minval[i] = 0; - maxval[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 write_settings(void) +void read_model_settings(unsigned char mod_no) { - // Dummy. Not used anywhere. Will be fleshed out to save settings to EEPROM. + 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)); + + Serial.print("Models base addr: "); + Serial.println( EE_MDL_BASE_ADDR ); + Serial.print("Model no: "); + Serial.println( mod_no, 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) ); + + // 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(input_cal_t); i++) + *p++ = EEPROM.read( model_address++ ); + + 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(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(input_cal_t); i++) + EEPROM.write( model_address++, *p++); + + lcd.setCursor(0 , 1); + lcd.print(".. done saving."); + delay(1000); } + + void scan_keys ( void ) { int i, r0, r1, r2; @@ -298,14 +508,61 @@ void scan_keys ( void ) void process_inputs(void ) { - int current_input, r0, r1, r2, adc_in; - for (current_input=0; current_input<=7; current_input++) { + int current_input, adc_in, fact; + float min, max; + + for (current_input=0; current_input= max_channels ) + if ( cchannel >= model.channels ) { set_ppm_output( HIGH ); long framesep = framelength - sum; @@ -338,9 +595,17 @@ void ISR_timer(void) if ( do_channel ) { set_ppm_output( HIGH ); - long next_timer = (( chwidht * channel[cchannel] ) + chmin); + + // New format on stick values + // model.stick contains percentages, -100% to 100% in float. To make the timer-handling + // here as simple as possible. We want to calc the channel value as a "ratio-value", + // a float in the range 0..1.0. So, by moving the lower bound to 0, then cutting the + // range in half, and finally dividing by 100, we should get the ratio value. + // Some loss of presicion occurs, perhaps the algo' should be reconsidered :P + long next_timer = (( chwidht * ((model.stick[cchannel]+100)/200) ) + chmin); // Do sanity-check of next_timer compared to chmax ... while ( chmax < next_timer ) next_timer--; + while ( next_timer < chmin ) next_timer++; sum += next_timer; // Done with channel separator and value, @@ -356,18 +621,17 @@ void serial_debug() { int current_input; for (current_input=0; current_input<=7; current_input++) { - int v = (int)(channel[current_input] * 100); Serial.print("Input #"); Serial.print(current_input); - Serial.print(" value: "); - Serial.print(channel[current_input]); Serial.print(" pct: "); - Serial.print(v); + Serial.print(model.stick[current_input]); + Serial.print(" raw value: "); + Serial.print(model.raw[current_input]); Serial.print(" min: "); - Serial.print(minval[current_input]); + Serial.print(input_cal.min[current_input]); Serial.print(" max: "); - Serial.print(maxval[current_input]); + Serial.print(input_cal.max[current_input]); Serial.println(); } Serial.print("Battery level is: "); @@ -380,8 +644,8 @@ void serial_debug() } void dr_inputselect( int no, int in ) { - if ( dr[menu_substate] < 0 ) dr[menu_substate] = 4; - if ( dr[menu_substate] > 4 ) dr[menu_substate] = 0; + if ( model.dr[menu_substate] < 0 ) model.dr[menu_substate] = 4; + if ( model.dr[menu_substate] > 4 ) model.dr[menu_substate] = 0; lcd.setCursor(0 , 0); lcd.print("D/R switch "); @@ -392,15 +656,15 @@ void dr_inputselect( int no, int in ) lcd.print(in+1); lcd.print(": "); - if ( ! dr[menu_substate] ) lcd.print("Off"); - else lcd.print(dr[menu_substate]); + if ( ! model.dr[menu_substate] ) lcd.print("Off"); + else lcd.print(model.dr[menu_substate]); if ( check_key(KEY_INC) ) { - dr[menu_substate]++; + model.dr[menu_substate]++; return; } else if ( check_key(KEY_DEC) ) { - dr[menu_substate]--; + model.dr[menu_substate]--; return; } // Wrap around. @@ -427,14 +691,14 @@ void dr_value() lcd.print( state ? "HI" : "LO" ); lcd.print(" Value :"); - lcd.print( dr[pos] ); + lcd.print( model.dr[pos] ); - if ( keys[KEY_INC] ) { - if ( dr[pos] < 100) dr[pos] += 5; + if ( !keys[KEY_INC] ) { + if ( model.dr[pos] < 100) model.dr[pos] += 5; return; } - else if ( keys[KEY_DEC] ) { - if ( dr[pos] > -100) dr[pos] -= 5; + else if ( !keys[KEY_DEC] ) { + if ( model.dr[pos] > -100) model.dr[pos] -= 5; return; } @@ -458,6 +722,10 @@ void ui_handler() return; } else if ( check_key(KEY_UP) && displaystate == TIMER ) { + displaystate = CURMODEL; + return; + } + else if ( check_key(KEY_UP) && displaystate == CURMODEL ) { displaystate = VALUES; return; } @@ -496,8 +764,8 @@ void ui_handler() lcd.print(" "); lcd.setCursor(col, row); // Display uses percents, while PPM uses ratio.... - int v = (int)(channel[current_input] * 100); - lcd.print(v); + // New format on stick values + lcd.print( (int)model.stick[current_input] ); } break; @@ -522,16 +790,16 @@ void ui_handler() lcd.clear(); lcd.print("Timer: "); - lcd.print( timer_running ? "Running" : "Stopped" ); + lcd.print( clock_timer.running ? "Running" : "Stopped" ); lcd.setCursor(5 , 1); - if ( timer_running ) + if ( clock_timer.running ) { - timer_value = millis() - (timer_start + timer_init); + clock_timer.value = millis() - (clock_timer.start + clock_timer.init); } - hours = ( timer_value / 1000 ) / 3600; - timer_value = timer_value % 3600000; - minutes = ( timer_value / 1000 ) / 60; - seconds = ( timer_value / 1000 ) % 60; + hours = ( clock_timer.value / 1000 ) / 3600; + clock_timer.value = clock_timer.value % 3600000; + minutes = ( clock_timer.value / 1000 ) / 60; + seconds = ( clock_timer.value / 1000 ) % 60; if ( hours ) { lcd.print(hours); lcd.print(":"); @@ -543,28 +811,40 @@ void ui_handler() lcd.print( seconds ); if ( check_key(KEY_INC) ) { - if ( !timer_running && !timer_start ) + if ( !clock_timer.running && !clock_timer.start ) { - timer_start = millis(); - timer_value = 0; - timer_running = true; - } else if ( !timer_running && timer_start ) { - timer_start = millis() - timer_value; - timer_running = true; - } else if ( timer_running ) { - timer_running = false; + clock_timer.start = millis(); + clock_timer.value = 0; + clock_timer.running = true; + } else if ( !clock_timer.running && clock_timer.start ) { + clock_timer.start = millis() - clock_timer.value; + clock_timer.running = true; + } else if ( clock_timer.running ) { + clock_timer.running = false; } return; } else if ( check_key(KEY_DEC) ) { - if ( !timer_running && timer_start ) { - timer_value = 0; - timer_start = 0; - timer_init = 0; + if ( !clock_timer.running && clock_timer.start ) { + clock_timer.value = 0; + clock_timer.start = 0; + clock_timer.init = 0; } return; } break; + + + case CURMODEL: + lcd.clear(); + lcd.print("Model #: "); + lcd.print( (int)current_model ); + lcd.setCursor(0 , 1); + lcd.print("NAME (not impl)"); + break; + + + case MENU: lcd.clear(); switch ( menu_mainstate ) @@ -586,13 +866,13 @@ void ui_handler() case INVERTS: - if ( menu_substate >= max_channels ) menu_substate = 0; - if ( menu_substate < 0) menu_substate = (max_channels - 1); + if ( menu_substate >= model.channels ) menu_substate = 0; + if ( menu_substate < 0) menu_substate = (model.channels - 1); lcd.print("Channel invert"); lcd.setCursor(0 , 1); lcd.print("Ch "); lcd.print(menu_substate+1); - lcd.print( (rev[menu_substate] ? ": Invert" : ": Normal")); + lcd.print( (model.rev[menu_substate] ? ": Invert" : ": Normal")); if ( check_key(KEY_UP) ) { menu_mainstate = TOP; @@ -612,7 +892,7 @@ void ui_handler() return; } else if ( check_key(KEY_INC) || check_key(KEY_DEC) ) { - rev[menu_substate] ^= 1; + model.rev[menu_substate] ^= 1; return; } break; @@ -717,4 +997,4 @@ void ui_handler() - +