So, at this point in the movie you already know about Arduino and other MCUs. Analog inputs and outputs hold no secrets for you, and you control PWM, LEDs, and moving little motors like no one else. Congratulations, you already know how to write lines of code in Arduino!
But you, who always want to improve, now want to learn how to program (well) in Arduino. And, my friend, programming is much more than putting together a bunch of lines of code that “just work”. If you want to play in the first division, your code has to be clean.
Speaking generally about the world of programming, more and more people are programming. Which is good… but it also means that the average quality of programming is going down.
Within the world of Arduino and other MCUs, and all the many (so many things!) we owe to this great ecosystem, the truth is that the lack of average quality borders on scandalous.
This is due, in large part, to a very marked and widespread existence of two types of user profiles:
- Novice user: who learns with effort on their own, reads tutorials on the Internet, and evolves as best they can.
- Expert user: who learned 40 years ago, using a PIC that had a bit and a half, and still maintains the same practices.
But, as we said, you want to program better. And that has brought you to this post where we are going to see a few tips and guidelines so you can improve your way of programming.
1) Keep Your Code Clean and Tidy

Your code must be clean, tidy, and have a logical order. Clean code contributes to shorter development times, fewer errors, better maintainability, greater ease of expansion…
Don’t mind stopping and refactoring from time to time, that is, rewriting and reordering parts of your program so that it does the same thing, but with cleaner and more structured code.
Let’s see some tips on code cleanliness and order.
Be Consistent with Your Style
Every master has their own book, and I won’t be the one to open the debate on style rules now. I don’t care if you use 3 spaces or a tab, if you put the ’{’ on the same line or the next one, if you call constants IN_UPPERCASE or LikeThis.
But yes, whatever you choose, always use them the same way throughout your entire program. Don’t mix styles, now this way, now that way.
Short Functions
The most important advice I’m going to give you today, keep your functions short. You only need to see a program consisting of a loop 5 screens long to know that your code is not good.
This type of long, intermingled, unstructured code is called Spaghetti Code.
A long function means more difficulty in understanding and maintaining, both for the next person and for yourself in a few months, when you open your code again and have to understand what is so clear to you today and you wrote in one go.
But, in addition, it denotes in itself a conceptual problem. A function should be responsible for performing a single action in the program. Divide your code into short functions like GetTemperature(), FillScreenBuffer(), ParseMessage()…
Divide Your Code into Files
Divide and conquer! One of the quintessential programming guidelines. The Arduino IDE allows you to work with code in multiple files. Of course, other more advanced IDEs do too. So divide your code into files, and add them with #include.
In the best case, your main Sketch should be practically empty. If you don’t feel like complicating yourselves with .h and .cpp files and safeguards, etc., then don’t. Simply create a .hpp file, move some of your functions to it, and “include” it.
When dividing code into files, try to group them by logical blocks. For example, the part related to measuring temperatures in one file, the RF part in another, the communication part in another, the display part in another.
And, if possible, try to create reusable modules, grouping into some files functions that do not depend on your particular program. This way you can reuse the code for the next program with little or no modification.
Create a Constants File
A good habit is to create a Constants.h file that contains all the fixed values involved in your program.
// example
const int NUM_CHANNELS = 3;
const int NUM_MEASURES = 10;
const float SENSOR_GAIN = 3.5;
// .... more constants
Not only does it improve code readability, but it allows you to control the behavior of your entire program by modifying a single file.
You Can Use Objects
Using objects is perfectly possible in Arduino. Contrary to popular belief, it is not necessary to create a library for it. You can create an object in any .hpp file, and even in your program’s own .ino file.
2) Good Code is Understood When Read

