Why nanopb is an Embedded MarvelRecently, while working on STM32/ESP32 microcontroller projects, I found that using the traditional Protocol Buffers library can almost deplete flash memory and RAM. Yet, there is a desire to use .proto to define messages, which is efficient and cross-language. nanopb is like a savior, with a code size of just a few KB and stack usage around 1KB, specifically designed to tackle memory constraints.
Small and Beautiful: Minimal Code Sizenanopb’s runtime consists of only<span>pb_common.c</span>
, <span>pb_encode.c</span>
, and <span>pb_decode.c</span>
, totaling about 5 to 20 KB after compilation (depending on optimization level and platform). You can compile just the encoder or just the decoder, halving the ROM size, making it incredibly lightweight.
Static Allocation: The Limits Without mallocIn many bare-metal scenarios, <span>malloc()</span>
is not appealing at all. By default, nanopb writes string and array lengths as constants, allocating directly in the global area or stack. If flexibility is needed, there is an optional malloc callback that can be enabled as required.
Easy Process: From .proto to Runtime
- 1. Write your
<span>message.proto</span>
- 2. Install Python, grpcio-tools, and protoc
- 3. Execute
python generator/nanopb_generator.py myproto.proto
- 4. Add the generated
<span>.pb.c</span>
,<span>.pb.h</span>
, and the three main C files to your project - 5.
<span>#include <pb_encode.h></span>
,<span>#include "myproto.pb.h"</span>
- 6. Call
<span>pb_encode()</span>
/<span>pb_decode()</span>
, and you’re done!
Cross-Platform Build: Pain-Free CompilationMakefile, CMake, Bazel, Conan, PlatformIO, pip, vcpkg… nanopb has corresponding integration examples. You can easily run an <span>examples/simple/Makefile</span>
, or directly add dependencies in Zephyr or Arduino projects, truly zero barriers.
Deep Customization: Unveiling Configuration OptionsWant to change field order? Add <span>sort_by_tag = true</span>
in the <span>.options</span>
file. Need to pack optional fields? Mark <span>[packed=true]</span>
in the <span>.proto</span>
. If you encounter large messages that cannot fit, the callback interface can immediately handle streaming without requiring a whole block of memory as a buffer.
Testing & Quality: Comprehensive Testing Suite IncludedTo verify portability to a new compiler/new platform, just run <span>cd tests && scons</span>
to execute all test cases. It has been tested on STM32, simavr AVR, and of course, Mac, Linux, and Windows, with continuous integration building every day.
Ecology & Integration: From Arduino to ZephyrYou can find the <span>Nanopb</span>
library in the PlatformIO Registry, and you can also <span>pip install nanopb</span>
to install the generator in a virtual environment. The official Zephyr documentation also includes examples of using serialization with nanopb, and it is supported in almost every embedded ecosystem.
Real Cases: How It Empowers Projects
- • Garmin and TomTom running watches use it for efficient transmission of heart rate and GPS data over BLE;
- • Cisco TelePresence Server uses it for streaming control protocols;
- • In my own IoT gateway project, nanopb reduced the size of JSON to only 1/5, enabling lightning-fast data transmission.
ConclusionIn the resource-constrained embedded world, nanopb allows you to enjoy the cross-language, flexible definitions, and efficient serialization that Protocol Buffers offer. With its small size, no malloc, customizability, cross-platform capabilities, and comprehensive testing, this combination provides a killer experience in microcontrollers. Next time you write a communication protocol or data exchange, don’t manually handle binary data; let nanopb take care of everything for you.
Project Address: https://github.com/nanopb/nanopb