Today its possible to replace a PC computer/Mach3 software, running a CNC milling machine, with an ESP32 microcontroller running the FluidNC software. Brian combined this with a Raspberry Pi Pico, which handles all the pendant functions.
When I retired a dozen years ago, I expected to lose access to the machine shop at work. While I had a fine carpentry shop at home, I didn’t have much in the way of metal milling equipment, and all my projects involved enclosures, generally aluminum ones. Prior to retiring, I built a custom-designed milling machine, shown in Figure 1. It involved a significant amount of mechanical construction, so when it came to adding the motors and drive electronics, I took the easy way out. I bought a Geckodrive G540 four-axis stepper controller, which is an excellent product designed to work with a PC computer running Mach 3 CNC software. Figure 2 shows the Geckodrive G540 controller.

In the foreground is the new CNC controller I build using an ESP32 running FluidNC firmware. The CNC mill is one I designed and built about 13 years ago.

This is the Geckodrive G540 Stepper motor controller that I have used on my CNC mill ever since I built it. It is a very reliable unit capable of driving four large stepper motors and a spindle drive motor.
Mach 3 has been around for many years and is a capable CNC machine controller. It was designed for PC computers and when it was conceived the only PC peripheral that could cope with the exacting timing requirements needed to run up to four stepper motors was the PC’s parallel printer port. This port had 17 digital IO pins—roughly split between inputs and outputs. That was enough to drive four stepper motors and a spindle drive, leaving enough IO pins available to monitor three limit and one E-Stop switch. I chose the Geckodrive G540 because it was specifically designed to plug into a PC computer’s parallel printer port. While Mach 3 software was not required for the G540, it was the most popular PC CNC control application available to me.
PC computers have not included parallel printer ports for more than 15 years now. Back then I was fortunate enough to have access to many older PCs that were being abandoned in favor of newer computers. I kept a few on hand to make sure that I would continue to be able to use the G540, with its parallel printer port interface.
Some years back, microcontroller enthusiasts began to develop CNC control software that would run on common MCUs. At the time this usually meant 8-bit MCUs, and the Atmel ATMEGA328 was very popular due to its being used on the first Arduino boards. A significant following of software/hardware individuals teamed up on this endeavor, and public domain software named “Grbl” became available. It’s a cryptic name that stands for G-Code Reference Block Library.
Before Grbl became available, I personally designed a CNC controller using two Atmel ATMEGA1284 MCUs. It handled the G-code translation to stepper commands that Grbl did, and also included a three-axis LED display, an SD card file system, and all the buttons that you would normally find on a CNC pendant. However, due to my modest math skills and beginners understanding level of G-codes, and machining in general, the resulting project didn’t work as well as Mach 3. So, I continued using Mach 3 on an old PC computer. I must admit that the team that came up with the Grbl software, running on MCUs less capable than the ATMEGA1284 that I used, were better programmers than I am!
Today, dirt-cheap 32-bit ARM MCUs are available that can implement multi-axis CNC controllers. MCUs such as the Espressif ESP32 go one step further. They can run such software, and using their Wi-Fi capability, also provide a web-based user interface. Grbl has been ported to many 32-bit ARM MCUs, including the ESP32. In this article, I’ll be looking at two such implementations, and delving more deeply into the one that specifically targets the ESP32.
Grbl for modern 32-bit MCUs
Porting the 8-bit Grbl software over to 32-bit MCUs (usually ARM-based) has its pros and cons. 32-bit MCUs have much more powerful timers, DMA facilities and boast MCU clock rates, which are 10-20x faster than 8-bit MCUs such as the ATMEGA328. That makes it much easier to implement the critical timing (and ramps) needed to run several stepper motors simultaneously.
However, there are many commonly used 32-bit MCU families available, not all of which are ARM-based. The code needed to initialize and operate these complex timers, and so forth, varies markedly from one device to another, even among those that are ARM-based.
Another complication arises when you consider that everyone’s CNC machine will be unique: different axis drive motors, number of axes, limit switches, speeds, accelerations, units of measure, and many more variables. Given the existence of so many variables, it becomes likely that any Grbl software will contain many configuration parameters and may likely have to re-compiled for each person’s machine. While compilation was not a big issue when dealing with 8-bit ATMEGA MCUs, it can be more daunting for the average CNC user (who may not be a programmer) when the target MCU is a 32-bit MCU, whether ARM-based or not. There were two distinct methods adopted when porting Grbl over to 32-bit MCUs:
- Write the software using a Hardware Abstraction Layer (HAL). Here, the Grbl core library routines maintain consistency, and specific driver software for the I/O, Timer and DMA functions is written for each MCU family. Writing these driver routines isn’t trivial, as the peripheral blocks in each family are quite different, and timing must be strictly controlled. The advantage to this approach is that it can run on many different MCUs: the Teensy 4.x family (NXP iMXRT1062), STM 32-bit MCUs (many different devices), ESP32, Infineon’s PSoC5LP, Atmel’s SAMD, Raspberry Pi RP2040, etc. This Grbl port is called grblHAL.
- Port the Grbl to a single MCU- the Espressif ESP32. This is the way it is done in the FluidNC firmware. There are many advantages to this approach. The ESP32 is very low-cost and small development boards, fitted with the original ESP32, are available from many sources. While the ESP32 family has expanded beyond the original ESP32 device, the FluidNC firmware was written for the original ESP32. I’ve not come across any FluidNC documentation that describes whether other members of the ESP32 family would work with this firmware. The original ESP32 contains a dual-core MCU, which lends itself to time-critical CNC operations—it can run its WiFi-based webserver on one core, leaving the other one free for time-critical CNC operations. Some newer ESP32 MCUs (the ESP32-C3 for example) contain only a single RISC-V core.
In both the discussed porting options, the software developers took into consideration the complications that arise if the user is forced to compile the software from source code, to match his/her specific CNC machine. How this was accomplished differs in each case.
For GrblHAL, there are many different target MCU families (let alone individual MCUs within a given family). With access to the source code for a specific MCU family, compilation is not too onerous for some of the MCU families. For example, the Atmel SAM and Teensy MCUs (with the applicable Arduino plug-in) are supported by Arduino and FluidNC and would not be too hard to compile or “build.” However, building the executable code from source, for the other MCUs is a much more complex task, unless you are already developing with them. To address this, grblHAL has a web-based build processor available. This is called Web Builder and is shown in Figure 3. Under the hood, the Web Builder contains compilers for all the different MCUs listed above (except the PSoC5LP). First you select the MCU you are interested in, and then choose a specific controller board containing that MCU. If you were building your own board, as I was, you would have to pick one of the available boards that was close to what you were planning on building. Once those are chosen, you are presented with a configuration screen, as displayed in Figure 3. You choose the type of connection to the Gcode sender, the motor configuration for each axis, spindle characteristics, and so forth. These are only the General configurations. There are separate tabs for Plugins, Networking, advanced features, and optional input signals. As you can see, it is possible to highly customize your version of grblHAL without the need to build the firmware yourself.

This is the grblHAL Web Builder webpage. It allows you to configure grblHAL for your own CNC configuration and then builds a firmware hex file to run on your chosen MCU/board.
With all this information specified, you then click the “Generate and download software” button to generate the hex code, which you can then download to your MCU. While I didn’t ultimately choose to use grblHAL, I generated firmware for the Teensy 4.1, flashed it, and quickly checked to see that it was responding to G-Code and sending out stepper motor pulses. It’s quite impressive.
Note that using an MCU running grblHAL is not in itself the complete solution. Figure 4 shows some configurations that handle the complete CNC operation, for both the grblHAL and FluidNC firmware. You must feed grblHAL a stream of Gcode—usually from a CAD program’s output file. This usually requires a Gcode “sender” program, run on a PC computer having access to the Gcode file you are interested in machining. Alternately, there are some grblHAL “plug-ins” that implement SD card functionality, allowing Gcode files to be streamed from an SD card. There are also various plug-ins for networking that handle protocols such as Telnet, Websocket, FTP, HTTP, and others. In general, these use an Ethernet port as the physical layer, although there is limited WiFi support for certain MCUs.

This is a block diagram showing the necessary components for a complete CNC control unit. This shows the setup for both a grblHAL option and a FluidNC option.
The second method, a dedicated ESP32 port of Grbl, uses a different approach in building firmware for a specific CNC machine configuration. This firmware is called FluidNC. That name threw me at first, as I was inclined to type in FluidCNC when entering it into my web browser. The first “C” in CNC stands for computer: Since we are using an MCU, not a full computer, the name makes good sense.
Since FluidNC is only targeting the ESP32 device, there is just one compiler/toolchain needed. The developers of FluidNC decided to write code containing the following major functions:
- All the timing-critical routines involved in driving the stepper motors (using several interface methods, including raw step/direction and higher-level driver chips).
- With the FluidNC, allow for up to 12 motors on up to six axes.
- Implement the G-Code to motor control translation.
- Include Spindle control as well as safety/limit switch monitoring.
- Include an SD card file system to store the G-Code files.
- Include an ESP32-based (SPIFFS) file system to store files containing all the configuration information needed to tailor the FluidNC firmware to the target CNC machine.
- Implement a web-based user interface (using the ESP32’s Wi-Fi capability). The webpage code is also stored in the SPIFFS partition.
- As well as the web-based UI version, allow for a separate version that accepts serial input (via USB-Serial or ESP32 UART) from a G-code “Sender” application.
Basically, you decide on the Web-based or Serial version and obtain the corresponding firmware Hex file. Once that is loaded into the ESP32, all necessary configuration is handled by a file called config.yaml. This file is user-generated and loaded into the ESP32’s flash SPIFFS partition. Most ESP32 users are familiar with uploading files to SPIFFS using the Arduino IDE’s “ESP32 Sketch Data Upload” tool. However, FluidNC provides several better ways of doing this, which I’ll cover later.
As you can see, you can fully configure FluidNC for your target CNC machine, just by composing a configuration file. There is no need to build the firmware yourself—just choose whether you want the web-based GUI or the Serial version. I recommend that you start out with the web-based version. Even if you don’t intend to use it on an ongoing basis, it’s much easier to figure out all the necessary configuration details using the web-based interface. It’s easy to upload the config.yaml file to the ESP32, using this web UI.
Choosing the FluidNC Option
I have built many projects using the Teensy 3.x and 4.x development modules and I enjoy working with them. The Teensy 4.0/4.1 modules utilize an NXP iMXRT1062 MCU, which runs at 600MHz and the Teensy 4.1 version, which has pinouts for many GPIO pins and contain a high-speed SD card interface/socket. However, I have also used many ESP32s in projects and have lots of experience with these MCUs as well. It’s easy to download the firmware hex file for the web-based UI version of FluidNC and check out how its web-based user interface looks/operates. When I did so, I was convinced that the FluidNC option was better suited to my needs. Let’s look at some of the features of the ESP32 that make it particularly suitable for a CNC controller.
Special Features of the ESP32
The ESP32 contains several function blocks that are particularly well-suited for timing-critical CNC control:
- The RMT block. This was designed to manage both transmission and reception of IR signals used in remote controls. It consists of a dedicated memory array made up of 16-bit elements. Each element defines a 15-bit timing interval and a 1-bit logic level field. In operation, each array element is accessed sequentially, and a user-specified GPIO pin is set high or low for the period specified by the 15-bit time field. This array is populated by the ESP32 MCU, using DMA, and the RMT block will generate a pulse train, which is defined by the RMT memory array. While designed to produce IR remote control patterns, it can be re-purposed to drive stepper motors very easily and accurately. The RMT block has a large memory area associated with it, which can be flexibly allocated to its eight discrete sections. While this would be enough to handle four stepper motors, the ERSP32 doesn’t usually have enough free GPIO pins to allow this, while still allowing for other things like limit switches, spindle drive, and more.
- An I2S port, normally used for audio purposes. This can be re-purposed into driving Serial-Parallel Shift Register chips (the 74HCT595 for example). Using just the single I2S port and two 74HCT595 chips, stepper motors for up to eight axes, can be controlled.
- LEDC block. This is a very flexible PWM generator with two separate sections each containing eight channels. This is capable of high resolution, high pulse rate PWM signals, which can be used for Spindle control and other purposes.
- WiFi and Bluetooth capability. This can be used to host a web-based user interface, running on either a PC or a tablet.
My CNC Milling Machine Configuration
I am basically “married” to my Geckodrive G540 stepper motor driver, which was expensive, but is a very high-quality controller that can drive up to four large axis stepper motors and one spindle motor. It has four status inputs which can be used for limit switches, an E-stop button, and more. All inputs and outputs that interface with the associated PC computer, are 5V TTL logic levels and are terminated in a DB25 socket. It uses a parallel printer cable to connect to the DB25 socket on a PC’s parallel printer port. Figure 2 shows this unit.
What I planned to do was eliminate the PC computer, running the Mach 3 CNC application. In its place, I would use an ESP32 running FluidNC in its web-based UI version. The web-browser needed to view the FluidNC’s user interface could run on a Raspberry Pi 4 driving the same video monitor previously connected to my old PC computer, running Mach 3. Alternately, I could just use my iPad tablet.
As part of this switchover, I also planned to replace an earlier CNC pendant that I had built. It was basically a USB keyboard emulator that activated buttons, did the jogging, and so forth, by sending keystrokes to the Mach 3 program. In its place, I designed a functionally similar unit that communicated with the FluidNC program via the ESP32’s UART port. FluidNC is set up to accept commands from the Web-UI, the ESP32’s USB emulated-serial port and one of the ESP32’s hardware UART ports.
To implement the pendant, I used a Raspberry Pi Pico board containing a RP2040 MCU. This MCU is very inexpensive and has become very popular with DIYers and makers. I’ll discuss this unit later in the article.
Before going into detail about the ESP32 circuitry and the configuration process for FluidNC, let’s first take a quick look at the FluidNC user interface, as it appears in a web-browser. Figure 5 shows the FluidNC’s main dashboard. I’ve sized this FluidNC window down to produce a figure that will fit the amount of space available on the magazine page. However, when sized up for a larger available monitor, more information will be displayed.

This is the Dashboard View in FluidNC. It displays many functions, but it would be nice if the X, Y, Z coordinate displays were larger, as they are hard to see from any distance.
At the upper left is an on-screen Jog control for three axes. Directly below it are readouts for the X, Y, and Z axes—in both machine coordinates (absolute) and work coordinates (relative). I should mention that FluidNC internally uses and displays in millimeter units. Here in Canada, we adopted the Metric system long ago, but I still prefer to work in Imperial units for machining purposes. I figured out how to adapt FluidNC for Imperial units. It involves a few configuration changes, which I’ll mention later. Below the X, Y, Z readouts is a panel that enables you to set the feed rates for the three axes, although default maximum feed rates for each axis are stored in the configuration file.
To the right, is a status indicator as well as a tab containing a series of buttons that can be used as Feed Rate over-rides. There is also a Spindle tab that contains buttons to turn the Spindle motor on/off and set its speed. Above the status indicator is a window that allows you to set the rate at which Grbl reports from the ESP32 are sent to this dashboard. By default, this initially defaults to the “none” option- this is tricky at first, as it appears that whatever buttons you are pressing are not doing anything (assuming you are not yet hooked up to the CNC machine where you can actually see it doing something.
At the dashboard’s lower left is a directory of the files present on an SD card plugged into FluidNC’s SD card socket. The Upload button brings up the file manager present on the computer running the web browser. This enables you to upload Gcode files that are either stored on that computer locally or can be reached by that computer over a network. You could just pop the SD card out of its socket and load files onto it using an available computer, but this upload feature is much easier to use.
To the right of each file entry are icons that allow you to run the GCode program contained in that file, send it back to the web browser PC for editing, or to delete or rename it.
Note that the files on the SD card are the working files only (i.e., Gcode files). The FluidNC configuration files, as well as the files making up the FluidNC’s Web-based UI, are stored separately, in the ESP32’s internal SPIFFS flash file system.
The last section of the dashboard, at the lower right, is the command list. This shows commands/messages received by FluidNC from:
- The dashboard or a running Gcode file.
- Any status messages that FluidNC reports. Such status reports would include tripped limit switches or other safety monitors being activated such as an emergency stop (E-Stop) button being pressed.
- Commands sent by the pendant hardware (via the UART port).
The rate at which this dashboard updates is dependent upon the “GRBL reports”—option buttons previously mentioned. In the middle of the screen is an icon with a CCW-oriented arrow—this performs a hardware reset on the ESP32.
At the top of the screen are tabs that select this Dashboard or two other Views: FluidNC and Tablet. The Tablet View has similar functionality to the Dashboard shown in Figure 5. However, it is configured to make it more usable on an iPad or similar tablet, containing a touch screen.
The FluidNC View is where you can examine the current configuration of the FluidNC firmware, as well as make changes to it. Some of these configuration parameters are stored in ESP32 flash—but are not part of the two configuration files located in the ESP32’s SPIFFS file system. These are in the “Flash settings” list.
However, there is also a “Config items” button that displays another lengthy list of configuration settings. Most of these will be familiar to you if you have already generated a “config.yaml” file, which is the main configuration file used by FluidNC. While you may edit these settings from this screen (and there is a discrete Set button for each of them), the changed values are only applicable while the ESP32 is powered up and running a FluidNC session. Once power is removed, these settings will revert to whatever they were defined as in the config.yaml file.
At the top of the FluidNC View are five colored buttons. They perform the following functions:
- View the system status (basically ESP32 hardware and WiFi connection status).
- View the configuration and webpage content files present within the ESP32’s SPIFFS file system. From here, newer versions of these files can be uploaded from the host PC running the web browser. Alternately, the existing ones can be deleted or downloaded to the host PC for editing.
- Update the actual FluidNC firmware itself via an OTA download. I have not actually tried this myself, as the initial firmware I loaded is working fine for my purposes.
- Restart the FluidNC firmware.
- Refresh the settings list (after uploading a new configuration file, for example).
Installing and Configuring FluidNC
The latest and easiest method of installing FluidNC to your ESP32 is via FluidNC’s Web Installer. To do so, you must be running a Chrome, Edge, or Opera web browser. If you don’t have one of these browsers, the installation can be done another way, as outlined later.
Use the following link to access it:
When you select the Connect button, the installer will bring up a list of virtual USB/COM ports that are present, and the ESP32 should be in this list—usually as a CP2102 USB-UART bridge controller, but this will vary depending upon the ESP32 board you have. You then have the option of Installing FluidNC or opening a Serial terminal to your ESP32 (which would be useful if you already had FluidNC installed, since you could interact with it via this USB-serial port). If you select the install option, you have the choice of which version of software you wish to install. Your ESP32 processor version will be displayed in a large blue rectangle—click on that to get started. You have three choices—WiFi, Bluetooth, or no wireless connection. Then you have a choice of:
- A fresh install (erases all information in any existing SPIFFS partition)
- An update (leaves existing filesystem intact)
- A filesystem update (just updates the filesystem leaving existing firmware in place)
When you’ve selected one of these options and the firmware is finished loading, a message to this effect will be displayed. When I selected the serial terminal option, and clicked on the re-start button, I got no response from the ESP32. I don’t think this is normal, because I was able to see messages from the ESP32 when I opened the appropriate COM port in another serial terminal application (Arduino serial terminal, for example).
At this point, the FluidNC firmware is running but is not able to connect to your local WiFi access point, as it doesn’t know the credentials. Therefore, it starts the ESP32 up in the AP mode. From a computer, tablet, or cellphone, you should select the WiFi Settings menu and let the computer/phone scan for access points—it should find one called FluidNC. When you select this entry, it should automatically take you to the FluidNC web user interface running on the ESP32 (on a phone or tablet anyway). If this doesn’t happen, you could enter http://192.168.0.1 into your browser, and you should then see the FluidNC web UI. If you select the FluidNC tab at the top of the webpage, you will see the Flash settings for the FluidNC configuration. Navigate to the WiFi Mode entry and select “STA>AP” and directly below it, enter your WiFi credentials (don’t forget to use the SET button to store these). After re-booting, FluidNC should now be able to connect to your WiFi access point and the FluidNC web UI should be available if you enter the “fluidNC.local” URL into your computer’s web browser. This should look like Figure 5.
For a non-online method of installation, go to the FluidNC’s project page on Github:
https://github.com/bdring/FluidNC
— ADVERTISMENT—
—Advertise Here—
I use Windows 11, so the following description applies to Windows and is different for other OS installations.
After you pick the release that you want, you download the appropriate Zip file. After you extract it, you will have two folders:
fluidnc-xxxxxxxxxxx (version info) and fluidNC-config-files-main
Within the first folder are several Windows batch files. These are really slick as they allow you to erase or burn the FluidNC firmware to your ESP32 via USB, without the need to have either the ESP32 IDF or the Arduino IDE (with ESP32 board support) already installed. They use Espressif’s “esptool.exe” to do this, which is included as part of the Zip file you downloaded.
Some of FluidNC’s batch files will try out all existing COM ports and proceed when it finds the one that is connected to the ESP32. However, it’s good to check Device Manager to see which COM port the ESP32 is being enumerated as, for later use.
After firmware installation, the batch file will run another batch file called fluidterm. This is basically a specialized terminal program that monitors the ESP32’s USB serial port and displays various status messages.
Next you must run the install-fs batch file. This installs the SPIFFS file system as well as the Web UI’s HTML content file. When the FluidNC firmware is run for the first time, it will fail to mount the SPIFFS file system because that doesn’t yet exist. This will produce a “SPIFFS:mount failed” error. Don’t worry about this as the FluidNC firmware will format the SPIFFS partition automatically after it fails to find it, the first time around. This formatting will take several minutes or more depending upon the amount of flash present on your ESP32 module.
At this point, FluidNC will be running a WiFi access point called “FluidNC” and you would follow the procedure described in the online installation section, to give it your WiFi access point credentials.
Configuring FluidNC with config.yaml
Most of the “permanent” configuration parameters defining the setup of your CNC machine, are contained in the config.yaml file. I wasn’t familiar with yaml files, but they are files meant to hold data structures and are readable by many computer languages. The extension “yaml” stands for “yet another markup language.” While such files are easily human-readable, they do impose a indentation structure (e.g., Python).
The easiest way to generate a config.yaml file, for your particular CNC machine setup, is to modify an existing yaml file. In the last section, I mentioned a zip file obtained from the FluidNC github site. One folder in that zip file is named fluidNC-config-files-main. This contains 35 official config.yaml files and many more contributed ones. One of these existing config.yaml files could match your configuration completely, but even with my homemade CNC machine, it wasn’t hard to find one that was close.
Figure 6 shows a snippet of a config.yaml file providing the general data structure.

This is a snippet of the configuration file “config.yaml”. Here, it is displayed by the Visual Code editor. Doing so highlights both the indentation (which is important) and the parameter labels and values.
I edited this file using the Visual Code (VC) application because VC highlights the different indentation levels clearly, and colors labels and parameters distinctly. I initially tried the Notepad text editor. However, when the FluidNC firmware boots up and reads this config.yaml file, it’s pretty discerning regarding indentation and exact syntax and will reject any parameters that are not perfectly presented. Luckily, during boot-up, FluidNC sends out progress/status messages via the USB/Serial port, and it flags any config.yaml parameters that it has rejected. You should always monitor this status message stream whenever you are making any changes to the existing config.yaml file. Note how GPIO pins are defined using a period before the actual pin number, as well as how the pin direction and pull-up/pull-downs are defined. Most of the entries shown in this snippet of configuration code are quite easy to understand.
Most of the configuration parameters needed by FluidNC can be found by examining an applicable official config.yaml file from the folder mentioned above and editing it as necessary. Here are some parameters that I had to add myself to suit my CNC machine, and the way I wanted to use it:
report_inches: true
use_line_numbers: true
macros:
startup_line0: “G20 M68 E0 Q50”
after_reset: “G20”
I added these lines to have the Dashboard report positions in Inches (including the G20 code, which specifies inches). The “M68 E0 Q50” Gcode command is used by FluidNC to start up a 10kHz PWM waveform with a 50% duty cycle, on the GPIO pin defined as E0, as defined by the following config.yaml entries:
user_outputs:
analog0_pin: gpio.17
— ADVERTISMENT—
—Advertise Here—
analog0_hz: 10000
This would normally be used to perform PWM control of a motor, or possibly set a RC servo to a specific position. In my case, the Geckodrive G540 stepper drive controller has a “charge-pump” input. This input expects to see a 10kHz (or higher) pulse train. In the absence of this pulse train, the G540 will shut down all its stepper motor drivers as well as the spindle motor. The above configuration code provides this “charge-pump” signal on the ESP32’s IO17 pin.
There were a few control/safety status functions that I wanted FluidNC to handle—over and above the limit switch monitoring that is defined for each axis (the X-axis limit shows up in Listing x). I added the following lines for this:
control:
safety_door_pin: gpio.35:low
cycle_start_pin: gpio.9:low:pu
feed_hold_pin: gpio.10:low:pu
FluidNC doesn’t specifically recognize an emergency stop switch (E-Stop) but I have such a switch as part of my CNC setup. I use the “safety door” condition to monitor the E-Stop button. Both the Cycle Start and Feed Hold functions are implemented using switches connected to the FluidNC’s ESP32 GPIO pins. Most of the other pushbutton-controlled functions are handled by the pendant circuitry (using the RP2040 MCU).
As I mentioned earlier, the parameters contained in the config.yaml file are treated as defaults and are permanently stored in SPIFFS flash, unless you explicitly edit them. However, the FluidNC web-based UI contains a FluidNC tab, containing a “Config items” option button. Here you will see the default parameter definitions retrieved from the config.yaml file. You can edit any of them at this point and use the “Set” button to make the new values active. However, they are only active until the ESP32 is powered down: the next power-up they revert to the settings found in the config.yaml file.
The FluidNC Controller Hardware
I decided to wire up a single board containing both the ESP32/associated circuitry running FluidNC and the RP2040/associated “pendant” circuitry. I place pendant in quotes because the combined unit was much larger than a normal CNC pendant.

This is the schematic of the ESP32 and associated circuitry that runs the FluidNC firmware. The signal definitions for X3, the DB25 socket that connect to the Geckodrive G540, are found in Table 1.
Figure 7 is the schematic of the FluidNC’s ESP32 circuitry. Table 1 gives the pin description for X3, the DB25 socket that connects this unit to the Geckodrive G540. The FluidNC firmware is written for the original ESP32 MCU and I decided to use a DF Robot DFR0478 development board (FireBeetle), which contains an Espressif ESP32-WROOM-32D. This board features a few nice features such as a LiPo charger and socket and exceptionally low power consumption in deep sleep. These features are not needed in this project, but I keep a stock of these boards on hand.

This shows the pin description for the DB25 socket, which forms the interface between this unit and the Geckodrive G540 Stepper motor driver.
The Geckodrive G540 stepper motor controller is meant to be connected to a PC computer’s parallel printer port, so it expects 5V logic signals. To satisfy that criterion, I used a 74LS244 octal buffer on hand, but the 74HCT244 would be the modern choice. The six stepper drive signals (for three axes) pass through this buffer and get level-shifted from the ESP32’s 3.3V logic levels.
I decided to supplement FluidNC’s limit/safety switch monitoring, implemented in software, with another layer of hardware protection. The 74LS244 has two Enable inputs—one for each of the two quad buffers in the device.
The limit switch signals, for all three axes, as well as my E-Stop switch, are ORed together using diodes D1-D5 and NAND gate IC2 to feed the Enable pins on the 74LS244 buffer. If any of these error conditions occur, the 74LS244 buffer outputs will go into a tri-state condition and all motor motion will stop. The B and C section of IC2 will stop the spindle motor if any of these error conditions occur.
The two limit switches for each axis are wired in series and are normally closed switches. This is the recommended configuration for limit switches as it is much less susceptible to any RF spikes produced by the CNC motors. The E-stop switch is located within the project’s enclosure and isn’t susceptible to this, and I used a normally open switch that was large enough to be pressed quickly, if I determined something was going astray.
— ADVERTISMENT—
—Advertise Here—
In Figure 7, IC3 is a PS2501-4A quad opto-isolator. This device is connected to the X, Y, Z limit switches as well as the E-Stop switch. This provides galvanic isolation between the switch wiring, which is routed around the CNC machine itself, and the ESP32 GPIO inputs (IO36 for X, IO25 for Y, IO16 for Z and IO35 for E-Stop). The G540 has a dedicated terminal strip for these four switch inputs and passes those signals along to its DB25 socket (labeled Inputs 1-4). I expect that some isolation or EMI protection for these four signals is present in the G540 itself (to protect the PC computer’s parallel printer port). I added the PS2501-4A opto-isolator just in case this wasn’t the case. PS1 is a small, isolated DC/DC converter that provides an isolated power supply to drive the PS2501-4A’s input LEDs, switched by the limit/E-stop switches.
The G540 controller includes a protection circuit that it calls the Charge Pump input. The Charge Pump input expects to see a 10kHz (or higher) pulse—if it is absent, all the G540s outputs will be disabled. The idea of this was that if the Mach 3 program running on the PC computer went astray, it would stop producing this 10kHz signal. This forces the G540 to disable all axis and spindle motors. This feature can be defeated using a switch on the G540, but the FluidNC firmware can be configured to produce a PWM signal at this frequency. Earlier I showed the config.yaml entries needed to generate this signal, which is output on ESP32 IO17 and converted to a 5V logic level signal by Q2.
Not all the ESP32’s GPIO pins can be configured for any particular FluidNC function—whether as an input, output, PWM generation, pull-up/pull-down configuration. A very handy feature of FluidNC is that when reading the config.yaml filer at bootup, it will produce an error message for any GPIO pin that cannot be configured for the purpose you have specified. Therefore, you should monitor the ESP32’s USB/serial port using a serial terminal program on your PC computer, while initially composing the config.yaml file. Some of the ESP32’s GPIO lines are used as boot mode selectors (or for other purposes) while the ESP32 is booting up (GPIO 0,2,12,15). You should avoid using these GPIO pins for FluidNC purposes.
FluidNC communicates with the user via the web-UI, the USB/serial port and the ESP32’s hardware UART port (Tx is IO01, Rx is IO03). The pendant functions are handled by a Raspberry Pi Pico module (RP2040 MCU) and communication between the ESP32 and the RP2040 is handled by this UART port, at 115,200 baud.
Power for both the FluidNC ESP32 and the pendant RP2040 is provided by a 5V-2A wall adapter.
The “Pendant”
A CNC pendant is normally a small, possibly hand-held unit, which can control the CNC machine, allowing the user to closely observe what is happening. In this project, both the FluidNC controller and my pendant circuitry are contained in a somewhat larger enclosure situated on the CNC machine’s base (see Figure 8). The FluidNC Web-UI is displayed on a video monitor, driven by a Raspberry Pi SBC. The monitor (with the Raspberry Pi mounted on back) is located on a separate stand and requires no wiring between it and the CNC machine, since it connects to FluidNC via WiFi.

This is a photo of the completed controller/pendant unit. I used an “Arcade” joystick for X/Y Jogging. It’s somewhat large for the unit, but these joysticks are readily available and inexpensive.
Figure 9 is a schematic diagram of the pendant circuitry. The RP2040 is a good fit for this purpose as it’s readily available, low cost and contains lots of GPIO pins. Basically, the RP2040 is monitoring the status of nine pushbuttons and the four switches associated with the X/Y joystick (Z-axis jogging is handled by two of the pushbuttons). All these switch inputs (except Fast/Slow Jog switch) are handled by RP2040 pin-change interrupts, insuring fast response time. I decided to use 10k pull-up resistors on all these switches to minimize contact bounce time (compared to the RP2040’s higher value internal pull-ups).

This is the schematic for the pendant part of the project. It uses a Raspberry Pi Pico board, which is now one of the most popular low-cost ARM MCU boards.
When Jogging is engaged, it will proceed at either a fast or slow rate, depending upon the position of the Fast/Slow Jog switch. The RP2040 MCU has no access to the feed rate parameters contained in the FluidNC’s config.yaml file, so I hard-coded suitable Fast/Slow Jog rates in the RP2040 firmware- found in the jogXplus() and jogXminus() routines (as well as in the routines for the other two axes).
Very fine manual control of each of the three-axis positions can be performed by rotating the MPG (Manual Pulse Generator) rotary encoder. Whichever axis was last jogged will be the one that the MPG encoder acts upon. The library “rp2040-encoder-library”, by Giovanni di Dio Bruno is directly available from the Arduino library manager.
FluidNC’s WebUI is very nice, but the X, Y, Z co-ordinates (for both Work and Machine coordinates) are displayed in a very small font, not readily readable at any distance. Therefore, I added three 8-digit LED readouts to this unit to display the working coordinates for all three axes. These 8-digit displays contain a MAX7219 LED matrix controller that interfaces with the RP2040 MCU using a three-wire serial protocol. The three separate displays are daisy-chained and can be controlled by one RP2040 SPI port plus a single *CS line. The library routine needed to drive these 8-digit LED displays is the LedControl library (by Eberhart Fahle), available directly from Arduino’s library manager. These 8-digit LED readouts are sold at very low-cost on Amazon by many vendors.
When the RP2040 was first introduced, the Raspberry Pi organization supplied a C toolchain for it. This was a text-based toolchain that required you to use a “make” file to define the build process. Since this toolchain was provided by Raspberry Pi themselves, it provided support quite quickly for the specialized functions that could be provided by the multi-section PIO blocks contained in this MCU. Some quite amazing capabilities, like VGA, DVI video generation have since been developed using these programmable IO blocks.
Not that long after it became available, Adafruit stepped up and ported its CircuitPython language to the RP2040. The RP2040 MCU contains a UF2 bootloader in ROM. Basically, when you plug the RP2040 into your PC’s USB port, while pressing its BOOTSEL button, it will show up in your PC’s File Manager as a flash drive. You just drag/drop your firmware hex files to this flash drive, and the RP2040 will program itself using this hex file. All the CircuitPython-supported MCUs use this UF2 bootloader. I considered using CircuitPython for the pendant firmware, because I like the fact that Adafruit has ported a ton of its in-house written libraries to CircuitPython. However, I found that when I tried to test out some necessary CircuitPython libraries with the RP2040, I got a lot of error messages indicating incompatibilities between those libraries and the (latest) version of CircuitPython, ported to the RP2040.
Therefore, I decided to add the RP2040 support package to my Arduino IDE and use the Arduino C++ toolchain to write the pendant code. I didn’t run into any difficulties using Arduino libraries for either the Max7219 LED display or the rotary encoder. Similarly, the pin-change interrupt routines, for all the switches, worked perfectly the first time. Even though this was the first time that I’ve used an RP2040 MCU, the program came together quite quickly. This is encouraging since the lower-end Teensy LC and 3.x MCU modules, that I have routinely used for such projects in the past, are no longer available—a victim of modern supply-chain problems.
Conclusions
It is quite amazing that the major of functions of the Mach 3 application, running on a full-fledged PC computer, could be replaced by an ESP32 MCU and not much else. Although I suspect that modern versions of Mach 3 can support controllers that are interfaced via USB, my older version was limited to using a legacy PC parallel printer port. At some point, I won’t have an old PC capable of accommodating a parallel printer port.
The developers of FluidNC have written a remarkable program. It has a broad range of configuration options, and the config.yaml and preferences.json file allow one to readily configure it. Since FluidNC comes as a pre-built firmware hex file, users don’t have to have an ESP32 toolchain running on their computer. I have done numerous ESP32-based projects, for which I use the Visual Micro plug-in for Visual Studio, which is a fancy “wrapper” around the Arduino IDE. However, I’m not familiar with Espressif’s ESP32 IDF, which is more advanced/cutting edge, since it is provided by the ESP32 manufacturer themself. Nor do I use PlatformIO, which is another IDE used for ESP32 development.
Looking at the FluidNC Github site, it looks to me like FluidNC was developed using either the Espressif IDF or possibly PlatformIO, and not as an Arduino sketch. Therefore, being able to use a pre-built firmware hex file is what allowed me to even try out FluidNC in the first place.
Figure 1 shows this project connected to my CNC milling machine.
RESOURCES
DF Robot | dfrobot.com
Geckodrive | www.geckodrive.com
Espressif Systems | www.espressif.com
Raspberry Pi | www.raspberrypi.com
grblHAL Github site: https://github.com/grbl/grbl
grblHAL Web Builder site: http://svn.io-engineering.com:8080/
FluidNC Github site: https://github.com/bdring/FluidNC
FluidNC Wiki: http://wiki.fluidnc.com/
Geckodrive G540 Stepper motor controller: https://www.geckodrive.com/product/g540-4-axis-digital-step-drive/
ESP32 Datasheet: https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf
DF Robot DFR0478 Dev. board: https://www.dfrobot.com/product-1590.html
Raspberry Pi Pico board: https://datasheets.raspberrypi.com/pico/pico-datasheet.pdf
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JUNE 2024 #407 – Get a PDF of the issue
Sponsor this ArticleBrian Millier runs Computer Interface Consultants. He was an instrumentation engineer in the Department of Chemistry at Dalhousie University (Halifax, NS, Canada) for 29 years.
