Sunday, December 21, 2008

Jogging and shuttling with Arduino.

As those who follow my dweenofurkling antics will be aware, Ive recently started experimenting with DDS VFOs (Direct Digital Synthesis Variable Frequency Oscillators) Ive been looking at the AD9851 from analog devices.

One of the easiest ways of building and experimenting with this device is via the DDS60 board from AmQRP This simple board just needs power and a serial data source. Unsurprisingly I plan on using an arduino to provide the correct data-word corresponding to the required output freq. A lot of the groundwork has already been tackled by others - Marxy has a good writeup on his blog which has formed the basis of quite a bit of my experiments to date. My contribution to the cause is towards practical real-world use of the DDS system. Link

The DDS itself has a frequency range of 0-60Mhz, in 1Hz steps. With such a wide range what would be really handy is the ability to scroll thru a wide range of frequencies rapidly, slow down, then fine tune over a few hertz. A combination of Keypads, up down buttons and rotary encoders are the usual method of changing frequency, combined with an LCD frequency readout. All work fine, but are really rather clunky methods of navigating up and down a wide range of frequencies, but with good fine tuning accuracy.


There is however a device that make this sort of thing really quite intuitive - the Jog/Shuttle Dial

The jog/shuttle dial has an outer ring which allows you to rapidly select +/- movement (usually of position within multi-media) with center stop. The greater the deflection, the higher the rate of change. The inner wheel with finger hole spinner allows fine tuning up down depending on direction of rotation like a conventional rotary encoder.

This type of control features occasionally on high-end radio transceivers for VFO control, but is usually well beyond the means of the typical home constructor. Partly because of cost, and partly because of the complexity of interfacing. HOWEVER!! these days that doesn’t have to be the case :)

The following few blog entries describe my tests interfacing a jog/shuttle control to an arduino for use with the DDS board. Hopefully others will find the tests useful and will incorporate this useful control type into other radio and electronic projects.

So, first things first - where do you get a jog/shuttle control?

Well. You can of course buy one, ALPS make them. Several suppliers stock them. The usual problems of minimum order quantity and cost are however factors to consider here - they are expensive items. There is however a fairly reliable source of jog/shuttle dials available second hand - as long as you dont mind a bit of disassembly to get at them. I refer of course to their use in video recorders. Quite a range of "pro-sumer" grade VCRs sold over the last 10 years will have featured a jog-shuttle dial. Ebay, junk stores and swapmeets etc are a good source.

Another place you may find a jog shuttle dial is on the remote control that went with the VCR - which is where I got the one Im experimenting with. (There are a couple of sellers on Ebay who sell just remotes - and the one I bought cost me about £12).

The case on this one had the usual single screw and snap fit casing lugs, so it was on the bench and plugged into the logic analyser within a few minutes of it arriving in the post.

Bench testing the control

I connected up the remote to a 3v supply via the battery terminals and tacked on some breakout leads to the switch connections to connect up my logic analyser.

The pinout was thoughtfully printed on the reverse of the PCB. I expected to find a simple two-pin-plus- ground AB quadrature system for the jog dial (as denoted by J1/J2, with 0v via C2) and 4 other pins plus 0v common C1 giving a grey coded binary output from the shuttle ring, and this seemed to be confirmed by the pins.



Each pin is pulled high via a 5k res and gives conventional high/low output
.


Firstly I decided to check that the jog dial was as expected - plain old 2bit greycoded quadrature.

Yup, fairly conclusive. I did however note that sometimes the jog dial would produce two transitions per click on the dial, and sometimes only one.












There doesnt seem to be any specific pattern I can find to this, it does just seem to be that the detents are just in unfortunate places. The mark and space ratio was also rather variable, especially when spinning at speed.
So far, so good.... So what about the shuttle dial? Slowly rotating the shuttle dial gave the expected single bit change at about 10 degree steps. Building a truth table took just a few moments.
(God knows how I used to do stuff like this before I had a logic analy
ser - perhaps I had more patience then!)


As expected, only a single bit changes for each step, all the clockwise positions resulting in an odd number, all the anti-clockwise positions resulting in an even number. Not too difficult to match up to either an array based lookup or some bitmath.




So, now its time to warm u
p the soldering iron again and make up a test rig :)

I carefully removed the control and placed it on a scrap of veroboard and added some 90degree header pins to make plugging it into a breadboard a bit more easy.

Although I could have used the on-board pullup resistors on the arduino board I opted to add pullups to this board as it could then be used stand-alone with my logic analyser and scope etc too.












Here we see the control in place in the breadboad connected to arduino and laptop.


Software:

As with the hardware tests I decided to start with the jog dial first. Initially I checked that I could actually see the 2bit grey code states change as I spun the dial. Once I had verified I could actually see the states I wrote this small test sketch:


-----------------------------------------

/*
Jog test - 1

(a minimal quadrature type decoder for jog dials)

*/

int clock = 6; // Define encoder pin A
int data = 7; // Define encoder pin B
int count = 0; // pre-init the count to zero
int c = LOW; // pre-init the state of pin A low
int cLast = LOW; // and make its last val the same - ie no change
int d = LOW; // and make the data val low as well

void setup() {
pinMode (clock,INPUT); // setup the pins as inputs
pinMode (data,INPUT);
Serial.begin (9600); // and give some serial debugging
}