Pure Agile philosophy. The good programmer makes code that is understood when read. This is greatly contributed to by what we saw in the previous section about your code not being Spaghetti, functions being short, and being responsible for a single action.
We are going to see it in these little tips that will help your code be more Agile.
Minimize Comments
Whaaaat??? But comments are good, I’ve been told that code has to be commented! Well, they lied to you. Comments are just another part of the code that takes up space and must be maintained.
There are few things more ridiculous than:
// read the temperature from the sensor
int temperature = analogRead(pin_sensor)
// multiply the temperature by two
temperature = temperature * 2;
At best, the only comments needed are those that document classes and functions, right above them. But every time you use a comment inside a function, a developer kitten cries.
If you follow the rest of the advice in this section and the previous one, you won’t need to use comments, because the goal is for your code to explain itself.
Use Representative Variable Names
I love this one, I will insist until the end of days, and I will never tire of being annoying about this. Don’t be lazy, and give representative names to variables.
// nope
int v = analogRead(ps);
float r = v * f;
float t = r * fs;
// yep
int voltage = analogRead(PIN_SENSOR);
float raw_temperature = voltage * CONVERSION_C_MV;
float temperature = raw_temperature * FACTOR_SENSOR;
Let’s compare readability? In the first one, no idea what it’s doing, and in the second one you can even understand it! And that’s an invented example! So, don’t save letters in variable names, or you’ll end up spending them on comments.
Use Representative Function Names
First cousin of the previous one, the same applies to function names. Although, in general, this error is less common and people tend to save fewer letters.
// nope
int DoMagic(int ps) { … }
// yep
int GetTemperature(int pin_sensor) { … }
But same philosophy, give functions names that explain what they are going to do. It helps a lot when functions are short and perform a single action, as we saw before.
Cleanliness in Function Parameters
Regarding function parameters, besides the fact that at this point it won’t surprise you that we say to give representative and self-explanatory names to parameters.
// nope
int GetTemperature(int ps, int i, int d) { … }
// meh
int GetTemperature(int pin_sensor, int interval, int delay) { … }
// yep
int GetTemperature(Config configuration) { … }
But, in addition, it is also advisable to use the fewest number of parameters. Zero parameters is perfect. One, it’s fine. Two, passable. Three, think about it. More than three, no. If you need three or more, consider creating a structure to contain them.
Avoid “Magic” Numbers
A magic number is one you see in the middle of the code and you don’t know where it comes from. Avoid… no… run like crazy from them. Always encapsulate them in a constant that identifies it, allows you to search for it, and change it from a single place.
//nope
int temperature = voltage * 3 * 1.674;
//yep
int temperature = voltage * NUM_CHANNELS * CONVERSION_C_MV
Don’t Avoid Intermediate Variables
Typical beginner mistake, chaining function calls to avoid creating intermediate variables. In fact, in general, avoid excessively long lines.
// example 1
//nope
bool result = Validate(GetTemperature(GetVoltage(PIN_SENSOR)));
//yep
int voltage = GetVoltage(PIN_SENSOR);
float temperature = GetTemperature(voltage);
bool isValid = Validate(temperature);
// example 2
//nope
if(GetMeasure(pin_sensor) { …}
//yep
auto isValid = GetMeasure(PIN_SENSOR);
if(isValid) { … }
By using intermediate variables you are “telling me the story” of what you are doing. You don’t need comments, I read it and understand it. If you are concerned about efficiency, the compiler will work its magic and both functions will be exactly the same when passed to the processor.
Use Enumerations
Enumerations exist for a reason, and in Arduino they sometimes seem to be feared. It takes you 5 seconds to make an enumeration.
// nope
if( Key == 0) MotorUp();
// yep
enum Control_Key { UP, DOWN, LEFT, RIGHT }
if( Key == Control_Key.UP) MotorUp();
Not only is it easier to understand, and you are telling me what you want to do just by reading it. But also, you prevent me from thinking of putting a 5 because I don’t know what the heck a Key is. And if one day you have to change the key assignments, you only have to modify the enumeration and all the code you make will continue to work.
A Single Exit Per Function
Avoid functions that have multiple return points. Combined with long functions, they make the code very difficult to understand. If you have a function that, depending on many things, returns one value or another, create a result variable and thus make clear what you are doing.
// nope
int myFunction()
{
if(this) return 5;
//... more code
for(whatever)
{
if(somethingElse) return 10;
}
if(yetAnotherConditional) return 12;
//... much more code
}
//yep
int myFunction()
{
int result;
if(this) result = 5;
//... more code
for(whatever)
{
if(somethingElse) result = 10;
}
if(yetAnotherConditional) result = 12;
//... much more code
return result;
}
We accept as an exception (it would be debatable) rejection returns. That is, a return placed at the beginning of the function that “bounces” the execution, as long as it is very clear what it does at a glance.
// meh acceptable
void MoveMotor(int parameter)
{
if(parameter < 10) return;
if(parameter > 210) return;
//... move motor
}
Adjust the Scope of Variables
You have probably read that the use of global variables is evil. In some ways this is true, and in programming there is a tendency to avoid them. Or, at least, to disguise it so that it seems we are not using them (ahem, ahem… singleton pattern… ahem).
In the case of an MCU we don’t have as many tools to avoid using global variables, but it is clear that abusing them is wrong. For example:
// nope
float raw;
float a;
float b;
float temperature;
void CalculateTemperature()
{
temperature= a * raw + b
}
// yep
float CalculateTemperature(float raw, float a, float b)
{
return a * raw + b
}
However, on the other hand, you are programming an MCU that does not have the memory and power of a computer. So if you have a variable/object (say) a servo, or an httpClient, and it really is something that is part of your entire program, don’t be ashamed to define them as a global variable (without abusing).
3) Keep It Simple

KISS, another programming guideline (and not just programming). The simpler something is, the better. Fewer chances of failures, of someone making a mistake, of wasting time understanding what someone else has written, or yourself when you open it three years later and say… was I drunk or what?
Get it into your head, simpler is better. And let’s see some tips about it.
Avoid Bit Operations and Masks
There is nothing more ‘fashionable’ to an old schooool programmer, or to one who has been seduced by the mathematical madness of their teacher, than to slap you with code like
int T = (REG >> 2 & b00001001 ) || ( TMCR_6 >> 3 & b00100101 );
I don’t care what the code is, that one in particular I made up. Every time you see something like that, it could surely be replaced with 2-3 lines of arithmetic operations and a conditional.
Avoid Pointers
Another whaaat? But C is based on pointers, and I’ve been told it’s “the top” of Arduino programming. Yes, but Arduino’s Wiring is based on C++, not C. And part of the improvements C++ had was minimizing the use of pointers.
In fact, most of modern computing seems destined to avoid saying the word “pointer”, precisely because they quickly become a readability hell.
C++ incorporated references, and 80% of the things you do with pointers you can do with a reference. So don’t get used to using pointers “just because”. On the contrary, use pointers only when “there is no other way”.
Avoid Touching Timers
In any traditional MCU programming, Timers were a fundamental part to achieve different functionalities.
However, Arduino, at least the Atmega 328p 32u2, etc., are very short on Timers. Furthermore, these are used by many libraries and functions of the ecosystem (millis and delay, for starters) both internal and external.
So take my advice, don’t play with Timers if you don’t want weird things to happen to you. Besides, you can do almost everything you could do by manipulating a Timer in other ways.
If you really need fine (very fine!) time adjustment, choose an MCU with more Timers. And even then, you will have libraries so that you never, ever have to go playing with the registers.
Pre-increment and Post-increment
The ++i and i++ operator is a plague that programming drags along, possibly only surpassed by the null disaster (I’ll tell you about that another day). They are a frequent source of error and how “fun” it is until you find it!
//nope
var myValue = myVector[i++] * 5.7;
//like this yes
i++;
auto myValue = myVector[i] * 5.7;
Basic rule, whenever you use a ++, — operator, on its own line and alone.
Use the Appropriate Loops
It seems quite obvious, but you have three types of loops. By far the most used is the for, followed by while and then dowhile (and the first would be foreach, but since we are talking about MCUs we’ll leave it).
Use each one for what they are, and avoid doing “clever things” like:
// nope, don't do that
for(;; condition)
{
}
Play with them. I will only comment that later we will see the section “just because something works doesn’t mean it’s right”.
Use the Appropriate Conditionals
Similar to the previous one, you have the classic if conditional, the switch, and the ternary operator ?. Use each one when appropriate, and avoid nesting conditionals like crazy.
Use the String Class
It’s amazing the sausages of code you sometimes see for something that could have been solved in one line with the String class. The String class has an undeserved bad reputation for causing problems with dynamic memory.
It has some reason, String is an object, and to create a new String it needs to be placed in memory. If we start creating Strings like possessed, and since Arduino doesn’t have a memory manager like a normal computer, you keep creating objects in different memory locations. And, even if you free them, you leave it like Swiss cheese and in the end, even though you have memory available, none of it fits your new String.
Then you have a nice memory collapse, which Arduino solves by restarting and getting rid of the little torture that your sketch has been by starting over with clean, brand-new memory.
That’s the way it is, and it can’t be argued. But it’s not the String class’s fault, it’s yours for creating objects like crazy. The String class, used well, is a great ally that will save you enormous amounts of code.
4) Don’t Obsess Over Efficiency

