How To Streamline Your Arduino Code: Use sprintf to Declutter Serial Calls
2023-07-05 | By Maker.io Staff
A previous article outlined why we should avoid adding debug outputs in our production code and how we can achieve that goal with minimal program adjustments. However, programs sometimes need to write data to the serial console — for example, when a program is communicating with other microcontrollers. In these specific cases, the output statements may take up many lines of code in the program. This article outlines how to use the string print formatted (sprintf) function to prevent cluttering the code with many individual print calls when outputting data on an Arduino’s serial port.
The Problems of Using Standard Serial Output Calls
Before discussing why using sprintf makes sense, the following example outlines the problems of making outputs using the standard serial print functions. Suppose the program should transmit the value of four variables separated by semicolons. The loop function could look like this:
int day = 11; int month = 8; float temperature = 78.25; float brightness = 12.6; void setup() { Serial.begin(9600); } void loop() { // Update variable values Serial.print(day); Serial.print("; "); Serial.print(month); Serial.print("; "); Serial.print(temperature); Serial.print("; "); Serial.println(brightness); // Some other tasks going on... delay(2500); }
As this simple example shows, even writing the value of four variables in such a manner already requires seven lines of code, which, in the long run, can lead to overly complex, long, and unreadable programs.
Using sprintf to Reduce the Required Number of Lines
However, what if there was a method to condense the seven lines of code from above into three lines? Doing so would make the resulting program much shorter and easier to both read and understand. You can utilize sprintf, a C++ standard function that lets you specify a formatted string containing multiple blanks to fill in, to accomplish this goal:
int day = 11; int month = 8; float temperature = 78.25; float brightness = 12.6; void setup() { Serial.begin(9600); } void loop() { // Update variable values char buff[21]; sprintf(buff, "%i; %i; %f; %f", day, month, temperature, brightness); Serial.println(buff); // Some other tasks going on... delay(2500); }
While the second example may initially look more complicated, it only takes up three lines of code that can be broken down into the following three parts. The first line defines a buffer that must be large enough to store the resulting string. In this example, the array must be able to hold at most 21 characters. The first two numbers will always contain at most two digits, the semicolon and space separators take up six additional spots, and the temperature and brightness both require five extra slots. Finally, the string termination character, which is automatically appended, needs another free place in the array. The buffer may always be larger than necessary, but it shouldn’t be massively oversized, as doing so takes up unnecessary memory space.
The sprintf call itself can be broken down into the following three parts. First is the target buffer, created just one line above. Then comes the formatted string itself. This string contains placeholders denoted by the percentage sign and a single character describing the type of value that can be filled in. Think of it as a form containing pre-defined text and blanks that you can complete. The method requires programmers to pass the variables to fill in the blanks defined in the formatted string. Note that the variables must occur in the same order as they should be inserted into the blanks in the final text:
The formatted string can mix regular characters and blanks, defined using the so-called format and character specifiers. The programmer needs to supply an appropriate number of variables to fill in all the blanks in the string.
The sprintf method replaces the blanks with the variable values and stores the result in the buffer. The last line of this example is a regular serial output call. However, note that only a single call is required this time, and the program passes in the pre-assembled array.
Understanding sprintf’s Character Specifiers
But what exactly are these ominous character specifiers used in the formatted string? These specifiers become easy to understand once you know what they mean. In order to fill in the blanks, sprintf needs to know what type of data it should expect for each blank. For example, programmers could tell the method that it should fill in a specific blank with a number. Character specifiers let programmers define the type of data that will be present in the finished string so that sprintf can choose the correct output representation when building the string. Note that supplying the wrong character specifier will usually not lead to errors. However, it will often result in incorrectly formatted output. Luckily, you don’t have to memorize many complicated combinations, as the following three are the most commonly used character specifiers:
Keep in mind that many more formatting options exist. It is, for example, possible to instruct sprintf to convert numbers to their hexadecimal representation before inserting the result into the string. At the same time though, you should not concern yourself with remembering all possible specifiers, as you can easily refer to the manual to look for a more specialized output format.
Arduino’s sprintf implementation may not support all specifiers. It does, for instance, also require programmers to use the %s specifier and supply floating-point numbers converted to strings on some Arduinos.
Summary
Sending variable values over an Arduino’s serial port can drastically increase the length of Arduino programs when using single serial print commands, as it’s often done. Therefore, using sprintf can significantly reduce the number of lines of code required to send data over the serial port.
Instead of printing one variable at a time, the sprintf command allows programmers to define a prepared string that contains blanks needing to be filled in with variable values. For that purpose, though, the programmer must know how many characters the string will occupy once assembled, and they must also ensure that the final text never overflows the buffer.
The Arduino IDE’s serial monitor shows the result of the modified program making use of the sprintf call
When defining the formatted string, programmers can use specifiers that let sprintf know how to treat the variable values when filling in prepared text blank spaces. While supplying an incorrect specifier will often not result in immediate errors, doing so will typically lead to malformed output and potential errors further down the line.
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum