Today in the workshop, we’re having a pizza party!
Now, before you get too excited, I should tell you that the pizza we’ll be serving up today isn’t the tomato sauce and cheese variety. Instead, it’s a fascinating new open-source product called PiZZa, and it can transform a Raspberry Pi Zero W or Zero 2 W into a very powerful (albeit somewhat limited) microcontroller that you can hook up and program using the Arduino IDE.
So let’s get cooking!
Introduction
You might be wondering why anyone would want to turn a perfectly good microcomputer into a microcontroller. As it turns out, there are some very good reasons for doing exactly that, and we’ll get into them shortly. If you’re anything like me, you probably have a drawer full of Raspberry Pi Zero boards that haven’t seen action in years, and PiZZa might just be the perfect way to put them back to work.
In this article, we’ll take a close look at how PiZZa works, and then we’ll install it on a Raspberry Pi Zero 2 W. Once it’s up and running, we’ll wire up a pushbutton and a couple of LEDs and work through a series of demonstration sketches. We’ll finish up by learning how to read and write files on the microSD card, which works a little differently with PiZZa than it does with the microcontrollers you’re used to.

So let’s go and serve ourselves up a slice of PiZZa (yes, I’m afraid the pizza puns have only just started – sorry about that!)!
PiZZa
PiZZa is an abbreviation for “Pi Zero with Zephyr for Arduino”. It’s an open-source project created by GitHub user jetpax, and it allows a Raspberry Pi Zero W or Zero 2 W to run a Zephyr real-time operating system in place of Linux. On top of Zephyr sits an Arduino Core, which means you can write, compile, and upload sketches to the Pi from the Arduino IDE – exactly as you would with an Arduino, an ESP32, or a Raspberry Pi Pico.
In other words, PiZZa turns a microcomputer into a microcontroller.
Why Turn a Microcomputer into a Microcontroller?
At first glance, this might seem like a strange thing to do. But take a look at the specifications of the Raspberry Pi Zero boards next to two of the most popular microcontrollers around, the Raspberry Pi Pico W and the classic ESP32, and the appeal becomes obvious.
| Pi Zero 2 W | Pi Zero W | Pico W | ESP32 | |
|---|---|---|---|---|
| Processor | Quad-core Cortex-A53 | Single-core ARM11 | Dual-core Cortex-M0+ | Dual-core Xtensa LX6 |
| Clock Speed | 1 GHz | 1 GHz | 133 MHz | 240 MHz |
| RAM | 512 MB | 512 MB | 264 KB | 520 KB |
| Storage | microSD (GBs) | microSD (GBs) | 2 MB Flash | 4 MB Flash (typ.) |
| WiFi | Yes | Yes | Yes | Yes |
| Hardware FPU | Yes | Yes | No | Single-precision |
The difference is dramatic. The Pi Zero boards have processors running at speeds an order of magnitude faster than their microcontroller counterparts, and their RAM is measured in megabytes instead of kilobytes – 512 megabytes, to be exact. For storage, they use a microSD card, which gives you gigabytes of space to play with. And both Zero boards include a hardware floating-point unit, something that will really pay off in math-intensive applications.
So if you have a project that needs a lot of memory, a lot of processing power, or a lot of storage, a Raspberry Pi Zero running PiZZa is a very interesting proposition – especially if you already have one sitting in a drawer.
How PiZZa Works
PiZZa replaces the entire Linux boot process on the Raspberry Pi with a lightweight, three-layer arrangement. Here’s the boot sequence:
- The Boot Layer. When the Pi powers up, it loads the PINN bootloader from the microSD card. PINN is an enhanced version of the NOOBS bootloader that the Raspberry Pi used to ship with. Instead of loading Linux as a Raspberry Pi normally would, PINN loads a Zephyr binary file (zephyr.bin).
- The Zephyr Loader. This layer is responsible for receiving compiled sketches from your computer over the USB connection and storing them on the microSD card.
- The Arduino Layer. This loads the Arduino Core on top of Zephyr, then loads and runs your sketch – just like any other microcontroller does.
Some of this terminology may be unfamiliar to you, so let’s take a moment to look at the two key ingredients in the PiZZa recipe – Zephyr and the Arduino Core.
The Zephyr RTOS
Zephyr is an open-source real-time operating system (RTOS) hosted by the Linux Foundation. Unlike a full-featured operating system such as Linux or Windows, a real-time operating system is small and very fast. Zephyr boots in milliseconds, or at most about a second, as opposed to the several seconds that Linux and Windows require.
It is quite possible that you may already have encountered Zephyr without realizing it. It is used inside many modern microcontroller products, including several of Arduino’s newest boards.
The Arduino Core
The Arduino Core is the layer of software that implements the Arduino API. That includes all of those familiar commands like digitalWrite(), analogRead(), and Serial.println(). Virtually every board that can be programmed with the Arduino IDE runs some version of the Arduino Core.
PiZZa uses ArduinoCore-zephyr, which is maintained by Arduino themselves and is the same core used in their newest microcontrollers, such as the Arduino UNO R4. So when you’re writing code for PiZZa, you’re using the very same Arduino Core that powers a variety of other microcontroller boards.
One Cable Does It All
One of the nicest features of PiZZa is the connection to your computer. You connect the Raspberry Pi Zero using its micro-USB data port – the port normally used for USB peripherals – and that single cable does everything. It carries the compiled sketches from the Arduino IDE to the Pi, it provides the serial monitor connection, and it also powers the board. You don’t need a separate power supply, and you don’t need to use the dedicated PWR USB connector at all.
There is one trade-off to be aware of: when the Pi is running from your computer’s USB port, the processor runs at its idle clock speed of about 600 MHz rather than its full 1 GHz. That may sound like a limitation, but 600 MHz is still several times faster than just about any microcontroller.

