In C++ it's common to stream data. You write to a file using the << operator. You read with >>.
example:
cout << "hello, world! << endl;
In this blog I'm making my own minimal in, out and in-out stream class for Pico UART.
I put embedded friendly in the title because this is a resource-friendly exercise. Can be used on the smallest controllers.
How would your code look like?
You 'd stream data to UART with <<
uartiostream u; u << "hello element14!\n" ; and stream from UART with >>
std::string s; u >> s;An example that first streams a constant string. Then echos everything you type in a serial monitor:
#include <string>
int main() {
// ... usual Pico UART enable
// c++ stream example
uartiostream u;
u << "hello element14!\n" ;
while (true) {
std::string s;
u >> s;
u << s;
}
}
Pico UART Stream classes: in, out and io

The code for the stream classes is surprisingly simple.
class uartostream {
public:
uartostream() {}
uartostream& operator << (const char* msg) {
uart_puts(UART_ID, msg);
return *this;
}
uartostream& operator << (const std::string& msg) {
uart_puts(UART_ID, msg.c_str());
return *this;
}
};
class uartistream {
public:
uartistream() {}
uartistream& operator >> (std::string& msg) { // blocking
char c = 0;
do {
c = uart_getc(UART_ID);
if (c != 255) {
msg += c;
}
} while (c != '\n');
return *this;
}
};
class uartiostream: public uartistream, public uartostream {
};
These classes are small in code size. And small in runtime resource use. Using them is similar for the Pico as calling the API uart_puts() and uart_getc() functions.
In essence: 30 lines of source code to get the streaming abstraction. And no runtime (memory, clock ticks, code size) overhead.
Example, with a few of the use cases mentioned above:
note: for the filter part, you have to add this to your cmake file:
set(CMAKE_CXX_STANDARD 26)
#include "hardware/gpio.h"
#include "hardware/uart.h"
#include <algorithm>
#include <array>
#include <ranges>
#include <string>
// UART defines
#define UART_ID uart0
#define BAUD_RATE 115200
#define UART_TX_PIN 0
#define UART_RX_PIN 1
class uartostream {
public:
uartostream(uart_inst_t *uart) : uart(uart) {}
uartostream& operator << (const char* msg) {
// write to UART
uart_puts(uart, msg);
return *this;
}
uartostream& operator << (const std::string& msg) {
uart_puts(uart, msg.c_str());
return *this;
}
private:
uart_inst_t * uart;
};
class uartistream {
public:
uartistream(uart_inst_t *uart) : uart(uart) {}
uartistream& operator >> (std::string& msg) { // blocking
char c = 0;
// msg.clear(); no. If the string has info, add to it.
do {
c = uart_getc(uart);
if (c != 255) {
msg += c;
}
} while (c != '\n');
return *this;
}
private:
uart_inst_t * uart;
};
class uartiostream: public uartistream, public uartostream {
public:
uartiostream(uart_inst_t *uart) : uartistream(uart), uartostream(uart) {}
};
class dog {
public:
dog (const std::string& name) : name(name) {};
operator std::string() const {
return "dog: " + name;
}
private:
const std::string name;
};
class cat {
public:
cat (const std::string& name) : name(name) {};
operator std::string() const {
return "cat: " + name;
}
private:
const std::string name;
};
struct customer {
customer (const std::string& name, int credit) : name(name), credit(credit) {}
const std::string name;
int credit;
operator std::string() const {
return name;
}
};
uart_inst_t * pico_init_uart() {
uart_init(UART_ID, BAUD_RATE);
gpio_set_function(UART_TX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_TX_PIN));
gpio_set_function(UART_RX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_RX_PIN));
return UART_ID;
}
int main() {
uartiostream u(pico_init_uart());
u << "hello element14!\n" ;
dog d("odie");
cat c("garfield");
u << d << "\n" << c << "\n" ;
std::array<customer, 3> customers {{{"joe", 3}, {"jane", 500}, {"averall", -8}}};
u << "insolvent customers:" << "\n";
for(const auto &c : customers | std::views::filter([](const auto& c){ return c.credit < 0;})) {
u << c << "\n";
}
u << "rich customers:" << "\n";
for(const auto &c : customers | std::views::filter([](const auto& c){ return c.credit > 100;})) {
u << c << "\n";
}
u << "full customer portfolio:" << "\n";
std::for_each(customers.begin(), customers.end(),[&u](const auto& c) {
u << c << "\n";
});
while (true) {
std::string s;
u >> s;
u << s;
}
}
Thoughts?
demo VSCode project: uart_stream_20251116.zip







