So, you’re using – or want to use – ROS on a Raspberry Pi. That’s great!
ROS + Raspberry Pi is such a powerful combination to create smart robots, with a somehow low cost, and very small electronic board embedded in the robot.
Although there are many advantages, there are also some limitations that you need to be aware of, and some good practices that you should follow.
In this post I’ll give you a few useful tips and best practices in order to bring more ease of use and performance to your ROS + Raspberry Pi projects. This guide will work for any recent Raspberry Pi version (4, 3B, 3B+).
You don’t need to follow all the tips, and they can all be applied separately. Take what’s best for you to improve your current project or workflow!
Use the right operating system for ROS
First things first, it will be much easier for you if you install an operating system on which ROS will run quite flawlessly.
The “default” operating system for Raspberry Pi is Raspbian. However, if you want to use ROS, you’d be better served by using a Ubuntu version for the Pi. Installing ROS packages and managing them on Raspbian can be quite difficult, whereas on Ubuntu it’ll work almost out of the box, just like on a standard computer or laptop.
One of the best Ubuntu version for the Pi so far is Ubuntu Mate. It comes with many programs installed, and also a desktop environment.
If you haven’t installed an OS (and ROS) yet on your Pi, check out the following tutorials:
Later on, if you aim to create a production version of your project, or if you just want the bare minimum an OS can bring, you can install Ubuntu server, which comes with no desktop, and far less programs installed.
Note: If you need more RAM, you can also disable some programs that are launched on boot, which you don’t necessarily need (auto apt daily updates, Bluetooth if you don’t use it, etc).
Use catkin_make with fewer cores
The most basic way to compile your code is to use the command line tool
catkin_make inside your catkin workspace.
On a standard computer or laptop (which is not 15 years old), you should not have any problem using this command. However, on Raspberry Pi, if you start to have big ROS Cpp programs, you may experience some slow compiling, or freezing, or even a compiling error that you don’t understand, which should look like:
c++: internal compiler error: killed (program cc1plus).
Why is that? Well, simply because the Raspberry Pi has a lower performance than your standard laptop. Usually less RAM, less CPU frequency, fewer cores. And this can lead to some unexpected issues while compiling code.
Fortunately, you can easily “not get stuck” anymore when compiling. You just have to reduce the number of cores you use when compiling. By default, when you run
catkin_make, the command line tool will find how many cores are available, and start one thread for each core. So, if you just run
catkin_make, you’ll see something like that:
$ catkin_make Base path: /home/ros/catkin_ws ... #### #### Running command: "make -j4 -l4" in "/home/ros/catkin_ws/build" ####
As there are 4 available cores on the Pi, the command
make (which is launched by
catkin_make) will have the “-j4 -l4” argument, so as to use all 4 cores.
Now that you’ve seen that, it becomes fairly easy to use less cores. I recommend you use 2 cores when compiling ROS on your Raspberry Pi. Only 1 will be too slow, and with 3 you may still experience some crashes when compiling.
So, you’ll use the command
catkin_make -j2, or
catkin_make -j2 -l2, both will work fine.
$ catkin_make -j2 -l2 Base path: /home/ros/catkin_ws ... #### #### Running command: "make -j2 -l2" in "/home/ros/catkin_ws/build" ####
You may think that fewer cores = worst performances, but I can assure that if you have big Cpp files, then it will compile much faster, because nothing will be stuck or freezing.
Optimize your ROS architecture for RAM usage
RAM is a very big topic when speaking about Raspberry Pi. You only have 1GB RAM, which is quite low compared to what you’re used to have on a standard computer: often the minimum is 4GB, but now 6 or 8GB is more common, and you can easily go up to 16/32GB.
The Raspberry Pi 4 board, released in 2019, has several versions that you can buy: one with 1GB, one with 2GB, and one with 4GB. So, basically, either you can optimize your ROS project for RAM, or you can use a Raspberry Pi with more RAM, that’s up to you. And of course, you can also do both.
So, what’s the problem with RAM? Well, if you check how much RAM a given program consumes, with command line tools like top or htop (
sudo apt install htop), you will be surprised at how much RAM nodes and launch files take.
From tests I made on my own, I consistently saw a 20MB use for each node, and about 20MB for each launch file, plus around 40MB for roscore.
So, if you’re thinking about using 5 launch files to launch 30 different nodes, you’ll need 40 + 5*20 + 30*20 = 740MB of RAM minimum, just for the nodes and launch files. That’s really huge, and with this amount of nodes (which is not uncommon) you’ll run out of RAM really quickly.
The solution? Well there is no magic solution that will solve all your problems, but here’s a combination of things you can do:
- Use fewer launch files and nodes. For example, instead of launching 5 nodes for different functionalities that you always start together anyway, you can just launch one node, and from this node, start the 5 different functionalities. It doesn’t mean you have to mix up all your code. You can still create a different class (more info: OOP with ROS in Cpp, and OOP with ROS in Python) or package for each functionality, and then import and start them all in only one node (see how to import a Cpp header, or a Python module from another ROS package). By doing this you would save 4*20 = 80MB of RAM.
- Use Nodelets with Cpp.
- Or, as discussed above, increase the amount of RAM you have with better hardware.
- And finally, as seen below, you can export some functionalities outside of the Pi, so you won’t use RAM for those.
Externalize heavy computation work
Let’s use an example for this one, that’ll be easier to understand.
So, imagine that you have a mobile base robot which needs to navigate in a 3D space, following a path not known in advance.
On your Pi, you may certainly have a few nodes (or programs grouped in one node) for getting data from various sensors (infra red, lidar, etc).
Then, you would probably have 2 planners: one local real-time planner, doing basic stuff like react to unexpected obstacles and avoid crashing into things. The other planner (let’s call it the global planner) would do some more heavy computation, and provide a complete path to follow every 5 seconds, depending on all the possible variables that are available from all sensors in the environment.
As you can guess, the real-time planner needs less computation power, but a really high frequency loop execution. So, keeping it on the Raspberry Pi is certainly the best choice to make here.
As for the global planner, you don’t need a high execution frequency (one execution every few seconds), but you need some heavy computations to be made, which is not something your Raspberry Pi can handle easily.
In this scenario, you could externalize the global planner to a standard computer/laptop, which is working remotely from the robot. Every 5 seconds, the fast computer will compute the new updated path and send it to the Raspberry Pi, which will try to follow it, and react accordingly to obstacles with the real-time planner.
There is a simple way to do that: you can use the multi-machine functionality from ROS. Basically you’d launch a ROS master in one of the two machines, and then connect the machines together, so they can exchange data through topics, services, actions, etc.
To go further, let’s imagine you now have a robot fleet, with 10 robots powered by Raspberry Pi, plus one central computer. The latter will be used to monitor all robots, and create a path for each one so they do not collide with the environment and other robots.
You’d create a multi-machine environment, where each robot follows the order from the “master” computer. Each robot would just run the necessary code to interact with hardware, and let the master computer do the heavy work.
Use the most appropriate language
When developing with ROS, mostly you’ll use Python and Cpp. I’ll stick with those 2 for this tutorial, but you can also think as interpreted languages when I write about Python, and compiled languages when I write about Cpp.
If you’re already writing all your code in Cpp, then this section probably isn’t relevant for you. But if you’re used to write most of your code in Python, then this can become a little bit trickier when working on Raspberry Pi.
So, again, on a good computer/laptop, if you use Python for most programs, everything will run fine. For example you can run a 250+Hz Python loop to read/write hardware from/to a USB sensor/actuator without any performance issue.
But try to do that on a Raspberry Pi, and you’ll see that it will quickly eat up a lot of available resources. This also applies to heavy mathematical operations.
So, basically, what you may experience, is that a Python code working fine on your laptop, may not work well on a Raspberry Pi.
In this case, you might want to rewrite this part of code in Cpp, as to use much fewer resources and gain more performance. You could also – depending on your application constraints – try the multi-machine solution as to externalize this heavy computation on a more powerful machine.
Write ROS wrappers for interacting with hardware
This tip is not much about performance, but more about having a better organized – and reusable – code.
If you use ROS on Raspberry Pi, you’ll probably also connect your Pi to some hardware components to actuate your robot and get feedback data.
A not-so-good practice here would be to import ROS in all your classes and programs that directly deal with hardware. It means that you can’t really export any hardware library for other non-ROS projects, and you’d have a hard time integrating already existing libraries/plugins to your ROS application.
The best way to do that is to create non-ROS programs (Cpp or Python) to talk to your hardware. And then, on top of those programs, you create a ROS wrapper, which will connect the program to your entire ROS environment.
For more info about that, check out this tutorial series on what is a ROS wrapper and how to create one.
Conclusion – ROS and Raspberry Pi best practices
If you try to follow most of the best practices from this post, you’ll be able to create quite robust, scalable, and high-performance projects with ROS and Raspberry Pi.
Also, one of the best thing you can use is common sense. For example, don’t forget to always backup your code and files, in case something bad happens to your SD card (if happens more than you think, and it’s often at the wrong time). Use git or a similar code versioning system to save your code somewhere, and be able to restore some previous versions if you need to.
If, after following all those tips, you still have problems with RAM and CPU, then maybe it’s because your application is really too heavy, and a Raspberry Pi is not the right tool for the job. In this case, you might want to look at alternative embedded board, such as Intel NUC boards. Also, in some rare cases you might have some problems with sub-dependencies when installing and compiling a ROS package through apt. This is because the architecture of the Raspberry Pi (ARM) is different from the architecture of a standard computer/Intel NUC (x86). It can happen that dependencies are not supported on all architectures. If you encounter such a problem (although it would be really rare), then you’d need to get an x86 board.