Another Arduino-based DSM KAP Controller

This page describes an Arduino-based KAP transmitter for controlling a Hover KAP rig. supports a standard, geared servo for panning as well as a servo that switches the camera between landscape (H) and portrait (V) modes. The HoVer rig it controls is described here. A more complex version, that has AUTOKap capability, is described here.

This transmitter uses an X1TX0 r/f board taken from a Spektrum DX4e transmitter (the DX5e uses the same board). Only 3 of the pins are used - ground, power and signal.

This board gives the transmitter much better range than the X10EMTX r/f board taken from the 'park-flyer' E-Flite MLP4DSM TX that I used in my earlier arduino-based TX, but is programmed in exactly the same way. The board's 12 pins are soldered to the main circuit board with a plastic separator between the two. I found the easiest way to remove the board was to use a rotary sander tool to sand the bottom of the pins off. The board could then be simply prised off. The transmitter also provided a multi-way plug so that I didn't need to solder wires directly to the r/f board. The antenna is connected to the board using an IPX socket, which makes it easy to fit a removable antenna to the TX box (see photo below).

I've provided a zipfile containing all the source code including the two libraries I used

Like my earlier DSM transmitter, 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. The one extra feature is a switch to flick the camera between Landscape (horizontal) and Portrait (vertical) positions. The new rig, as well as having HoVer capability, is also designed to carry a 5.8GHz video downlink since panning is done using a simple rocker switch and there's no other feedback of the orientation of the rig.

Since the DSM 2 board requires 3.3v I decided again 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 Wheeler’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.

Note: this rig uses a standard servo, geared up by a factor of 4, so panning is limited to 360°. When the limit is reached, the servo is automatically rotated back through approximately 360° so from the operator's point of view, panning is effectively continuous. Dave used the same technique on his Arduino-based transmitter.

Here's a picture of the completed transmitter with external antenna with the 5.8GHz video reeceiver mounted on top.

Below you will find:


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.

TX Component List

I used the following components for the TX:

The video downlink uses the following components:

TX Circuit Diagram

Here's the TX circuit diagram. Note that when 5v is applied to the Arduino RAW pin the VCC pin outputs 3.3v - just what is needed for the r/f board and the tilt potentiometer.

here's a picture of the components wired up at the testing stage (using the original antenna), and plans for the box parts:

The r/f board is Velcro'd to the base of the box and the prototype board holding the Arduino is screwed to a wood strip glued to the base.

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/8MHz board (under the Tools/Board ... menu). The sketch looks like this:

  Sketch for Spektrum DSM TX - Dave Mitchell
  September/November 2016
  This simple sketch is written for a 3.3v Arduino Pro Micro and assumes:
     a SPEKTRUM X1TX0 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 HoVer switch attached to pin A0
     a tilt potentiometer attached to pin A10
  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 normal servo geared 4:1 so a 90 degree servo movement translates into 
  a 360 degree pan. A rocker switch pans left or right at a rate of approximately 15 degrees
  a second (4 degrees for the servo). As written, when an attempt is made to rotate past 
  360 degrees, the servo rotation is reversed and slews through 360 degrees instead. 
  A video downlink is advised if the rig is far from the KAPer, otherwise it may be hard 
  to know in which direction the rig is pointing.
  The tilt pot is arranged so that a 90 degree turn moves between 0 and some positive value 
  (typically around 320). The code scales this so the actual value sent to the servo varies 
  between 0 and 800 (a 90 degree servo movement). The scale value used here is 2.5 (25/10). 
  The sketch supports a 'HoVer' switch to rotate the camera beween Portrait and Landscape.
  The X1TX0 DSM board I used was extracted from a Spektrum DX4e transmitter. This board 
  expects serial packets (not PPM signals).
  Note: with modifications the code should work with DSMX versions too.

#include "Arduino.h"

// the radio board is controlled by the DSM2-tx library written by Erik Elmore
// see
// to compile the library I had to make a few changes:
//  1. delete the line
//		#include 
//  2. change all 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

// buttons are debounced using the Bounce library written by Thomas Fredericks
// See
#include "Bounce.h"

#define BOUNCE_DELAY (10) // in milliseconds

// define the pins we use
#define PAN_LEFT       (A2)
#define PAN_RIGHT      (A1)
#define HOVER_SW       (A0)
#define TILT_POT       (A10)

// 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        (25)     // scaling factor for tilt 
#define P_MAX          (1000)	// defines the maximum allowed pan servo value
#define P_MIN          (20)     // defines the minimum allowed pan servo value

#define P_MIDDLE       (510)	// defines the initial pan servo value

#define PADJ           (5)      //	pan bump up/down value (15 degrees/sec approx)
#define SADJ           (20)     //	slew bump up/down value (60 degrees/sec approx)
#define PINT           (100)    //	pan bump interval (100 ms)
#define PRD            (-1)     //	pan right/down
#define PSLEWOFF       (0)      //	pan slew off
#define PSU            (1)      //	pan left/up

#define SHUTTER_UP     (170) 	// defines the 'press the shutter' signal 
#define SHUTTER_DOWN   (800)  	// defines the 'release the shutter' signal
#define HOVER_LAND     (50)   	// defines the HoVer landscape value 
#define HOVER_PORT     (850)  	// defines the Hover portrait value

