Saturday, October 8, 2011

ISR Lessons Learned

If you follow my Blog, (see previous posts) you will know that I have been playing with an Arduino work-a-like known as the Teensy. The plan is use it to help make my Homebrew Projects smarter and maybe more interesting.

So far, I have been "playing" with it to build multi-tasking template for future projects. The first hurtle was to write a predicable Rotary Encoder Test routine. Which I have done and published on the previous post. Actually, the code that I published has been replaced three time as I learn more. Finally I think the code is solid and would not mind others to commit or post reviews.

It was a struggle getting to this point. I have several chats with my friend Jeff - KO7M, about several major issues that were . . . just kicking my butt. The program just did not operate the way I had in mind. But in the end, it now works better than expected.

There were several lessens learned along the way, many are simple and probable known by heavy software types, but for me they were large stumbling blocks.

The lessons learned:
  • When writing software it is very important to have a friend to review your code. The process of expainning code bring new eyes on the problem.
  • Interrupt Service Routines (ISR) should be as short as possible, I instinctive knew this, but initially did not follow my own advice. It is just too easy to add one more line to the ISR. Note: only the second (PinB) needs to be read here as the other (PinA) is already known to be HIGH, as the RISING edge of the pulse is what created the Interrupt.

void doEncoder0() {    // Rotary Encoder
    digitalRead(encoder0PinB) ? g_encoder0Pos++ : g_encoder0Pos--;

  • The variable used by the ISR should be declared as global and type "volatile byte", as:

// Set Initial Encoder Values
volatile byte g_encoder0Pos = 0;

  • Jeff suggested the "g_" conventions for global variables.
  • The main program routine that takes advantage of the data from the ISR should read it with one machine instruction, so that the process can NOT be interrupted by yet another ISR event. For an 8 bit processor that means use an assign statement between two variables of type byte. In my case, the flag that indicates that the assignment was complete (ISR data extracted), was another byte assignment of value zero to the source variable. Once the data is assigned, it can be accumulated or used as necessary.

int doGetEncoderValue() {
    byte tmp;

   // Get Encoder input
   tmp = g_encoder0Pos;
   g_encoder0Pos = 0;

   . . . . 


  • Each of these two assignments are NOT interrupt able and therefore does not pose a problem if another ISR event occurs. Yes, there is a very-very narrow window where one event may be lost, but for my Rotary Encoder application that will not be a problem. If I had used; "int", "long", or  "float" data type multiple machine instructions would have been necessary to complete an assignment. And then, if an event occurred during the the assignment, there would have been a good chance the data would have be corrupt.

The methods used within the test program, have already been incorporated into another more complex multi-tasking program that I am working on.

The following is an excerpt from that program:

        byte btmp; int itmp;

        lcd.print("Adj Backlight");
        // Get and Decode Rotary Encoder Data
        btmp = g_encoder0Pos;
        g_encoder0Pos = 0;
        if(btmp < 128) itmp = btmp;
        if(btmp > 127) itmp = (btmp - 256);
        level += itmp * 16;
        if(level < 0) level = 0; // Constrain
        if(level > 256)level = 256;

        if(level<10) lcd.print("0");
        if(level<100) lcd.print("0");
        // Some Sound Feedback, just for fun
        if(itmp) {
            tone(speaker, 2000 + 2 * level, 10);
            g_HoldOffReset = true;

It will be just more fun stuff.


No comments:

Post a Comment