Tips / books about software architecture for microcontrollers  - Page 1
Forum section: electronics / projects

author: Spikee
author_rank: Frequent Contributor
timestamp: August 08, 2015, 08:53:38 pm
content: Hello everybody,I have programmed in C / C++ with just classes and Object Oriented Programming and RTOS for microcontrollers.But one thing I find really difficult is the software architecture / planning.Should I make a driver layer (for example IO, ADC, I2C devices ...) , application layer (adc settings, a class for each I2C device ..) and a main code where the interrupts/timers all happen?Or (what I have also seen in jobs) just make one big class that does everything and a main that calls the functions.This is not teached in UNI and everybody kinda seems to do their own thing.I have been doing contracting work for a while now and this lack in experience has caused me to turn down some lucrative projects because I do not want to cause delays or problems for project because of this.The goal is keeping the classes as versatile as possible but not take to much time I.E. money to make.One project I worked on consisted of three hardware devices that were designed that generally had the same hardware but different firmware. Because OOP was used it was easy to adapt the firmware for each specific device.The final firmware ended up being one project with 3 defines that determined which apparatus it was (eeprom value) which determined which code was used.Instead of 3 different projects and firmware to maintain and debug it ended up being one. This is still and has been working great.I'm looking for better ways to do this.Thanks!

author: Siwastaja
author_rank: Super Contributor
timestamp: August 08, 2015, 09:27:51 pm
content: What you are describing sounds horribly bloated for many microcontrollers, although, something like that may work on some of the larger 32-bit devices. There is a common misconception that adding many abstraction layers etc. keeps development time down by sacrificing performance, but in reality, it's usually also a huge waste of development time.In real world, MCU applications often are a few thousands of lines of C code, and the "abstraction layer" for peripheral devices such as ADC, I2C, etc., is typically one or two functions of a dozen line max.If this is not the case, it should be first investigated whether you are doing something horribly wrong, before jumping into over-abstraction or "frameworking".Also, in the microcontoller world, it's usually better to accept that you are writing your code for one particular device. Although, when you design your modules (C files) and functions right, most of the code can be reused if migrating to another device.You should really use C (not C++) for most microcontroller work and forget about object-orientation. One may argue that C++ has its uses, but definitely not in the microcontroller world, at least in general sense. Using C++ for MCU development is really forcing round peg in square hole. They are fundamentally different. I know there will be people who will disagree loudly, but it's their right to be wrong. Really, you should try to understand what you are doing on a very fundamental level. PC software world stopped being easy in early 90's, but it's understandable, because it's a general-purpose environment; just don't try to force that development model or those software ideas on embedded systems.Many microcontroller applications do not even use dynamic memory management at all, in any form. In most microcontroller projects, it's super important to keep the state space as small and predictable as possible. You are running your particular application, and it will never run out of memory or have errors opening devices, because the memory is statically allocated for the job and the devices are always provided by the MCU and there is no driver to fail.It depends heavily on the complexity of the projects and microcontrollers being used; some larger projects that utilize large 32-bit MCUs may start benefitting from the more complex software architecture, in which case an operating system may be a good idea; but this is the special case, and you need to really know what you are doing, and do a proper analysis on the reasons for that kind of general-purpose approach.But in many many cases, minimum no-bullshit no-"framework" C "bare metal" is the only sane and feasible development model for microcontrollers.So, there is no general answer. It really depends. But seeing your background, I really suggest you start by doing some simplistic bare-metal C projects; it will be the basic lesson you simply cannot live without even if some of your projects ended up using operating system, device drivers and OOP. software development and computer science stuff are totally and utterly irrelevant skillsets when designing for microcontrollers. Forget it completely and start from scratch. It's sad that microcontroller development is not really taught properly anywhere AFAIK. Don't mix that up with modern computer thing; it's very different, in a very fundamental way!

author: Spikee
author_rank: Frequent Contributor
timestamp: August 09, 2015, 12:01:17 am
content: I have done about a dozen or so "bare metal c" projects on various micro-controllers ranging from an atmega8 to cypress and st 32bit micro-controllers. The specific project I mentioned did not have the OOP as know on pc development but more of a c / c++ way of doing a little bit of abstraction without using dynamic memory allocation and all that kind of stuff. Each of the functions generally had only a few lines of code moving some data in the avr registers.The total code size was about a 1000 lines in total which included three devices. Due to some abstraction a significant portion of the code could be reused without copying and pasting it in another function like what is done a lot in older c code. The existing code was written in C and was basically done like you described.The cons were:3 programs to maintain instead of oneThe code was less readable thus harder to maintain or update by a third partyBetter test ability and less chances of bugs in the "driver layer"Total lines of code were more than using c++ and some form of abstraction (because of copy pasta)I can't reply on the memory usage because it was some time ago, it could have been higher or lower,but the avr8 had plenty of grunt left.As for only using c in micro-controllers is in my opinion a bit outdated. Even atmel has been supporting C++ development for their avr8 series for a long time. I personally have not done the comparison between execution time for c and c++ code on lower end micro-controllers like the avr 8 series but I doubt it is significant if any unless you are doing something really inefficient or using special functions of c++. If bare metal is really needed than assembly might be a better choice...The old and "slow" microcontroller like the avr 8 series are becoming redundant due to their high price and supply issues.A STM F0 or F1 is in most cases cheaper, has more performance, more peripherals, lower power and (generally) has a better supply chain behind it.I think that in the following years higher performing 32 bit micro-controllers will be the way to go with some king of low level or high level "OS" due to energy, space ,cost and performance constraints.Anyway I do not want to make this a C vs C++ war or even a c99 vs c... discussion...

author: nctnico
author_rank: Super Contributor
timestamp: August 09, 2015, 12:17:23 am
content: Quote from: Siwastaja on August 08, 2015, 09:27:51 pmThis must be the worst advice ever!When used right C++ can solve a lot of bug-prone pointer juggling. Actually I would rather use a language where I wouldn't have to worry about pointers or buffer overruns at all! It seems Ada is finally available for ARM controllers!Most of the larger microcontroller projects I have worked on are organised in an object oriented manner. Each module has an init (constructor), a run function (process) and functions to exchange data with the module (methods). I'm using this approach for both high level modules and low level hardware drivers.The only thing I look for is whether an extra layer really adds extra functionality of is some kind of glue between incompatible modules. If it's just glue I make the modules compatible which usually is an improvement.

author: technix
author_rank: Super Contributor
timestamp: August 09, 2015, 02:10:27 am
content: From a functionality point of view, any software architecture that works correctly will work. The difference is all about maintainability and modularity.If you are 100% sure that you and everybody else involved in this project can still understand your spaghetti code after you have finished the project for three months, just go ahead.If not, some code style management have to be done. I don't like C++ but I still do object oriented programming using C (and Objective-C conventions.) Also I have recursive layers of drivers for on-chip and off-chip peripherals. Most importantly, I have a cooperative scheduler. My code can look like this:A generic driver that implements using an interface: (In reality PCF8574 driver also implements GPIO interface protocol, which will make the driver slightly more complicated)Code:typedef struct pcf8574{struct device super;i2c_device_t device;} *pcf8574_t;pcf8574_t pcf8574_init(pch8574_t self, i2c_device_t device){if (self = device_init((device_t)self)){self->device = device;pcf8574_set_bits(self, 0);}return self;}void pcf8574_set_bits(pcf8574_t self, uint8_t bits){i2c_write_byte(self->device, bits);}uint8_t pcf8574_get_bits(pcf8574_t self){return i2c_read_byte(self->device)}And application code using itCode:app_t app_init(app_t self){self->gpio_chip = pcf8574_init(class_alloc(PCF8574_SIZE), atm328p_i2c(0x20));self->counter = 1;return self;}void app_loop(app_t self){pcf8574_set_bits(self->gpio_chip, self->counter);self->counter = (self->counter == 0x80) ? 1 : (self->counter << 1);}

author: AndreasF
author_rank: Frequent Contributor
timestamp: August 09, 2015, 06:15:11 am
content: I started reading Elicia White's "Making Embedded Systems" which seems to be what you're after.

author: codeboy2k
author_rank: Super Contributor
timestamp: August 09, 2015, 09:04:15 am
content: Quote from: nctnico on August 09, 2015, 12:17:23 amI don't know about Ada, I might have to give it a try one day. However, I am a big proponent of Forth for embedded systems. Although you don't get rid of the pointer problem or buffer overrun problem in Forth, pointers are on the stack (usually!) and are thus automatic and much more manageable. Buffers can still be overrun if you're not careful.The biggest advantage when using Forth in an embedded system is that you can easily simulate anything on a desktop Forth system, and when have finished the simulation, much of your code transports to the embedded system without change. Then, when you install the interpreter into an embedded system, you automatically get an embedded command line monitor that can be used to examine memory, registers, and add dynamic code (like python)You can debug on the hardware directly, replacing Forth words with new ones and develop truly reusable libraries for future projects.Yes, I love Forth. Sadly, I don't get to do enough development in Forth anymore, not since the 90's. These days, it's usually C for me.

