Introducing C++ programming on the Raspberry Pi Pico

When I started programming the Raspberry Pi Pico, I used the C language because I’ve worked with it before. The Pico’s SDK also supports C++, but I’ve never used C++. When I started Mac programming in the early 1990s, C was the clear choice. By the time I needed to do object-oriented programming, Apple had bought NeXT and the way to do OOP on macOS was Objective-C not C++. The Pico has given me chance to join the party.

If you’re an old C++ hand, you’ll undoubtedly be already up and running — you probably need read no further. If you’re not steeped in C++ experienced, the good news is that transitioning from C to C++, or even just jumping straight to C++, is very straightforward. The Pico C/C++ SDK works smoothly across both languages, and the CMake build tool is able to work with C++ code as well as with C. If you’ve set up Visual Studio Code to use CMake, it too is ready to help you write with C++ Pico applications.

So here’s basic introduction to C++ to help you get started writing C++ code for the Pico if you’re new to the language or have only a limited experience of it.

Adding C++ files to a project is just a matter of creating them and adding them to the project’s CMakeLists.txt file within the add_executable() unit.

Headers

Your header files will #include the same SDK files as before, but you’ll need to #include some different, C++ specific files, for example:

#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <cstdint>

The last three provide C++ code with access to, respectively, the C standard library of functions, C’s string functions and the standard C integer types, eg. uint16_t. You’ll notice, though, that C++ has its own string library, which provides all of the functionality you might expect from a modern programming language: adding strings together (aka ‘concatenation’) with the + operator, comparing strings with ==, for example.

C strings are really just arrays, and if the C++ string library soups those strings up to be more flexible and more powerful, the vector library does the same with regular arrays.

The iostream library handles C++ input and output to stdin, stdout and stderr. For example, to output a debug string, you’d write:

std::cout << "The size of cpp_string is " << std::cpp_string.length() << " bytes.\n"

This concatenates the three parts after the first << and between the remaining <<, converting the integer string length on the way, and outputting it via std::cout — iostream’s name for stdout.

Namespaces

What’s with the std::? C++ introduced a concept call ‘namespacing’. This allows you to organise things like functions, classed and their instances, and variables into groups called namespaces. Within a given namespace, function names, for example, have to be unique, but two different namespaces can both contain functions with the same name. That pair of colons separate the namespace from the function, variable or instance identifier.

Take the iostream library. Its members are placed in the std namespace, so you access its members with prefixing them std::.

The string library is also part of std, so you’d access it thus:

std::string a_new_string = "Hello!";
int string_length = a_new_string.length();

You can avoid writing std:: out every time by adding

using std::string;

to your .cpp file after you #include the header file and before you use a member function or variable. It shouldn’t go in any header file that will itself be #include-d, as this is a bad thing in C++. Now you just call:

string a_new_string = "Hello!";

This is not a million miles away from Python’s from Module import Function, which likewise ensures you don’t need to add Module. every time you call Function.

You don’t want to just add

using std;

As this covers all of std, not just the string sub-namespace. So if vector, which is also part of std, also has functions of the same name as those of string, the compiler won’t know which to use and your build will fail. Better to add:

using std::string;
using std::vector;

Which has the same effect, but doesn’t break anything.

You can get rid of std:: in the output line by adding:

using std::cout;

References and pointers

Other useful points to C++ include the inclusion of the bool type — you don’t need a separate library or typedefs defining true and false as you do with C.

C++ works with ‘references’ to indicate the location of data or code rather than pointers the way C does. So if you write:

string food = "Pizza";
string &dinner = food;

dinner is a reference to the string food. From this point on, you can use dinner in place of food. You don’t need to dereference it to access the original string, which is mutable:

dinner[0] = 'p';

Note the use of single quotes, because we’re still putting in place a char, not a string.

The & tells the compiler that dinner holds a reference, but once set it can used to point to other strings:

string food = "Pizza";
string not_food = "Iron";
string &dinner = food;
// Not a good meal:
dinner = not_food;

I mentioned pointers a moment ago, and you can continue to use them the same way in C++, but you should try to use references. However, you will use pointers when working with libraries that include C code.

A feature I like from other languages is default function parameters: set a value that will be used as an argument if none is passed in:

void my_function(string food = "Pizza") {
    cout << food << "\n";
}

int main() {
    my_function("Chocolate");
    my_function();
    return 0;
}

This outputs:

Chocolate
Pizza

And of course, functions take references so that they can change the values of variables outside of their scope, or work with complex data structures that need to be passed by reference anyway:

void swap_two_numbers(int &a, int &b) {
    int c = a;
    a = b;
    b = c;
}

Familiar territory

At this point you’re probably thinking that you’ve seen a lot of all this in other programming languages. Most likely that’s because they were themselves inspired by C++. This is why I found C++ in many ways a more comfortable fit than C because I’m now so used to creating classes, instantiating them and passing those instances around by reference from my work with Swift, JavaScript, Squirrel and Rust.

For example, when returning to C, I naturally wrote loops like this:

for (uint8_t i = 0 ; i < something ; ++i) { … }

I now learn the declaration of the index variable within the for statement is frowned upon in C and was introduced with C++. But I’ve been doing it that way for years, so naturally I did so in my recent C code. Of course, uint8_t is a C type. C++ uses int, but you can use fixed-length types is you add #include <cstdint> to your header file.

The upshot is that you will probably, like me, find C++ more intuitive than you thought it might be. So give it a go!

More on the topic of C++ programming on the Pico in a future post. In the meantime, I’ve updated my makepico project set-up script (for Z shell) to support C++ projects: just use the new -c switch. You can find it in my Pico GitHub repo.

More Raspberry Pi Pico