CNC6040 router project – spindle speed linearisation

A known issue with the common CNC6040 router and similar devices is very poor calibration / linearity of the spindle motor response to gcode Sx commands.

Above is the system block diagram. The grbl_ESP32 gcode interpeter processes a gcode S (speed) command, converting it to a variable duty cycle PWM waveform on parallel port pin 1.

Above, the PWM wave at low duty cycle (period is 200µs).

Diagnosing the problem

The first step in that path is quite linear, the graph above shows the relationship between measured PWM duty cycle and requested speed (Sx).

Above, the end to end response is quite non-linear.

A possible solution

A simple method of improving the response is to segment the measured curve into a number of segments where a straight line approximation will be sufficiently accurate.

Above is the plot from about 5000-24000rpm, and a curve fit (equation shown).

The same process was followed to obtain in all three segments.


First step was to make a minimal modification to the existing code to pull in a customer user calculation if such a file exists.

This is the change to the existing spindle_control_cpp to perform a conditional include of the user file spindle_curve.h (if it exists) at a key point in the calculation of the PWM duty cycle.

    // Compute intermediate PWM value with linear spindle speed model.
    // NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight.
    sys.spindle_speed = rpm;
#if defined __has_include
  #if __has_include("spindle_curve.h")
    #include "spindle_curve.h"
    pwm_value = floor((rpm-settings.rpm_min)*pwm_gradient) + SPINDLE_PWM_MIN_VALUE;

The next step is a user function that implements compensation of the non linear response as described above.

//optional user customisation of spindle PWM curve
//needs consistent settings.rpm_min and settings.rpm_max
//use block to make vars private
//    static long spdcuri[]={0,24000,1000000};
//    static float spdcurm[]={0,1,0};
//    static long spdcurb[]={0,0,24000};
    static long spdcuri[]={1000,3360,5760,24000,1000000};
    static float spdcurm[]={0,0.1412,0.325,0.8872
    static float spdcurb[]={0,1013,382,-2592,24000};
    int i;


The code changes were implemented and tested. They work quite well, though at very low rpm the VFD drive calibration appears a bit unstable. Notwithstanding that, calibration is quite good from about 1500-24000rpm.

Note that the calibration coefficients used here are specific to the instance, and appropriate coefficients for other instances must be established by measurement as described.