author: Spikee
author_rank: Frequent Contributor
timestamp: August 09, 2015, 09:30:01 am
content: Quote from: technix on August 09, 2015, 02:10:27 amIn one of the many podcast episodes I listen to a objective c book was mentioned and was aimed at embedded project.Might have to search for that.Quote from: nctnico on August 09, 2015, 12:17:23 amThis is how I have been doing it lately. I also agree that one has to look out for non-functional layers as you wrote in the post. This requires some planning.Quote from: AndreasF on August 09, 2015, 06:15:11 amI actually listen to the embedded , the amphour , the spark gap and engineering commons podcast weekly for a few years. The book has been mentioned plenty on the Embedded podcast but for some reason I never looked seriously at it. After looking at a few pages it talks about various problems a firmware programmer can expect and how to solve them in a good manner. Definitely will buy the ebook or the hard copy.Any other books worth looking at ?

author: Siwastaja
author_rank: Super Contributor
timestamp: August 09, 2015, 09:35:41 am
content: Indeed, if your project itself is complicated enough that the state space will be unpredictable no matter what, it may make sense to use higher-level languages and an operating system. It's not that much about resources, today you indeed have enough to waste. You don't "need" simple bare-metal C approach for that reason. The reason is elsewhere: you should design the project to have a small, predictable state space, i.e., be as "linear" as possible, especially if it is safety-critical. The idea is to have 100 potential bugs instead of 100 000, some of which are very rare from special combination of events.I have been able to reduce all my microcontroller projects to predictable state space, which means they are robust, and won't suffer from buffer overruns or invalid pointers. Very rarely, I need dynamic features, but they are so restricted in nature that it is easy to write them in a robust way in C in those rare cases.In fact, I have done dozens and dozens of MCU projects and had zillions of bugs, but I don't remember having a buffer overrun or a pointer problem ever, not even once. This is mostly thanks to the KISS principle and the very static nature of doing things. OTOH, when I write PC software, I do have these bugs all the time, and I totally suck at PC programming because I try to force the microcontroller way there, having the round peg in square hole problem again.Quite the opposite, many of the MCU bugs I had early when learning were related to stack overflow events (due to small memory size available), and making thingsmorepredictable andlessdynamic is the right way to solve that. Hiding the stack data transfers behing higher abstraction only makes things worse.But, it is true that some embedded projects today are closer to the PC world. For example, if your project has a sophisticated user interface, or a lot of customizability, things change.Modern vs. old is not an adequate argument. You can only evaluate things based on how suitable they are for the task. Sometimes you don't need to reinvent the wheel. The traditional bare metal C way is very good for small to medium-small MCU projects, even though C as a language is far from perfect and has a multitude of problems. But those problems are mostly details; the level of abstraction is just right, and the idea that it's very predictable in terms of software-to-hardware mapping, not as much as ASM but quite close. I guess similar reasons are behind the principle of writing the Linux kernel completely in C (even though it's not a small project at all!), in a somewhat microcontrollish way, because it will be "near to the hardware" and it needs to be predictable and robust.

author: nctnico
author_rank: Super Contributor
timestamp: August 09, 2015, 11:41:23 pm
content: IMHO C and the words 'predictable' and 'robust' don't go hand in hand. With C you have to be constantly aware of avoiding buffer overflows and pointer problems. This wastes energy which could have been used to improve a project or finish it quicker (see MISRA rules).@codeboy2k: Ada is related to VHDL and Pascal. I never used it before but the language is (AFAIK) the defacto standard in mission critical systems.

author: Spikee
author_rank: Frequent Contributor
timestamp: August 09, 2015, 11:48:33 pm
content: I have been reading making embedded systems as mentioned in this topic and I can recommend it for others.She mentioned lots of techniques I have used before in pc programming and documentation. Will defiantly use these techniques in upcoming projects.

author: tombi
author_rank: Regular Contributor
timestamp: August 10, 2015, 01:32:26 am
content: Hmm. I'm surprised you can run Ada on something as tiny as an ATMEGA. Ada supports threads natively in the language and features such as generics generate code-bloat like there is no tomorrow. Having said that I only every used the pre 9x Ada.There are features in C++ that cost - for example exceptions will generate loads of code which is why embedded tool chains won't support them. Virtual pointers cost but really they are just setting up a jump table once and then you have the cost of a pointer lookup when you call a function (which I think is minimal).Unless you are making everything global (with all the pain that induces) if you are coding in C and working with structures you end up passing a pointer to the structure into your functions. Methods on C++ objects are just doing this automatically. I don't see this as a cost.Passing objects by value - yeah that will cost. Don't do it.Operator overloading - presumably if you do this you know what you are doing so the cost is justified.The linker is smart - it will remove functions not being used and so if you include a class but don't use some of the functionality it won't even get linked.Abstraction allows re-use. What is worse from bloat perspective - pasting the same code all over the place or calling the same code from all over the place? Clearly pasting could be far worse.I think if you avoid the big-ticket items you still get significant benefits for modest cost.

author: John Coloccia
author_rank: Super Contributor
timestamp: August 10, 2015, 08:49:19 am
content: Geez...lots of bad advice and misunderstanding how C++ actually works. Tombi is getting to the heart of the matter. There's nothing wrong with C++ for embedded code. Where you will potentially run into trouble is sucking in things like stdlib. Often times, a controller will have libraries available that should be used in place of the standard libraries. That cuts down on bloat, and also sometimes gives you nice features likes printf that automatically goes to IDE console display, and things like that.To answer the OP, the reasons so many embedded projects resemble state machines built within a main loop is that:1) you often times have no RTOS running so no multi-tasking support...it's just you and the machine.2) it's simple and predictableA very common design pattern is:-interrupt driven I/O that does very simple tasks such as dumping characters into a buffer, handle low level protocols (setting bits and things like that), maybe a watchdog if the controller doesn't have one built it, handle timers, etc etc-main loop that drives a state machine, handles high level communications, and generally interacts with the outside world.Why do anything more complex than this when this is sufficient to get the task done? That's why you see this pattern over and over and over again. Also, state machines tend to be extremely reliable because things that can easily be described as a state machine can also very easily be verified for correctness. It's often times not the prettiest code in the world, because it tends to be repetitive and long winded, but it's usually extremely simple code driven by very simple case statements.So an intelligent use of C++ would be to use it to build your state machine class. Then your main loop simply iterates through the list of state machines. That's a great way to clean up and organize things. I don't think there's just one simple answer. It depends on the specific project and environment. Does it make sense to have classes and things like that for ADCs? It depends...does that simplify your life or does it just add a layer of abstraction for no reason? If I had 10 different ADCs, that all required handling in 10 different ways and was used by multiple software entities, I might consider taking the time and energy to wrap those with classes.If I have 1 or 2 ADCs that everyone wants to see, I might just stuff it in a structure that gets passed to everyone, or I may even just make a global structure and not bother constantly passing around this stupid pointer that everyone gets anyway. Maybe I compromise and have a global pointer instead of a global struct, so I can intelligently change datasets for debugging, changing modes, whatever. Yeah, I know. That breaks all of the rules. So what? Who's rules? My software, my rules.My formal training was as a software engineer. I've always taken a very practical approach to software. I understand and respect all of the "rules", and I keep them in mind when I'm developing. But then I toss them out at whim when doing that ultimately simplifies my life. So how to break things up into classes? Easy: do whatever simplifies your life. Start out by making a prototype...a REAL prototype, meant to be thrown away. Toss it together as quickly as you possibly can with no regard for design or anything else.When you're done with that, THROW IT AWAY. Seriously, don't update it in place. Start a completely new project. Use the lessons learned from the prototype to drive your design. I promise you that if you try this, and you're serious about it (THROW AWAY THE PROTOTYPE...PLAN TO DO THIS FROM THE BEGINNING), that a clear design will tumble out. The prototype is part of your design phase. From that, you'll be able to see how it makes sense to start breaking things up...where classes make sense, and where maybe it makes sense just to hard code things because they're driven by hardware or other requirements and simply can not change.That's just my opinion.

author: nctnico
author_rank: Super Contributor
timestamp: August 11, 2015, 01:01:32 am
content: IMHO one of the pitfalls of embedded software design is trying to make code fit into the cheapest possible microcontroller without looking at the engineering time versus cost of the product. For almost every project I worked on the price of the microcontroller didn't really matter because the volume wasn't very high. Even if a product sells in 5000 pieces spending one month of work to save $1 on the microcontroller isn't worth it. That time is usually better spend on improving the product or work on new products.Trying to fit code into the smallest microcontroller may be fun but you are likely reducing the company's profit and thereby lowering your income (no bonus and/or no raise).

author: Spikee
author_rank: Frequent Contributor
timestamp: August 11, 2015, 06:43:04 pm
content: My contracting experience also tell me that the microcontroller is such a insignificant part of the overall price of a low volume product that is it not worth squeezing the last bit out of it.Most of the time they want the biggest amount of flash that the package can fit. So they do not need to change it for future updates.Going high volumes is of course another story... but that is more in the consumer area while most of my contracting work is in the industrial space.