12/23/2014

Signal Generator Part 2b - Interrupting Oneself

Now that I've finally bulled my way into successfully reading the encoder status, I can move on to the final part of my rotary encoder experimentation.

The motivation for this final part of playing with the encoder comes from the blog post "Continual Interruptions" from Paul (M0XPD). He points out the problem of missing changes in the encoder status if the polling of the encoder is in the main loop() code. When the encoder status is read using the process() function and it is the only code in the loop, there is literally no chance of missing any status changes as the encoder shaft is rotated. Essentially the "sampling window," as Paul terms it, is 100% of the Arduino processing time.

As the loop is filled with other bits and pieces of code, the percentage of processor time available to read and process the encoder status begins to shrink. If the window becomes small enough the code will begin to miss encoder status changes, resulting in sluggish response to user input, incorrect direction decoding, or "stuttering" of output events as buffered changes flood the code. The actual impact is highly dependent on what functions/libraries are used to read and process the encoder status. In this signal generator implementation using the SQ9NJE Rotary library, we would only suffer sluggishness or occasional incorrect direction events. Note that the problem becomes significantly worse as we stuff the "loop" code full of delay(milliseconds) calls as we often do.

The "polling" method of reading the encoder is kind of like us asking the processor "has the encoder been rotated" with the risk that if we wait too long to ask, we'll miss a change. A better approach, and the one that Paul describes, is to let the processor tell us "hey, buddy, the encoder rotated, what do want me to do now?" This is the "interrupt" method of input processing and as a bonus, it's generic to any pin input code we might envision.

It's highly unlikely that the final signal generator product will have such processor intensive code or be so full of delay(milliseconds)that it will suffer the symptoms described above. That said, and because I very much enjoy the challenge, I chose to handle the reading of the encoder in an interrupt routine.

Handling pin changes on an interrupt basis seems a fairly straightforward concept in ArduinoLand: Tell the chip that it needs to handle an interrupt, tell it what to listen for, and tell it how to respond when the interrupt fires.

What appears to be the most common implementation, as used in M0XPD's Si5351 code, is to enable the pin change interrupt for one of the pins connected to the encoder (only one pin needs to be "listened to"), then write an Interrupt Service Routine (ISR) which is used to handle the arriving data from the encoder. Here's a clip of how he implements this.

Notice the "bit-fiddling" going on in lines 3 and 4. These are bit masking manipulations that one uses to ensure that the Arduino is paying attention to the correct pins and ports for the interrupt we are creating. The friendly names you see here are SPECIFIC to the processor AND the hardware pins used by the designer. So this explain why the code wouldn't even compile for me using a Micro, as I'm quite sure that M0XPD is using an Uno or other non-Leondardo based Arduino.

No amount of purposeful (or random) bit fiddling and deep reading on the variables used in the masking routines could avail my problem. Sidenote: The documentation on this subject is miserable, both at Arduino.cc and elsewhere.

Thankfully, there is more than one way to do interrupts. The other, easier, but more limited approach is to use external interrupts. This method avoids the need to mangle bit-masking to listen correctly; rather, in the "setup()"code we make a single call to attachInterrupt(pin, ISR, mode) and create an ISR function of any name. The first two of three arugments are an integer pin number (or friendly name) and the name of the ISR function (notice the lack of parentheses on the name). The last argument is the interrupt mode, or at what point in a pin change event will the interrupt handler fire. There are several choices for mode, but what makes the most sense for this project was to trigger on CHANGE. Line 40 in the code below shows how I implemented this, along with the rest of the signal generator code so far. The final ISR implementation is on lines 29-35; note that the function name is no longer ISR() and matches the handler called out in the attachInterrupt() function.

I still need to do some more experimentation on the Pin Change Interrupt approach. Even though the system is doing what I want, I'm not fond of not reaching my objective.

Next up, getting the actual Si5351 Clock Generator on board the prototype.

No comments:

Post a Comment