void loop() {
c = digitalRead(clock); // read pin A as clock
d = digitalRead(data); // read pin B as data

if (c != cLast) { // clock pin has changed value... now we can do stuff
d = c^d; // work out direction using an XOR
if ( d ) {
count--; // non-zero is Anti-clockwise
}else{
count++; // zero is therefore anti-clockwise
}
Serial.print ("Jog:: count:");
Serial.println(count);
cLast = c; // store current clock state for next pass
}
}

-----------------------------------------

None of that should need much explaining, the comments cover pretty much how it works. In an ideal world I would have the polling replaced by interrupts, but as a test for now it seems fine.























As can be seen from the screendump, it was fairly easy to make a running count which was increased and decreased by spinning the jog dial.



Next I wrote a small sketch to work with the shuttle ring.


-----------------------------------------
/*
* Shuttle test prog -1
*/


// define grey code positions stored in 0-15 array in global scope
int gray2pos[] = { -60,+50,-70,+60,-50,+40,-80,+70,-30,+20,-20,+10,-40,+30,-10,0 };


void setup()
{
Serial.begin(9600); // set up Serial library at 9600 bps
pinMode(8, INPUT); // sets the digital pins as input
pinMode(9, INPUT);
pinMode(10, INPUT);
pinMode(11, INPUT);
}


void loop()
{
Serial.print("Shuttle :: Bin:");
Serial.print(PINB,BIN); // scan PORTB (pins 8-13) and print as a binary
Serial.print(" Dec:");
Serial.print(PINB,DEC); // print the same data as a decimal
Serial.print(" Pos:");
Serial.print(gray2pos[PINB]);
Serial.println(176, BYTE); // extended ascii val to get the degrees symbol
delay(1000);
}

-----------------------------------------


This bit might need a little more explaining. As you can see from the sketch, I included an array at the top which held the 16 possible positions the shuttle ring could be in with its correct angle in the array position which correcponded with the decimal value of the 4 data bits from the switch - ie if the switch binary pattern was 0110 th at gives us a decimal value of 6 - so look in array position 6 and we find the value "-80" which indicates the switch was turned 80 degrees anti-clockwise.























The other bit thats possibly not clear right away is where I get this number from... well the easiest way to do quick bit bashing on an arduino is to read all the pins you want as a single port - ie to read each of the bits as its binary equiv. This is done using the PINB command. the B port is actually digital pins 8-13 but I am only using 8-11



So, now lets put both of those together

-----------------------------------------
/*
* Full Jog/Shuttle test prog
*/

// global scope vars
int clock = 6; // Define jog pin A
int data = 7; // Define jog pin B
int c = LOW; // pre-init the state of pin A low
int cLast = LOW; // and make its last val the same - ie no change
int d = LOW; // and make the data val low as well
int count = 0; // pre-init the count to zero

// define counter shuttle steps array
int gray2pos[] = { -64,32,-128,+64,-32,16,-256,+128,-8,4,-4,2,-16,8,-2,0 };


void setup()
{
Serial.begin(9600); // set up Serial library at 9600 bps
pinMode (clock,INPUT); // setup the jog pins as inputs
pinMode (data,INPUT);
pinMode (8, INPUT); // sets the shuttle pins as input
pinMode (9, INPUT);
pinMode (10, INPUT);
pinMode (11, INPUT);
}


void loop()
{
checkjog(); // move each of the checks out to a function
checkshuttle();

Serial.print("Jog/Shuttle:");
Serial.println(count);
}


int checkjog(){
c = digitalRead(clock); // read pin A as clock
d = digitalRead(data); // read pin B as data
if (c != cLast) { // clock pin has changed value... now we can do stuff
d = c^d; // work out direction using an XOR
if ( d ) {
count--; // non-zero is Anti-clockwise
}else{
count++; // zero is therefore anti-clockwise
}
cLast = c; // store current clock state for next pass
}
}

int checkshuttle(){
count += gray2pos[PINB];
}

-----------------------------------------
and we end up with























And thats pretty much it really. Rotating the shuttle ring further produces a step size that gets progressively larger positive or negative. spinning the jog dial allows fairly precise setting of any value. As the detent clicks on this dial tend to be in odd places, Im tempted to modifiy the code so that it takes several clicks, possibly even a complete turn to increment the count by one. (simple adding of a fractional value and INTing the value could accomplish this).

As high speed isnt expected, its quite OK to use routines like the above, though use of interrupts will of course be a better option.

Its now a fairly simple matter to add this functionality to the exisiting DDS word gen code from the Marxy blog - though Ive just not had chance to start on this yet. Future challenges will however be ensuring that there are enough pins to drive an LCD display for output and the DDS serial stream.

More to follow when I get some more bench time...

Housekeeping...

Regular watchers of my assorted blogs will have noticed Ive had a bit of a tidy up. As my assorted blogs have covered travel, climbing, mountain biking, running, electronics and radio, astronomy, photography, family, and life in general some of the blogs have got a little dis-jointed and may not have been of interest to everyone all of the time. So it got to be time for a tidy up. Some stuff has been moved, some stuff that didnt have an obvious home has been removed. Hopefully the revised structuring will make a little more sense and will (hopefully!) be easier to stick to.

The G7NBP - Random noise blog continues to focus on experiments in radio and electronics, but now also encompasses some of my Arduino based experiments too, as they generally have relevance to more general radio and electronics hobby areas.

Other blogs and assoreted life streams are of course still being aggregated at my swurl site - http://chrisw.swurl.com/