Control Panel for K40 laser cutters/engravers
What it CAN do:
- Monitor input/output coolant flow and temperature (using "YF-B4"-type sensors and thermistors)
- Monitor lids opening state (using two microswitches)
- Monitor for fire (using an LM393 IR flame sensor module)
- Control the CO2 laser interlock based on the current state
- Control a 5V/12V alarm indicator
- Control 5V/12V lights
- Control 5V/12V accessories
- Control a motorized bed (based on this design, but it should work with other ones as long as the stepper can be controled using a DRV8825 driver)
- Retrieve the current state of the machine and move the laser head through UART (requires a FluidNC board)
- Enable/disable air assist (requires a FluidNC board)
- Expose sensors/state data through an API
- Run in headless mode with a UART-based serial protocol (for instance with Phosphoros)
What it CANNOT do (yet):
- Start or control a job
- Trigger the laser
For the control panel:
- 1x 38 pins ESP32-S with a spacing of 24mm between pin rows (not 25.5mm)
- 1x ST7796 (recommended) or ILI9488 (slower) 3.95" LCD screen (not needed if you want to use it in headless mode, for instance with Phosphoros)
- 1x DRV8825 stepper driver (only if you want to drive your motorized bed from this board, GRBL is also supported)
- 1x TSR-1-2450 5V regulator (or a less-efficient L7805CV - you will want to add a heatsink if you go this way)
- 4x IRLML6244 N-channel MOSFETs
- 1x MF-LSMF200/24X-2 resettable fuse
- 4x 1N4007 diodes
- 1x SRDA3.3-4DR2G diode
- 4x 220Ω 1/4W R1206 SMD resistor
- 3x 1kΩ 1/4W R1206 SMD resistor
- 3x 2.2kΩ 1/4W R1206 SMD resistor
- 9x 10kΩ 1/4W R1206 SMD resistor
- 1x 15kΩ 1/4W R1206 SMD resistor
- 1x 51kΩ 1/4W R1206 SMD resistor
- 2x 100kΩ 1/4W R1206 SMD resistor
- 1x 0.1uF electrolytic capacitors
- 1x 100uF electrolytic capacitors
- 4x 0.1uF ceramic capacitors
- 1x 0.33uF ceramic capacitors
- 4x 2.2-3V 0805 SMD LEDs
- 1x SRD-12VDC-SL-C relay
- 1x 2P PCB terminal blocks (5mm pitch)
- some 2.54mm pin headers / XH connectors / jumpers
Other things you may need:
- 1x FluidNC controller board
- 1x motorized bed with a NEMA17 stepper and a limit switch
- 1x air compressor for air assist
- 2x YF-B4 flow sensor with a thermistor (preferably one with a
50kΩresistance at 25°C andB=3950K) - 1x LM393 IR flame sensor module
- 2x micro switches (1 for each lid you want to monitor, you can use a jumper instead if desired)
- 1x 5V/12V accessory
- 1x 5V/12V LED light (for the enclosure)
- 1x 5V/12V alarm module (for instance a blinking light or a relay)
Make sure you have Python 3.x installed on your system, then run:
# Install/update Platform.io
pip install --upgrade platformio
# Build the firmware (headless mode by default)
pio run
# Build for a specific display driver instead
pio run -e ST7796
pio run -e ILI9488Then flash your ESP32 using the upload target.
OTA updates are available using the OTA target. In order to use it copy the .env.dist file to .env and change the values of OTA_UPLOAD_URL, OTA_LOGIN and OTA_PASSWORD in it accordingly to the settings of your control panel.
You can also take a .bin file and upload it through the ElegantOTA interface available at http://<YOUR_PANEL_IP>/update.
Tip
The design fits on a 100x100mm PCB, so you should be able to get it built for really cheap by online suppliers.
Production files can be found in the gerber folder of this repository.
This PCB requires some SMD soldering, this is not that hard to do but I would still recommend practicing a bit before trying to build that board.
Some notes:
- If you are using a different PSU for the control panel and your FluidNC board make sure that both of them share a common ground in order for UART to work as intended
- Some MS1/MS2/MS3 jumpers have to be installed if you want to use microstepping, refer to the DRV8825 datasheet to know which ones
- Also use jumpers to select the voltage for "Accessory" and "Lights" connectors
- Use shielded cables if needed (and ground the shield on one side only)
GET http://<YOUR_PANEL_IP>/api/info
{
"firmware": {
"version": "9da28a82",
"build_date": "Apr 30 2023 18:31:43"
},
"system": {
"chip": {
"model": "ESP32-D0WDQ5",
"revision": 1
},
"heap": {
"free": 80268,
"total": 257148
},
"cpu": {
"freq_mhz": 240,
"load_percent": {
"core_0": 0,
"core_1": 0.100000001
}
},
"tasks": {
"display_update": {
"state": "ready",
"priority": 1,
"high_water_mark": 1348
},
"bed_update": {
"state": "blocked",
"priority": 0,
"high_water_mark": 1044
},
"state_update": {
"state": "ready",
"priority": 0,
"high_water_mark": 600
},
"grbl_rx": {
"state": "blocked",
"priority": 1,
"high_water_mark": 1508
},
"grbl_tx": {
"state": "blocked",
"priority": 1,
"high_water_mark": 1992
},
"settings_save": {
"state": "blocked",
"priority": 0,
"high_water_mark": 472
},
"cpu_monitor": {
"state": "blocked",
"priority": 0,
"high_water_mark": 340
},
"async_tcp": {
"state": "running",
"priority": 3,
"high_water_mark": 15064
}
},
"reset_reason": "POWERON"
}
}GET http://<YOUR_PANEL_IP>/api/sensors
{
"sensors": {
"cooling": {
"flow": {
"input": 5.619999886,
"output": 5.60124556
},
"temp": {
"input": 18.89999962,
"output": 21.3
}
},
"lids": {
"front": "opened",
"back": "closed"
},
"flame_sensor": {
"triggered": false
}
}
}GET http://<YOUR_PANEL_IP>/api/alerts
{
"alerts": {
"cooling": false,
"lids": true,
"flame_sensor": false
}
}GET http://<YOUR_PANEL_IP>/api/relays
{
"relays": {
"interlock": false,
"alarm": false,
"lights": true,
"accessory": true
}
}GET http://<YOUR_PANEL_IP>/api/grbl
{
"state": 1,
"w_pos": {
"x": 0,
"y": 0,
"z": 0
},
"m_pos": {
"x": 0,
"y": 0,
"z": 0
},
"wco": {
"x": 0,
"y": 0,
"z": 0
},
"buffer": {
"planned_buffer_available_blocks": 100,
"rx_buffer_available_bytes": 20
},
"feed": {
"rate": 0,
"spindle_speed": 0
},
"line_number": 0,
"active_pins": {
"x": false,
"y": false,
"z": false,
"p": false,
"d": false,
"h": false,
"r": false,
"s": false
}
}When built in headless mode (the default), the control panel communicates over UART using a JSON-based serial protocol. This is used for instance by Phosphoros.
- UART2, 115200 baud, 8N1
- TX pin: GPIO 21, RX pin: GPIO 18
- Each message is a single JSON object terminated by a newline (
\n)
Format: {"a": <action_type>, "p": <payload>}
| Action | Type | Payload | Description |
|---|---|---|---|
0 |
GRBL | {"message": "<gcode>", "id": <int>} |
Forward a GRBL command. The panel responds with a GRBL_ACK message. |
1 |
SETTINGS_SET | Settings object (key/value pairs) | Update settings. The panel responds with a SETTINGS message containing the full updated settings. |
2 |
SETTINGS_GET | none | Request current settings. The panel responds with a SETTINGS message. |
3 |
STATUS | none | Request an immediate status update. |
4 |
RELAYS_SET | {"interlock": <bool>, "air_assist": <bool>, "lights": <bool>, "accessory": <bool>} |
Set relay/accessory states. All keys are optional. |
Format: {"t": <message_type>, "p": <payload>}
Sent periodically (every 500ms) or on request. Contains sensors, alerts, relays and UART connection status.
{
"t": 0,
"p": {
"sensors": {
"cooling": {
"flow": { "in": 5.62, "out": 5.60 },
"temp": { "in": 18.9, "out": 21.3 }
},
"lids": {
"front": "opened",
"back": "closed"
},
"flame_sensor": "ok"
},
"alerts": {
"cooling": false,
"lids": true,
"flame_sensor": false
},
"relays": {
"interlock": false,
"alarm": false,
"lights": true,
"accessory": true,
"air_assist": false
},
"uart": 1
}
}The uart field indicates the GRBL serial connection status: 0 = connecting, 1 = connected, 2 = disconnected.
Grbl status report. Similar to the /api/grbl HTTP endpoint but also includes alarm and active_accessories fields.
{
"t": 1,
"p": {
"state": 1,
"alarm": 0,
"w_pos": { "x": 0, "y": 0, "z": 0 },
"m_pos": { "x": 0, "y": 0, "z": 0 },
"wco": { "x": 0, "y": 0, "z": 0 },
"buffer": {
"planned_buffer_available_blocks": 100,
"rx_buffer_available_bytes": 20
},
"feed": { "rate": 0, "spindle_speed": 0 },
"line_number": 0,
"active_pins": {
"x": false, "y": false, "z": false, "p": false,
"d": false, "h": false, "r": false, "s": false
},
"active_accessories": {
"spindle_cw": false,
"spindle_ccw": false,
"flood_coolant": false,
"mist_coolant": false
}
}
}Coordinate objects (w_pos, m_pos, wco), buffer and feed can be null when not yet reported by the GRBL controller.
Raw GRBL message forwarded from the FluidNC board.
{
"t": 2,
"p": { "message": "[MSG:INFO ...]" }
}Acknowledgment for a previously sent GRBL command (action type 0).
{
"t": 3,
"p": { "id": 1, "success": true }
}Current settings dump, sent in response to a SETTINGS_GET or SETTINGS_SET action.
{
"t": 4,
"p": { ... }
}



