EmbeddedRelated.com
Blogs
Memfault Beyond the Launch

C++ on microcontrollers 3 – a first shot at an hc595 class with 8 output pins

Wouter van OoijenNovember 2, 2011

 previous parts: 1, 2

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.

In the first part of this series I showed a class that allows an HC595 chip to be used as a single output pin. That is of course a bit crude, the chip has 8 output pins! So let’s create a class that provides access to all 8 individual pins. A simple approach would be to provide a set() method that takes both the pin number and the new value. This method updates a private buffer, because when one pin is changed the others must be kept unchanged. After the update the set() method calls the (private) flush() method that will write the buffer to the HC595 chip. This flush() function, the constructor, and the private data have been discussed in part one, so that code is omitted here.

class hc595 {

private:

      void flush(){ . . . }

public:          

   set( unsigned int pin, bool x ){

      if( x ){

         buffer |= 1 << x;          

      } else {

         buffer &= ~ ( 1 << x );    

      }   

      flush();

   }

};

 

void blink(

   hc595 & sr, unsigned int pin, unsigned int delay = 200 * 1000

){

   for(;;){

      sr.set( pin, 1 );

      wait(  delay );

      sr.set( pin, 0 );

      wait(  delay );

   }     

};

 

lpc1114 target;                     

int main( void ){         

   hc595 sr( target.pio1_11, target.pio2_11, target.pio0_9 );

   blink( sr, 2 );

}

This class does provide access to all eight pins, but it does so by providing an interface that is different from the output_pin class. As a consequence, we must change the blink function if we want to blink a LED on the HC595. That is not good, it violates the idea that a library need not to be changed when a different kind output pin is to be used. The hc595 class must somehow provide the exact same interface as the output_pin class. But it can’t inherit from that base class, because then it would provide only a single output_pin. A solution is that we politely ask the hc595 for an output_pin object for a specific pin of the HC595 chip. Then we can pass that object to the (unmodified) blink function.

class output_pin_595 : public output_pin { . . . }

class hc595 {

   . . .  

   output_pin_595 pin( unsigned int n ){ . . . } 

};

int main( void ){         

     hc595 sr( target.pio1_11, target.pio2_11, target.pio0_9 );

     output_pin_595 led = sr.pin( 2 );

     blink( led );

}

Note that the pin() method does not return a plain output_pin object, but an object of a class that is specific to the hc595 class, so we can implement it appropriately. But that output_pin_595 class does inherit from output_pin, so it is perfectly acceptable for the original (unmodified) blink() function.

To get this mechanism working, we must arrange for the public output_pin object to call the hc595 object when a pin must be set. In order to do that the output_pin_595 object must know the hc595 object it belongs to, and it must know the number of the pin it belongs to. Hence we pass both pieces of information to the output_pin_hc595 constructor, who saves it in private variables. The set() method simple calls the set() method of the hc595 object with the stored pin number and the desired new pin value.

The hc595 object provides the pin() method. You pass it a pin number, and it returns you an output_pin_595 object configured to call the hc595 object itself with the pin number you requested. Note the *this: inside a class ‘this’ is a pointer to the current object, hence *this is that object itself.

class hc595;

 

class output_pin_595 : public output_pin {

private:

   hc595 &chip;

   unsigned int pin;

public:  

   output_pin_595( hc595 &chip, unsigned int pin ):

      chip( chip ), pin( pin ){}      

   void set( bool x ){

     chip.set( pin, x );  

   }

};

class hc595 {

public:  

   . . .  

   output_pin_595 pin( unsigned int n ){

      return output_pin_595( *this, n );       

   } 

};

The above code will not compile, because compilation of the output_pin_595 class needs the hc595 class:

  • ·         The output_pin_595 class has a private attribute ’hc595 &chip’.
  • ·         The set() method of the output_pin_595 class calls the set() method of the hc595 class.

But we have not declared the hc595 class yet :(

Placing the hc595 class above the output_pin_595 class will not help, because its pin() method returns an output_pin_595, so it needs the output_pin_hc595 class.

To get out of this catch-22 situation we first declare that an hc595 class exist, without giving further details about it. After this the compiler will allow the use of pointers and references to that class, because they are both fixed size (4 bytes in our case). Now we can write the output_pin_hc595 class, except for the body of the set() method. But we don’t need to that yet. Up to now we have put the body of a class method inside the class definition. This is not necessary, it is in fact the exception: In most cases the body of a method will be placed in a .cpp file, not in the .h file in which the class definitions are normally placed. So we can put the body of the set() method after the hc595 class. At that point we can use the set() method of the hc595 class (which we need to call to do the real work). The set() method of the output_pin_595 class is now no longer inside its class definition, so we must prefix it with ’output_pin_595::’ to show to which class it belongs.

class hc595;

class output_pin_595 : public output_pin {

private:

   hc595 &chip;

   unsigned int pin;

public:  

   output_pin_595( hc595 &chip, unsigned int pin ):

      chip( chip ), pin( pin ){}      

   void set( bool x );

};

 

class hc595 {

public:  

   . . .  

   output_pin_595 pin( unsigned int n ){

      return output_pin_595( *this, n );       

   } 

};

 

void output_pin_595::set( bool x ){

   chip.set( pin, x );    

}

While writing this code (and getting it to run on the LPCXpresso base board, which took some time  because one jumper was set wrong) I tried a number of pins. At one stage I made a small error. Check the code below.

class lpc1114 {

public:

   pin_lpc1 pio0_7; 

   pin_lpc1 pio0_9; 

   pin_lpc1 pio1_2; 

   pin_lpc1 pio1_9; 

   pin_lpc1 pio1_10; 

   pin_lpc1 pio1_11; 

   pin_lpc1 pio2_5; 

   pin_lpc1 pio2_11; 

   lpc1114():

      pio0_7( 0,  7 ),

      pio0_9( 0,  9 ),

      pio1_2( 1,  2 ),

      pio1_9( 1,  8 ),

      pio1_10( 1, 10 ),

      pio1_11( 1, 11 ),

      pio2_5( 2, 5 ),

      pio2_11( 2, 11 ){}

};

Did you see it? The initialization of pio1_9 is wrong. This the problem with this approach is that I *can* make this error: I have to write pioX_Y at two places, and then supply the ( X, Y ) arguments. It must all be right, and the correspondence between the name and the parameters is not checked. There must be a better way?

Of course there is! We can apply the trick we used in the hc595 class to provide eight pins from a single object: instead of individual pins we create port objects, that have a method that returns a pin object for a requested pin.

class output_pin_lpc1: public output_pin {

   . . .

  

public:

   output_pin_lpc1( unsigned int port, unsigned int pin ){

    *gpioreg( port, 0x8000 ) |= 1 << pin;

       write_pointer = gpioreg( port, 0x04 << pin );        

   }

   . . .

};

 

class lpc1114 {

public:

   output_pin_lpc1 pin( unsigned int port, unsigned int pin ){

      return output_pin_lpc1( port, pin );

   }

};

 

lpc1114 target;                     

int main( void ){         

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

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

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

   hc595 sr( hclk, sclk, data );

   output_pin_595 led = sr.pin( 2 );

   blink( led );

}

Now suppose that we ran out of pins on our microcontroller. Suppose we have the three pins available to interface to an hc595 chip, but we need more than the eight outputs provided by that single chip. The obvious solution would be to put two hc595 chips in series, but that would be no fun. Instead we use three outputs of the first hc595 chip to drive a second one. And just to prove the point, we use three outputs of that second hc595 to drive a third.

int main( void ){         

   output_pin_lpc1 data1 = target.pin( 0, 2 );

   output_pin_lpc1 hclk1 = target.pin( 0, 3 );

   output_pin_lpc1 sclk1 = target.pin( 0, 4 );

   hc595 sr1( hclk1, sclk1, data1 );

    

   output_pin_595 data2 = sr1.pin( 1 );

   output_pin_595 hclk2 = sr1.pin( 2 );

   output_pin_595 sclk2 = sr1.pin( 3 );

   hc595 sr2( hclk2, sclk2, data2 );

 

   output_pin_595 data3 = sr2.pin( 1 );

   output_pin_595 hclk3 = sr2.pin( 2 );

   output_pin_595 sclk3 = sr2.pin( 3 );

   hc595 sr3( hclk3, sclk3, data3 );

    

   output_pin_595 led = sr3.pin( 2 );

   blink( led );

}

This works like a charm. I have no PCB that has three HC595’s connected in such a silly way, so I built it on a solderless breadboard.

The PCB is a LPC1114 target board I designed for my webshop. Many of my clients are hobbyists, so the small package of the LPC1114 would be a problem. But that problem might be solved for me: Recently NXP has announced DIP28 versions of its small Cortex chips. Interestingly Microchip will be slightly earlier with DIP versions of some of its PIC32 chips (MIPS core). This is IMHO very exciting news for hobbyists, students, and small-scale professionals, which might shift their focus from 8 bit microcontrollers to 32 bit chips. Those groups might be a totally insignificant part of the microcontroller market, but students of today might become the corporate designers of tomorrow, and the hobbyists create a large part of the web coverage for microcontrollers.

The files can, as always, be found on my website.



Memfault Beyond the Launch

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: