C++ on microcontrollers 1 - introduction, and an output pin class
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. |
I am lazy. I am also a programmer. Luckily, being a lazy programmer is a virtue: a programmer should never do something more than once. Especially not when ‘something’ is making a particular type of error, but that is another story. This story is about writing some functionality once and only once.
When I daydream, I need only a few keystrokes to build a sophisticated application from libraries I carefully prepared. Then I wake up, those libraries are nowhere to be found (at least not the ones I want), and I feel the urge to write such libraries myself. Portability between different computer languages is very very hard, so I will have to choose a language. But which?
Eons ago (actually, in 2001) I started using the then brand-new PIC 16C84 microcontroller. No cheap compilers were available, so I started writing my libraries in Microchip assembler. After lengthy struggles with the macro system (I wanted at least automated stack-wise variable allocation and dead-code removal) I realized that was a dead end. So I created Jal (Just Another Language), my own language, compiler, and library for PICs. That was fun, but I did not have enough time to develop it into a real product. Instead of letting it die a slow death I released the sources. Kyle York picked up the development (actually he kept the language design but rewrote the compiler from scratch) and added cool new features. The result can be found at http://www.casadeyork.com/jalv2/. A group of Jal enthusiasts developed an impressive library, which can be found at http://code.google.com/p/jallib/. Nowadays almost free C compilers are available for PICs, and I teach assembler, C and C++, so my use of Jal is limited.
Meanwhile cheap 32 bit microcontrollers have become available (ARM, AVR32, PIC32/MIPS, Cortex) which are targeted by the free GCC compiler, and can run C++. This language has a lot of features (for instance multiple inheritance, templates, overloading, exceptions) that make the writing of reusable libraries much easier than C, Jal or assembler, so it seems like a good choice. The even more powerful languages that are gaining ground on the desktop (think Python, Java, PHP) are not yet within reach for serious development on microcontrollers, and probably never will be due to their reliance on automatic memory management. So C++ it will be.
I like working at the boundary between hardware and software (probably because I am neither a real hardware specialist nor a real software specialist, and by working at the boundary nobody will notice). The most basic interface between microcontroller software and the hardware connected to it are the IO pins. The software on the microcontroller makes the pins outputs and writes 1’s and 0’s to them to make them high or low, or makes them inputs and reads the high or low values presented to the pins as 1’s or 0’s. This simple mechanism can be used to interface to a wide range of peripheral circuits, from the blinking LED up to a full-color graphic LCD. Most microcontrollers also have more sophisticated peripherals (UART, I2C, PWM, etc) that can be used instead of direct manipulation of I/O pins, but for a lot of purposes direct use of the pins is sufficient, and it is much more portable than the use of the more sophisticated peripherals.
It is not difficult to write a library, for instance for a HD44780 character LCD, that is independent of the I/O pins that connect to the LCD. The pins can for instance be numbered, and I/O functions can be created that accept the I/O pin number. The LCD library is configured with the appropriate pin numbers, and passes the numbers to the pin I/O function when it needs to access a pin. The LCD library does not know how a pin is accessed, and the I/O functions don’t know what the pins are used for.
So far so good, but we often need more I/O pins that our microcontroller can provide, or we need pins that can supply more current. Or we need a bunch of pins on a different PCB, and running wires for all of them is unpractical, so we would like to put an I2C I/O extender chip on that PCB and run only the two I2C wires to the PCB. Now we have different types of I/O pins, which require different function calls. That is not a good situation: I want to write my libraries independent from the type of I/O pin that is involved. Remember, I am lazy, so I want to write that LCD library just once.
A possible approach would be to adapt the I/O functions to handle both the pins of the microcontroller and all types of remote I/O pins. But that would lead to big fat functions that have to be modified for each new type of remote I/O pin. No good. Functional abstraction, the dominant abstraction mechanism in functional languages like C, is not sufficient here. Object Orientation can be used to achieve a much more pleasing (and reusable) result.
The first step is to define the abstraction for an I/O pin. For a start, I will define a pin that is output only. We can extend this later to input, input-output, and open-collector pins. What do we want to do with an I/O pin? That’s simple: write a 0 or 1 to it. ”0 or 1” sounds like a boolean value, so we declare a class that has one method, set(), with one bool parameter. (C++, unlike C, has a dedicated boolean type.) A method is OO speak for a function that is bound to a specific object. This is an abstraction, not yet an implementation, so we mark the method with ”virtual” and ”= 0” to indicate that the implementation must be supplied in a derived class.
class output_pin { public: virtual void set( bool x ) = 0; }; |
Now that we have abstracted an output pin, we can write a function that uses it. The following function will blink a LED that is connected to the pin that provided to the function. The pin is passed as reference (the & sign) to avoid copying. This is equivalent to passing by pointer in C, but without the & in the function call and the *’s in the function body, and you can’t accidentally do pointer arithmetic with a reference parameter or use it as an array. The blink (half) period can be specified in the second parameter, but a suitable default is provided, which is used when the caller does not provide a second parameter. Remember: I am lazy! The function mkt_wait_us() is provided by my development environment, it causes a delay for the specified number of microseconds.
void blink( output_pin &led, unsigned int delay_us = 200 * 1000 ){ for(;;){ led.set( 1 ); mkt_wait_us( delay_us ); led.set( 0 ); mkt_wait_us( delay_us ); } } |
We can blink the LED on our output pin abstraction, but that is not much use without a concrete output_pin implementation. Such an implementation will unavoidably be specific for a target chip and development environment. The one presented here uses the mkt_pin_configure and mkt_pin_write calls provided by my environment. You can replace it with the appropriate calls for your environment.
The first line states that the pin_lpc2 class inherits from the outpout_pin class. This means that a pin_lpc2 object will behave like an output_pin object: in every context where an output_pin is required (like for instance as first parameter to our blink function) a pin_lpc2 object will be acceptable. This obliges the pin_lpc2 class to implement the set() method that was announced but not implemented in the output_pin class.
The output_pin class has a section marked ”private:”. Declarations in this section are for use by the methods of the pin_lpc2 class only, they are not visible for other code. In this case the private section contains a value n that identifies the pin. The method pin_lpc2 looks a bit odd when seen with C eyes: it has no return type, and a ”:n( nr )” part that is definitely not C. This method has the same name as its class, which identifies it as a so called constructor: it is a method that creates a new pin_lpc2 object. It has no return type, not even void, because a constructor can’t return anything. This particular constructor takes an integer argument, the number that identifies the IO pin, which it stores in the n inside the private section by the n( nr ) syntax. Next the constructor calls mkt_pin_configure() to configure the the pin as a general-purpose output pin.
The set() method simply calls the mkt_pin_write() function to write the value to the IO pin.
class pin_lpc2: public output_pin { private:
const unsigned int n;
public:
pin_lpc2( unsigned int nr ): n( nr ) { mkt_pin_configure( n, mkt_output ); }
void set( bool x ) { mkt_pin_write( n, x ); }
}; |
My target board has an LPC2148 chip with a LED connected to I/O pin 0:31. So I define an lpc2148 class that has one IO pin, named pio0_31, because such is the NXP convention for naming pins on this chip. The constructor for this class initializes that one IO pin with the appropriate pin number. The syntax “: pio0_31( 31 )” is in effect a call to the constructor of the pin_lpc2 class.
class lpc2148 { public: pin_lpc2 pio0_31; lpc2148(): pio0_31( 31 ) {} }; |
In our main we must construct an lpc2148 object (up to now we have only a class, not a useable object). Next we call the blink function with the pio0_31 pin as first parameter and we are done. An object can be seen as a struct with associated functions, so the syntax ”target.pio0_31” selects the pio0_31 attribute of the target object.
int main( void ){ lpc2148 target; blink( target.pio0_31 ); } |
So what do we have up to now? We can blink a LED connected to one of the microcontroller’s I/O pins. Big deal, we could have done that easily without all that OO junk! So let’s try something a little bit more difficult. My target board also has eight LEDs connected to a 74HC595 chip, which is a shift register with an output holding register. Two write data to that chip, the first bit is presented on its data input, and the shift clock input is made low and high again. This is repeated for the next seven data bits. After that, the holding register clock is made low and high again, and the eight fresh data bits appear on the output pins of the HC595 chip. If this is too quick for you, check a 74HC595 datahseet.
We already have a blink function. To prove that it is really independent of the type of I/O pin that it must blink we will use it to blink a LED on an HC595 chip, without modifying a single line of the blink function. To do so, we must implement a class that inherits from output_pin, and implements its set method in a way that is appropriate for writing to a HC595 output pin. To keep things simple, we will (for now) write the same level to all 8 output pins of the HC595.
Our hc595 class needs to know to which of the microcontrollers pins the HC595 is connected. The constructor of the hc595 class takes the three pin references (holding clock, shift clock, data) as parameters and stores these references in the private section of the class. The syntax ”hclk( hclk )” might seem a bit strange, but the compiler knows that the first hclk can only be de private data object, while for the second hclk it will first consider the constructors’ parameters and find a match there. So it does what is has to do, without forcing the programmer to come up with an alternative name for each parameter.
The other method is the obligatory set(). (It is obligatory because the hc595 class wants to act as an good output_pin should.) It uses the set methods of the IO pin references to communicate with the hc595 chip. First it outputs the value to be written to the data pin, next it toggles the shift clock pin eight times, and finally it toggles the holding clock once. This will make the new value appear on all eight output pins of the hc595 chip. The mkt_wait_us() function calls insert a 1 us delay between the clock edges. Note that the loop counter i is declared within the for() statement, this is allowed in C++, and the preferred style.
class hc595 : public output_pin { private:
output_pin &hclk, &sclk, &data;
public:
hc595( output_pin &hclk, output_pin &sclk, output_pin &data ): hclk( hclk ), sclk( sclk ), data( data ){}
void set( bool x ){ data.set( x ); for( unsigned int i = 0; i < 8; i++ ){ sclk.set( 0 ); mkt_wait_us( 1 ); sclk.set( 1 ); mkt_wait_us( 1 ); } hclk.set( 0 ); mkt_wait_us( 1 ); hclk.set( 1 ); mkt_wait_us( 1 ); }
}; |
So we have a hc595 class. To use it, we must extend the lpc2148 class with the three IO pins that connect to the HC595 chip. This does not introduce any new concepts.
class lpc2148 { public: pin_lpc2 pio0_4; pin_lpc2 pio0_6; pin_lpc2 pio0_16; pin_lpc2 pio0_31;
lpc2148(): pio0_4( 4 ), pio0_6( 6 ), pio0_16( 16 ), pio0_31( 31 ) {} } |
In the main() we must declare an hc595 object, initialized with the appropriate IO pins. This object behaves like an output_pin, so we can pass it to our blink() function exactly like we did with the microcontroller pin that connects directly to a LED.
int main( void ){ lpc2148 target;
hc595 shift_register( target.pio0_16, target.pio0_4, target.pio0_6 );
blink( shift_register ); } |
Look mommy, no hands! We used the exact same blink() function to blink either a directly connected LED or an indirectly connected LED.
Now that we have done the basic grunt work the fun begins. Rember, the blink() function is used here as an example, but it stands for any big piece of the tested-approved-and-rubberstamped library code that we want to use, but are not allowed to modify.
Suppose that we want our blink() function to blink two LEDs simultaneously? We can do that by creating a class tee (like a T-pipe fitting) which presents itself externally as one output_pin but internally writes to two output_pins. The constructor of this class accepts two output pins and stores them in the private section. The set() method simply writes the same value to both underlying output_pins.
class tee : public output_pin { private:
output_pin & first, & second;
public:
tee( output_pin & first, output_pin & second ): first( first ), second( second ) {}
void set( bool x ){ first.set( x ); second.set( x ); }
}; |
The tee class inherits from the output_pin class, so we can pass a tee object to the blink() function. Now, without changing a line of the blink function itself, it will simultaneously blink both the directly connected LED, and the LEDs on the HC595 chip.
int main( void ){ lpc2148 target; hc595 shift_register( target.pio0_16, target.pio0_4, target.pio0_6 ); tee both( shift_register, target.pio0_31 ); blink( both ); } |
Now suppose we have an asymmetric blink function, maybe 200 ms on and 800 ms off. This function wants to turn the LED on by outputting a high level, but for some reason (shit happens) the LED is not connected to ground but to the power, so we must output a low level to turn the LED on. Should we modify the blink() function? Of course not, remember that the blink function represents a big and well-tested pice of library code that we don’t want to change. But we can change the connection between the blink() function and the IO pin! Let’s create a class that inverts the value that is written to a pin. By now you should be able to figure this class out without further explanation.
class invert : public output_pin { private:
output_pin & pin;
public:
invert( output_pin & pin ): pin( pin ){}
void set( bool x ){ pin.set( !x ); }
}; |
Now we can create an inverting version of the LED’s IO pin and pass that to the blink() function.
int main( void ){ lpc2148 target; invert led_inverted( target.pio0_31 ); blink( led_inverted ); } |
Suppose that our hardware department has been even more creative: they have connected the LED between two IO pins! (Go check what they have been smoking, and while you are there check whether they included a series resistor…) But when you think of other things beside the LED that might be connected and require a differential drive (a piezo buffer, an H-bridge, a pair of drivers feeding a twisted pair) this situation might not be so farfetched. Now how do we driver the two pins in anti-phase? Combine the tee and the inverter and we are done! We must of course add the second pin to the lpc2148 class, but that is trivial.
int main( void ){ lpc2148 target; invert led_inverted( target.pio0_31 ); tee both( led_inverted, target.pio0_30 ); blink( both ); } |
I hope to have given you a glimpse of the power of the C++ when applied to something as down-to-earth as microcontroller IO pins. There is plenty more to explore: input pins, open collector pins, groups of pins which can be treated as a unit (aka ports), protocols like SPI (which is more general than what we have do with the HC595) and I2C, effective (buffered) use of remote I/O pins, C++ operators, templates, exceptions, and the (not) using of dynamic memory, issues of ownership and lifetime. If you keep bugging me for more, I will most probably write more. If not, I might write it anyway, but don’t count on it.
The code that I showed can be found at my website, at http://www.voti.nl/erblog. It is for my mkt development environment, but when you provide the mkt_wait_us(), mkt_pin_configure() and mkt_pin_write() functions you should be able to run it with yours. If you have a suggestion for a free C++-capable IDE for LPC1xxx chips I will seriously consider switching.
- Comments
- Write a Comment Select to add a comment
C is not a functional language, but an imperative one. Haskell is a functional language, e.g.
Saying that it is is like saying that it is also OO because you can emulate classes via structs, void pointers and function pointers.
so, I have to write several functions, which will do actual job.
mkt_pin_write(int N, bool X) - set the pin N to X state, all clear here
mkt_wait_us(int delay) - makes some stuff by using mkt_pin_write(int,bool).
mkt_pin_configure( n, mkt_output ) - inits pin as necessary, where "n" is a pin number, and "mkt_output".. ? what is it?
similarly 2nd led will take a total of 5 times the swith been made on and off and the same way 4th led will take total of 10 times switch made on and off...
this all have been achieved ............... now the problem is ,,,, i have not been able to use timer ..........
suppose the 4 leds take 8 mins to glow then the 5th led will be made on for 2 minutes ,,,,,,,, and if the 4 leds take 9 and half minute to glow then the 5th led will be made on for 30 sec ......... and the loop starts again.........
if the 4 leds take 10 minutes to glow then the 5th led will remain off and the loop start again ......
i mean if the 3rd led glow and 10 minutes have been completed the loop will have to start again............
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: