### GRBL Custom RPM To PWM Mapping (Custom CNC Part 5)

If you are just joining us,

- Part 1: Custom Built CNC, an Introduction
- Part 2: Building the Structure, Folding CNC
- Part 3: CNC Electronics
- Part 4: Working with GRBL

### The Problem:

My router has two different attachments, a wood router and a laser burner. They both need to be controlled using the same Mega and I want different RPM input to PWM output mappings. Secondly, my requested RPM doesn’t ever seem to match the results. I ask for 6,000 rpm and I get 5,000. I ask for 13,000 and I get 10,000. Its a problem, so lets fix it.

### Handling the Router Map

My CNC router has a Hitachi wood router that has had a SuperPID modification made to it. The SuperPID is a router speed controller that allows the router to get down to 5,000 rpm and up to 30,000 rpm. It a pretty fancy piece of tech and worlds above any of the traditional router speed controllers you can buy. You can read all about it at: http://superpid.com/

In my configuration the Super PID takes a variable input (0-5VDC) to control the routers speed. I produce this input from the Arduino MEGA’s PWM output, which simulates a variable voltage. The Arduino MEGA controls this voltage by setting its PWM value from 0 to 1024 which produces a 0-5VDC output.

The problem with this scheme is that my router’s voltage to speed mapping is not direct. I set GRBL up to have a Min RPM of 0 ($31=0) and a Max RPM of 1024 ($30=1024). Then in an increment of 64 I set the router speed from 0 to 1024. At each speed setting I recorded the resulting RPM from the SuperPID Tach readout. Here are my results:

GRBL has a non Linear piece wise spindle mapping option in its config but I was never able to get satisfactory results. After playing with the built in function I decided to do it myself to fit my exact situation. At first I wrote code that took two arrays, one of RPM and one of the corresponding PWM to produce that RPM. However, once looking at my data above I realized that my RPM is actually really linear with the exception of PWM values between 0 and 256. This is because my router can’t go slower than 5,000 rpm. This indicates that I really just need an offset. I decided to use a linear curve fit to adjust my data. using an equation of the form Y = MX + B would result in one floating point multiplication and one addition. This should be pretty quick and shouldn’t result in any performance decrease. To find this equation I went to excel. First I plotted the desired RPM by the required PWM, which is just the inverse of the above plot:

If we did a curve fit now, we would have a lot of bias near the low RPM and it would result in lower than expected RPMs (Trust me, I tried). To fix this, before doing a curve fit you need to take out your data hammer and clean the data. Remove all RPM 5000 or below except the one with the highest PWM. Then produce a linear curve fit using excel and display the equation:

The equation above (PWM=0.0309*RPM+85.606) will produce the orange line (PLEASE REMEMBER, your map will be different). Since the router can’t go slower than 5,000 rpm any PWM value given by the above equation for rpm less than 5,000 would still result in 5,000 rpm. The exception to this is that GRBL can be configured that with an RPM input of 0, the PWM output is automatically driven to 0. This is how I have it configured. To use this equation to map our desired RPM to PWM you will need to open the Spindle.c file in the GRBL source. Then, find the function:

`uint16_t spindle_compute_pwm_value(float rpm) `

scroll to the end of the code and comment out the line:

`pwm_value = floor((rpm-settings.rpm_min)*pwm_gradient) + SPINDLE_PWM_MIN_VALUE;`

and add below it the following:

`pwm_value = (uint16_t)(0.0309f*rpm+85.606f);`

With that, I uploaded the code to the Mega and gave it a shot. I have never had such a good map. I ask for 6,000 rpm and I got 6,000 rpm +/- 50rpm. The same thing happened all of the way up to 30,000rpm. Success!!! (Please take note, I make this sound like I got it the first try. I didn’t, it actually took me a few false starts before coming to this solution)

#### Handling the Laser

A while back I purchased a little blue burning laser that was very similar to this one from Amazon:

I will go into details of finding a decent laser and installing it on a different post. In this post I want to talk about controlling it. Just like the superPID above, my laser takes a 0-5VDC variable input to control the power level. I have no way of measuring the power level out of the laser vs input voltage so I assumed it was linear (Bad assumption, it isn’t) and will have to just learn what power levels give me the results I want. I felt the easiest map to maintain was a 0% (0 PWM) to 100% (1024 PWM). This way, I would command a spindle speed (S) of 0 to 100 for 0% to 100%. The equation to do this is pretty simple, its PWM = 10.24f * RPM. In code this looked like:

```
if(rpm <= 0)
{
rpm = 0;
}
if(rpm > 100)
{
rpm = 100;
}
sys.spindle_speed = rpm;
pwm_value = (uint16_t)(10.24f * rpm);
```

To make it so that this code is only run while I am in Laser mode (GRBL Setting $32 = 1) and run my other router mapping when I wasn’t ($32 = 0) I would need a logical branch. To test if I am in Laser mode, I used the following if statement, which returns true if we are:

`if (settings.flags & BITFLAG_LASER_MODE)`

Putting it all together, the resulting function for converting rpm to pwm looked like this:

```
uint16_t spindle_compute_pwm_value(float rpm) // Mega2560 PWM register is 16-bit.
{
uint16_t pwm_value;
rpm *= (0.010*sys.spindle_speed_ovr); // Scale by spindle speed override value.
if (settings.flags & BITFLAG_LASER_MODE) {
if(rpm <= 0)
{
rpm = 0;
}
if(rpm > 100)
{
rpm = 100;
}
sys.spindle_speed = rpm;
pwm_value = (uint16_t)(10.24f * rpm);
}
else
{
// Calculate PWM register value based on rpm max/min settings and programmed rpm.
if ((settings.rpm_min >= settings.rpm_max) || (rpm >= settings.rpm_max)) {
// No PWM range possible. Set simple on/off spindle control pin state.
sys.spindle_speed = settings.rpm_max;
pwm_value = SPINDLE_PWM_MAX_VALUE;
} else if (rpm <= settings.rpm_min) {
if (rpm == 0.0) { // S0 disables spindle
sys.spindle_speed = 0.0;
pwm_value = SPINDLE_PWM_OFF_VALUE;
} else { // Set minimum PWM output
sys.spindle_speed = settings.rpm_min;
pwm_value = SPINDLE_PWM_MIN_VALUE;
}
} else {
// 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;
pwm_value = (uint16_t)(0.0307f*(float)rpm+88.827f);
//pwm_value = floor((rpm-settings.rpm_min)*pwm_gradient) + SPINDLE_PWM_MIN_VALUE;
}
}
```

I uploaded these changes and gave the laser a test run. Note that GRBL keeps you from turning the laser on when in feedhold. If you try to set an rpm value (S=anything) the laser will not turn on. You will need to have a small toolpath to test the functionality. I quickly generated up a 4x4in profile path and ran that to test the laser mode.

### The result

In the end it all actually worked. When GRBL is in router mode ($32=0) the spindle_compute_pwm_value function maps to our linear curve fit. When it is in laser mode ($32=1) it maps with our simple 0 to 100% scale. I am now able to run both tool heads exactly how I want, using the exact input model I want and getting the exact output I want. I would call today a win.