OpenPnP w. sFOC


Do you think we can include txt files with explanations taken from eg, Protocol - Klipper documentation (

And reference them in the promt file?

Maybe! I’m not sure.
To be honest, I only had good results with AI generated code in small samples. I don’t think it has the memory or function to look at wide codebase and produce any workable result.

I think you are right, hmm… still early I guess. :laughing:

This was made using gpt-engineer:

#include "CircularBuffer.h"

CircularBuffer::CircularBuffer(int size) : size(size), head(0), tail(0) {
    buffer = new char*[size];

void CircularBuffer::push(char* item) {
    buffer[head] = item;
    head = (head + 1) % size;

char* CircularBuffer::pop() {
    if (isEmpty()) {
        return nullptr;
    char* item = buffer[tail];
    tail = (tail + 1) % size;
    return item;

bool CircularBuffer::isEmpty() {
    return head == tail;

class CircularBuffer {
    CircularBuffer(int size);
    void push(char* item);
    char* pop();
    bool isEmpty();

    char** buffer;
    int head;
    int tail;
    int size;


#include "commander.h"
#include "TrapezoidalPlanner.h"

Commander::Commander() : buffer(256) {}

void Commander::processCommand(char* command) {
    if (strncmp(command, "M400", 4) == 0) {
    } else {

void Commander::checkForNewCommands() {
    if (Serial.available()) {
        char command[256];
        Serial.readBytesUntil('\n', command, 256);

void Commander::executeCommand(char* command) {

void Commander::executeM400() {

Pretty good?

Hmm… I see, there is already a flag in the TrapezoidalPlanner:

 isTrajectoryExecuting = true;

I guess the controller should react with OK when it receives a regular G0/G1 command? And suspend OKs if getting a M400 command, until the move is complete ?

Seriously, just try the step-dir interface with the feed forward branch. It’s very effective.

Also agree on only using AI generated code in small, independently testable, chunks.

That would limit all further development to steps only. OpenPnP has some pretty advanced motion profile options. I think the goal is to keep that door open.

So G0 for no tool engagement moves, G1 for cutting speeds/feeds etc.

The motion planner part is usually separate from the motion execution. So if OpenPNP already does the planner, then you just need to make sFOC follow the planned motion. You shouldn’t need the trapezoidal velocity planner, in that case.

Getting the control loop to follow the planned route is the tricky part, especially if you’ve got multiple axes.

Yes, OpenPnP handles all sub_driver setup and seperation when sending out gcodes, position, speeds, M_commands.

The planner is dynamic, so if it receives a new command while moving it will adjust the parameters, using what ever new trapezoidal profile, unless if it is a M400 case.

I guess I was confused, I was thinking that OpenPNP also had some firmware that interpreted the G-code, and translated it into steps or position vs time arrays. G-code interpretation is a level above what I’ve been considering.

I think there is certainly room for development using the advanced stuff.

Motion Controller Firmwares · openpnp/openpnp Wiki (

Only the GcodeAsyncDriver allows you to use the Simulated3rdOrderControl mode (see below), where a high volume of commands must be sent to the controller at great speed.

Ok, we need this in the doTrapezoidalPlannerCommand();

In case the M_command is part of a message with G as the first letter.

 case 'M':
        commandSrt = commandSrt.substring(1);


Have to convert to char;

commandSrt.toCharArray(M_buf, 10);

Edit Edit: hmm

G00 X0 Y0 Z0   ; Rapid positioning to the starting point
G01 X10 Y10 Z5 ; Linear move to a specific point
M03            ; Turn on the spindle
G02 X20 Y20 I5 J0 ; Clockwise circular interpolation
M05            ; Turn off the spindle
As you can see, the G-codes are used for motion control (G00, G01, G02), while the M-codes are used to control the spindle (M03, M05). They are separate commands used in a coordinated manner to achieve the desired machining operations.

CircularBuffer buffer = CircularBuffer(100);

is compiling…

Instead of doing doTrapezoidalPlannerCommand();

Im doing :laughing:

void TrapezoidalPlanner::doGcommandBuffer(char *gCodeCommand){



So all move commands should be buffered.

M commands will just be executed.

We then have to check if there is commands in the buffer and set up some rules for M400 commands


Could this work?

void TrapezoidalPlanner::runPlannerOnTick(){
    // This should get entered 100 times each second (100Hz)
    if ((unsigned long)(millis() - plannerTimeStap) > plannerPeriod){
        plannerTimeStap = millis();
        // see if we are in a move or not
        if (isTrajectoryExecuting){
            // we are in a move, let's calc the next position
            float timeSinceStartingTrajectoryInSeconds = (millis() - plannerStartingMovementTimeStamp) / 1000.0f;
            motor->target = Y_;

            // see if we are done with our move
            if (timeSinceStartingTrajectoryInSeconds >= Tf_){
                // we are done with move
                // motor.monitor_downsample = 0; // disable monitor
                #ifdef __debug
                    Serial.println("Done with move");
                isTrajectoryExecuting = false;
                 if (m400_flag){
                 m400_flag = false;


             if (!buffer.isEmpty() && !m400_flag){
                   char* cmd = buffer.pop();

Maybe gCode feedrate translates better to constant velocity ?

Maybe we should do it stepwize,

This should be quite easy to setup and test.

The NullMotionPlanner will execute moves one by one. It will wait for each move to complete, before allowing OpenPnP to go on. The machine will always move from complete still-stand in a straight line to complete still-stand, again. This is a very simple and robust model that is equally simple to understand. It is therefore still a valid and attractive choice for those who want to keep it simple .

Maybe its actually easier to do the profiling in the TrapezoidalPlanner, like acceleration dependent on speed etc.

We could just scale the acceleration to resemble a s-curve from the set cruise speed. Like divide the ramp into some chunks or write a function to handle it smoothly.

Awesome, we now have jogging control of stepper from OpenPnP.

Just basic trapezoidal moves with M400 commands.

Had to play some around with the commands and parsing/converting from mm to rad and back.

When I get the machine up and running, we can better assess the driver precision and fine tune things.

Im seeing some vibration when at standstill. Its like the motor is trying to hold the angle too hard. Sometimes I can hear it bussing a tiny amount when standing still. Other times its silent.

M Command :  114
GGode command: M114

M Command :  114
GGode command: M114

M Command :  114
GGode command: M114

M Command :  114

This is converted to mm for OpenPnP. I’m pretty sure it behaves different under load.

If you have to write all the M-codes and G-codes yourself, wouldn’t it be easier to rewrite the sFOC commander, too?
Replace it with a Gcode interpreter.

Or even better, use another open source 3D printer firmware which has hundreds and thousands G/M codes included and use the step/dir listener?

Why invent the wheel new? Is it a personal journey for you or do you expect making it better than anybody else?

OpenPNP only use a few for basic motion. My goal is to use a regular stepper driver for the Z axis and rotation etc.

The advantage of having a separate controller board for the Z, C (rotation), lighting (for cam) etc. is that you can have the controller placed on the head. This in turn means no wires for a lot of steppers to run in the belts.