Problem Uploading To Board Arduino Improve your Arduino code with C++ – inheritance and composition explained
Lets create a blink sketch for both boards. For the generic dev board. This is as easy. In the setup function we set the GPIO pin to output and then in the loop we just switch. The pin between high and low. The TinyPICO uses an APA102 Dotstar for its built in LED and comes with a little helper library. To make this a bit easier to use.. We create the TinyPICO object and then use that in the loop to set turn the LED on and then off.. These are both trivial sketches. But what if we want to have one sketch that will deal with either of the boards with minimal code changes? How do we do that? A typical approach we see in a lot of Arduino code is to use preprocessor statements to switch in different implementations.. Here we have a define at the top of our file. That tells us which board we are targetting.. We can switch between our generic board and our TinyPICO board.. We then have various ifdef statements to switch in and out functionality.. If were using the TinyPICO board, then we include the TinyPICO library and create a variable to hold it. For the Generic board. We set up the led pin to output in the setup function. In the loop. We have two different code paths for turning the LED on and off. For, the Generic board. We set the LED, pin, high and low for the TinyPICO. We power the DotStar on and off and set the DotStar colour.
. This works perfectly and is fine for small bits of code, but it rapidly starts to get hard to understand when you have larger codebases. Heres an example that I recently came across. We have multiple nested preprocessor directives. Its really hard to work out. What this code is doing. Lets have a look at how we can make our multi board blink sketch a bit easier to understand., Well start with a bit of a recap on classes and inheritance. In C Im not going to go into a huge amount of detail, as there are a lot of resources available. If you want to have a deep dive.. Well, then use what weve learnt to implement a concrete example with our blink sketch. And yes, there is a joke in there about abstract and concrete classes.. Well, then, have a quick chat about what the phrase prefer composition over inheritance means. And how that impacts, how we architect things.. But first a word about the channel sponsor PCBWay. PCBWay have been supporting the channel for a while. They offer PCB Production, CNC and 3D Printing PCB Assembly and much much more. Ive made quite a few boards with them and they are great to work with.. You can find a link to their details in the description.. This is a pretty standard example of inheritance.. We have a base class that represents an Animal.. The terms parent class or super class are often used interchangeably with base class, but they all mean the same thing.
Weve, given our Animal class some behaviours and said that it can walk and talk. From our base class. Weve derived a Dog class.. This is known as a subclass and it inherits any behaviour present in the base. Class. It can either keep the existing behaviour or override it to behave in a new way. In the Dogs case were saying that a dog says Woof. Weve. Also got a Duck. Class. Weve said that when a duck talks it says Quack. Weve also added some additional behaviour to a Duck and says that ducks can fly.. Looking at how this is implemented. In C, we can see some interesting things in our base. Class.. The talk method has been declared as a virtual function.. This tells the C compiler that it needs to resolve the implementation of the function at runtime. Weve also added this piece of code to the end of the method declaration. This turns the method into a pure virtual function.. This tells the compiler that our derived classes in our case Dog and Duck, must provide an implementation for the function.. If they dont, then the compiler will generate an error., Adding a pure virtual function to a class. In C turns the class into something called an abstract class.. In C, we can have a mixture of pure virtual functions and normal methods on a class.. Our two animals, the Dog and the Duck provide implementations for the abstract class. They are called Concrete, classes.
. Our Dog says woof and our Duck says Quack.. Both our Dog and Duck classes derive from the Animal class.. You can see here that we specify public before Animal. C supports three different access: specifiers public protected and private. Its highly unlikely that youll ever need or want to use anything other than public when doing inheritance, but Private and Protected access specifiers can be used to hide The public methods in the parent class. To override the talk method from the base class. We simply create a method with the same signature and provide our implementation for it.. Taking a look at the code that is using our Animal class, you can see that it doesnt know or care if the Animal is a Dog or a Duck, it just calls the talk method and the correct thing: happens.. Our variable is declared as an Animal, but the program knows that it should use the talk implementation from Dog. If we change our Animal to point to a Duck. The program now knows that it should get the implementation from Duck.. This is all very interesting, but whats. The relevance to our LED problem: If we look at what our loop function is trying to do, then we can see that all it wants is a way to turn the LED on and off it doesnt care how that is implemented.. This gives us a good clue as to what our base class needs. Well create an abstract base class that has an on and an off method.
. Our loop code can now just be changed to use this class.. All we need now is our two implementations. For the generic dev board. We set up the GPIO pins in the constructor and then provide an implementation for the on and off methods that just write to the GPIO pin. For the TinyPico. We create the TinyPICO helper and for switching the LED on and off. We use the helper function to turn the DotStar LED on and off. In our setup function. We pick the implementation that we want to use. Theres, no real way here to get away from the preprocessor, but at least its all in one place, and not all over. Our code. Weve now got a nice easy to understand piece of code in our loop function and we can immediately see what the intention of the code is.. If we get a new dev board with a different kind of built in LED, then it would be easy to add a new implementation without searching through the code and updating all the ifdef blocks.. Now Ive seen some arguments against this kind of approach. That say, you end up with extra space being used from the unused classes, but the compiler is very good at stripping out unused code. So if the derived class is never instantiated, the dead code will be removed.. We can easily demonstrate this by comparing the code sizes for the two different implementations.. If we use the generic version, then we get a size of 261.
15 kilobytes for our program.. If we switch over to the TinyPICO version, then we get a size of 262.44 kilobytes.. So you can see that if the TinyPICO version is not used, then it doesnt get included in the firmware.. We can double check this by dumping out the symbols from the firmware. If we dump the symbols without creating the TinyPICO version, then we dont find any references to our TinyPicoLED class.. If we compile it with the TinyPico enabled, then we do find a reference.. This was a very trivial example. What about more complex code., For example? We might have a temperature sensor, an ADC reading from a potentiometer and a buzzer to sound an alarm when the temperature goes above some level.. This could be implemented using a fairly common pattern where the business logic is implemented in the base class, with derived classes filling in the blanks to provide specific implementations for parts of the logic.. Here we can see our function that implements our logic., And here we have some pure virtual functions that can be overridden to provide different implementations.. This would allow us to support a variety of different ways to read the temperature get the potentiometer value and sound the alarm.. We just need to create derived classes that implement the required behaviour.. So, for example, if we have some hardware that has a DS18B20 temperature sensor, a piezo speaker and a built in ADC, we can create a derived class that interfaces with these three bits of hardware.
. If we get some new hardware say one that has a TMP36, we could derive another class to handle this.. But now weve got duplicated code for the buzzer and threshold reading.. The temptation would be to say Ill just derive from the class that has that functionality already implemented, but you can see this is going to get messy quite quickly and its going to get difficult to know which bit of code is being run.. This is where the phrase prefer composition over inheritance comes in., Its much better in this situation to make the different functionality available via smaller, specialised classes.. These smaller classes derive from an abstract class that defines the behaviour they need to offer.. So a temperature class may have a method that reads the current temperature in centigrade.. Our concrete classes would implement that method.. A buzzer class may have a method that will start and stop playing a sound., And the potentiometer class may have a method that returns the current value on a scale between 0 and 1.. The concrete implementations would be injected into our class and then used by the business logic.. This pattern makes it very easy to change the implementations of parts of our system without rewriting large amounts of code.. This can lead to very powerful architectures that are very flexible and also very testable., Its easy to stub out parts of the system and see if they are working correctly., So thats it for this video.
I hope this one was interesting.. Let me know what you think in the comments.