EmbeddedRelated.com
Blogs
The 2024 Embedded Online Conference

C++ on microcontrollers 4 – input pins, and decoding a rotary switch

Wouter van OoijenNovember 12, 20112 comments

previous parts: 1, 2, 3

 This blog series is about the use of C++ for modern microcontrollers. My plan is to show the gradual development of a basic I/O library. I will introduce the object-oriented C++ features that are used step by step, to provide a gentle yet practical introduction into C++ for C programmers.  Reader input is very much appreciated, you might even steer me in the direction you find most interesting.

So far I have used pins only as output. Now let’s add input pins. An input pin is easily defined: it is something from which you can get a binary value. Contrast this with an output pin, which you can set to a binary value.

class output_pin {

public:

   virtual void set( bool x )=0;

};

 

class input_pin {

public:

   virtual bool get()=0;

};

But in the real world of microcontrollers both input-only and output-only pins are rare, nearly all pins are input-output. Such pins can be configured by the application as either an input pin or an output pin. But pure input pins and pure output pins do exist, so we want to keep those classes. Hence we need a new class for input-output pins. We want such pins to be useable whenever an input_pin or an output_pin is required. This is achieved by making the input_output_pin class inherit from both output_pin and input_pin. The consequence is that an input_output pin supports both the set() method and the get() method, which is what we want. But an input_output pin needs one more method: direction_set(), which configures the direction as either input or output.

enum direction { input, output };

class input_output_pin : public output_pin, public input_pin {

public:

   virtual void direction_set( direction d );

};

This introduces a problem. It is not strictly true that an input_output pin can be used when an output_pin is required (like for instance for the blink() function). You must first set its direction to output, and only after that you can use it as an output_pin. As the input_output class is defined now, the obligation is on the code that passes the input_output pin to the function that uses the output_pin. There are two problems with this:

  • It is easy for the caller to forget calling direction_set() first
  • It is against the principle that complexity should be in called code, not in the calling code (it should be in the library, not left to the user of the library)

We could solve this by making input_output_pin an independent class, that does not inherit from the other classes, but adds two methods that, from an input_output pin, construct and return an input_pin or an output_pin. That ‘constructing’ is of course calling direction_set() with the appropriate direction. This seems an elegant solution, but it makes it impossible to pass the same pin to both a method that requires an input_pin and (later in the execution of the application) to a method that requires an output_pin. (It is for instance common practice to use 4 I/O pins both as (output) data pins for an HD44780 LCD, and as (input) pins to read a 4 x 4 matrix keypad.)

My solution is to change the semantics of the input_pin and output_pin classes. They both  get an additional method that sets the direction appropriately. It is up to the code that uses such a pin (for instance the blink() function) to set the direction. For a pin that can only be an output there is no need to do anything, so we supply an empty default implementation of for these methods.

class output_pin {

public:

   virtual void set( bool x )=0;

   virtual void direction_set_output(){}

};

 

class input_pin {

public:

   virtual bool get()=0;

   virtual void direction_set_input(){}

};

Consequently, the blink() function must now configure its pin as output before entering the blink loop.

void blink( output_pin &led, unsigned int delay = 200 * 1000 ){

    led.direction_set_output();

    for(;;){

       led.set( 1 );

       wait(  delay );

       led.set( 0 );

       wait(  delay );

   }     

};

The input_output_pin class inherits the two direction_set_xxx() methods. We keep the direction_set() method, because it is sometimes convenient to pass a direction value and use it later to set the direction. The two direction_set_xxx() methods can be implemented using the direction_set() method. Alternatively, the direction_set() method can be implemented using the two direction_set_xxx() methods. These implementations are provided as defaults. A concrete input_output_pin class must override either the two direction_set_xxx() methods, or the single direction_set() method, whichever it finds more convenient. (Or it can override all three, but that seems unlikely.)

class input_output_pin : public output_pin, public input_pin {

public:

   virtual void direction_set( direction d ){

      if( d == input ){

         direction_set_input();

      } else {

         direction_set_output();

      }

   }

 

   void direction_set_input(){

      direction_set( input );

   }

 

   void direction_set_output(){

      direction_set( output ); 

   }                     

};

