An Arduino-based DSM KAP Controller


This page describes an Arduino-based radio controller for Kite Aerial Photography. Having built a version of Dave Wheeler's KAP controller for my EOS M rig, and having an old E-Flite MLP4DSM transmitter (designed for Blade helicopters) sitting unused on a shelf, I decided to try to build a small, lightweight Arduino-based TX using the E-Flite’s very small R/F board for my Canon S100 rigs which have continuous rotation servos.

It turns out that this board does not expect PPM signals like the board used in Dave’s TX. Instead it is controlled by sending simple 14 byte serial ‘frames’ using the Arduino TXD pin. Even better, there’s a library on GitHub, written by Erik Elmore, that makes controlling the board really easy.

I decided to make the transmitter as simple as possible. It has a simple push-button to operate the shutter, a 3-position rocker switch to pan left and right and a simple knob to control the tilt. There’s no LCD display so there was very little programming to do.

Since the DSM board requires 3.3v I decided to use a 3.3v version of the Arduino Pro Micro (which runs at 8MHz rather than the 16MHz speed of the Pro Micro used in Dave’s design). Make sure you select the right board in the Tools/Board menu of the Arduino IDE – you can damage the Arduino if you select the 5v board. The controller uses the small X10EMTX DSM2 2.4GHz transmitter board found in the E-Flite MLP4DSM R/C transmitter. Here are pictures of the completed transmitter:


The tape is a temporary measure to stop the box coming open. On this page you will find:

Binding

All 2.4GHz r/c systems require the receiver to be bound to the transmitter (so it will ignore 2.4GHz signals from any other source). Although the receiver you use may have been bound to the original transmitter, it's necessary to redo the binding since the binding packets sent by the TX will not match those sent by the original TX. To bind a receiver to the transmitter:

  1. a bind plug must be inserted into the battery socket of the RX (and the battery inserted into one of the servo sockets)
  2. the shutter button must be held pressed when the TX is switched on (see the 'setup' routine in the Arduino sketch below)

If you do this you will see the RX LED flash when power is applied to the RX and then cease flashing once binding has been achieved.

Note: multiple receivers can be bound to the same TX - I have two rigs that can be controlled by my TX, a picavet rig and a pendulum rig.

Component List

I planned to use the following components:

Circuit Diagram

Here's the circuit diagram:

and here's an annotated picture of the components wired up at the prototyping stage.

In the final version I replaced the rocker switch with a home-built flatter switch as the 12v rocker was too deep to fit in the box. I also wired a charging plug to the AA pack so the cells could be recharged without opening the box - the plug just exits the side of the box. Here's what the current version looks like when disassembled.


The r/f board is Velcro'd to the base of the box and the prototype board holding the Arduino is screwed to two wood strips glued to the base (you can see the two screws either side of the USB socket of the Arduino). The small power LED came from the original E-Flite TX.

The Arduino Sketch

I used the standard Arduino IDE (Version 1.6.4) to compile and upload the sketch. Once again, it's important to make sure you select the Sparkfun Pro Micro 3.3V/*MHz board (under the Tools/Board ... menu). The sketch looks like this:


/*
  Sketch for Spektrum DSM TX - Dave Mitchell
  March/April 2016
  
  This simple sketch is written for a 3.3v Arduino Pro Micro and assumes:
     an X10EMTX DSM2 2.4GHz transmitter board attached to the TXD serial output pin
     a shutter button attached to pin A3
     a 'pan left' button attached to pin A2
     a 'pan right' button attached to pin A1
     a tilt potentiometer attached to pin A0
     a pan creep adjustment potentiometer attached to pin A6 (multi-turn type)
  Note pin A6 is also known as D4 (and labelled as such in the circuit diagram above).
  
  At the receiver, the shutter signal typically operates the shutter of a Canon P&S
  camera via a GentLED device (either using InfraRed or the USB port via CHDK or SDM.
  Panning controls a continuous rotation servo. Since it is very hard to adjust such 
  servos so they don't move at all for a neutral signal, the TX incorporates a small 10-turn
  potentiometer which is used to define the neutral signal. Creep can be eliminated by
  making small adjustments to this.    
     
  The X10EMTX board I used was extracted from an E-flite MLP4DSM transmitter (DSM2).
  This board expects serial packets (not PPM signals) and although the transmitter only
  uses four channels (two joysticks controlling throttle, aileron, rudder and elevator), 
  the board supports six channels.
     
  Note: with modifications the code should work with DSM and DSMX versions too.
     
     For a parts list, circuit diagrams, plans for a box to hold the TX, see:
        https://zenoshrldu.com/KAPDSMTX
*/

#include "Arduino.h"


// the radio board is controlled by the DSM2-tx library written by Erik Elmore
// see https://github.com/IronSavior/dsm2_tx
// to compile the library I had to make a few changes:
// 1. delete the line
//		#include <cstring>
// 2. change 3 references to 'Serial.' to 'Serial1.' (since Serial on an Arduino Pro Micro
//    outputs to the console whereas Serial1 outputs to the TXD pin)
       
// Note that there are a few debug outputs to Serial to check that things are working       

#include "dsm2_tx.h"

 DSM2_tx tx(6);	// define a 6 channel transmitter (though the MLP4DSM TX only exposes 4)

// buttons are debounced using the Bounce library written by Thomas Fredericks
// See http://www.arduino.cc/playground/Code/Bounce
#include "Bounce.h"

#define BOUNCE_DELAY (10) // in milliseconds

// define the pins we use
#define SHUTTER_BUTTON (A3)
#define PAN_LEFT       (A2)
#define PAN_RIGHT      (A1)
#define ADJUST_POT	   (A6) // aka D4
#define TILT_POT	   (A0)

// define some magic numbers that may need modifying
#define T_MIDDLE       (512)	// defines the default for unused channels
#define T_MAX          (800)	// defines the maximum allowed tilt servo value
#define T_SCALE        (200)	// scaling factor for tilt (as a percentage)

#define DIFF  (25)		// pan bump up/down from middle (stop) position
#define SHUTTER_DOWN (170) 	// defines the 'press the shutter' signal 
#define SHUTTER_UP   (850)  	// defines the 'release the shutter' signal

// define the R/C channels we use (Spektrum names - other TX's have different assignments)
#define S_CHANNEL (0) // Throttle used for Shutter
#define P_CHANNEL (1) // Aileron used for Panning
#define T_CHANNEL (2) // Elevator used for Tilting

/*
 * This callback for the bind process is defined by the DSM2_tx library 
 * It's designed to control the UI during the bind process (not that we have a UI!)
 * Bind completed:  state = 0
 * Bind in progress:  state = 1
 * Bind error:  state = 2
 */
void bind_cb( int state, byte model_id ) {
  if (state == 0 ) {
    Serial.write("Bind complete");
  } else if (state == 1) {
    Serial.write("Bind in progress");
  } else if ( state == 2 ) {
    Serial.write("Bind Error");
  }
}

// state variables
	int tilt;
	int panadjust;
	bool panleft;
	bool panright;
	bool shutter;
	
// model holds current state - set by updateModel()
	struct PanTilt_s {
    	  int pan;
    	  int tilt;
    	  bool shutter;
	} model;
	
    byte modelid; 	// DSM transmitters can be used to control multiple models (with
    			// different trims). We're only using one here.

// define the debounced buttons						 
Bounce bouncerS = Bounce(SHUTTER_BUTTON, BOUNCE_DELAY); 
Bounce bouncerL = Bounce(PAN_LEFT,  BOUNCE_DELAY); 
Bounce bouncerR = Bounce(PAN_RIGHT, BOUNCE_DELAY); 

// routine to get all the input values and store them
void getInputs() {
  bouncerS.update();
  bouncerL.update();
  bouncerR.update();
  shutter  = (bouncerS.read() == LOW); 	
  panright = (bouncerR.read() == LOW); 	
  panleft  = (bouncerL.read() == LOW); 
  panadjust = analogRead(ADJUST_POT);
  
  // scale so that 90 degree movement of tilt pot equals 90 degree movement of tilt servo
  tilt = (analogRead(TILT_POT)  * T_SCALE) / 100;
  if (tilt < 0)
     tilt = 0;
  // ensure tilt servo does not move too far beyond 90 degrees  
  if (tilt > T_MAX)
     tilt = T_MAX;   
}

// routine to use input values to update model state
void updateModel() { 
int pan; 
  if (shutter) {
  	Serial.println("Shutter pressed");
  }		
  model.shutter = shutter;
  pan = panadjust;	// use the adjust potentiometer to define the neutral position
  if (panright) {
  	Serial.println("Right pressed");	
  	pan = pan + DIFF;
  }
  if (panleft) {
  	Serial.println("Left pressed");
  	pan = pan - DIFF;
  }
  model.pan = pan;
  model.tilt = tilt;  
}

// routine to convert model state into a transmitter frame and send it
void transmitFrame() {
  int n;
  if (model.shutter)
     n = SHUTTER_DOWN;
  else
     n = SHUTTER_UP;   
  tx.set_channel(S_CHANNEL,n);
  tx.set_channel(P_CHANNEL, model.pan);
  tx.set_channel(T_CHANNEL, model.tilt);
  // we just set the other three channels to a default value
  for( byte i = 3; i < tx.channel_count; i++ ) {
    tx.set_channel(i, 0, T_MIDDLE);
  }
  tx.send_frame(modelid);
}

// routine executed just once at startup to set the initial state. Note that the three 
// buttons are all connected to ground (LOW) when pressed. Defining them as INPUT_PULLUP 
// means that when not pressed they will read HIGH.
void setup() {  
  pinMode(SHUTTER_BUTTON, INPUT_PULLUP);	
  pinMode(PAN_LEFT, INPUT_PULLUP);
  pinMode(PAN_RIGHT, INPUT_PULLUP);
  
  panleft = panright = shutter = false;
  model.tilt = T_MIDDLE;
  modelid = 0;	
  tx.begin();
  /* at start try binding if shutter button pressed (i.e. OFF) */
  if (!digitalRead(SHUTTER_BUTTON)) {
    	Serial.println("Bind started");
     tx.bind(bind_cb);
       	Serial.println("Bind stopped");
  }   
}

// after setup has executed, this routine is called repeatedly until the TX is switched OFF.
void loop() {
  int n;
  
  getInputs();
  updateModel();
  transmitFrame();
}

Comments, suggestions and bug reports welcome. Dave@zenoshrdlu.com.

For other SDM and CHDK-related stuff of mine, see here and here.