This is the most controversial point and, for many, the hardest to understand. Code cleanliness versus execution efficiency. We will agree that both are desirable, and you always have to keep an eye on both.
The good news is, many times both go hand in hand, and clean code is more efficient. But (there had to be a but), also often you will have a compromise between cleanliness and efficiency.
Attention, this can blow some people’s minds. When you have to choose between cleanliness and efficiency you should choose cleanliness, except on occasions that you can count on one hand.
Optimize with your head
What’s the point of writing a much more complex code fragment to save 10 nanoseconds, if right after you’re going to use a delay(…)?
// a round of applause for this lack of common sense
int T = (REG >> 2 & b00001001 ) || ( TMCR_6 >> 3 & b00100101 );
delay(5000);
Or, what’s the point of killing yourself to scratch nanoseconds here, when you then pass an object through the stack and that operation is a million times slower than anything you could have scraped before.
In any program, be it Arduino or another, most of the time is spent on certain especially slow operations. It is in those that you must pay attention. The rest, don’t waste time on them.
Let the compiler do its job
Long ago, compilers were not as smart as they are now and programmers did a lot of nasty tricks in the code to improve efficiency. There was no other way. But times have changed, and compilers are much smarter than they used to be.
Don’t try to “cheat” to try to make your program faster. In fact, if you try to “do the work” of the compiler, you will often find that your code ends up being slower and not faster, because you have cut certain optimizations that it could do, but since you wrote it in a weird way, it couldn’t do them.
Trust the compiler and let it do its job. Write your program in the cleanest way, and leave the machine language for the machines.
Avoid using #define
#define is a precompiler directive. It serves to tell the compiler, don’t compile this, compile something else, and generate different programs for the same code. However, in Arduino you will be very used to using #define to define constants. Don’t do it.
// nope
#define pin_sensor 5;
// yep
const int PIN_SENSOR = 5;
Differences? Well, apart from an important concept, because for you PIN_SENSOR is really a constant, the biggest difference is that with #define the compiler goes to where pin_sensor is and embeds a 5 without checking the typing, nor warning of a cast, etc.
On the other hand, if you are using a more advanced compiler than the standard IDE (like Visual Studio) the IDE will suggest the name of the variable, allow you to rename it, when you put the cursor over it, it will tell you the type it is, among many other advantages you have when you use each thing for what it is.
Leave the #define for when you really want to modify the program, for example, to compile a branch if it is an Arduino and another branch for an ESP32. For the rest, use const.
Consider if it is the right machine
At this point, if you are really making a program in which Arduino is very tight on power… maybe you should consider spending 1.5€ instead of 2.5 and buying something more powerful.
It is often said, iron (hardware) is cheap, programmers are expensive (taking into account development time, maintenance, etc). Without using it as an excuse to make slow code, the truth is that it is true.
Also, keep in mind, it is not the same when you do something in a run of 1-10 units, as when you are going to manufacture 10 thousand. There, if it is convenient to kill yourself in efficiency, because every penny counts.
And if the time is personal, because you do it at home for enjoyment, and there is no “cost”? Even more so. Don’t waste your time optimizing something to fit on a 1.5€ processor, if it would be easier on a larger one.
Not to mention, all these processors are going to evolve. In X years (it’s already happening) they will be more like “normal” programming (pc, web) than MCUs. What do you prefer to spend your time on, something that will disappear in a few years? Invest your time in programming well. If the machine limits you too much, don’t rule out changing machines.
That something works doesn’t mean it’s right
The code has to be clean, it’s not enough for it to work. If it’s not clean, it’s wrong, just like if it didn’t work. Drill this into your head. If you don’t believe it, repeat it over and over again until you convince yourself.
It’s not an excuse “I’ve been doing it this way for 30 years”, all the more reason for you to think that maybe the movie has changed a lot. The “I saw it on the Internet” or “my teacher told me” doesn’t work either. Develop your own opinion.
The excuse “I understand my super complex code because I’m very smart” doesn’t work either. In the first place… it’s not true. You spent 30 minutes trying a one-line code, when if you had written it in 3 lines it would have taken you 1 minute.
Also, if someone else has to read it, they won’t understand anything. But what’s more, when you pick it up again, you’ll wonder “what the heck does that line do”. Every time your program fails, you’ll reconsider… is this part here? And that’s not programming well.
And yes, it’s true, it works. That very long code with two-letter variables, no functions or objects, with numbers in the middle, taped together with duct tape, works. But no, it’s not right.
Programming is more than making complicated code that works. It’s structuring, abstracting, and making clean code that explains itself. Follow the advice in this post, short functions, variables with names, understandable code, not repetitive, etc.
Conclusion
We have seen some guidelines and tips for cleaner programming, and to help improve the quality of your code as a programmer. Of course, they are just advice, not laws.
We know it’s not the same when an Adafruit programmer uses a long loop in a library because they really need it, as when someone who is learning does it because they don’t know it’s better to avoid it.
It is also not the same when you make a prototype of 1 unit, or a series of 10 thousand. Nor when you make code to teach in a class, where it is logical to have more comments. Nor is it the same if you know how to make clean code and have to skip it in a section, as doing it out of ignorance.
Most of the guidelines we have seen come from the Agile world, and collect some of the main currents that have prevailed in “normal” programming (PC, web) for years. They are open to debate, but, in general, they have a high degree of consensus.
In Arduino and MCUs programming in general, these currents are more difficult to reach. Part of the reason is the user profile (very senior, or very new). Another important part is the difference in resources available in an MCU versus a computer.
However, the boundaries between both worlds are becoming increasingly blurred. And, just as it is important to program “cleanly” in “normal” development (PC, Web), it is also important for you to get used to doing it in MCUs, Arduinos, etc.
Final advice, it is advisable to see other programming languages. Learn C# or Java, learn web development. You will see how the code is structured in a high-level language, or in a framework like React, Angular or VueJs. This will change the way you program, and will improve the quality of your programs in Arduino and other MCUs.