The LPCXpresso base board has a rotary switch (also called quadrature encoder, by or any other permutation of those words) which I use as input in the following program. A rotary switch has two switch contacts A and B that are closed in a so-called quadrature pattern. Depending on the direction in which the shaft is turned, either A or B closes first. Then the other contact closes, the contact that closed first opens, and the other one opens too. The program shows the status of the two switches on two segments of the seven-segment display. When you turn the shaft very slowly you can clearly see the pattern. The seven-segment LEDs are active low. I added a port_set() method to the hc595 class, so I can clear all segments by writing 0xFF to the shift register. The implementation of this method is trivial, check the code if you are interested.

int main( void ){  

   lpc1114 target;

  

   input_output_pin_lpc1  hclk  = target.pin( 1, 11 );

   input_output_pin_lpc1  sclk  = target.pin( 2, 11 );

   input_output_pin_lpc1  data  = target.pin( 0,  9 );

  

   hc595 sr( hclk, sclk, data );

   sr.port_set( 0xFF );

    

   output_pin_595 led_a = sr.pin( 1 );

   output_pin_595 led_b = sr.pin( 7 );

   led_a.direction_set_output();

   led_b.direction_set_output();

     

   input_output_pin_lpc1  rotary_a  = target.pin( 1, 0 );

   input_output_pin_lpc1  rotary_b  = target.pin( 1, 1 );


   rotary_a.direction_set_input();

   rotary_b.direction_set_input();  


   for(;;){

      wait( 1000 );

      led_a.set( rotary_a.get() );

      led_b.set( rotary_b.get() );

   }

   return 0;

}

Most rotary switches are dented: when you turn the shaft you feel clicks. The rotary switch on the PLCXpresso base board has such clicks, and in any ‘rest’ position both switches are open. Quadrature encoders as found in computer mice and on motors generally do not have clicks.

A rotary switch has four states (two switches = two bits = four values). These states can be arranged as corners of a square. Transitions between opposite states can’t occur: the two switches won’t change at exactly the same moment. Turning the shaft in one direction will rotate through the four states clockwise; turning the shaft in the other direction will rotate through the states counter-clockwise.

A=open, B=open

← →

A=open, B=closed

 

A=closed, B=open

← →

A=closed, B=closed

To count the ‘clicks’ of a rotary encoder we must increment for each clockwise rotation through these four states, and decrement for each counter-clockwise rotation. So it seems to be safe to let’s say attach the increment action to one of the clockwise transitions, and the decrement action to one of the counter-clockwise transitions, as shown in the next table. (Sorry for the extra lines, the MSW tables I used seem to be too difficult for the blog software.)

A=open, B=open

A=open, B=closed

increment

decrement

 

A=closed, B=open

A=closed, B=closed

         

This works, but only when encoder always makes a clean transition from its base state through the other three states and back. But consider what happens when switch A closes, which corresponds to the transition from  the top left to bottom left state, but it bounces a few times like all practical switches do. That would correspond to a number of cycles between the top left and bottom left states, decrementing on each transition from top-left to top-bottom. This is definitely not what we want! The solution to this problem is simple: the increment and decrement operations must be attached to the transactions back and forth between the same two states. It does not matter which two (adjacent) states are selected. (Note that for non-dented encoders we would probably increment/decrement on each state transition. But if we did that for our encoder we would count by 4 for each click.)

A=open, B=open

A=open, B=closed

decrement

Increment

 

A=closed, B=open

A=closed, B=closed

         

This diagram is easily translated to code. We must check (poll) the rotary encode often enough to detect all changes. We need the old state of the two switches, and the new states. When switch B is open in both the old and the new states, we check for a change in switch A, and increment or decrement the counter appropriately. Finally, we update the old values of the switches.

   bool old_a, old_b;

   int count;

   void poll(){

      bool new_a = a.get();

      bool new_b = b.get();

      if( ! old_b ){

         if( ! old_a && new_a ) ++count;

         if( old_a && ! new_a ) --count;                       

      }     

      old_a = new_a;

      old_b = new_b;          

   }