// 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
#define H_CHANNEL (3) // Rudder used for HoVer

 * 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 tilta; // actual pot reading
	int tilt;  // computed tilt value
	int pan;
	bool panleft;
	bool panright;
	int pslew;
	bool shutter;
	bool hover;
	unsigned long interval = PINT; 	// 1/10 sec)
	int panadjst  = PADJ;           // approx 15 degrees pan a sec) 
	int slewadjst = SADJ;			 
	unsigned long pst;              // slew timers
	unsigned long mst;
	unsigned long pt;               // pan timers
	unsigned long mt;
// model holds current state - set by updateModel()
	struct PanTilt_s {
    	  int pan;
    	  int tilt;
    	  bool hover;
    	  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); 
Bounce bouncerH = Bounce(HOVER_SW, BOUNCE_DELAY); 

// routine to get all the input values and store them
void getInputs() {
  shutter  = ( == LOW); 	
  hover    = ( == LOW); 	
  panright = ( == LOW); 	
  panleft  = ( == LOW); 

  // scale so that 90 degree movement of tilt pot equals 90 degree movement of tilt servo
  tilta = analogRead(TILT_POT);

// routine to scale and adjust tilt
void adjustTilt() {
  tilt = ((tilta  * T_SCALE) / 90) * 9;
  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 compute pan servo from state of pan rocker
void computePan() {
  int pa, psa;
  if (pslew != PSLEWOFF) {
//     Serial.println("Slewing");
     psa = pslew * slewadjst;
     mst = millis();
     if (pst == 0) {	// slew switch only just set
//    	Serial.println("Slewing started");
	    pst = mst;
	    pan += psa;
     } else if ((mst - pst) > interval) {	// has it been on long enough for another pan slew?
		   pan += psa;
//    	Serial.print("Slew Pan =");
//    	Serial.println(pan);
		   pt = mt;
     if (pslew == PSU && pan > P_MAX - SADJ)   // go roughly 360 degrees
       pslew = PSLEWOFF;
     else if (pslew == PRD && pan < P_MIN + SADJ)   
       pslew = PSLEWOFF;    
  } else if (panleft || panright) {
     pa = (panleft ? PLU : PRD) * panadjst;
     mt = millis();
     if (pt == 0) {	// pan switch only just set
//    	Serial.println("Pan Left or Right pressed");
	    pt = mt;
	    pan += pa;
     } else {	
	    if ((mt - pt) > interval) {	// has it been on long enough for another pan bump?
		   pan += pa;
//    	Serial.print("Pan =");
//    	Serial.println(pan);
		   pt = mt;
     // the following tests ensure that rotation can be effectively continuous - on reaching 
     // a stopping point the servo slews forward or back roughly 90 degrees (and the rig 360 degrees) 
     if (pan < P_MIN )
        pslew = PLU;
     else if (pan>P_MAX)
        pslew = PRD;
 } else {
     pt = 0;
     pst = 0;

// routine to use input values to update model state
void updateModel() { 

//  if (shutter) {
//    Serial.println("Shutter pressed");
//  }		
//  if (hover) {
//  	Serial.println("Hover pressed");
//  }
//  if (model.shutter != shutter)
//     Serial.println("shutter changed");		
//  if (model.pan != pan)
//     Serial.println("pan changed");		
//  if (model.tilt != tilt)
//     Serial.println("tilt changed");		
//  if (model.hover != hover)
//     Serial.println("hover changed");		

  model.shutter = shutter;
  model.pan = pan;
  model.tilt = tilt; 
  model.hover = hover; 

// routine to convert model state into a transmitter frame and send it
void transmitFrame() {
  int s, h;
  if (model.shutter)
     s = SHUTTER_DOWN;
     s = SHUTTER_UP;  
  if (model.hover)
     h = HOVER_LAND;
     h = HOVER_PORT;       
  tx.set_channel(P_CHANNEL, model.pan);
  tx.set_channel(T_CHANNEL, model.tilt);
  tx.set_channel(H_CHANNEL, h);

  // we just set the other two channels to Hover value
  for( byte i = 4; i < tx.channel_count; i++ ) {
    tx.set_channel(i, 0, h);

// routine executed just once at startup to set the initial state. Note that the buttons 
// and switches are all connected to ground (LOW) when pressed. Defining them as INPUT_PULLUP 
// means that when not pressed they will read HIGH.
void setup() {  
  panleft = panright = shutter = hover = false;
  pan = P_MIDDLE;
  tilt = T_MIDDLE;
  mt = pt = mst = pst = 0;
  pslew = PSLEWOFF;
  model.tilt = tilt;
  model.pan = pan;
  model.shutter = shutter;
  model.hover = hover;
  modelid = 0;	

  /* at start try binding if shutter button pressed (i.e. OFF) */
  if (!digitalRead(SHUTTER_BUTTON)) {
    	Serial.println("Bind started");
       	Serial.println("Bind stopped");
  Serial.println("setup ended"); 

// after setup has executed, this routine is called repeatedly until the TX is switched OFF.
void loop() {

Note: because the pan servo is not a continuous 360° one, when the pan switch is toggled left or right panning has to be done in a timed manner. The code accomplishes this by keeping account of how long the switch is pressed and making a small pan (about 1.5°) every tenth of a second. In the same way, when the pan has reached a limit and the rig has to be rotated back by 360°, rather than doing this in one swift movement, the code slews through 360 degrees at a rate of about 6° every tenth of a second.

Finally, note that in order that the rig tilts through 90° when the tilt potentiometer is rotated through 90°, the code assumes that one extreme has the pot at its minimum (0) value.

Version History

Comments, suggestions and bug reports welcome.

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