I set out to use a trackball to control an alarm clock. It seemed to me like an interesting experiment in UI and another small lesson in electronics. I envisioned using the two axes for controlling hours and minutes (separately and simultaneously) and the tracking speed to specify increments (faster scrolling would mean larger incremcents).
Finding a trackball was not easy. In fact I could only find two – a small clicky trackball from RS (sold also by digikey and mouser – feels pretty crappy) and a beautiful ALPS trackball that was sold by an Aussie company that seemingly unloaded its stock to Roman Black (I got mine directly from him after I mailed him cash). The trackball was surprisingly accurate – no hardware or software filtering was required to receive pure gray code.
After hooking up the trackball I understood that I could not rely directly on its movement to control numeric values with precision as it was too unpredictable. Using a simple “N trackball ticks equal 1 value” did not translate into an acceptable user experience as it proved too difficult to stop at the value we wanted (often going too far and then having to scroll back to correct or conversely, having too great of a dead zone). This would probably work well enough for applications which do not require pin point accuracy like controlling contrast, brightness, movement, etc. However, for setting a precise time this was not good enough.
I decided to decode the trackball gestures instead of numeric value (number of ticks). This would mean defining a set of hand movements against the trackball that would translate into an operation on the clock. To simplify, I decided to control a cooking timer and not an alarm clock as I had already felt at this point that the trackball was not a suitable interface but still wanted to explore detecting gestures. I defined two simple movements – a slow track and a fast track; slow offsetting the timer by one minute and fast by 15. For example, if a user would like to set 50 minutes he would fast track 3 times and then slow track 5 times (on one axis).
Below is an overview of detecting slow/fast tracks with the ALPS trackball. I was able to achieve > 95% success in detecting these two simple gestures (that is less than 1 in 20 movements are detected incorrectly) but found that the trackball is not comfortable enough to operate a timer (currently working on something that hopefully is, details will follow when complete) and decided not to hone this mechanism.
The hardware layer: connecting everything up
The hardware prototype was standard. I used a PIC18F2525 running at 40MHz (10MHz crystal w/PLL – of course you do not need this much horsepower for this task), 4 seven segment displays (multiplexed), a MAX232 for serial and the ALPS trackball. You can read more detail about the ALPS hardware at Roman Black’s excellent page and more about quadrature encoders on google (there are many articles about rotary/quadrature encoders and there’s nothing I can add on this matter). The only worthy thing to report is that I connected the trackball quadrature encoder outputs (2 for each axis) directly to the PIC’s I/O port.
The low level software: Decoding the quadrature encoding
This portion of software needs to perform decoding of the gray code outputted by both axes of the trackball and indicate to the higher layers how many ticks occurred for each axis in each direction. Since, as explained earlier, the gray code is perfect at the PIC’s inputs – there is no special debouncing or filtering required at this stage. There are a few ways of going about this:
- Polling the inputs in a while() loop: Very simple to implement but should your application be busy doing something like populating an LCD while a change occurred, it might skip an encoder transition and you will receive false counts.
- Polling the inputs periodically, using a TMR interrupt: By setting up a timer interrupt, you could perform the same polling as using a while() loop, only there would be no chance of missing an encoder transition assuming your application does not mask interrupts for more than a few microseconds. The downside to this is that your application will always spend cycles looking for encoder transitions even when you don’t want it to.
- Using IO interrupts: Setting up the PIC so that it will generate an interrupt on a change of one of the 4 trackball outputs. I could not do this with the 18F2525 because it did not have 4 free interrupt on change pins.
- Using a dedicated hardware decoder/counter: For example, LSI offers a hardware quadrature decoder (you would need one for each axis). Some PICs even offer a quadrature decoder peripheral (QEI) – e.g. 18F2331.
I opted for using the TMR polling mechanism as it was the simplest to code and efficient in terms of cost and availability. It is not trivial to guess the polling frequency and there is no “right answer”. On one hand you want to poll as infrequently as possible to preserve cycles but on the other – you might miss transitions and have false reports (you would move the trackball in one direction yet still receive some counts indicating it has moved in the other). I found that there was an extremely small error rate (probably up to the mechanics of my finger slightly moving in the wrong direction as I started to spin the trackball) – at most 2 counts – when I had properly configured the polling rate.
The reason for there being no “right answer” is because this depends on your trackball resolution (256 ticks for one full rotation for the ALPS) and, more importantly, how fast you think the user can spin the trackball (this could be up to the trackball electronics, as well). I found that I could not spin the ALPS faster than about 320uS for one encoder full phase (that is, the bit length of a single encoder output was no less than 320uS). Since the second output will transition in the middle of this bit, we need to poll at least twice in 320uS (or about 160uS). I opted to poll at twice that rate and set up my TMR to 80uS with excellent results. When I used incorrect values (say 250uS) it seemed that for every movement in one direction there is about 10%-30% movement in the other. This happens due to missing transitions and incorrectly reporting the direction as a result.
Once we’ve settled on a way of when to read the quadrature encoder, we need to make sense of the inputs. I’ve adopted Roman Black’s method of comparing bit A of the previous state with bit B of the current state – whether they are equal or not defines the direction. This requires shifting and comparing the inputted bytes but it’s fairly straightforward. The low level software would simply increment or decrement a counter accordingly. This counter was read periodically (using a delay) by the high level portion of the software.
Note: While this project only uses one axis, I thought about how to separate the X and Y axes. It occurred to me that usually a user would only want to operate one axis at a time but would probably fail to move the trackball in only one axis. To solve this “axis bleeding” I devised a theory to check for the ratio between X and Y axis counts during a given high level period. If they were close to 1:1 they would register to both axes, otherwise only the axis with the larger amount of counts would be registered, while the other would register as 0. This would mean that if the user moved the trackball up (intentionally) but also inadvertently to one side, this would be filtered out. However, if the user moved the trackball diagonally – this would indicate that he would like to modify both axes at the same time. Using the count ratio this would be possible – the ratio allowed between X and Y counts would determine the width of allowed diagonal movement.
The high level software: detecting gestures
I wanted to see how exactly a short pulse and fast track looked in terms of counts vs. time to see how I can differentiate between them. I outputted the counts I received to the serial and then plotted a graph. It is very easy to see that slow tracks take longer and contain generally up to 20 counts per high level polling period while the fast tracks are very short with a high amount of ticks per period. I also wanted to have only one event per track – regardless of how long it took. Continuous movement is not feasible with a finger driven trackball.
Using this information a devised a simple state machine.
- The state machine would sit in Idle state looking at how many counts occurred in the last period. If the counts were 0, this would forever be the case. Once counts are received (indicating that the low level software has detected trackball movement), the values are compared against a threshold – if below, we would move into debounce state; If above, we would move directly into fast track state.
- When in Debounce state, we wait for the next readout – if it is zero, we would go back to idle and declare no event (thus effectively debouncing small unintentional movements); If it is still not zero but below the high threshold, we would move into slow track state. If it is above, we go into the fast track state.
- When in Slow track state, we will wait for readouts to occur – if one of them is above the threshold we would move into fast track state. If they are all below the threshold we wait until we receive a zero count (indicating the trackball stopped) and then declare a “slow track” event.
- Fast track state is very similar – wait until a zero count arrives and then declare “fast track” event.
The state machine is depicted as follows:
You can see the code for this project here. You may also notice that I included a “consecutive medium hits” counter – this accounts for cases that the user performed a fast track that occurred exactly during the polling period. This would cause the large number of counts to be split over two polling periods and not pass the fast track threshold.
Of course, this is a very primitive and non-optimized implementation I prepared over a lazy Saturday. Should your implementation benefit from a finger driven trackball, you may find the ideas in this article useful.