This code works, so let’s rewrite it in a re-useable form. In practice we often want the counter to saturate: when it reaches 0 it should not decrement, when it reaches a pre-set maximum, it should no longer increment. Or we want it to wrap around, an increment at the maximum should bring it to 0. Hence we create a bunch of classes. First a basic counter class, that simply counts without any regards for boundaries. But we give it a virtual after_update() method. (Virtual means that can be redefined in a derived class, so such special behavior can be introduced later.) The counter class has a get() method that is also virtual, so if we need a ‘user’ value that is somehow different from the actual count (maybe logarithmic?) that can also be added later.

The increment and decrement methods are provided as operators, which makes it possible to use the ++counter and –counter notation. The constructor sets the counter to the specified initial value, or when no value is specified, to zero.

class counter {

protected:

   int value;

 

public:

   counter( int start = 0 ): value( start ){}

 

   virtual void after_update(){}

 

   virtual int get(){

      return value;

   }      

 

   void operator++(){

      value++;

      after_update();

   }   

 

   void operator--(){

      value--;

      after_update();

   }   

};

The LPCXpresso base board has a seven-segment display (connected via our old friend, the HC595 shift register), so I want the turning of the rotary encoder to be show as a count from 0 to 9 on this display, saturating at 0 and 9. The following class extends the counter class by providing an after_update() method that limits the counter value to the low … high range. The low and high values (and the initial count) are set by the constructor.

class saturating_counter : public counter {

private:

   int low, high;         

 

public:

   saturating_counter( int start, int low, int high ):

      counter( start ), low( low ), high( high ){}

 

   void after_update(){

      if( value > high ){

         value = high;

      }  

      if( value < low ){

        value = low;

      }

   }   

};

The next class is the one that does the quadrature decoding. It is essentially the code I have shown before, but wrapped in a class, so you can make a decoder object by specifying the two pins and a counter object.

class quadrature_decoder {

private:

   input_pin &a;

   input_pin &b;

   bool old_a;

   bool old_b;

   counter &count;

 

public:

   quadrature_decoder( input_pin &a, input_pin &b, counter &count ):

      a( a ), b( b ), count( count )

   {

      a.direction_set_input();

      b.direction_set_input();

      old_a = a.get();

      old_b = b.get();   

   }

   void poll(){

      bool new_a = a.get();

      bool new_b = b.get();

      if( ! old_b ){

         if( ! old_a && new_a ) ++count;

         if( old_a && ! new_a ) --count;                  

      }     

      old_a = new_a;

      old_b = new_b;           

   }       

};

To show a digit on the seven segment display we must convert the value we want to show to the bit pattern of segments that must be on and off. The segments on our board are connected in a bizarre way, so the handy tables like we can find on the wiki page are useless. But somewhere in the LPCXpresso demo code I found this set of values that does the translation for 0..9. It is not a nice solution, but it will do for now.

const unsigned char seven[] = {

   0x24, 0xAF, 0xE0, 0xA2, 0x2B, 0x32, 0x30, 0xA7, 0x20, 0x22

};

With all the building blocks in place the application can be short. First pins that connect to the HC595 shift register and the register itself are defined. Next are the pins that connect to the rotary switch. A saturating counter is declared for values 0..9 and start value 0. A decoder is declared that uses this counter, and monitors the rotary encoder pins. The main loop calls the decoder.poll() method to update the counter, and displays the value (converted to a seven segment pattern) on the display.

int main( void ){  

   lpc1114 target;

  

   input_output_pin_lpc1  hclk  = target.pin( 1, 11 );

   input_output_pin_lpc1  sclk  = target.pin( 2, 11 );

   input_output_pin_lpc1  data  = target.pin( 0,  9 );

  

   hc595 sr( hclk, sclk, data );

     

   input_output_pin_lpc1  rotary_a  = target.pin( 1, 0 );

   input_output_pin_lpc1  rotary_b  = target.pin( 1, 1 );

  

   saturating_counter count( 0, 0, 9 );

   quadrature_decoder decoder( rotary_a, rotary_b, count );

  

   for(;;){

      wait( 1000 );

      decoder.poll();      

      sr.port_set( seven[ count.get() ] );

   }

   return 0;

}



The 2024 Embedded Online Conference
[ - ]
Comment by karthi1729January 19, 2012
i want to know the Name the board which has exnoys processor
[ - ]
Comment by wovoJanuary 20, 2012
Can you give any hint on the relation with this blog before I delete your comment?

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: