Skip to content

Instantly share code, notes, and snippets.

@jancumps
Last active April 28, 2025 17:32
Show Gist options
  • Select an option

  • Save jancumps/c66e8af42dc30ee6dfbdfc06aea496e1 to your computer and use it in GitHub Desktop.

Select an option

Save jancumps/c66e8af42dc30ee6dfbdfc06aea496e1 to your computer and use it in GitHub Desktop.
pio_stepper_lib example with 2 motors
#include "hardware/pio.h" // some constant definitions used
#include "pico/stdlib.h" // for demo section (printf)
#include <array> // for demo section (commands container)
#include <iterator> // for demo section (commands container)
#include <span> // for demo section (commands container)
#include <algorithm> // for_each
#include <numeric> // for demo section (accumulate)
import stepper; // PIO stepper lib
// TODO: adapt to your board design
// TODO driver IC dependent. see dummy_driver.cpp
import dummy_driver;
using driver_t = dummy_driver;
std::array<driver_t, 2> drivers;
// #define MICROSTEP_8
#undef MICROSTEP_8
#ifdef MICROSTEP_8
const float clock_divider = 3; // works well for 8 microsteps
const uint microstep_x = 8;
#else
const float clock_divider = 16; // works well for no microsteps
const uint microstep_x = 1;
#endif
// TODO: adapt to your board design
const uint motor1_dir = 4U; // implies that step is gpio 5
const uint motor2_dir = 6U; // implies that step is gpio 7
using motor_t = stepper::stepper_callback_controller;
std::array<motor_t, 2> motors {{{pio0, 0}, {pio0, 1}}};
std::array<volatile size_t, motors.size()> commands_per_motor;
void on_complete(const motor_t &stepper) {
size_t index = 0U;
for (auto &m: motors) {
if (&m == &stepper) {
commands_per_motor[index] = commands_per_motor[index] - 1;
printf("motor %d executed command\n", index + 1);
break;
}
index++;
}
}
void init_pio() {
// program the pio used for the motors
// do this only once per used pio
motor_t::pio_program(pio0); // not needed if all sms run on pio1
motor_t::pio_program(pio1); // not needed if all sms run on pio0
// individual settings
motors[0].pio_init(motor1_dir, clock_divider);
motors[1].pio_init(motor2_dir, clock_divider);
// common settings
// initialise and enable the motor state machine
for (auto &m: motors) {
m.register_pio_interrupt(0, true);
m.enable(true);
// and the notification, for demo purpose
m.on_complete_callback(on_complete);
}
}
void init_everything() {
stdio_init_all();
// TODO driver IC dependent. see dummy_driver.cpp
for (auto &d: drivers) {
d.init();
}
init_pio();
}
// stepper demo: execute a series of commands ================================
// struct to hold command and motor
struct motor_command {
stepper::command cmd;
size_t motor;
motor_command(stepper::command cmd, size_t motor) : cmd(cmd), motor(motor) {}
};
using commands_t = std::span<motor_command>;
void run_commands(const commands_t& cmd, uint delay0, uint delay1) {
motors[0].set_delay(delay0);
motors[1].set_delay(delay1);
for (auto& count: commands_per_motor) { count = 0; }
for (auto& c : cmd) {
// increment commands expected for the motor
commands_per_motor[c.motor] = commands_per_motor[c.motor] + 1;
}
for (auto& c : cmd) {
if (commands_per_motor[c.motor] > 0U) {
motors[c.motor].take_steps(c.cmd);
}
}
size_t todo = 0U;
do {
todo = std::accumulate(commands_per_motor.begin(),commands_per_motor.end(),0);
} while (todo > 0U);
sleep_ms(100); // pause for demo purpose
}
void full_demo(const commands_t & cmd) {
// stepper driver IC should wake up here, if not yet
// TODO driver IC dependent. see dummy_driver.cpp
for (auto &d: drivers) {
d.enable(true);
}
// sleep_ms(1); // some drivers require time between enable and step
// demo 1 :run all in sequence
for (auto &c: cmd) {
std::array<motor_command,1> single_cmd {{c}};
run_commands(single_cmd, 9000, 7000);
}
// demo 2: run all in parallel
run_commands(cmd, 7000, 4300);
// TODO: stepper driver IC can go back to sleep here
// TODO driver IC dependent. see dummy_driver.cpp
for (auto &d: drivers) {
d.enable(false);
}
}
int main() {
init_everything();
std::array<motor_command, 7> cmd{{
{ {200 * microstep_x, true}, 0},
{ {200 * microstep_x, true}, 1},
{ {300 * microstep_x, false}, 1},
{ {200 * microstep_x, false}, 1},
{ {10 * microstep_x, true}, 1},
{ {200 * microstep_x, false}, 1},
{ {20 * microstep_x, true}, 0}
}};
while (true) {
full_demo(cmd);
// sleep_ms(200); // pause for demo purpose
}
return 0;
}
cmake_minimum_required(VERSION 3.28)
# set(PICO_BOARD pico_w) # for PICOW
set(PICO_BOARD pico)
# copy this file in project root directory. Get a copy from root of Pico C SDK you are using
include(pico_sdk_import.cmake)
project(pio_stepper_2motors C CXX ASM)
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmodules-ts -fcommon -fno-rtti -fno-exceptions")
pico_sdk_init()
include(FetchContent)
FetchContent_Declare(stepper
GIT_REPOSITORY "https://github.com/jancumps/pio_stepper_lib.git"
GIT_TAG "origin/main"
)
FetchContent_MakeAvailable(stepper)
# start DRIVER IC specific
FetchContent_Declare(stepper_driver
GIT_REPOSITORY "https://github.com/jancumps/stepper_driver_lib.git"
GIT_TAG "origin/main"
)
FetchContent_MakeAvailable(stepper_driver)
add_library(driver)
target_sources(driver
PUBLIC
FILE_SET cxx_modules TYPE CXX_MODULES FILES
${stepper_driver_SOURCE_DIR}/source/stepper_driver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dummy_driver.cpp
)
# end DRIVER IC specific
add_executable(${CMAKE_PROJECT_NAME})
target_sources(${CMAKE_PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/2_motor_pio_stepper_example.cpp
# PICOW ${CMAKE_CURRENT_SOURCE_DIR}/shabaz.cpp
)
target_sources(${CMAKE_PROJECT_NAME}
PUBLIC
FILE_SET cxx_modules TYPE CXX_MODULES FILES
)
target_link_libraries( ${CMAKE_PROJECT_NAME}
pico_stdlib
hardware_gpio
stepper
# pico_cyw43_arch_none # for PICO W
# start DRIVER IC specific
driver
# end DRIVER IC specific
)
pico_add_extra_outputs(${CMAKE_PROJECT_NAME} )
module;
import stepper_driver;
export module dummy_driver;
/*
for demo purposes
replace with your own stepper driver IC code
or create your own driver IC class derived from stepper_driver::stepper_driver
for smooth integration
*/
export class dummy_driver: public stepper_driver::stepper_driver {
public:
virtual bool init() override { return true; }
virtual bool microsteps(unsigned int microsteps) override { return true; }
virtual void enable(bool enable) override {}
};
@jancumps
Copy link
Author

STEP pins capture of parallel and in sequential demo:

image

documentation: Raspberry PIO stepper library documentation - 3: control multiple motors with 1 or more PIOs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment