]> git.defcon.no Git - rctxduino/blob - source/RCTXDuino/RCTXDuino.pde
0c63499223ca8dee79159714bc4524884a98476c
[rctxduino] / source / RCTXDuino / RCTXDuino.pde
1 #include <LiquidCrystal.h>
2 #include <TimerOne.h>
3 #include <EEPROM.h>
4
5 // Undefine this whenever a "release" or "flight-test" build is made.
6 // Defining DEBUG sets some crazy values for things like battery warning,
7 // and includes a whole bunch of debugging-related code ...
8 #define DEBUG 1
9
10 #define MAX_INPUTS 8
11
12 // Update this _every_ time a change in datastructures that
13 // can/will ber written to EEPROM is done. EEPROM data is
14 // read/written torectly into/from the data structures using
15 // pointers, so every time a data-set change occurs, the EEPROM
16 // format changes as well..
17 #define EEPROM_VERSION 7
18
19 // Some data is stored in fixed locations, e.g.:
20 // * The EEPROM version number for the stored data (loc 0)
21 // * The selected model configuration number (loc 1)
22 // * (add any other fixed-loc's here for doc-purpose)
23 // This means that any pointer-math-operations need a BASE
24 // adress to start calc'ing from. This is defined as:
25 #define EE_BASE_ADDR 10
26
27 // Having to repeat tedious base-address-calculations for the
28 // start of model data should be unnessecary. Plus, updating
29 // what data is stored before the models will mean that each
30 // of those calculations must be updated. A better approach is
31 // to define the calculation in a define!
32 // NOTE: If new data is added in front of the model data,
33 // this define must be updated!
34 #define EE_MDL_BASE_ADDR (EE_BASE_ADDR+(sizeof(input_cal_t)+ 10))
35
36 // Just as a safety-precaution, update/change this if a chip with
37 // a different internal EEPROM size is used. Atmega328p has 1024 bytes.
38 #define INT_EEPROM_SIZE 1024
39
40 #define MAX_MODELS 4 // Nice and random number..
41
42
43 // --------------- ADC related stuffs.... --------------------
44
45 struct input_cal_t // Struct type for input calibration values
46 {
47 int min[MAX_INPUTS];
48 int max[MAX_INPUTS];
49 int center[MAX_INPUTS];
50 } ;
51 input_cal_t input_cal;
52
53 struct model_t
54 {
55 int channels; // How many channels should PPM generate for this model ...
56 float stick[8]; // The (potentially recalc'ed) value of stick/input channel.
57 int raw[8];
58 boolean rev[8];
59 int dr[8]; // The Dual-rate array uses magic numbers :P
60 /* dr[0] = Input channel #1 of 2 for D/R switch #1. 0 means off, 1-4 valid values.
61 dr[1] = Input channel #2 of 2 for D/R switch #1. 0 means off, 1-4 valid values.
62 dr[2] = Input channel #1 of 2 for D/R switch #2. 0 means off, 1-4 valid values.
63 dr[3] = Input channel #2 of 2 for D/R switch #2. 0 means off, 1-4 valid values.
64 dr[4] = D/R value for switch # 1 LOW(off). Value -100 to 100 in steps of 5.
65 dr[5] = D/R value for switch # 1 HIGH(on). Value -100 to 100 in steps of 5.
66 dr[6] = D/R value for switch # 1 LOW(off). Value -100 to 100 in steps of 5.
67 dr[7] = D/R value for switch # 1 HIGH(on). Value -100 to 100 in steps of 5.
68 */
69 };
70 volatile model_t model;
71 unsigned char current_model; // Using uchar to spend a single byte of mem..
72
73 // ----------------- Display related stuffs --------------------
74 LiquidCrystal lcd( 12, 11, 10, 6, 7, 8, 9);
75 // Parameters are: rs, rw, enable, d4, d5, d6, d7 pin numbers.
76
77 // ----------------- PPM related stuffs ------------------------
78 // The PPM generation is handled by Timer0 interrupts, and needs
79 // all modifiable variables to be global and volatile...
80
81 volatile long sum = 0; // Frame-time spent so far
82 volatile int cchannel = 0; // Current channnel
83 volatile bool do_channel = true; // Is next operation a channel or a separator
84
85
86 // All time values in usecs
87 // TODO:
88 // The timing here (and/or in the ISR) needs to be tweaked to provide valid
89 // RC PPM signals accepted by standard RC RX'es and the Microcopter...
90
91 #define framelength 21000 // Max length of frame
92 #define seplength 300 // Lenght of a channel separator
93 #define chmax 1600 // Max lenght of channel pulse
94 #define chmin 495 // Min length of channel
95 #define chwidht (chmax - chmin)// Useable time of channel pulse
96
97 // ----------------- Menu/IU related stuffs --------------------
98
99 // Keys/buttons/switches for UI use, including dual-rate/expo
100 // are digital inputs connected to a 4051 multiplexer, giving
101 // 8 inputs on a single input pin.
102 #define KEY_UP 0
103 #define KEY_DOWN 1
104 #define KEY_RIGHT 2
105 #define KEY_LEFT 3
106 #define KEY_INC 4
107 #define KEY_DEC 5
108 #define KEY_DR1 6
109 #define KEY_DR2 7
110
111 // Voltage sense pin is connected to a 1/3'd voltage divider.
112 #define BATTERY_CONV (10 * 3 * (5.0f/1024.0f))
113
114 #ifdef DEBUG
115 // The following values are for DEBUGGING ONLY!!
116 #define BATTERY_LOW 92
117 #define BATTERY_CRITICAL 0
118 #else
119 #define BATTERY_LOW 92
120 #define BATTERY_CRITICAL 92
121 #endif
122
123 enum {
124 VALUES,
125 BATTERY,
126 TIMER,
127 CURMODEL,
128 MENU
129 }
130 displaystate;
131
132 enum {
133 TOP,
134 INVERTS,
135 DUALRATES,
136 EXPOS, // Some radios have "drawn curves", i.e. loopup tables stored in external EEPROM ...
137 DEBUG_DUMP,
138 SAVE
139 }
140 menu_mainstate;
141 int menu_substate;
142
143 boolean keys[8];
144 boolean prev_keys[8];
145
146 int battery_val;
147
148 // The display/UI is handled only when more
149 // than UI_INTERVAL milliecs has passed since last...
150 #define UI_INTERVAL 250
151 unsigned long last = 0;
152
153 struct clock_timer_t
154 {
155 unsigned long start;
156 unsigned long init;
157 unsigned long value;
158 boolean running;
159 } clock_timer;
160
161 #ifdef DEBUG
162 // ----------------- DEBUG-STUFF --------------------
163 unsigned long prev_loop_time;
164 unsigned long avg_loop_time;
165 unsigned long t;
166 #endif
167
168 // ---------- CODE! -----------------------------------
169
170 // ---------- Arduino SETUP code ----------------------
171 void setup(){
172 pinMode(13, OUTPUT); // led
173
174 pinMode(2, OUTPUT); // s0
175 pinMode(3, OUTPUT); // s1
176 pinMode(4, OUTPUT); // s2
177 pinMode(5, OUTPUT); // e
178
179 lcd.begin(16,2);
180 lcd.print("Starting....");
181
182 Serial.begin(9600);
183 Serial.println("Starting....");
184 delay(500);
185
186 model_defaults();
187 read_settings();
188
189 displaystate = VALUES;
190
191 // Arduino believes all pins on Port C are Analog.
192 // In reality they are tri-purpose; ADC, Digital, Digital Interrupts
193 // Unfortunately the interrupt mode is unusable in this scenario, but digital I/O works :P
194 pinMode(A2, INPUT);
195 digitalWrite(A2, HIGH);
196 scan_keys();
197 if ( !keys[KEY_UP])
198 calibrate();
199
200 #ifdef DEBUG
201 // Debugging: how long does the main loop take on avg...
202 t = micros();
203 avg_loop_time = t;
204 prev_loop_time = t;
205 #endif
206
207 // Initializing the stopwatch timer/clock values...
208 clock_timer = (clock_timer_t){0, 0, 0, false};
209
210 pinMode(A5, OUTPUT); // PPM output pin
211 do_channel = false;
212 set_timer( seplength );
213 Timer1.initialize(framelength);
214 Timer1.attachInterrupt(ISR_timer);
215
216 }
217
218 void model_defaults( void )
219 {
220 // This function provides default values for model data
221 // that is not a result of stick input, or in other words:
222 // provides defautls for all user-configurable model options.
223
224 // Remember to update this when a new option/element is added
225 // to the model_t struct (preferably before implementing the
226 // menu code that sets those options ...)
227
228 // This is used when a user wants a new, blank model, a reset
229 // of a configured model, and (most important) when EEPROM
230 // data format changes.
231 // NOTE: This means that stored model conficuration is reset
232 // to defaults when the EEPROM version/format changes.
233 model.channels = 8;
234 model.rev[0] = model.rev[1] = model.rev[2] = model.rev[3] =
235 model.rev[4] = model.rev[5] = model.rev[6] = model.rev[7] = false;
236 model.dr[0] = model.dr[1] = model.dr[2] = model.dr[3] = 0;
237 model.dr[4] = model.dr[5] = model.dr[6] = model.dr[7] = 100;
238
239 }
240
241 // ---------- Arduino main loop -----------------------
242 void loop ()
243 {
244
245 process_inputs();
246
247 // Wasting a full I/O pin on battery status monitoring!
248 battery_val = analogRead(1) * BATTERY_CONV;
249 if ( battery_val < BATTERY_LOW ) {
250 digitalWrite(13, 1); // Simulate alarm :P
251 }
252 if ( battery_val < BATTERY_CRITICAL ) {
253 displaystate = BATTERY;
254 }
255
256 if ( millis() - last > UI_INTERVAL )
257 {
258 last = millis();
259 ui_handler();
260 }
261
262 #ifdef DEBUG
263 if ( displaystate != MENU )
264 {
265 // Debugging: how long does the main loop take on avg,
266 // when not handling the UI...
267 t = micros();
268 avg_loop_time = ( t - prev_loop_time + avg_loop_time ) / 2;
269 prev_loop_time = t;
270 }
271 #endif
272
273 // Whoa! Slow down partner! Let everything settle down before proceeding.
274 delay(5);
275 }
276
277 // ----- Simple support functions used by more complex functions ----
278
279 void set_ppm_output( bool state )
280 {
281 digitalWrite(A5, state); // Hard coded PPM output
282 }
283
284 void set_timer(long time)
285 {
286 Timer1.detachInterrupt();
287 Timer1.attachInterrupt(ISR_timer, time);
288 }
289
290 boolean check_key( int key)
291 {
292 return ( !keys[key] && prev_keys[key] );
293 }
294
295 void mplx_select(int pin)
296 {
297 digitalWrite(5, 1);
298 delayMicroseconds(24);
299
300 digitalWrite(2, bitRead(pin,0)); // Arduino alias for non-modifying bitshift operation
301 digitalWrite(3, bitRead(pin,1)); // us used to extract individual bits from the int (0..7)
302 digitalWrite(4, bitRead(pin,2)); // Select the appropriate input by setting s1,s2,s3 and e
303 digitalWrite(5, 0); // on the 4051 multiplexer.
304
305 // May need to slow the following read down to be able to
306 // get fully reliable values from the 4051 multiplex.
307 delayMicroseconds(24);
308
309 }
310
311 // ----- "Complex" functions follow ---------------------------------
312
313 void calibrate()
314 {
315 int i, adc_in;
316 int num_calibrations = 200;
317
318 lcd.clear();
319 lcd.print("Move controls to");
320 lcd.setCursor(0,1);
321 lcd.print("their extremes..");
322 Serial.print("Calibration. Move all controls to their extremes.");
323
324 for (i=0; i<MAX_INPUTS; i++) {
325 input_cal.min[i] = 1024;
326 input_cal.center[i] = 512;
327 input_cal.max[i] = 0;
328 }
329
330 while ( num_calibrations-- )
331 {
332 for (i=0; i<MAX_INPUTS; i++) {
333 mplx_select(i);
334 adc_in = analogRead(0);
335
336 // Naive min/max calibration
337 if ( adc_in < input_cal.min[i] ) {
338 input_cal.min[i] = adc_in;
339 }
340 if ( adc_in > input_cal.max[i] ) {
341 input_cal.max[i] = adc_in;
342 }
343 delay(10);
344 }
345 }
346
347 // TODO: WILL need to do center-point calibration after min-max...
348
349 lcd.clear();
350 lcd.print("Saving to EEPROM");
351 write_calibration();
352 lcd.setCursor(0 , 1);
353 lcd.print("Done calibrating");
354
355 Serial.print("Done calibrating");
356 delay(2000);
357 }
358
359 void write_calibration(void)
360 {
361 int i;
362 unsigned char v;
363 const byte *p;
364
365 // Set p to be a pointer to the start of the input calibration struct.
366 p = (const byte*)(const void*)&input_cal;
367
368 // Iterate through the bytes of the struct...
369 for (i = 0; i < sizeof(input_cal_t); i++)
370 {
371 // Get a byte of data from the struct...
372 v = (unsigned char) *p;
373 // write it to EEPROM
374 EEPROM.write( EE_BASE_ADDR + i, v);
375 // and move the pointer to the next byte in the struct.
376 *p++;
377 }
378 }
379
380 void read_settings(void)
381 {
382 int i;
383 unsigned char v;
384 byte *p;
385
386 v = EEPROM.read(0);
387 if ( v != EEPROM_VERSION )
388 {
389 // All models have been reset. Set the current model to 0
390 current_model = 0;
391 EEPROM.write(1, current_model);
392
393 calibrate();
394 model_defaults();
395 // The following does not yet work...
396 for ( i = 0; i < MAX_MODELS; i++)
397 write_model_settings(i);
398
399
400 // After saving calibration data and model defaults,
401 // update the saved version-identifier to the current ver.
402 EEPROM.write(0, EEPROM_VERSION);
403 }
404
405 // Read calibration values from EEPROM.
406 // This uses simple pointer-arithmetic and byte-by-byte
407 // to put bytes read from EEPROM to the data-struct.
408 p = (byte*)(void*)&input_cal;
409 for (i = 0; i < sizeof(input_cal_t); i++)
410 *p++ = EEPROM.read( EE_BASE_ADDR + i);
411
412 // Get the previously selected model from EEPROM.
413 current_model = EEPROM.read(1);
414 read_model_settings( current_model );
415 }
416
417 void read_model_settings(unsigned char mod_no)
418 {
419 int model_address;
420 int i;
421 unsigned char v;
422 byte *p;
423
424 // Calculate the EEPROM start adress for the given model (mod_no)
425 model_address = EE_MDL_BASE_ADDR + (mod_no * sizeof(model_t));
426
427 // Do not try to write the model to EEPROM if it won't fit.
428 if ( INT_EEPROM_SIZE < (model_address + sizeof(model_t)) )
429 {
430 lcd.clear();
431 lcd.print("Aborting READ");
432 lcd.setCursor(0 , 1);
433 lcd.print("Invalid location");
434 delay(2000);
435 return;
436 }
437
438 lcd.clear();
439 lcd.print("Reading model ");
440 lcd.print( (int)mod_no );
441
442 // Pointer to the start of the model_t data struct,
443 // used for byte-by-byte reading of data...
444 p = (byte*)(void*)&model;
445 for (i = 0; i < sizeof(model_t); i++)
446 *p++ = EEPROM.read( model_address++ );
447
448 #ifdef DEBUG
449 serial_dump_model();
450 #endif
451
452 lcd.setCursor(0 , 1);
453 lcd.print("... Loaded.");
454 delay(1000);
455 }
456
457 void write_model_settings(unsigned char mod_no)
458 {
459 int model_address;
460 int i;
461 unsigned char v;
462 byte *p;
463
464 // Calculate the EEPROM start adress for the given model (mod_no)
465 model_address = EE_MDL_BASE_ADDR + (mod_no * sizeof(model_t));
466
467 // Do not try to write the model to EEPROM if it won't fit.
468 if ( INT_EEPROM_SIZE < (model_address + sizeof(model_t)) )
469 {
470 lcd.clear();
471 lcd.print("Aborting SAVE");
472 lcd.setCursor(0 , 1);
473 lcd.print("No room for data");
474 delay(2000);
475 return;
476 }
477
478 lcd.clear();
479 lcd.print("Saving model ");
480 lcd.print( (int)mod_no);
481
482 // Pointer to the start of the model_t data struct,
483 // used for byte-by-byte reading of data...
484 p = (byte*)(void*)&model;
485
486 // Write/serialize the model data struct to EEPROM...
487 for (i = 0; i < sizeof(model_t); i++)
488 EEPROM.write( model_address++, *p++);
489
490 lcd.setCursor(0 , 1);
491 lcd.print(".. done saving.");
492 delay(200);
493 }
494
495 #ifdef DEBUG
496 void serial_dump_model ( void )
497 {
498 int i;
499 int model_address;
500 // Calculate the EEPROM start adress for the given model (mod_no)
501 model_address = EE_MDL_BASE_ADDR + (current_model * sizeof(model_t));
502 Serial.print("Current model: ");
503 Serial.println( (int)current_model );
504 Serial.print("Models base addr: ");
505 Serial.println( EE_MDL_BASE_ADDR );
506 Serial.print("Model no: ");
507 Serial.println( current_model, 10 );
508 Serial.print("Size of struct: ");
509 Serial.println( sizeof( model_t) );
510 Serial.print("Model address: ");
511 Serial.println( model_address );
512 Serial.print("End of model: ");
513 Serial.println( model_address + sizeof(model_t) );
514
515 Serial.println();
516
517 Serial.print("Channel reversions: ");
518 for ( i = 0; i<8; i++)
519 {
520 Serial.print(i);
521 Serial.print("=");
522 Serial.print(model.rev[i], 10);
523 Serial.print(" ");
524 }
525 Serial.println();
526
527 Serial.print("DR1 inp 0: ");
528 Serial.println(model.dr[0]);
529 Serial.print("DR1 inp 1: ");
530 Serial.println(model.dr[1]);
531 Serial.print("DR1 LO val: ");
532 Serial.println(model.dr[4]);
533 Serial.print("DR1 HI val: ");
534 Serial.println(model.dr[5]);
535 Serial.print("DR2 inp 0: ");
536 Serial.println(model.dr[2]);
537 Serial.print("DR2 inp 1: ");
538 Serial.println(model.dr[3]);
539 Serial.print("DR2 LO val: ");
540 Serial.println(model.dr[6]);
541 Serial.print("DR2 HI val: ");
542 Serial.println(model.dr[7]);
543
544 for (i=0; i<MAX_INPUTS; i++) {
545 Serial.print("Input #");
546 Serial.print(i);
547 Serial.print(" pct: ");
548 Serial.print(model.stick[i]);
549 Serial.print(" min: ");
550 Serial.print(input_cal.min[i]);
551 Serial.print(" max: ");
552 Serial.print(input_cal.max[i]);
553 Serial.println();
554 }
555 }
556 #endif
557
558 void scan_keys ( void )
559 {
560 boolean key_in;
561
562 // To get more inputs, another 4051 analog multiplexer is used,
563 // but this time it is used for digital inputs. 8 digital inputs
564 // on one input line, as long as proper debouncing and filtering
565 // is done in hardware :P
566 for (int i=0; i<=7; i++) {
567 // To be able to detect that a key has changed state, preserve the previous..
568 prev_keys[i] = keys[i];
569
570 // Select and read input.
571 mplx_select(i);
572 keys[i] = digitalRead(A2);
573 delay(2);
574 }
575 }
576
577
578 void process_inputs(void )
579 {
580 int current_input, adc_in, fact;
581 float min, max;
582
583 for (current_input=0; current_input<MAX_INPUTS; current_input++) {
584
585 mplx_select(current_input);
586 adc_in = analogRead(0);
587
588 model.raw[current_input] = adc_in;
589 // New format on stick values
590 // The calculations happen around the center point, the values
591 // need to arrive at 0...100 of the range "center-to-edge",
592 // and must end up as negative on the ... negative side of center.
593
594 if ( adc_in < input_cal.center[current_input] )
595 {
596 // The stick is on the negative side, so the range is
597 // from the lowest possible value to center, and we must
598 // make this a negative percentage value.
599 max = input_cal.min[current_input];
600 min = input_cal.center[current_input];
601 fact = -100;
602 }
603 else
604 {
605 // The stick is at center, or on the positive side.
606 // Thus, the range is from center to max, and
607 // we need positive percentages.
608 min = input_cal.center[current_input];
609 max = input_cal.max[current_input];
610 fact = 100;
611 }
612 // Calculate the percentage that the current stick position is at
613 // in the given range, referenced to or from center, depending :P
614 model.stick[current_input] = fact * ((float)adc_in - min ) / (max - min);
615
616 // If this input is configured to be reversed, simply do a sign-flip :D
617 if ( model.rev[current_input] ) model.stick[current_input] *= -1;
618
619 // Dual-rate calculation :D
620 // This is very repetitive code. It should be fast, but it may waste code-space.
621 float dr_val;
622 // Test to see if dualrate-switch #1 applies to channel...
623 if ( ( current_input == ( model.dr[0]-1) ) || ( current_input == ( model.dr[1]-1) ) )
624 {
625 if ( !keys[KEY_DR1] )
626 dr_val = ((float)model.dr[4])/100.0;
627 else
628 dr_val = ((float)model.dr[5])/100.0;
629
630 model.stick[current_input] *= dr_val;
631 }
632 else
633 // Test to see if dualrate-switch #1 applies to channel...
634 if ( ( current_input == ( model.dr[2]-1) ) || ( current_input == ( model.dr[3]-1) ) )
635 {
636 if ( !keys[KEY_DR1] )
637 dr_val = ((float)model.dr[6])/100.0;
638 else
639 dr_val = ((float)model.dr[7])/100.0;
640
641 model.stick[current_input] *= dr_val;
642 }
643 }
644 }
645
646
647 void ISR_timer(void)
648 {
649 Timer1.stop(); // Make sure we do not run twice while working :P
650
651 if ( !do_channel )
652 {
653 set_ppm_output( LOW );
654 sum += seplength;
655 do_channel = true;
656 set_timer(seplength);
657 return;
658 }
659
660 if ( cchannel >= model.channels )
661 {
662 set_ppm_output( HIGH );
663 long framesep = framelength - sum;
664
665 sum = 0;
666 do_channel = false;
667 cchannel = 0;
668 set_timer ( framesep );
669 return;
670 }
671
672 if ( do_channel )
673 {
674 set_ppm_output( HIGH );
675
676 // New format on stick values
677 // model.stick contains percentages, -100% to 100% in float. To make the timer-handling
678 // here as simple as possible. We want to calc the channel value as a "ratio-value",
679 // a float in the range 0..1.0. So, by moving the lower bound to 0, then cutting the
680 // range in half, and finally dividing by 100, we should get the ratio value.
681 // Some loss of presicion occurs, perhaps the algo' should be reconsidered :P
682 long next_timer = (( chwidht * ((model.stick[cchannel]+100)/200) ) + chmin);
683 // Do sanity-check of next_timer compared to chmax and chmin...
684 while ( chmax < next_timer ) next_timer--;
685 while ( next_timer < chmin ) next_timer++;
686
687 // Update the sum of elapsed time
688 sum += next_timer;
689
690 // Done with channel separator and value,
691 // prepare for next channel...
692 cchannel++;
693 do_channel = false;
694 set_timer ( next_timer );
695 return;
696 }
697 }
698
699
700 #ifdef DEBUG
701 void serial_debug()
702 {
703 int current_input;
704 for (current_input=0; current_input<MAX_INPUTS; current_input++) {
705
706 Serial.print("Input #");
707 Serial.print(current_input);
708 Serial.print(" pct: ");
709 Serial.print(model.stick[current_input]);
710 Serial.print(" raw value: ");
711 Serial.print(model.raw[current_input]);
712 Serial.print(" min: ");
713 Serial.print(input_cal.min[current_input]);
714 Serial.print(" max: ");
715 Serial.print(input_cal.max[current_input]);
716 Serial.println();
717 }
718 Serial.print("Battery level is: ");
719 Serial.println(battery_val);
720
721 Serial.print("Average loop time:");
722 Serial.println(avg_loop_time);
723
724 Serial.print("Free RAM:");
725 Serial.print( FreeRam() );
726 Serial.println();
727 }
728 #endif
729
730 void dr_inputselect( int no, int in )
731 {
732 if ( model.dr[menu_substate] < 0 ) model.dr[menu_substate] = 4;
733 if ( model.dr[menu_substate] > 4 ) model.dr[menu_substate] = 0;
734
735 lcd.setCursor(0 , 0);
736 lcd.print("D/R switch ");
737 lcd.print( no + 1 );
738 lcd.print(" ");
739 lcd.setCursor(0 , 1);
740 lcd.print("Input ");
741 lcd.print(in+1);
742
743 lcd.print(": ");
744 if ( ! model.dr[menu_substate] ) lcd.print("Off");
745 else lcd.print(model.dr[menu_substate]);
746
747 if ( check_key(KEY_INC) ) {
748 model.dr[menu_substate]++;
749 return;
750 }
751 else if ( check_key(KEY_DEC) ) {
752 model.dr[menu_substate]--;
753 return;
754 }
755 // Wrap around.
756 return;
757
758 }
759
760 void dr_value()
761 {
762 int pos;
763 int state;
764
765 if ( menu_substate == 4) state = keys[KEY_DR1];
766 else state = keys[KEY_DR2];
767
768 pos = 4 + (menu_substate - 4) * 2;
769 if (state) pos++;
770
771 lcd.setCursor(0 , 0);
772 lcd.print("D/R switch ");
773 lcd.print( menu_substate - 3 );
774 lcd.print(" ");
775 lcd.setCursor(0 , 1);
776 lcd.print( state ? "HI" : "LO" );
777 lcd.print(" Value :");
778
779 lcd.print( model.dr[pos] );
780
781 if ( !keys[KEY_INC] ) {
782 if ( model.dr[pos] < 100) model.dr[pos] += 5;
783 return;
784 }
785 else if ( !keys[KEY_DEC] ) {
786 if ( model.dr[pos] > -100) model.dr[pos] -= 5;
787 return;
788 }
789
790 return;
791 }
792 void ui_handler()
793 {
794 int row;
795 int col;
796 scan_keys();
797
798 if ( displaystate != MENU )
799 {
800 menu_substate = 0;
801 if ( check_key(KEY_UP) && displaystate == VALUES ) {
802 displaystate = BATTERY;
803 return;
804 }
805 else if ( check_key(KEY_UP) && displaystate == BATTERY ) {
806 displaystate = TIMER;
807 return;
808 }
809 else if ( check_key(KEY_UP) && displaystate == TIMER ) {
810 displaystate = CURMODEL;
811 return;
812 }
813 else if ( check_key(KEY_UP) && displaystate == CURMODEL ) {
814 displaystate = VALUES;
815 return;
816 }
817
818 else if ( check_key(KEY_DOWN) ) {
819 displaystate = MENU;
820 return;
821 }
822 }
823
824 digitalWrite(13, digitalRead(13) ^ 1 );
825
826 switch ( displaystate )
827 {
828 case VALUES:
829 int current_input;
830 for (current_input=0; current_input<MAX_INPUTS; current_input++) {
831 // In channel value display, do a simple calc
832 // of the LCD row & column location. With 8 channels
833 // we can fit eight channels as percentage values on
834 // a simple 16x2 display...
835 if ( current_input < 4 )
836 {
837 col = current_input * 4;
838 row = 0;
839 }
840 else
841 {
842 col = (current_input-4) * 4;
843 row = 1;
844 }
845 // Overwriting the needed positions with
846 // blanks cause less display-flicker than
847 // actually clearing the display...
848 lcd.setCursor(col, row);
849 lcd.print(" ");
850 lcd.setCursor(col, row);
851 // Display uses percents, while PPM uses ratio....
852 // New format on stick values
853 lcd.print( (int)model.stick[current_input] );
854 }
855 break;
856
857
858 case BATTERY:
859 lcd.clear();
860 lcd.print("Battery level: ");
861 lcd.setCursor(0 , 1);
862 lcd.print( (float)battery_val/10);
863 lcd.print("V");
864 if ( battery_val < BATTERY_LOW ) lcd.print(" - WARNING");
865 else lcd.print(" - OK");
866 break;
867
868
869
870 case TIMER:
871 unsigned long delta;
872 int hours;
873 int minutes;
874 int seconds;
875
876 lcd.clear();
877 lcd.print("Timer: ");
878 lcd.print( clock_timer.running ? "Running" : "Stopped" );
879 lcd.setCursor(5 , 1);
880 if ( clock_timer.running )
881 {
882 clock_timer.value = millis() - (clock_timer.start + clock_timer.init);
883 }
884 hours = ( clock_timer.value / 1000 ) / 3600;
885 clock_timer.value = clock_timer.value % 3600000;
886 minutes = ( clock_timer.value / 1000 ) / 60;
887 seconds = ( clock_timer.value / 1000 ) % 60;
888 if ( hours ) {
889 lcd.print(hours);
890 lcd.print(":");
891 }
892 if ( minutes < 10 ) lcd.print("0");
893 lcd.print( minutes );
894 lcd.print(":");
895 if ( seconds < 10 ) lcd.print("0");
896 lcd.print( seconds );
897
898 if ( check_key(KEY_INC) ) {
899 if ( !clock_timer.running && !clock_timer.start )
900 {
901 clock_timer.start = millis();
902 clock_timer.value = 0;
903 clock_timer.running = true;
904 } else if ( !clock_timer.running && clock_timer.start ) {
905 clock_timer.start = millis() - clock_timer.value;
906 clock_timer.running = true;
907 } else if ( clock_timer.running ) {
908 clock_timer.running = false;
909 }
910 return;
911 } else if ( check_key(KEY_DEC) ) {
912 if ( !clock_timer.running && clock_timer.start ) {
913 clock_timer.value = 0;
914 clock_timer.start = 0;
915 clock_timer.init = 0;
916 }
917 return;
918 }
919 break;
920
921
922
923 case CURMODEL:
924 lcd.clear();
925 lcd.print("Model #: ");
926 lcd.print( (int)current_model );
927 lcd.setCursor(0 , 1);
928 lcd.print("NAME (not impl)");
929 break;
930
931
932
933 case MENU:
934 lcd.clear();
935 switch ( menu_mainstate )
936 {
937 case TOP:
938 lcd.print("In MENU mode!");
939 lcd.setCursor(0 , 1);
940 lcd.print("Esc UP. Scrl DN.");
941 menu_substate = 0;
942 if ( check_key(KEY_UP) ) {
943 displaystate = VALUES;
944 return;
945 }
946 else if ( check_key(KEY_DOWN) ) {
947 menu_mainstate = INVERTS;
948 return;
949 }
950 break;
951
952
953 case INVERTS:
954 if ( menu_substate >= model.channels ) menu_substate = 0;
955 if ( menu_substate < 0) menu_substate = (model.channels - 1);
956 lcd.print("Channel invert");
957 lcd.setCursor(0 , 1);
958 lcd.print("Ch ");
959 lcd.print(menu_substate+1);
960 lcd.print( (model.rev[menu_substate] ? ": Invert" : ": Normal"));
961
962 if ( check_key(KEY_UP) ) {
963 menu_mainstate = TOP;
964 return;
965 }
966 else if ( check_key(KEY_DOWN) ) {
967 menu_mainstate = DUALRATES;
968 return;
969 }
970
971 if ( check_key(KEY_RIGHT) ) {
972 menu_substate++;
973 return;
974 }
975 else if ( check_key(KEY_LEFT) ) {
976 menu_substate--;
977 return;
978 }
979 else if ( check_key(KEY_INC) || check_key(KEY_DEC) ) {
980 model.rev[menu_substate] ^= 1;
981 return;
982 }
983 break;
984
985 case DUALRATES:
986 if ( menu_substate > 5 ) menu_substate = 0;
987 if ( menu_substate < 0) menu_substate = 5;
988
989 if ( check_key(KEY_UP) ) {
990 menu_mainstate = INVERTS;
991 return;
992 }
993 if ( check_key(KEY_DOWN) ) {
994 menu_mainstate = EXPOS;
995 return;
996 }
997 if ( check_key(KEY_RIGHT) ) {
998 menu_substate++;
999 return;
1000 }
1001 else if ( check_key(KEY_LEFT) ) {
1002 menu_substate--;
1003 return;
1004 }
1005 switch (menu_substate)
1006 {
1007 case 0:
1008 dr_inputselect(0, 0);
1009 return;
1010 case 1:
1011 dr_inputselect(0, 1);
1012 return;
1013 case 2:
1014 dr_inputselect(1, 0);
1015 return;
1016 case 3:
1017 dr_inputselect(1, 1);
1018 return;
1019 case 4:
1020 case 5:
1021 dr_value();
1022 return;
1023 default:
1024 menu_substate = 0;
1025 break;
1026 }
1027 break;
1028
1029 case EXPOS:
1030 //________________
1031 lcd.print("Input expo curve");
1032 lcd.setCursor(0 , 1);
1033 lcd.print("Not implemented");
1034 // Possible, if input values are mapped to +/- 100 rather than 0..1 ..
1035 // plot ( x*(1 - 1.0*cos (x/(20*PI)) )) 0 to 100
1036 // Run in wolfram to see result, adjust the 1.0 factor to inc/red effect.
1037 // Problem: -100 to 100 is terribly bad presicion, esp. considering that
1038 // the values started as 0...1024, and we have 1000usec to "spend" on channels.
1039
1040 // NEW IDEA provided my ivarf @ hig: use bezier curves og hermite curves!
1041 // Looks like a promising idea, but the implementation is still a bitt off
1042 // on the time-horizon :P
1043 if ( check_key(KEY_UP ) ) {
1044 menu_mainstate = DUALRATES;
1045 return;
1046 }
1047 #ifdef DEBUG
1048 if ( check_key(KEY_DOWN ) ) {
1049 menu_mainstate = DEBUG_DUMP;
1050 return;
1051 }
1052 #else
1053 if ( check_key(KEY_DOWN ) ) {
1054 menu_mainstate = TOP;
1055 return;
1056 }
1057
1058 #endif
1059 break;
1060
1061 #ifdef DEBUG
1062 case DEBUG_DUMP:
1063 lcd.setCursor(0 , 0);
1064 lcd.print("Dumping debug to");
1065 lcd.setCursor(0 , 1);
1066 lcd.print("serial port 0");
1067 serial_debug();
1068 if ( check_key(KEY_UP ) ) {
1069 // FIXME: Remember to update the "Scroll up" state!
1070 menu_mainstate = EXPOS;
1071 return;
1072 } else if ( check_key(KEY_DOWN ) ) {
1073 menu_mainstate = SAVE;
1074 return;
1075 }
1076 break;
1077 #endif
1078 default:
1079 lcd.print("Not implemented");
1080 lcd.setCursor(0 , 1);
1081 lcd.print("Press DOWN...");
1082 if ( check_key(KEY_DOWN ) ) menu_mainstate = TOP;
1083 }
1084 break;
1085
1086
1087 default:
1088 // Invalid
1089 return;
1090 }
1091
1092 return;
1093 }
1094
1095 #ifdef DEBUG
1096 /* The following code is taken from the
1097 Arduino FAT16 Library by William Greiman
1098 The code may or may-not survive in the long run,
1099 depending on what licensing-terms we decide on.
1100 The license will be open source, but the FAT16lib
1101 is GPL v3, and I (fishy) am personally not so sure about that...
1102
1103 On the other hand... This code is a very "intuitive approach",
1104 so contacting the author may give us the option of relicencing just this bit...
1105 */
1106 static int FreeRam(void) {
1107 extern int __bss_end;
1108 extern int* __brkval;
1109 int free_memory;
1110 if (reinterpret_cast<int>(__brkval) == 0) {
1111 // if no heap use from end of bss section
1112 free_memory = reinterpret_cast<int>(&free_memory)
1113 - reinterpret_cast<int>(&__bss_end);
1114 } else {
1115 // use from top of stack to heap
1116 free_memory = reinterpret_cast<int>(&free_memory)
1117 - reinterpret_cast<int>(__brkval);
1118 }
1119 return free_memory;
1120 }
1121 #endif
1122
1123
1124
1125