Hi Folks,
recycling time - after buying a broken Panasonic NV-F75 VHS tape recorder from eBay, and no way to rescue the appliance as a whole, I stripped the valuable parts from it and now it's time to take a closer look at that Jog/Shuttle wheel that the more exclusive Panasonic models featured since the 90s. It actually consists of two nested axes, the outer one having fixed limits to the left and to the right and a center notch (shuttle wheel) while the jog dial in the center spins endlessly in clockwise or counterclockwise direction, with approximately eight "stops" per rotation as a feedback for the user.
I always loved that feature because it gave you a great way of controlling playback, allowed cueing forwards and backwards in three different speeds, and with the center wheel you could also navigate to one specific frame if you wanted, e.g. to continue recording a movie after the commercial break with frame exactness (well, nearly...)
Plus it added a professional touch to the whole device without giving it the monstrous appearance of typical studio equipment. Great thing! That would make a nice two-in-one control after all, and it appears a nice exercise to get somewhere with the Arduino microcontroller.
So here it is:
View from the back - the two latches left and right of the center are not actually contacts with electrical meaning. I assume they mainly serve for fixing the unit properly on the PCB where it is mounted.
The 8-pin connector that I soldered to the unit is meant to adjust the contacts to fit the encoder's custom pin spacing to match a standard breadboard spacing. Figuring out the pinout from outside seemed a little hard, so I decided to open the unit and take a close look inside.
It is quite easy to open it. Just pry open the four clips on the back as indicated here and the unit comes apart - the two latches left and right from the center may be a little tricky though:
It consists of 5 pieces in total:
From left to right:
- backplate and center axis
- rotary encoder for jog dial
- interconnect place with 3 conductors directed to the bottom for the jog dial and 6 pointing to the top for the shuttle wheel
- shuttle wheel ground plate (contacts on the backside, see below)
- top ring to hold it all together and limit the range of motion for the shuttle wheel
Here are some closeups:
There is a lubricant across all contacts that I didn't want to remove to keep the unit in good shape. What we see here is part of the jog dial encoder. All golden surfaces are interconnected. Still there are three tracks. The center track is ground, and as the two other (partially covered) tracks are running parallel, the encoding trick must take place on the interconnect plate.
The interconnect plate. On the left, we see six contacts that face to the front (to touch the bottom of the shuttle wheel - we'll see that later). The right side offers three contacts. Remeber the layout of the encoding wheel? We can see here that the left (ground) and middle contact are on the same level while the right contact is a little longer. That means that in comparison to the middle contact, the right one will connect to ground either later (in a clockwise rotation) or earlier (in counterclockwise rotation) than the middle contact. That gives a decent way of figuring out which direction the jog wheel turns. I'll discuss that later in detail.
Bottom side of the shuttle wheel. That looks like a binary encoder indeed. The inner ring seems to be the most significant bit (MSB) as it has only two different states from one end to the other (left: off, right: on). The further outside the rings are, the more frequently they change their on/off states.
Guessing from the layout of the traces on the interconnect plate, the left four or five contacts (seen from the top of the unit) probably deal with the shuttle wheel whereas at least two of the right contacts deal with the jog dial. One pin is probably the common ground line.
Using a multimeter, I found out that the pinout seen from the direction shown in the photo is (from left to right):
Shuttle Bit 3 (MSB)
Shuttle Bit 2
Shuttle Bit 1
Shuttle Bit 0 (LSB)
Common Ground
Jog Dial Middle Contact (I'll call that A from now on)
Jog Dial Right Contact (calling this one B from now on)
Examining the Jog Dial
This closeup shows the encoding magic. The left of the three connectors is ground, and the middle one is alternating between ground and NC as the dial is turned. Same goes for the outer pin but that one catches the ground / NC status a little before or after the center pin. This relation in timing lets us determine the rotary direction eventually:
Turning the jog dial clockwise would cause the center pin to change first from NC to ground or vice versa. As the outer pin is a bit longer, it takes more time for it to reach the same connection state. Likewise, when turning counterclockwise, the outer pin reaches ground first, and the center pin shortly after that.
We can draw two bits from there. When any change is detected, we need to compare the previous set of bits to the current set. Depending on the direction, the sequence of values will give us a clue. I'll discuss that in detail further below.
Examining the Shuttle Wheel
Now that the ground wire and the four bits for the shuttle wheel data are known in the pinout, it is time to connect the whole thing and link up some LEDs to determine the order of values we get for each position of the wheel:
I prepared a breadboard with six LEDs and set up the pins to match the ones on the encoder unit.
The left four LEDs are the bits from the shuttle wheel (left = MSB, then bits 2, 1, 0). The other two LEDs are what I have called A and B earlier, and they reflect the current jog dial activity.
One might have derived the values from the encoder backplate, but I preferred to try to get the most accurate results. Why waste time guessing?
This is what I found out:
See how nicely this pattern repeats on the backplate of the shuttle wheel? Only one odd thing I found: the shuttle is supposed to have a center position and the same number of positions to the left and to the right, but that would require an odd number of states. In fact, I found 16 different states, which means that the center position is not actually the center. There should be two values indicating center, but the center notch is clearly linked to the value 12. Well, the value 4 appears only a very short time when turning right from the center notch, so it would probably be good to interpret both 12 and 4 as "centered".
Logic Behind the Jog Dial
We still have to figure out how to detect clockwise / counterclockwise motion on the jog dial. The sequence of bits is like this (as measured on the breadboard):
So rotating clockwise will give this sequence: 2, 3, 1, 0, 2, 3, 1, 0 etc.
Counterclockwise results in: 1, 3, 2, 0, 1, 3, 2, 0 etc.
The orange arrows just indicate where the pattern repeats. This is to cover the sequence "0, 2" for clockwise and "0, 1" for counterclockwise rotation that one might otherwise miss.
Now we don't want to sample four values until we are sure about the direction, so let's take a look at all possible combinations of one value followed by the next, because in most cases just two samples are enough to give a clue.
For instance, if we look at the clockwise sequence above once more, there are four possible pairs contained:
- 2, 3
- 3, 1
- 1, 0
- 0, 2
Same for the counterclockwise list:
- 1, 3
- 3, 2
- 2, 0
- 0, 1
So from the twelve possible sets, we can at least identify these eight doubtlessly:
Value1 always preceeds Value2 in time, so in order to measure a movement, we need to have a "previous" value buffered to compare with the current measurement. We have four states that clearly indicate a clockwise motion because these four sequences appear only as a subset of the clockwise sequence shown above. Same goes for the counterclockwise sequences of which we have also four. Eventually there are four states that are indeterminate because these sequences appear neither on the clockwise nor on the counterclockwise original sequence. If we measure something like this, this should just cause a repetition of the last clearly identified motion.
I think the easiest way to manage values from the jog dial is a single value that either decreases (when rotating counterclockwise) or increases, like a slider control in current computer operating systems.
The following "jog states" we might detect (again, this presumes we have a "previous" state = State1 and a current one, State2):
Read the table like this:
- for 1st line: if there was no known previous state, and the current state indicates clockwise movement, then increase the value
- for 3rd line: if the previos state was a clockwise rotation, and the current state is indeterminate, just act like the jog dial is still turned clockwise, and increase the value
- for 5th line: if the previous state was clockwise rotation, but the current one is counterclockwise, decrease the value
I have made some notes during my breadboard experiments. Most of it is covered above in nice Excel tables, but this also shows the controller pinout. As you can see here, there are only seven positions associated with the "left" side of the shuttle wheel (i.e. if it is turned far right, that means it has position 8), while there are eight for the "right" side, leaving one that clearly indicates center position (12). I'll interpret the value 4 as "center", too, because it is very small compared to the range of motion, and this balances the number of values on both sides.
That's it for now. Soon I will try to use this in combination with an LCD display on the Arduino Duemilanove. Might take a while because that C language keeps driving me nuts. Once there is something to be shown, I will post it here of course!
Thanks for reading! CU soon!
Follow-up: Arduino Sketch
As requested by Fentronics, here is the Arduino sketch I used to read both components of the device, the rotary encoder (jog wheel) and the Gray-encoded shuttle wheel:
// pinout:
const int LED = 13;
const int JOG0 = 7; // digital 7
const int JOG1 = 6; // digital 6
const int SHUTTLE0 = 2; // digital 2
const int SHUTTLE1 = 3; // digital 3
const int SHUTTLE2 = 4; // digital 4
const int SHUTTLE3 = 5; // digital 5
const int ROT_CW = 1;
const int ROT_CCW = 2;
const int ROT_NA = 0;
byte prevJogCode = 0;
byte prevJogState = ROT_NA;
int jogValue = 0; // value of the jog wheel
// jog state to direction translation
const byte jogStates[16] = { ROT_NA, ROT_CCW, ROT_CW, ROT_NA,
ROT_CW, ROT_NA, ROT_NA, ROT_CCW,
ROT_CCW, ROT_NA, ROT_NA, ROT_CW,
ROT_NA, ROT_CW, ROT_CCW, ROT_NA };
byte shuttleValue = 0; // value of the shuttle wheel
byte prevShuttleValue = 0;
// gray to binary translation
const byte shuttleValues[16] = { 8, 9, 11, 10, 14, 15, 13, 12, 4, 5, 7, 6, 2, 3, 1, 0 };
void setup()
{
pinMode(LED, OUTPUT); //we'll use the debug LED to output a heartbeat
pinMode(JOG0, INPUT);
pinMode(JOG1, INPUT);
digitalWrite(JOG0, HIGH);
digitalWrite(JOG1, HIGH);
pinMode(SHUTTLE0, INPUT);
pinMode(SHUTTLE1, INPUT);
pinMode(SHUTTLE2, INPUT);
pinMode(SHUTTLE3, INPUT);
digitalWrite(SHUTTLE0, HIGH);
digitalWrite(SHUTTLE1, HIGH);
digitalWrite(SHUTTLE2, HIGH);
digitalWrite(SHUTTLE3, HIGH);
Serial.begin(9600);
}
void loop()
{
// read jog rotary encoder (2 bits)
byte thisJogCode = (digitalRead(JOG1) == LOW ? 2 : 0) +
(digitalRead(JOG0) == LOW ? 1 : 0);
// compare to previous
bool jogChange = (thisJogCode != prevJogCode);
if (jogChange)
{
// determine current direction from the states table
// using the previous and current value
byte jogState = jogStates[prevJogCode * 4 + thisJogCode];
// if the new state is NA, continue into the same direction
// as last time
if (jogState == ROT_NA) jogState = prevJogState;
// increase/decrease counter
switch (jogState)
{
case ROT_CCW: jogValue--; break;
case ROT_CW: jogValue++; break;
}
prevJogCode = thisJogCode;
prevJogState = jogState;
}
// read shuttle bits
byte gray = (digitalRead(SHUTTLE3) == LOW ? 8 : 0) +
(digitalRead(SHUTTLE2) == LOW ? 4 : 0) +
(digitalRead(SHUTTLE1) == LOW ? 2 : 0) +
(digitalRead(SHUTTLE0) == LOW ? 1 : 0);
// convert to binary (0 = far left, 15 = far right)
byte shuttleValue = grayToBinary(gray);
bool shuttleChange = (shuttleValue != prevShuttleValue);
prevShuttleValue = shuttleValue;
// announce to serial if there are any changes
if (jogChange || shuttleChange)
{
digitalWrite(LED, true);
Serial.print("Shuttle: ");
Serial.print(shuttleValue);
Serial.print(" Jog: ");
Serial.println(jogValue);
digitalWrite(LED, false);
}
}
byte grayToBinary(byte grayCode)
{
for (int i = 0; i <= 15; i++)
{
if (shuttleValues[i] == grayCode) return 15 - i;
}
return 0;
}
The code is quite primitive. It would be much cleaner to use interrupts so the processor only has something to do on any bit change instead of testing each bit thousands of times per second. Well, this is only for testing so I left it at this.
The code will measure all pins and derive the value information from their states. Whenever a state change is detected in one of both components, a line is written to the serial interface reflecting both values, and the LED (pin 13) is flashing.
The jogStates and shuttleValues tables are directly reused from the tables shown in the original post.
Feel free to ask if you have any questions, and have a lot of fun :)
This is how to wire it up - couldn't be easier, really!