Arduino-based TX supporting a 360° servo for panning


This page describes the code for a minor variation on my Arduino-based KAP transmitter written for Bill Blake that supports a 360° servo for panning instead of the geared version I wrote for myself. Apart from the changes to the code, the rig pan servo and the addition of a small multi-turn pot to cancel pan creep, everything else is exactly as described here. I've provided a zipfile containing all the source code including the two libraries I used

The Arduino Sketch

I used the standard Arduino IDE (Version 1.6.4) to compile and upload the sketch. Note that Bill and I both failed to compile the sketch using the latest 1.8 version but luckily the 1.6.4 version is available here.


  Sketch for Spektrum DSM TX - Dave Mitchell
  September/November 2016
  April 2017 - modified for continous servo for panning
  
  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
     a pan creep adjustment potentiometer attached to pin A6 (multi-turn type)
  
  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).
  A rocker switch pans left or right. Panning controls a 360 degree 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.     
  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 https://github.com/IronSavior/dsm2_tx
// 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 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)
#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 DIFF  (25)		// pan bump up/down from middle (stop) position - determines panning rate

#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;
	int panadjust;	// this value defines the midpoint of the pan servo
	bool panleft;
	bool panright;
	bool shutter;
	bool hover;
	
	    
// 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() {
  bouncerS.update();
  bouncerL.update();
  bouncerR.update();
  bouncerH.update();
  shutter  = (bouncerS.read() == LOW); 	
  hover    = (bouncerH.read() == LOW); 	
  panright = (bouncerR.read() == LOW); 	
  panleft  = (bouncerL.read() == LOW); 
  panadjust = analogRead(ADJUST_POT); //  set pan servo midpoint

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

// 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 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");		
  if (panright) {
// Serial.println("Left pressed"); 	
  	pan = panadjust + DIFF;
  } else if (panleft) {
// Serial.println("Right pressed");
  	pan = panadjust - DIFF;
  } else {
    pan = panadjust;
  }

  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;
  else
     s = SHUTTER_UP;  
  if (model.hover)
     h = HOVER_LAND;
  else 
     h = HOVER_PORT;       
  tx.set_channel(S_CHANNEL,s);
  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);
  }
  tx.send_frame(modelid);
}

// 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() {  
  pinMode(SHUTTER_BUTTON, INPUT_PULLUP);	
  pinMode(PAN_LEFT, INPUT_PULLUP);
  pinMode(PAN_RIGHT, INPUT_PULLUP);
  pinMode(HOVER_SW, INPUT_PULLUP);	
  
  panleft = panright = shutter = hover = false;
  tilt = T_MIDDLE;
  model.tilt = tilt;
  model.pan = pan;
  model.shutter = shutter;
  model.hover = hover;
  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");
  }  
  Serial.println("setup ended"); 
}

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

Version History

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

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