The Arduino programming language looks like C++, but it is weird in its own way: there's no C++ standard library.
In C++, you can print a variable to the debug console with std::cerr << x;
.
In Arduino, you have to write a function call: Serial.print(x);
.
I love that the Streaming library brings back the familiar syntax Serial << x;
through some template and macro magic.
But when it comes to a uint64_t
variable, nothing works!
error: call of overloaded 'print(uint64_t&)' is ambiguous
Serial.print(x);
^
note: candidates are:
note: size_t Print::print(const __FlashStringHelper*) <near match>
note: no known conversion for argument 1 from 'uint64_t {aka long long unsigned int}' to 'const __FlashStringHelper*'
note: size_t Print::print(const String&) <near match>
note: no known conversion for argument 1 from 'uint64_t {aka long long unsigned int}' to 'const String&'
The cause of this error is: Arduino does not know how to print a uint64_t
!
How Arduino Prints Stuff
Arduino contains a subset of C++ standard library, plus some additional headers known as the "Arduino Core".
Each microcontroller has its own Arduino Core: AVR, SAMD, ESP8266, ESP32.
They follow roughly the same API, one of which is the Print
class that knows how to print stuff.
class Print
{
public:
virtual size_t write(uint8_t) = 0;
size_t print(const __FlashStringHelper*);
size_t print(const String&);
size_t print(long, int base = DEC);
size_t print(unsigned long, int base = DEC);
size_t print(double);
size_t print(const Printable&);
// + many more overloads
size_t println(const __FlashStringHelper*);
// + many more overloads
};
Every object with print
and println
functions derives from the Print
class.
The subclass (such as serial port or network socket) is expected to implement the write
method that prints one character, and then the Print
class will convert whatever variable into a series of characters and invoke write
once for every character.
print
function has many overloads for different variable types.
It supports String
, int
, long
, double
, and so on.
However, there isn't a print
overload for uint64_t
, and that's why trying to print a uint64_t
causes a compile error.
Luckily, Print::print
accepts a Printable
object.
The Printable
interface allows new classes to tell Print
how to print themselves.
The IPAddress
class prints nicely by implementing the Printable
interface.
Make uint64_t
a Printable
uint64_t
is not a class.
There's no way to make uint64_t
a subclass of Printable
.
However, we can encapsulate it: we create a new class that wraps a uint64_t
value and implements the Printable
interface.
Then, we could print a uint64_t
like this:
uint64_t x = 0;
Serial.print(PriUint64<DEC>(x));
I made a little PriUint64 library for this purpose.
The library offers a PriUint64
class template that wraps a uint64_t
value, and can print it in different radices.
Integrate with Streaming Library
I love the <<
syntax from Streaming library so much, that I added integration with it.
If you #include <Streaming.h>
before including <PriUint64.h>
, you'll be able to print a uint64_t
value directly without wrapping it with PriUint64
.
This is achieved, of course, by overloading operator<<
.
uint64_t x = 0;
Serial << x << endl;
Supporting different number bases, i.e. _HEX
, _DEC
, _OCT
, and _BIN
macros, is trickier.
It requires quite a bit of template magic, and I had to use <type_traits>
.
AVR does not have this header (not in their subset of C++ standard library), so this feature only works on ESP8266 and ESP32.
uint64_t x = 0;
Serial << _HEX(x) << _DEC(x) << _OCT(x) << _BIN(x) << endl;