element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Raspberry Pi
  • Products
  • More
Raspberry Pi
Blog modern C++ on a Pico: use C++ Standard Library algorithms - embedded friendly
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Raspberry Pi to participate - click to join for free!
Featured Articles
Announcing Pi
Technical Specifications
Raspberry Pi FAQs
Win a Pi
GPIO Pinout
Raspberry Pi Wishlist
Comparison Chart
Quiz
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 7 Nov 2025 6:43 PM Date Created
  • Views 119 views
  • Likes 3 likes
  • Comments 3 comments
Related
Recommended
  • Modern C++
  • pico
  • c++26
  • stl
  • PIO
  • object oriented
  • c++

modern C++ on a Pico: use C++ Standard Library algorithms - embedded friendly

Jan Cumps
Jan Cumps
7 Nov 2025
modern C++ on a Pico: use C++ Standard Library algorithms - embedded friendly

C++ has embedded friendly mechanisms that can help to structure your firmware. While keeping the design resource-lean.
In this post, I use classes,  C++ stl containers and algorithms to manage close-to-the-hardware resources with objects. With runtime costs that are similar to a good C design.

example context:

  • you want to use PIO state machines (example:  you have a set of stepper motors that you want to control with PIO state machines)
  • you only want to program the PIOs and state machines that have a stepper motor assigned to it
  • you are a C++ fanboy and want to handle the motors with objects
I'm using stepper motors and Pico PIO co-controllers as real world examples. 
The mechanism works for many real world designs where you want to process, filter, aggregate ...
(I also used these concepts for a GPS data parser)

In this little post, I use STL algorithms that take care that:

  • a PIO is only programmed if at least one of its state machines serves a motor, and
  • a state machine is only initialised if it serves a motor.

pseudocode:

if a PIO is used

  program it

  initialise its state machines that you are using

post condition 1: PIOs and state machines that are not used, are not touched
post condition 2: PIOs that are used are programmed
post condition 3: state machines that are used are initialised

declare a motor handler class

In this post, the class that can handle a motor is called dummy_handler, because this is demo code. For a real class that can handle a stepper motor, check pio_stepper_lib.

struct dummy_handler {
    dummy_handler(PIO pio, uint sm) : pio(pio), sm(sm) {}
    PIO pio;
    uint sm;
};

I tried to make this as small as possible. Just enough to be usable in this example.

declare a container with stepper motor handlers

In my example, I have 6 motors. 4 run on the state machines of the first PIO. 2 others run on the second PIO.

image

I use an array, because that's a good choice for an embedded application container. 

std::array<dummy_handler, 6> handlers {{
      {pio0, 0}, {pio0, 1}, {pio0, 2}, {pio0, 3}
    , {pio1, 0}, {pio1, 1}
}};

program the PIOs

I only want to load my PIO code in the PIOs that have at least 1 motor assigned to them. In this example I use PIO 1 and 2. If this runs on a Pico2, I don't want to program PIO3, because it doesn't control any stepper motor.

any_of

I use the stl any_if algorithm to check, for each PIO, if my container has at least one motor for that PIO. If yes, I program it.

    for (uint u = 0u; u < NUM_PIOS; u++) {
        if (std::ranges::any_of(handlers, [u](auto& h){ return h.pio == PIO_INSTANCE(u); })) {
            uint offset  = pio_add_program(PIO_INSTANCE(u), &run_program); 
            
            // more code later :)
        }        
    } 

This code loops over the available PIOs on the Pico. only if it finds at least one motor in the array that runs on that PIO, it programs it.

for_each with filter

Then, it'll initiate each used state machine of that PIO:

            for(auto &h : handlers | std::views::filter([u](const auto& h){ return h.pio == PIO_INSTANCE(u);})) {
                run_program_init(h.pio, h.sm, offset);

pio_add_program() and run_program_init() are functions that any PIO developer will recognise. They are part of the Pico C SDK.

The for will only invoke code on the items in the container that match the current PIO in our loop. The filter takes care that the loop only returns items that match that condition. For Linux users: this is similar to filtering pipes.

everything together

This is all of the code. It takes care that only the used PIOs are programmed. And that only the used state machines of those PIOs are initialised:

    // program each PIO, and initialise each sm - only if they are used
    for (uint u = 0u; u < NUM_PIOS; u++) {
        if (std::ranges::any_of(handlers, [u](auto& h){ return h.pio == PIO_INSTANCE(u); })) {
            uint offset  = pio_add_program(PIO_INSTANCE(u), &run_program); 
            for(auto &h : handlers | std::views::filter([u](const auto& h){ return h.pio == PIO_INSTANCE(u);})) {
                run_program_init(h.pio, h.sm, offset);
            }
        }        
    } 

This may look odd to a C++98 user, or if you are used to C. It compiles to efficient code though.
There are 4 learning ramps to climb:
  • the OO ramp. The motor handlers are objects. This will be common to any C++ user: learn classes
  • the stl container ramp: all motors handlers reside in an stl array:  learn stl containers
  • the stl array is defined via a template. It is specialised (and perfectly sized) to manage motor handler objects: learn templates
  • the stl algorithms. any_if and filter take care that only those objects inside the container that match your condition (predicate) are handled: learn modern C++

I put embedded friendly in the title of this post, because this is resource light. If you would write similar functionality in C, your logic would have a comparable cost.

Thank you for reading.

  • Sign in to reply
  • Jan Cumps
    Jan Cumps 14 hours ago in reply to Jan Cumps

    and a little more condensed:

        // program each PIO, and initialise each sm - only if they are used
        for (uint u = 0u; u < NUM_PIOS; u++) {
            if (std::ranges::any_of(handlers, [u](auto& h){ return h.pio == PIO_INSTANCE(u); })) { // is this PIO used?
                printf("programming PIO %u\n", u);
                uint offset  = pio_add_program(PIO_INSTANCE(u), &run_program); 
                for(auto &h : handlers | std::views::filter([u](const auto& h){ return h.pio == PIO_INSTANCE(u);})) { // is this sm used?
                    printf("setting up sm %u on PIO %u\n", h.sm, PIO_NUM(h.pio));
                    run_program_init(h.pio, h.sm, offset);
                    pio_irq_manager_t::register_interrupt(IRQ_CHAN, h.pio, h.sm, true);
                    pio_irq_manager_t::register_handler(h.pio, h.sm, &h, true);
                }
            }        
        }
        
        // enable and start each sm
        for (auto &h: handlers) {
            pio_run_forever(h.pio, h.sm, 1);
        }

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 15 hours ago

    the mechanism becomes more interesting when you're doing more with these motors.
    In my firmware, I want that each of those motors throws an interrupt when it has executed a command (a command is a number of steps, clockwise or counter-clockwise). 

    Registering that interrupt becomes simple, because I have the infrastructure set up already:

        for (auto &h: handlers) {
            pio_irq_manager_t::register_interrupt(IRQ_CHAN, h.pio, h.sm, true);
        }
        

    This is true for any logic that applies to more than one motor.

    • disable all motors: use a for loop
    • slow down all motors on PIO 0: use a loop with filter predicate
    • stop all motors except 1: same filter mechanism ...

    Here is my full code, where I program PIOs, initialise state machines, enable interrupt, register interrupt handler and start the state machines:

        // program each PIO, and initialise each sm - only if they are used
        for (uint u = 0u; u < NUM_PIOS; u++) {
            if (std::ranges::any_of(handlers, [u](auto& h){ return h.pio == PIO_INSTANCE(u); })) {
                printf("PIO %u programmed\n", u);
                uint offset  = pio_add_program(PIO_INSTANCE(u), &run_program); 
                for(auto &h : handlers | std::views::filter([u](const auto& h){ return h.pio == PIO_INSTANCE(u);})) {
                    run_program_init(h.pio, h.sm, offset);
                    printf("SM %u on PIO %u initialised\n", h.sm, PIO_NUM(h.pio));
                }
            }        
        }
        
        // tell each sm that it has to forward interrupt on channel 0
        for (auto &h: handlers) {
            pio_irq_manager_t::register_interrupt(IRQ_CHAN, h.pio, h.sm, true);
        }
        
        for (auto &h: handlers) {
            pio_irq_manager_t::register_handler(h.pio, h.sm, &h, true);
        }
        
        // enable and start each sm
        for (auto &h: handlers) {
            pio_run_forever(h.pio, h.sm, 1);
        }
        

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 16 hours ago

    if you prefer C, or you know exactly what motors you'll connect to which PIO state machine: this code is doing the same:

     offset = pio_add_program(pio0, &run_program);
    run_program_init(pio0, 0, offset);
    run_program_init(pio0, 1, offset);
    run_program_init(pio0, 2, offset);
    run_program_init(pio0, 3, offset);
    offset = pio_add_program(pio1, &run_program);
    run_program_init(pio1, 0, offset);
    run_program_init(pio1, 1, offset);


    /* these Pico PIO resources aren't used, so not programmed / initiated:
    run_program_init(pio1, 2, offset);
    run_program_init(pio1, 3, offset);
    #if (NUM_PIOS > 2) // pico 2
    offset = pio_add_program(pio2, &run_program);
    run_program_init(pio2, 0, offset);
    run_program_init(pio2, 1, offset);
    run_program_init(pio2, 2, offset);
    run_program_init(pio2, 3, offset);
    #endif
    */

    In this case, it's the programmer that drives the decisions, not the data set ...

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube