]> git.defcon.no Git - rctxduino/commitdiff
Initial code import, contains "fairly runnable" RCTXDuino code.
authorjon.langseth@lilug.no <jonl@P04073.(none)>
Mon, 29 Aug 2011 12:37:12 +0000 (14:37 +0200)
committerjon.langseth@lilug.no <jonl@P04073.(none)>
Mon, 29 Aug 2011 12:37:12 +0000 (14:37 +0200)
README [new file with mode: 0644]
hardware/README [new file with mode: 0644]
hardware/prototype_1/rctxduino.sch [new file with mode: 0644]
source/RCTXDuino/RCTXDuino.pde [new file with mode: 0644]
source/README [new file with mode: 0644]
tools/README [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..4ca7df0
--- /dev/null
+++ b/README
@@ -0,0 +1,3 @@
+All the README files in this project are going to
+need updating with useful content. Just adding
+some README files with dummylike content first.
diff --git a/hardware/README b/hardware/README
new file mode 100644 (file)
index 0000000..1c09113
--- /dev/null
@@ -0,0 +1,4 @@
+Anticipating several versions/editions before a final design
+of the hardware is landed, this directory is split up from the
+start. This way, we do not have to throw away the old designs
+to make room for new ones :P
diff --git a/hardware/prototype_1/rctxduino.sch b/hardware/prototype_1/rctxduino.sch
new file mode 100644 (file)
index 0000000..839c46b
Binary files /dev/null and b/hardware/prototype_1/rctxduino.sch differ
diff --git a/source/RCTXDuino/RCTXDuino.pde b/source/RCTXDuino/RCTXDuino.pde
new file mode 100644 (file)
index 0000000..cf711bb
--- /dev/null
@@ -0,0 +1,720 @@
+#include <LiquidCrystal.h>
+#include <TimerOne.h>
+
+// --------------- 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];
+
+// ----------------- Display related stuffs --------------------
+LiquidCrystal lcd( 12, 11, 10,     6,  7,  8,  9);
+// Parameters are: rs, rw, enable, d4, d5, d6, d7 pin numbers.
+
+// ----------------- 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)
+
+// ----------------- Menu/IU related stuffs --------------------
+
+// Keys/buttons/switches for UI use, including dual-rate/expo
+// are digital inputs connected to a 4051 multiplexer, giving
+// 8 inputs on a single input pin.
+#define KEY_UP    0
+#define KEY_DOWN  1
+#define KEY_RIGHT 2
+#define KEY_LEFT  3
+#define KEY_INC   4
+#define KEY_DEC   5
+#define KEY_DR1   6
+#define KEY_DR2   7
+
+// Voltage sense pin is connected to a 1/3'd voltage divider.
+#define BATTERY_CONV (10 * 3 * (5.0f/1024.0f))
+#define BATTERY_LOW 92
+
+enum {
+  VALUES,
+  BATTERY,
+  TIMER,
+  MENU
+} 
+displaystate;
+
+enum {
+  TOP,
+  INVERTS,
+  DUALRATES,
+  EXPOS, // Some radios have "drawn curves", i.e. loopup tables stored in external EEPROM ...
+  DEBUG,
+  SAVE
+} 
+menu_mainstate;
+int menu_substate;
+
+boolean keys[8];
+boolean prev_keys[8];
+
+int battery_val;
+
+// The display/UI is handled only when more
+// than UI_INTERVAL milliecs has passed since last...
+#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;
+
+// -----------------  DEBUG-STUFF --------------------
+unsigned long prev_loop_time;
+unsigned long avg_loop_time;
+unsigned long t;
+
+
+// ---------- CODE! -----------------------------------
+
+// ---------- Arduino SETUP code ----------------------
+void setup(){
+  pinMode(13, OUTPUT);   // led
+
+  pinMode(2, OUTPUT);    // s0
+  pinMode(3, OUTPUT);    // s1
+  pinMode(4, OUTPUT);    // s2
+  pinMode(5, OUTPUT);    // e
+
+  lcd.begin(16,2);
+  lcd.print("Starting....");
+
+  Serial.begin(9600);
+  Serial.println("Starting....");
+  delay(500);
+  read_settings();
+  scan_keys();
+  if ( keys[KEY_UP])
+    calibrate();
+
+  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.
+  // 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); 
+  
+  // Debugging: how long does the main loop take on avg...  
+  t = micros();
+  avg_loop_time = t;
+  prev_loop_time = t;    
+  
+  dr[0] = dr[1] = dr[2] = dr[3] = 0;
+  dr[4] = dr[5] = dr[6] = dr[7] = 100;
+
+}
+
+// ---------- 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;
+
+  process_inputs();
+
+  // Wasting a full I/O pin on battery status monitoring!
+  battery_val = analogRead(1) * BATTERY_CONV;
+  if ( battery_val < BATTERY_LOW ) {
+    digitalWrite(13, 1); // Simulate alarm :P
+    displaystate = BATTERY;
+  }
+
+  if ( disp )
+  {
+    ui_handler(); 
+  }
+  if ( displaystate != MENU )
+  {
+    // Debugging: how long does the main loop take on avg,
+    // when not handling the UI...
+    t = micros();
+    avg_loop_time = ( t - prev_loop_time + avg_loop_time ) / 2;
+    prev_loop_time = t;    
+  }
+
+  // Whoa! Slow down partner! Let everything settle down before proceeding.
+  delay(5);
+}
+
+// ----- Simple support functions used by more complex functions ----
+
+void set_ppm_output( bool state )
+{
+  digitalWrite(A5, state); // Hard coded PPM output
+}
+
+void set_timer(long time)
+{
+  Timer1.detachInterrupt();
+  Timer1.attachInterrupt(ISR_timer, time);  
+}
+
+boolean check_key( int key)
+{
+  return ( keys[key] && !prev_keys[key] );
+}
+
+void mplx_select(int pin)
+{
+  digitalWrite(5, 1);
+  delayMicroseconds(24);
+
+  digitalWrite(2, bitRead(pin,0));  // Arduino alias for non-modifying bitshift operation
+  digitalWrite(3, bitRead(pin,1));  // us used to extract individual bits from the int (0..7)
+  digitalWrite(4, bitRead(pin,2));  // Select the appropriate input by setting s1,s2,s3 and e
+  digitalWrite(5, 0);               // on the 4051 multiplexer.
+
+  // May need to slow the following read down to be able to
+  // get fully reliable values from the 4051 multiplex.
+  delayMicroseconds(24);
+
+}
+
+// ----- "Complex" functions follow ---------------------------------
+
+void calibrate()
+{
+  int i, r0, r1, r2, adc_in;
+  int calcount = 0;
+  int num_calibrations = 200;
+
+  lcd.clear();
+  lcd.print("Move controls to");
+  lcd.setCursor(0,1);
+  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;
+  }
+  while ( calcount <= num_calibrations )
+  {
+    for (i=0; i<=7; i++) {
+      mplx_select(i);
+      adc_in = analogRead(0);
+
+      // Naive min/max calibration
+      if ( adc_in < minval[i] ) {
+        minval[i] = adc_in;
+      }
+      if ( adc_in > maxval[i] ) {
+        maxval[i] = adc_in;
+      }
+      delay(10);
+    }
+
+    calcount++;
+  }
+
+  // TODO: WILL need to do center-point calibration after min-max...
+
+  lcd.clear();
+  lcd.print("Done calibrating");
+
+  Serial.print("Done calibrating");
+  delay(2000);  
+}
+
+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;
+  }
+}
+
+void write_settings(void)
+{
+  // Dummy. Not used anywhere. Will be fleshed out to save settings to EEPROM.
+}
+
+void scan_keys ( void )
+{
+  int i, r0, r1, r2;
+  boolean key_in;
+
+  // To get more inputs, another 4051 analog multiplexer is used,
+  // but this time it is used for digital inputs. 8 digital inputs
+  // on one input line, as long as proper debouncing and filtering
+  // is done in hardware :P
+  for (i=0; i<=7; i++) {
+    // To be able to detect that a key has changed state, preserve the previous..
+    prev_keys[i] = keys[i];
+
+    // Select and read input.
+    mplx_select(i);
+    keys[i] = digitalRead(A2);
+    delay(2);
+  }
+}
+
+
+void process_inputs(void )
+{
+  int current_input, r0, r1, r2, adc_in;
+  for (current_input=0; current_input<=7; current_input++) {
+
+    mplx_select(current_input);
+    adc_in = analogRead(0);
+
+    channel[current_input] = ((float)adc_in - (float)minval[current_input]) / (float)(maxval[current_input]-minval[current_input]);    
+    if ( rev[current_input] ) channel[current_input] = 1.0f - channel[current_input];  
+  }
+}
+
+
+void ISR_timer(void)
+{
+  Timer1.stop(); // Make sure we do not run twice while working :P
+
+  if ( !do_channel )
+  {
+    set_ppm_output( LOW );
+    sum += seplength;
+    do_channel = true;
+    set_timer(seplength);
+    return;
+  }
+
+  if ( cchannel >= max_channels )
+  {
+    set_ppm_output( HIGH );
+    long framesep = framelength - sum;
+
+    sum = 0;
+    do_channel = false;
+    cchannel = 0;
+    set_timer ( framesep );
+    return;
+  }  
+
+  if ( do_channel )
+  {
+    set_ppm_output( HIGH );
+    long next_timer = (( chwidht * channel[cchannel] ) + chmin);
+    // Do sanity-check of next_timer compared to chmax ...
+    while ( chmax < next_timer ) next_timer--;
+    sum += next_timer;
+
+    // Done with channel separator and value,
+    // prepare for next channel...
+    cchannel++;
+    do_channel = false;
+    set_timer ( next_timer );
+    return;
+  }
+}
+
+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(" min: ");
+    Serial.print(minval[current_input]);
+    Serial.print(" max: ");
+    Serial.print(maxval[current_input]);
+    Serial.println();
+  }
+  Serial.print("Battery level is: ");
+  Serial.println(battery_val);
+
+  Serial.print("Average loop time:");
+  Serial.println(avg_loop_time);
+
+  Serial.println();
+}
+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;
+
+       lcd.setCursor(0 , 0);
+       lcd.print("D/R switch ");
+       lcd.print( no + 1 );
+       lcd.print("    ");
+       lcd.setCursor(0 , 1);
+       lcd.print("Input ");
+       lcd.print(in+1);
+       
+       lcd.print(":  ");
+       if ( ! dr[menu_substate] ) lcd.print("Off");
+       else lcd.print(dr[menu_substate]);
+       
+       if ( check_key(KEY_INC) ) {
+               dr[menu_substate]++;
+               return;
+       }
+       else if ( check_key(KEY_DEC) ) {
+               dr[menu_substate]--;
+               return;
+       }
+       // Wrap around.
+       return; 
+
+}
+
+void dr_value()
+{
+       int pos;
+       int state;
+
+       if ( menu_substate == 4) state = keys[KEY_DR1];
+       else state = keys[KEY_DR2];
+       
+       pos = 4 + (menu_substate - 4) * 2;
+       if (state) pos++;
+       
+       lcd.setCursor(0 , 0);
+       lcd.print("D/R switch ");
+       lcd.print( menu_substate - 3 );
+       lcd.print("    ");
+       lcd.setCursor(0 , 1);
+       lcd.print( state ? "HI" : "LO" );
+       lcd.print(" Value :");
+       
+       lcd.print( dr[pos] );
+       
+       if ( keys[KEY_INC] ) {
+               if ( dr[pos] < 100) dr[pos] += 5;
+               return;
+       }
+       else if ( keys[KEY_DEC] ) {
+               if ( dr[pos] > -100) dr[pos] -= 5;
+               return;
+       }
+       
+       return;
+}
+void ui_handler()
+{
+  int row; 
+  int col;
+  scan_keys();
+
+  if ( displaystate != MENU )
+  {
+       menu_substate = 0;
+    if ( check_key(KEY_UP) && displaystate == VALUES  ) { 
+      displaystate = BATTERY; 
+      return; 
+    }
+    else if ( check_key(KEY_UP) && displaystate == BATTERY ) { 
+      displaystate = TIMER; 
+      return; 
+    }
+    else if ( check_key(KEY_UP) && displaystate == TIMER ) { 
+      displaystate = VALUES; 
+      return; 
+    }
+
+    else if ( check_key(KEY_DOWN) ) { 
+      displaystate = MENU; 
+      return; 
+    }
+  }
+
+  digitalWrite(13, digitalRead(13) ^ 1 );
+
+  switch ( displaystate )
+  {
+    case VALUES:
+      int current_input;
+      for (current_input=0; current_input<=7; 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;
+        }
+        // Overwriting the needed positions with
+        // blanks cause less display-flicker than
+        // actually clearing the display...
+        lcd.setCursor(col, row);
+        lcd.print("    ");
+        lcd.setCursor(col, row);
+        // Display uses percents, while PPM uses ratio....
+        int v = (int)(channel[current_input] * 100);
+        lcd.print(v);
+      }
+      break;
+
+
+    case BATTERY:    
+      lcd.clear();
+      lcd.print("Battery level: ");
+      lcd.setCursor(0 , 1);
+      lcd.print( (float)battery_val/10);
+      lcd.print("V");
+      if ( battery_val < BATTERY_LOW ) lcd.print(" - WARNING");
+      else lcd.print(" - OK");
+      break;
+      
+    
+
+       case TIMER:
+               unsigned long delta;
+               int hours;
+               int minutes;
+               int seconds;
+               
+               lcd.clear();
+               lcd.print("Timer: ");
+               lcd.print( timer_running ? "Running" : "Stopped" );
+               lcd.setCursor(5 , 1);
+               if ( timer_running )
+               {
+                       timer_value = millis() - (timer_start + timer_init);
+               }
+               hours = ( timer_value / 1000 ) / 3600;
+               timer_value = timer_value % 3600000;
+               minutes = ( timer_value / 1000 ) / 60;
+               seconds = ( timer_value / 1000 ) % 60;
+               if ( hours ) {
+                       lcd.print(hours);
+                       lcd.print(":");
+               }
+               if ( minutes < 10 ) lcd.print("0");
+               lcd.print( minutes );
+               lcd.print(":");
+               if ( seconds < 10 ) lcd.print("0");
+               lcd.print( seconds );
+               
+        if ( check_key(KEY_INC) ) { 
+                       if ( !timer_running && !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;
+                       }
+                       return; 
+        } else if ( check_key(KEY_DEC) ) { 
+                       if ( !timer_running && timer_start ) {
+                               timer_value = 0;
+                               timer_start = 0;
+                               timer_init = 0;
+                       }
+                       return; 
+        }
+               break;
+
+    case MENU:
+      lcd.clear();
+      switch ( menu_mainstate )
+      {
+        case TOP:
+          lcd.print("In MENU mode!");
+          lcd.setCursor(0 , 1);
+          lcd.print("Esc UP. Scrl DN.");
+          menu_substate = 0;
+          if ( check_key(KEY_UP) ) { 
+            displaystate = VALUES; 
+            return; 
+          }
+          else if ( check_key(KEY_DOWN) ) { 
+            menu_mainstate = INVERTS; 
+            return; 
+          }
+          break;
+    
+    
+        case INVERTS:
+          if ( menu_substate >= max_channels ) menu_substate = 0;
+          if ( menu_substate < 0) menu_substate = (max_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"));
+
+          if ( check_key(KEY_UP) ) { 
+            menu_mainstate = TOP; 
+            return; 
+          }
+          else if ( check_key(KEY_DOWN) ) { 
+            menu_mainstate = DUALRATES; 
+            return; 
+          }
+
+          if ( check_key(KEY_RIGHT) ) { 
+            menu_substate++; 
+            return; 
+          }
+          else if ( check_key(KEY_LEFT) ) { 
+            menu_substate--; 
+            return; 
+          }
+          else if ( check_key(KEY_INC) || check_key(KEY_DEC) ) { 
+            rev[menu_substate] ^= 1; 
+            return; 
+          }
+          break;
+          
+        case DUALRATES:
+          if ( menu_substate > 5 ) menu_substate = 0;
+          if ( menu_substate < 0) menu_substate = 5;
+               
+          if ( check_key(KEY_UP) ) { 
+            menu_mainstate = INVERTS; 
+            return; 
+          }          
+          if ( check_key(KEY_DOWN) ) {
+            menu_mainstate = EXPOS;
+            return;
+          }
+          if ( check_key(KEY_RIGHT) ) { 
+            menu_substate++; 
+            return; 
+          }
+          else if ( check_key(KEY_LEFT) ) { 
+            menu_substate--; 
+            return; 
+          }
+          switch (menu_substate)
+                 {
+                       case 0:
+                               dr_inputselect(0, 0);
+                               return;
+                       case 1:
+                               dr_inputselect(0, 1);
+                               return;
+                       case 2:
+                               dr_inputselect(1, 0);
+                               return;
+                       case 3:
+                               dr_inputselect(1, 1);
+                               return;
+                       case 4:
+                       case 5:
+                               dr_value();
+                               return;
+                       default:
+                               menu_substate = 0;
+                               break;
+                 }               
+          break;
+        
+        case EXPOS:
+                   //________________
+          lcd.print("Input expo curve");
+          lcd.setCursor(0 , 1);
+          lcd.print("Not implemented");
+          // Possible, if input values are mapped to +/- 100 rather than 0..1 ..
+          // plot ( x*(1 - 1.0*cos (x/(20*PI)) )) 0 to 100
+          // Run in wolfram to see result, adjust the 1.0 factor to inc/red effect.
+          // Problem: -100 to 100 is terribly bad presicion, esp. considering that
+          // the values started as 0...1024, and we have 1000usec to "spend" on channels.
+          if ( check_key(KEY_UP ) ) { 
+            menu_mainstate = DUALRATES; 
+            return; 
+          }          
+          if ( check_key(KEY_DOWN ) ) {
+            menu_mainstate = DEBUG;
+            return;
+          }
+          break;
+          
+        case DEBUG:
+          lcd.setCursor(0 , 0);
+          lcd.print("Dumping debug to");
+          lcd.setCursor(0 , 1);
+          lcd.print("serial port 0");
+          serial_debug();          
+          if ( check_key(KEY_UP ) ) { 
+            // FIXME: Remember to update the "Scroll up" state!
+            menu_mainstate = EXPOS; 
+            return; 
+          } else if ( check_key(KEY_DOWN ) ) {
+            menu_mainstate = SAVE;
+            return;
+          }
+          break;
+    
+        default:
+          lcd.print("Not implemented");
+          lcd.setCursor(0 , 1);
+          lcd.print("Press DOWN...");
+          if ( check_key(KEY_DOWN ) ) menu_mainstate = TOP;
+      }
+      break;
+      
+      
+    default:
+      // Invalid
+      return;
+  }
+
+  return;
+}
+
+
+
+
+\r
diff --git a/source/README b/source/README
new file mode 100644 (file)
index 0000000..f2fa38d
--- /dev/null
@@ -0,0 +1,3 @@
+The reason for the folder-in-a-folder here, is to keep things
+fairly clean, while at the same time satisfy the Arduino requirement
+that the PDE file must live in a folder of the same name.
diff --git a/tools/README b/tools/README
new file mode 100644 (file)
index 0000000..b1daebb
--- /dev/null
@@ -0,0 +1,6 @@
+Yeah. Tools. Must have tools.
+Possible tool examples: 
+ * scripts that generate stuff.
+ * scripts that verify stuff.
+ * other stuff.
+