GPIO and Arduino Pin Mapping
When you write sketches for PiZZa, you refer to pins by their Arduino data pin numbers (D0, D1, D2, and so on) rather than by the Raspberry Pi’s BCM GPIO numbers or the physical header pin numbers. Sixteen data pins are available, although some of them are dedicated to specialized functions like I2C, SPI, and PWM.
The chart below relates the Arduino data pins to the Raspberry Pi GPIO. Keep it handy – you’ll be referring to it constantly when you wire up your own projects. The pins used in this article’s experiments are included, along with the fixed-function bus pins:

Note that PWM is only available on pins D15 and D16, and the I2C bus is fixed to GPIO 2 and GPIO 3 – physical pins 3 and 5 on the 40-pin header, the same pins used for I2C on every Raspberry Pi.
PiZZa Limitations
For all of its wonders, PiZZa does have some limitations, and you should know about them:
- No analog functions. This is a hardware limitation of the Raspberry Pi itself – there is no analog-to-digital or digital-to-analog converter onboard, so analogRead() has nothing to read. If your project needs analog inputs, you’ll want to add an external ADC on the I2C or SPI bus.
- Limited PWM. Only two pins, D15 and D16, are PWM capable.
- Board differences. The Zero 2 W and the original Zero W have different peripheral capabilities, and not everything available on the Zero 2 W is available on the Zero W.
- Power consumption. The Pi Zero boards draw considerably more current than an ESP32 or a Pico, so PiZZa is not a good candidate for battery-powered projects.
- Single-core operation. At present, PiZZa runs on a single CPU core, and when powered from the USB host port, it runs at about 600 MHz.
Despite these limitations, PiZZa gives you a remarkably fast microcontroller with an enormous amount of RAM and storage – essentially for free, if you already own the board.
So let’s go and have a meal of PiZZa!
Installing PiZZa
Installing PiZZa is a two-part process. First, we’ll download a PiZZa image and burn it onto a microSD card for the Raspberry Pi. Then we’ll set up the Arduino IDE with a Boards Manager entry so that it knows how to compile and upload sketches for our new “microcontroller”.
What You’ll Need
- A Raspberry Pi Zero W or Zero 2 W. Both boards work; the Zero 2 W is the more capable of the two. (You could probably also use the non-W versions – I haven’t tried it myself, but aside from the WiFi sketches, I don’t see any reason why it wouldn’t work.)
- A microSD card. Officially, you need a card of 2 GB or larger, but in this day and age, it’s hard to find one smaller than 16 GB. Don’t worry – that extra space won’t go to waste, as you’ll be able to use it for storage in your own programs.
- A micro-USB data cable to connect the Pi to your computer. Make sure the cable can carry data and not just power; you need both.
- A computer running the Arduino IDE.

Getting the PiZZa Image
The PiZZa image files are available on the Releases page of the PiZZa GitHub repository. This is a rapidly evolving project: at the time I recorded the video, the latest release was version 0.6.2, but that release contained only source code – the downloadable image files were attached to version 0.6.1. By the time you read this, 0.6.2 or an even newer release may well have images available, so look for the most recent release that includes image files under its “Assets” section.
When you expand the Assets for a release, you’ll find downloadable files that include two disk images – one for the Raspberry Pi Zero 2 W and one for the original Zero W. Download the image that matches the board you’ll be using.

Burning the Image to the microSD Card
Once you’ve downloaded the image file, you’ll need to burn it onto your microSD card, and there are several ways to do that.
I used Balena Etcher, a free, cross-platform utility that is a very common choice for writing image files to microSD cards. Alternatively, you can use the official Raspberry Pi Imager – it has a “Use custom” option that lets you select a downloaded image file and write it to a card.
Whichever tool you choose, the process is the same: select the image file you downloaded, select your microSD card, and write it. When it’s finished, insert the card into the Raspberry Pi, and we are ready to go.
Setting Up the Arduino IDE
Now let’s get the Arduino IDE ready. Just as with the ESP32 or the Pico, we need to add a Boards Manager entry for PiZZa.
The first step is to add a JSON file to the IDE preferences. If you’ve installed third-party boards before, this will be a familiar step.
Open the Arduino IDE and go to File → Preferences (on a Mac, Arduino IDE → Settings). Near the bottom of the Preferences window, you’ll see a field labeled “Additional boards manager URLs”. If the field is empty, you can paste the PiZZa JSON URL directly into it. If you already have entries there, click the button beside the field to open the list editor, and paste the URL on a new line at the end. Here is the URL you’ll need:
|
1 |
https://raw.githubusercontent.com/jetpax/PiZZa/main/apps/Arduino/package_pizza_index.json |

Click OK to close the list, and OK again to close Preferences. You’ll notice the IDE doing some work in the status bar at the bottom as it downloads the new board index.
Next, open the Boards Manager – either from the icon on the left sidebar or from Tools → Board → Boards Manager – and type “pizza” into the search box. The PiZZa boards package will appear; click Install and let the installation complete. Once it finishes, your Arduino IDE is ready to start working with PiZZa.
The Mandatory Blink Test
So now we essentially have a brand-new microcontroller, and there’s really only one proper way to christen it – the Blink sketch!
PiZZa actually includes its own version of Blink in the sample code that comes with the boards package, but I used the standard Blink sketch from the Arduino IDE’s built-in examples (File → Examples → 01.Basics → Blink). I did this to illustrate an important point: the standard sketch uses LED_BUILTIN, and PiZZa has mapped LED_BUILTIN to the onboard LED of the Raspberry Pi. So completely unmodified Arduino example code should work as-is.
Select your PiZZa board and its port, and upload the sketch. Watch the output window as it loads – the upload process looks a little different from what you may be used to with other microcontrollers, but after a few moments, you’ll get a “Done uploading” message. And if you look at your Raspberry Pi, you’ll see its onboard LED happily blinking away.
Wonder of wonders – we can blink an LED! It may not seem like much, but it proves we have a fully working microcontroller. Now let’s go and explore it a little further.

Using PiZZa
Now that PiZZa is installed and we’ve passed the Blink test, it’s time to connect some I/O devices and put our new microcontroller through its paces. By “I/O devices”, I simply mean a pushbutton switch and a couple of LEDs – nothing fancy, but more than enough to demonstrate that PiZZa is just as easy to use as any other microcontroller under the Arduino IDE.
We’ll wire everything up once, and then run four demonstration sketches: a basic input/output demo, a PWM LED fade, a WiFi connection test, and an I2C bus scanner.
The Hookup
Here’s what we’ll need to hook up to our Pi Zero for today’s experiments:

Note that we placed the second LED on D15 deliberately – it’s one of the two PWM-capable pins, and we’ll need that for the LED fade demo. Now let’s start experimenting with PiZZa!
Demo 1 – Basic Input & Output
Our first demonstration is a simple I/O test using the pushbutton and the LED on pin D3. When we press the button, the LED lights; when we release it, the LED goes out. The sketch also prints the button status to the serial monitor. It’s about as basic as it gets, but that’s precisely the point – to show that fundamental I/O operations with PiZZa are no different from any other Arduino-style microcontroller.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* PiZZa I/O demo pizza-io-demo.ino Demonstrates push button input and LED output For Raspberry Pi Zero (2) W with PiZZa DroneBot Workshop 2026 https://dronebotworkshop.com */ const int LED_PIN = 3; // D3 = GPIO17, header pin 11 const int BUTTON_PIN = 2; // D2 = GPIO4, header pin 7 void setup() { pinMode(LED_PIN, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); Serial.begin(115200); while (!Serial) {} Serial.println("PiZZa I/O demo ready"); } void loop() { bool pressed = (digitalRead(BUTTON_PIN) == LOW); // INPUT_PULLUP: LOW = pressed digitalWrite(LED_PIN, pressed ? HIGH : LOW); static bool lastState = false; if (pressed != lastState) { Serial.println(pressed ? "Button pressed" : "Button released"); lastState = pressed; } } |
The sketch begins by defining two constants for the LED and button pins. Note that these are the Arduino data pin numbers – 3 and 2 – not the GPIO or physical pin numbers, which is where the pin mapping chart from earlier in this article comes in handy.
In Setup, we configure the LED pin as an OUTPUT, and the button pin as an INPUT_PULLUP. Using the internal pullup resistor means we don’t need an external resistor for the button – the input is held HIGH until the button connects it to ground, so a LOW reading means the button is pressed. We also start the serial monitor at 115,200 baud.
The Loop is pretty straightforward. We do a digitalRead of the button pin and store the result in a boolean called “pressed”, then digitalWrite that value out to the LED. Whenever the button state changes, we print “Button pressed” or “Button released” to the serial monitor.
Load the sketch onto the board and give it a try. On my workbench, I had the whole hookup on a solderless breadboard alongside a Pi Zero 2 W, powered entirely through the single micro-USB cable. Press the button and the LED lights, and the serial monitor reports “Button pressed”; release it, and the LED goes out, with “Button released” on the monitor.

It’s exactly what you would expect – which is exactly what we wanted to prove. I/O operations using PiZZa are virtually identical to any other microcontroller.
Demo 2 – LED Fade with PWM
Next up is a pulse-width modulation demonstration – the classic LED fade. PiZZa includes its own PWM fade example in its bundled sample code, so you can check that out as well, but the sketch we’re using here follows the traditional Arduino fade pattern. The one thing to keep in mind is that only pins D15 and D16 are PWM-capable, which is why we wired our second LED to D15.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/* PiZZa PWM fade pizza-led-fade.ino Fades LED LED on pin 32 (D15 - GPIO 26) - Not all pins support PWM For Raspberry Pi Zero (2) W with PiZZa DroneBot Workshop 2026 https://dronebotworkshop.com */ const int PWM_PIN = 15; void setup() { pinMode(PWM_PIN, OUTPUT); Serial.begin(115200); while (!Serial) {} Serial.println("PWM fade demo — LED2 on header pin 32"); } void loop() { for (int b = 0; b <= 255; b++) { analogWrite(PWM_PIN, b); delay(5); } for (int b = 255; b >= 0; b--) { analogWrite(PWM_PIN, b); delay(5); } } |
The sketch defines the PWM pin (D15, which is physical pin 32), sets it up as an output, and starts the serial monitor. The Loop consists of two for loops. The first counts up from 0 to 255, doing an analogWrite of the count value to the pin each time through. This is followed by a 5-millisecond delay. The second loop does exactly the same thing in reverse, counting down from 255 to 0.
The result is an LED that smoothly brightens to full intensity and then fades back down to darkness, over and over. If you’d like to change the speed of the fade, simply adjust the delay value between brightness steps.
Load it up and watch the show. It does pretty well what you’d expect, and it illustrates the PWM capability available on a couple of pins when running a Raspberry Pi Zero with PiZZa.

Demo 3 – Connecting to Wi-Fi
For our next demonstration, we’re going to use Wi-Fi. Both the Zero W and Zero 2 W have it built in (that’s what the “W” in their names stands for!).
Working with Wi-Fi under PiZZa is pretty much the same as it is with any other Wi-Fi-equipped microcontroller.
This sketch connects to your wireless network, then reports the connection status and signal strength every few seconds.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/* PiZZa WiFi connect + status demo pizza-wifi-connect.ino Basic WiFi Connection Measures signal strength For Raspberry Pi Zero (2) W with PiZZa DroneBot Workshop 2026 https://dronebotworkshop.com */ #include <WiFi.h> const char* ssid = "YOUR SSID"; const char* password = "YOUR PASSWORD"; void setup() { Serial.begin(115200); while (!Serial) {} Serial.print("Connecting to "); Serial.println(ssid); int result = WiFi.begin(ssid, password); Serial.print("WiFi.begin() returned status: "); Serial.println(result); } void loop() { int st = WiFi.status(); Serial.print("Status: "); Serial.print(st); Serial.print(st == WL_CONNECTED ? " (connected)" : " (not connected)"); if (st == WL_CONNECTED) { char* ssidName = WiFi.SSID(); Serial.print(" SSID: "); Serial.print(ssidName ? ssidName : "unknown"); Serial.print(" RSSI: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm"); } else { Serial.println(); } delay(3000); } |
The sketch starts off the way most Wi-Fi sketches do, by including the Wi-Fi library. We then define two variables holding the SSID and password of the network – naturally, you’ll need to replace these with the credentials for your own network before uploading.
In Setup, we start the serial monitor and then call WiFi.begin(), passing it the SSID and password. The call returns a status code, which we print out.
In the Loop, we read the Wi-Fi status with WiFi.status() and print it, along with a note on whether we are connected. If we are connected, we also print the SSID of the network and the RSSI. The RSSI (Received Signal Strength Indicator) is the strength of the Wi-Fi signal measured in dBm. If we aren’t connected, we just print a blank line. Then we delay for three seconds – a value you can change if you like – and do the whole thing over again.
One thing worth mentioning: when you compile this sketch, you’ll see a warning at the top of the output stating that the Wi-Fi library may be incompatible with the current board running on the Zephyr architecture. This is a warning you can safely ignore – as you’re about to see, the sketch connects and reports correct results just fine.
Running the demo on my workbench, the board connected to my network right away and reported a very strong signal strength – which makes sense, as one of my mesh WiFi units is right in the workshop with me, not far from the board.
Connecting to WiFi with PiZZa is just about as simple as it is with any other WiFi-enabled microcontroller.

PiZZa also ships with a couple of other WiFi examples in its included sample code, and they’re worth checking out as well.
Demo 4 – Scanning the I2C Bus
The last of our four demos is an I2C scanner. This sketch scans the I2C bus and reports the address of every I2C device that it finds. On the Raspberry Pi, the I2C bus is on physical pins 3 (SDA) and 5 (SCL) of the GPIO header , the standard pins for a Raspberry Pi.
If you’re getting the impression by now that code for PiZZa is really no different from code for any other microcontroller, you would be correct – and this scanner is yet another case in point.
Here is the code for our scanner:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/* PiZZa I2C scanner pizza-i2c-scan.ino SDA pin 3 (GPIO 8), SCL pin 5 (GPIO 9) For Raspberry Pi Zero (2) W with PiZZa DroneBot Workshop 2026 https://dronebotworkshop.com */ #include <Wire.h> void setup() { Serial.begin(115200); while (!Serial) {} Wire.begin(); Serial.println("I2C scanner starting..."); } void loop() { int found = 0; for (byte addr = 1; addr < 127; addr++) { Wire.beginTransmission(addr); if (Wire.endTransmission() == 0) { Serial.print("Device found at 0x"); if (addr < 16) Serial.print("0"); Serial.println(addr, HEX); found++; } } Serial.print(found); Serial.println(" device(s) found. Rescanning in 5s...\n"); delay(5000); } |
The sketch uses the Wire library, the built-in Arduino library that handles I2C.
In Setup, we start the serial monitor and initialize the bus with Wire.begin().
In the Loop, we declare an integer named “found” to count the devices we discover, initializing it to zero. We then step through addresses 1 to 127 (written in decimal here for simplicity, even though we normally express I2C addresses in hexadecimal).
For each address, we begin a transmission and then check the result of endTransmission(). A return value of zero means a device acknowledged at that address, so we print “Device found at” followed by the address in hexadecimal, and increment our counter.
After stepping through the entire address range, we print how many devices were found, delay five seconds, and scan all over again. That way, if new devices appear on the bus – or old ones are removed – we’ll know about it.
To give the scanner something to find, I added a small breadboard with an AHT20 temperature and humidity sensor, a common and inexpensive I2C device, and wired it to the I2C bus on the Raspberry Pi. Sure enough, the serial monitor reported a device found at hexadecimal 0x38, which is indeed the address of the AHT20. You can use pretty much any I2C device to test this, and if you have several devices on the bus in parallel, the scanner would pick up each of their addresses.
I2C support opens up a world of opportunities to add sensors, displays, and other peripherals to the Raspberry Pi using PiZZa – and, as noted earlier, it’s also the natural route to adding the analog inputs that the Pi lacks, using an inexpensive external ADC.

What About the HDMI and Camera Ports?
The Raspberry Pi Zero boards have a couple of interesting peripherals – an HDMI output and a camera port – and you might be wondering whether you can use them with PiZZa.
Unfortunately, at least at this stage, the answer is no – or at least, I haven’t found a way. If you connect an HDMI monitor, you will get an image, but what you’re seeing is the console of the underlying Zephyr real-time operating system, not something your sketch can draw to.
Likewise, I couldn’t find any way to access the camera port from within a PiZZa sketch. Perhaps you can figure it out, or perhaps a future release of PiZZa will open these up – this is a young and rapidly evolving project, after all, and camera access in particular would be a really great capability to have.
But there is one onboard peripheral you most definitely can get at – the microSD card. And that deserves a section all of its own.
Using the MicroSD Card
As I mentioned at the beginning of the article, PiZZa only requires a 2 GB microSD card – but you’ve probably installed at least a 16 GB card, which leaves an enormous amount of unused space. It would be very nice to put that space to work as storage for your projects, and the good news is that you can indeed do exactly that.
However, this is one area where PiZZa differs from the microcontrollers you’re used to, so let’s take a moment to understand how it works.
A Different Way of Working with Storage
With most microcontrollers, we access a microSD card using the familiar SD library. The SD library talks to the SPI bus, which in turn talks to a microSD card module.
That’s not how it works with PiZZa. Here, the microSD card is part of the real-time operating system itself – Zephyr mounts the card automatically at boot, and you access it much like you would access a drive in Microsoft Windows, using the Zephyr file system API.
Instead of including SD.h, we include the file-handling portion of the Zephyr library, and we refer to files on the card with a path that begins with the mount point “/SD:” – for example, “/SD:/pizzalog.txt”. If the drive-letter-with-a-colon format reminds you of MS-DOS or Windows, you’ve got the right idea.
Our demonstration sketch is a simple data logger. Every five seconds, it appends the current uptime (in milliseconds) to a log file on the microSD card, and then reads the entire file back and prints it to the serial monitor. As simple as that is, it demonstrates all the essential file operations (creating, opening, writing, reading, and closing) that you’ll need to use the microSD card in your own code.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
/* PiZZa SD logger with read-back pizza-sd-logger.ino SD card auto-mounts Writes uptime data then reads it back For Raspberry Pi Zero (2) W with PiZZa DroneBot Workshop 2026 https://dronebotworkshop.com */ #include <zephyr/fs/fs.h> const char* LOG_PATH = "/SD:/pizzalog.txt"; void setup() { Serial.begin(115200); while (!Serial) {} Serial.println("Zephyr fs logger with read-back ready"); } void loop() { // --- Write a new entry --- struct fs_file_t wfile; fs_file_t_init(&wfile); int rc = fs_open(&wfile, LOG_PATH, FS_O_CREATE | FS_O_APPEND | FS_O_WRITE); if (rc == 0) { char buf[64]; int n = snprintf(buf, sizeof(buf), "uptime_ms=%lu\n", (unsigned long)millis()); fs_write(&wfile, buf, n); fs_close(&wfile); Serial.print("Logged uptime: "); Serial.println(millis()); } else { Serial.print("fs_open (write) failed, rc="); Serial.println(rc); delay(5000); return; } // --- Read the whole file back --- struct fs_file_t rfile; fs_file_t_init(&rfile); rc = fs_open(&rfile, LOG_PATH, FS_O_READ); if (rc == 0) { Serial.println("---- log contents ----"); char rbuf[65]; ssize_t got; while ((got = fs_read(&rfile, rbuf, sizeof(rbuf) - 1)) > 0) { rbuf[got] = '\0'; Serial.print(rbuf); } Serial.println("---- end of log ----"); fs_close(&rfile); } else { Serial.print("fs_open (read) failed, rc="); Serial.println(rc); } delay(5000); } |
How the SD Logger Works
At the top of the sketch, we include zephyr/fs/fs.h – the Zephyr file system header – and define the path to our log file. I called the file pizzalog.txt, but you could call it anything you want, really. Note the “/SD:” prefix on the path, which tells Zephyr the file lives on the automatically mounted microSD card.
Setup is about as basic as it gets – it just starts the serial monitor.
The Loop is in two halves: a write, followed by a read-back.
Writing to the file. We start by declaring a file handle – a Zephyr fs_file_t structure – and initializing it. We then open the file, passing flags that tell Zephyr to create the file if it doesn’t exist, open it for writing, and append to the end rather than overwriting.
The open call returns a result code: anything other than zero means the open failed, and we print an error. If it’s zero, the file is open, and we’re in business. We build up a single line of text containing the current uptime in milliseconds, write it to the file, and then close the file.
That close is important – it’s the point at which the write is actually committed to the card. Finally, we print the logged uptime to the serial monitor.
Reading it back. The read-back follows the same pattern. We set up a second file structure, open the same file – this time for reading – and again check for a zero return code. We then declare a small character buffer and loop, reading the file a buffer-full at a time and printing each chunk to the serial monitor, until we’ve read the whole thing.
Then we close the file. Since our microcontroller keeps running and logging, the file grows a little every cycle, so each read-back is slightly longer than the last.
After the read, the sketch delays for five seconds and does the whole thing over again. So what you’ll observe on the serial monitor is each new uptime entry as it’s logged, followed by a complete listing of the log file – write, read back, repeat.
Running the SD Card Demo
I actually left this sketch running on my workbench for quite a while before capturing the results, which is why my log file grew rather large! Scrolling to the top of the serial monitor, you can see the pattern clearly: the sketch logs an uptime entry, then prints the log contents, and each time around, the log has grown by one line. With auto-scroll enabled, you can sit back and watch the activity in real time.

Being able to read and write the microSD card directly opens up a lot of possibilities. There’s a great deal of space on that card – you could use a 256 GB card if you really needed to, and even a 16 GB card will hold an awful lot of data.
Conclusion
You might be wondering whether you should use this. To be honest, I wouldn’t specifically run out and buy a Raspberry Pi Zero just to run PiZZa on it – at the current price of a Zero, you can get an excellent microcontroller like an ESP32 or a Pico that will suit most purposes just fine. But that’s not really the point of PiZZa. Here’s where I think it genuinely shines:
- Reviving your old boards. This, to my mind, is the real application. The original Raspberry Pi Zero W was released roughly a decade ago – and it’s not very likely you’ll be building any new microcomputer projects around one. But as a microcontroller, that same board is a powerhouse. If you have a drawer full of retired Zeros, PiZZa is a great way to put them back into service.
- Memory-hungry projects. With 512 MB of RAM, you can work with data structures, buffers, and datasets that would be unthinkable on a conventional microcontroller – think large lookup tables, audio buffers, or image data.
- Processing power. Even at its 600 MHz USB-powered clock and with a hardware floating-point unit, a PiZZa board will breeze through math-heavy tasks like signal processing and filtering that would bog down lesser chips.
- Big data logging. With gigabytes of directly accessible microSD storage, PiZZa makes a wonderful long-term data logger – pair it with some I2C sensors and let it run for months without ever worrying about filling up.
Keep in mind that PiZZa is a young and rapidly evolving project – by the time you read this, some of its current limitations may already have been addressed, so it’s worth keeping an eye on the GitHub repository for new releases.
Until next time, take care of yourselves, and enjoy your PiZZa!
Parts List
Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases.
Resources
- Code for this article – All of the sketches used in this article, in one handy ZIP file.
- PDF Version – A PDF version of the article (also in a ZIP file)
- PiZZa on GitHub – The PiZZa repository, including the Releases page with the downloadable images
- Zephyr Project – The home of the Zephyr real-time operating system.
- ArduinoCore-zephyr – Arduino’s Zephyr-based core
- Balena Etcher – Utility for burning image files to microSD cards
- Raspberry Pi Imager – The official Raspberry Pi imaging utility





It appears I2C is not working on the PI Zero W.