Pin Mapping
Pin mapping (pinmapping) in grblHAL is done at compile time through C preprocessor macros in board map header files. Each supported MCU platform has its own driver repository with a set of *_map.h files under a boards/ directory, one per supported physical board.
The user selects a board by uncommenting a #define BOARD_xxx line in my_machine.h. The driver.h for that platform then #includes the corresponding map header. If no board is selected, a generic_map.h fallback is used.
1. Four Pin Numbering Models
grblHAL uses four different pin numbering conventions depending on the MCU platform:
Model A: Direct GPIO Numbers
Used by ESP32 and iMXRT1062 (Teensy 4.x). Pins are specified as a single numeric value.
ESP32 uses GPIO_NUM_x constants from the ESP-IDF:
#define X_STEP_PIN GPIO_NUM_12
#define X_DIRECTION_PIN GPIO_NUM_26
iMXRT1062 (Teensy 4.x) uses raw pin numbers with a u suffix:
#define X_STEP_PIN (2u)
#define X_DIRECTION_PIN (3u)
#define Y_STEP_PIN (4u)
Model B: GPIO Port + Pin Pairs
Used by STM32F4xx, STM32F7xx, STM32H7xx, SAM3X8E (Arduino Due), and LPC176x. Signals split into a _PORT and a _PIN macro to allow any GPIO port and pin combination.
STM32F4xx example (per-axis step on individual ports):
#define X_STEP_PORT GPIOC
#define X_STEP_PIN 0
#define Y_STEP_PORT GPIOC
#define Y_STEP_PIN 5
#define Z_STEP_PORT GPIOC
#define Z_STEP_PIN 9
#define X_DIRECTION_PORT GPIOA
#define X_DIRECTION_PIN 0
#define Y_DIRECTION_PORT GPIOA
#define Y_DIRECTION_PIN 4
#define X_ENABLE_PORT GPIOA
#define X_ENABLE_PIN 1
#define Y_ENABLE_PORT GPIOB
#define Y_ENABLE_PIN 12
STM32F4xx with shared step port (all step pins on the same port):
#define STEP_PORT GPIOC
#define X_STEP_PIN 0
#define Y_STEP_PIN 5
#define Z_STEP_PIN 9
LPC176x example:
#define X_STEP_PORT 0
#define X_STEP_PIN 17
#define Y_STEP_PORT 0
#define Y_STEP_PIN 21
SAM3X8E (Due) example (uses PIO_PORT):
#define STEP_PORT PIO_PORT_A
#define X_STEP_PIN 11
#define Y_DIRECTION_PORT PIO_PORT_A
#define Y_DIRECTION_PIN 29
Model C: GPIO Port Constants (RP2040/RP2350)
The RP2040 driver defines symbolic port constants in driver.h that abstract the I/O technology used:
| Constant | Meaning |
|---|---|
GPIO_OUTPUT |
Direct GPIO output |
GPIO_INPUT |
Direct GPIO input |
GPIO_PIO |
PIO state machine for step pulses (consecutive pins) |
GPIO_PIO_1 |
PIO1, per-axis step pins |
GPIO_MAP |
GPIO register mapping mode |
GPIO_SR8 |
8-bit shift register for step/dir |
GPIO_SR16 |
16-bit shift register for output expansion |
GPIO_IOEXPAND |
External I/O expander |
GPIO_SHIFT0..GPIO_SHIFT28 |
Bit-shifted GPIO output mode |
GPIO_DIRECT |
Direct GPIO output |
EXPANDER_PORT |
Output via external expander (e.g. 74HCT595) |
RP2040 PIO stepping (all step pins driven by a PIO state machine):
#define STEP_PORT GPIO_PIO // N_AXIS consecutive pins
#define STEP_PINS_BASE 2 // First pin used by PIO
// X_STEP_PIN = 2, Y_STEP_PIN = 3, Z_STEP_PIN = 4 (auto-assigned)
#define DIRECTION_PORT GPIO_OUTPUT
#define X_DIRECTION_PIN 5
#define Y_DIRECTION_PIN 6
#define Z_DIRECTION_PIN 7
#define DIRECTION_OUTMODE GPIO_SHIFT5
RP2040 per-axis PIO stepping (separate PIO instances):
#define STEP_PORT GPIO_PIO_1
#define X_STEP_PIN 12
#define Y_STEP_PIN 14
#define Z_STEP_PIN 16
RP2040 with shift register step/dir:
#define SD_SHIFT_REGISTER 8
#define SD_SR_DATA_PIN 14
#define SD_SR_SCK_PIN 15
#define STEP_PORT GPIO_SR8
#define DIRECTION_PORT GPIO_SR8
Model D: I2S Output Expansion (ESP32)
ESP32 boards that use a 74HCT595 shift register driven by the I2S peripheral define step/dir pins via the I2SO(n) macro. This maps logical bit positions on the shift register to pin numbers above a base (default 64).
#include "use_i2s_out.h"
#define I2S_OUT_BCK GPIO_NUM_22
#define I2S_OUT_WS GPIO_NUM_17
#define I2S_OUT_DATA GPIO_NUM_21
#define X_STEP_PIN I2SO(0)
#define X_DIRECTION_PIN I2SO(1)
#define Y_STEP_PIN I2SO(2)
#define Y_DIRECTION_PIN I2SO(3)
The I2SO(n) macro expands to (I2S_OUT_PIN_BASE + n). The base is 64 by default. The use_i2s_out.h header reroutes DIGITAL_OUT() calls for pins >= 64 through the I2S expander.
2. Motor and Axis Mapping
Primary Axes (X, Y, Z)
Every board map defines step and direction pins for the three primary axes:
#define X_STEP_PIN ...
#define X_DIRECTION_PIN ...
#define Y_STEP_PIN ...
#define Y_DIRECTION_PIN ...
#define Z_STEP_PIN ...
#define Z_DIRECTION_PIN ...
On platforms using the port+pin model, each motor signal may have a unique port:
#define X_STEP_PORT GPIOF
#define X_STEP_PIN 13
#define Y_STEP_PORT GPIOG
#define Y_STEP_PIN 0
ABC Motors (M3, M4, M5, M6, M7)
Additional motors beyond XYZ are ganged/secondary axes controlled by the N_ABC_MOTORS setting. Each motor is gated by preprocessor conditionals:
#if N_ABC_MOTORS >= 1
#define M3_AVAILABLE
#define M3_STEP_PIN ...
#define M3_DIRECTION_PIN ...
#define M3_LIMIT_PIN ...
#define M3_ENABLE_PIN ...
#endif
#if N_ABC_MOTORS >= 2
#define M4_AVAILABLE
...
#endif
#if N_ABC_MOTORS >= 3
#define M5_AVAILABLE
...
#endif
Maximum supported motors varies by platform:
| Platform | Max Motors | Typical Boards |
|---|---|---|
| STM32H7xx | 8 | Octopus Pro, Dresco Octave |
| STM32F4xx | 8 | Octopus Pro (F446/F429) |
| RP2040 | 8 | generic 8-axis map |
| LPC176x | 4 | BTT SKR 1.4 Turbo |
| ESP32 | 6 | BDRING I2S, Root CNC Pro |
| iMXRT1062 | 5 | T41U5XBB, GRBLHAL2000 |
| SAM3X8E | 3 | tinyg2_due, RAMPS |
Per-Axis Enable vs Shared Enable
Boards can use either a single shared stepper enable or individual per-motor enables:
Shared enable (ESP32 generic, LPC176x generic):
#define STEPPERS_ENABLE_PIN GPIO_NUM_13
Per-axis enable (STM32F4xx Octopus Pro, iMXRT1062 T41U5XBB, RP2040 BTT SKR Pico):
#define X_ENABLE_PIN GPIO_NUM_10
#define Y_ENABLE_PIN GPIO_NUM_40
#define Z_ENABLE_PIN GPIO_NUM_39
#if N_ABC_MOTORS > 0
#define M3_ENABLE_PIN GPIO_NUM_38
#endif
The shared or per-axis pattern is determined by the board map and cannot be changed at runtime.
Ganged Axes and Auto-Square
When N_ABC_MOTORS >= 1, the extra motor can be used either as an ABC axis or as a ganged (second) motor for an existing axis. Ganging and auto-squaring are enabled in my_machine.h:
#define X_GANGED 1
#define X_AUTO_SQUARE 1
#define Y_GANGED 1
When an axis is ganged, its extra motor follows the primary motor's step commands. Auto-squaring adds homing logic to re-synchronize ganged motors at startup.
3. Limit Switch Pins
Minimum Limits
#define X_LIMIT_PIN GPIO_NUM_2
#define Y_LIMIT_PIN GPIO_NUM_4
#define Z_LIMIT_PIN GPIO_NUM_15
On port+pin platforms:
#define X_LIMIT_PORT GPIOG
#define X_LIMIT_PIN 6
ABC Motor Limits
Only enabled when N_ABC_MOTORS is high enough and the board defines them:
#if N_ABC_MOTORS >= 1
#define M3_LIMIT_PIN ...
#endif
Some boards conditionally enable ABC motor limits with M3_LIMIT_ENABLE:
#if M3_LIMIT_ENABLE
#define M3_LIMIT_PORT GPIOC
#define M3_LIMIT_PIN 11
#endif
Shared Limits for Auto-Square
Auto-squared axes share limit pins between the primary and secondary motor. For example, the BlackBox X32 uses a shared Z limit pin that is routed to M3 during auto-square homing via board_init():
// BlackBoxX32.c board_init() routes shared Z limit to M3
Limit Input Mode
Port+pin platforms define how the limit port is read:
#define LIMIT_INMODE GPIO_MAP // or GPIO_BITBAND, GPIO_SHIFT12
For RP2040:
#define LIMIT_PORT GPIO_INPUT
#define LIMIT_INMODE GPIO_MAP
4. IO Expanders
grblHAL supports several types of IO expanders to increase the number of available outputs.
HC595 SPI Shift Register (ESP32 Ooznest)
The 74HCT595 is driven via SPI chip select. Outputs are accessed through the EXPANDER_PORT mechanism:
#define USE_EXPANDERS
#define HC595_CS_PIN GPIO_NUM_8
#define STEPPERS_ENABLE_PORT EXPANDER_PORT
#define STEPPERS_ENABLE_PIN 3
#define SPINDLE_ENABLE_PORT EXPANDER_PORT
#define SPINDLE_ENABLE_PIN 0
#define COOLANT_FLOOD_PORT EXPANDER_PORT
#define COOLANT_FLOOD_PIN 2
The EXPANDER_OUT(pin, state) macro writes to the shift register:
#define EXPANDER_OUT(pin, state) { if(iox_out[pin]) iox_out[pin]->set_value(iox_out[pin], (float)state); }
PCA9654E I2C I/O Expander (CNC BoosterPack)
The PCA9654E is an I2C-based 8-bit I/O expander. Enabled via PCA9654E_ENABLE:
#define PCA9654E_ENABLE 1
#define USE_EXPANDERS
#define STEPPERS_ENABLE_PORT EXPANDER_PORT
#define STEPPERS_ENABLE_PIN 6 // PCA9654E output bit
#define SPINDLE_ENABLE_PORT EXPANDER_PORT
#define SPINDLE_ENABLE_PIN 7
The default I2C address is 0x40.
OUT_SHIFT_REGISTER (RP2040 PicoCNC)
The RP2040 PicoCNC board uses a dedicated 16-bit shift register (sr16_out.c) for output expansion:
#define USE_EXPANDERS
#define OUT_SHIFT_REGISTER 16
#define OUT_SR_DATA_PIN 17
#define OUT_SR_SCK_PIN 18 // includes next pin (19) automatically as latch
#define ENABLE_PORT EXPANDER_PORT
#define X_ENABLE_PIN 0
#define Y_ENABLE_PIN 1
#define Z_ENABLE_PIN 2
#define M3_ENABLE_PIN 3
#define SPINDLE_PORT EXPANDER_PORT
#define COOLANT_PORT EXPANDER_PORT
The SR16 driver in sr16_out.c uses a PIO state machine to clock data out to the shift register chain.
FlexGPIO Expander (FlexiHAL 2350)
The FlexiHAL RP2350 board uses an external RP2040-based FlexGPIO I/O expander over I2C:
#define USE_EXPANDERS 1
#define IOX_PIN_COUNT 48
#define ENABLE_PORT EXPANDER_PORT
#define X_ENABLE_PIN 29
#define Y_ENABLE_PIN 28
#define Z_ENABLE_PIN 27
The FLEXGPIO_ENABLE must be defined to activate FlexGPIO communication.
IOEXPAND Generic Pattern (ESP32-S3 BoosterPack)
Some board maps use a generic IOEXPAND symbolic pin that gets resolved at runtime:
#define STEPPERS_ENABLE_PIN IOEXPAND
#define SPINDLE_DIRECTION_PIN IOEXPAND
#define SPINDLE_ENABLE_PIN IOEXPAND
This is handled at startup by aux_ctrl_claim_ports() which matches symbolic expander pins to available physical expander ports.
5. Auxiliary Output Pool (AUXOUTPUT)
grblHAL maintains a pool of auxiliary output pins that other features (spindle, coolant, etc.) can reference.
Defining AUXOUTPUT Pins
#define AUXOUTPUT0_PIN GPIO_NUM_17 // Model A
#define AUXOUTPUT1_PIN GPIO_NUM_18
#define AUXOUTPUT0_PORT GPIOB // Model B
#define AUXOUTPUT0_PIN 15
AUXOUTPUT Count by Platform
| Platform | Typical Range | Example Board |
|---|---|---|
| ESP32 | 0-7 | Root CNC Pro (up to 9) |
| RP2040 | 0-7 | generic_map (0-7) |
| STM32F4xx | 0-10 | Octopus Pro (0-11) |
| iMXRT1062 | 0-8 | E5XMCS_T41 (up to 8) |
| LPC176x | 0-7 | BTT SKR 1.4 Turbo |
Cross-Referencing AUXOUTPUTs
Spindle and coolant pins typically reference AUXOUTPUT pins rather than using raw GPIO numbers:
#define AUXOUTPUT0_PIN GPIO_NUM_17 // Spindle PWM
#define AUXOUTPUT1_PIN GPIO_NUM_18 // Spindle enable
#define AUXOUTPUT2_PIN GPIO_NUM_5 // Spindle direction
#define AUXOUTPUT3_PIN GPIO_NUM_16 // Coolant flood
#define AUXOUTPUT4_PIN GPIO_NUM_21 // Coolant mist
#if DRIVER_SPINDLE_ENABLE & SPINDLE_PWM
#define SPINDLE_PWM_PIN AUXOUTPUT0_PIN
#endif
This allows the spindle plugin to claim the pin at runtime. If AUXOUTPUT0 is used for something else, spindle PWM checks for the macro and silently skips if not defined.
PWM-Capable AUX Outputs
Some boards define AUXOUTPUTn_PWM_PIN for servo/PWM output:
#define AUXOUTPUT0_PWM_PIN 29 // BLTouch servo output
#define AUXOUTPUT0_PWM_PORT GPIOA
These are used by BLTOUCH_ENABLE and PWM_SERVO_ENABLE plugins.
6. Auxiliary Input Pool (AUXINPUT)
The auxiliary input pool supplies pins for control signals, probes, and other inputs.
Defining AUXINPUT Pins
#define AUXINPUT0_PIN GPIO_NUM_35
#define AUXINPUT1_PIN GPIO_NUM_32
#define AUXINPUT2_PIN GPIO_NUM_34 // Reset/EStop
#define AUXINPUT3_PIN GPIO_NUM_36 // Feed hold
#define AUXINPUT4_PIN GPIO_NUM_39 // Cycle start
Port+pin form:
#define AUXINPUT0_PORT GPIOB
#define AUXINPUT0_PIN 14
Cross-Referencing Control and Probe Pins
#if CONTROL_ENABLE & CONTROL_HALT
#define RESET_PIN AUXINPUT2_PIN
#endif
#if CONTROL_ENABLE & CONTROL_FEED_HOLD
#define FEED_HOLD_PIN AUXINPUT3_PIN
#endif
#if CONTROL_ENABLE & CONTROL_CYCLE_START
#define CYCLE_START_PIN AUXINPUT4_PIN
#endif
#if PROBE_ENABLE
#define PROBE_PIN AUXINPUT1_PIN
#endif
#if SAFETY_DOOR_ENABLE
#define SAFETY_DOOR_PIN AUXINPUT0_PIN
#endif
Shared AUXINPUT Pins for Optional Features
Many board maps conditionally assign AUXINPUT pins based on feature conflicts:
#if SPINDLE_ENCODER_ENABLE
#define SPINDLE_PULSE_PIN 21
#define SPINDLE_INDEX_PIN 22
#else
#define AUXINPUT0_PIN 22
#define AUXINPUT1_PIN 21
#endif
#ifndef M3_LIMIT_PIN
#define AUXINPUT2_PIN 11 // Repurpose ABC limit pin as aux input
#endif
7. Claiming Pins from the Aux Pool
grblHAL has a runtime pin claiming system that assigns features (probe, safety door, motor fault, etc.) to available auxiliary pins.
Input Pin Claiming
The aux_claim_explicit() callback iterates over enabled features and matches them to available AUXINPUT pins by port number:
static bool aux_claim_explicit (aux_ctrl_t *aux_ctrl)
{
// Match aux_ctrl->port to inputpin[] entries with PinGroup_AuxInput
// Then call aux_ctrl_claim_port() to bind the function
switch(aux_ctrl->function) {
case Input_Probe: probe_add(Probe_Default, ...); break;
case Input_Probe2: probe_add(Probe_2, ...); break;
case Input_Toolsetter: probe_add(Probe_Toolsetter, ...); break;
case Input_SafetyDoor: safety_door = ...; break;
// etc.
}
}
Output Pin Claiming
The aux_out_claim_explicit() callback handles output claiming:
bool aux_out_claim_explicit (aux_ctrl_out_t *aux_ctrl)
{
#ifdef USE_EXPANDERS
if(aux_ctrl->gpio.port == (void *)EXPANDER_PORT) {
// Allocate in iox_out[] array
iox_out[aux_ctrl->gpio.pin] = malloc(sizeof(xbar_t));
} else
#endif
// Claim digital output pin
pin = ioport_claim(Port_Digital, Port_Output, &aux_ctrl->port, NULL);
ioport_set_function(pin, aux_ctrl->function, NULL);
}
Silent Failure
If a required pin is not available (not defined in the board map), the assignment silently fails. The feature is compiled out:
#if SAFETY_DOOR_ENABLE && !defined(SAFETY_DOOR_PIN)
#warning "Safety door input is not available!"
#undef SAFETY_DOOR_ENABLE
#define SAFETY_DOOR_ENABLE 0
#endif
Custom Pin Claiming in board_init()
Boards that need to claim or repurpose pins at startup implement board_init():
#define HAS_BOARD_INIT
void board_init (void)
{
uint8_t port;
// Enumerate and claim an output pin
if(ioports_enumerate(Port_Digital, Port_Output, (pin_cap_t){}, find_port, &port))
ioport_claim(Port_Digital, Port_Output, &port, "N/A");
}
Inspecting Claimed Pins
At runtime, $PINS lists all pins with their assignments, and $PINSTATE shows detailed state. This is the primary debugging tool for verifying pin claims.
8. Control Input Pins
Control inputs (Reset, Feed Hold, Cycle Start) are defined by the CONTROL_ENABLE bitmask:
#ifndef CONTROL_ENABLE
#define CONTROL_ENABLE (CONTROL_HALT|CONTROL_FEED_HOLD|CONTROL_CYCLE_START)
#endif
Each bit in the mask enables the corresponding control:
| Bit | Macro | Pin Define |
|---|---|---|
(1<<0) |
CONTROL_HALT |
RESET_PIN |
(1<<1) |
CONTROL_FEED_HOLD |
FEED_HOLD_PIN |
(1<<2) |
CONTROL_CYCLE_START |
CYCLE_START_PIN |
Boards that do not have all three control inputs can set a reduced mask:
#undef CONTROL_ENABLE
#define CONTROL_ENABLE CONTROL_HALT // Only reset available
On port+pin platforms, each control signal has both _PORT and _PIN:
#define RESET_PORT AUXINPUT7_PORT
#define RESET_PIN AUXINPUT7_PIN
#define FEED_HOLD_PORT AUXINPUT8_PORT
#define FEED_HOLD_PIN AUXINPUT8_PIN
9. Spindle Pins
Primary Spindle
Spindle signals use a bitmask (DRIVER_SPINDLE_ENABLE) to select which signals are active:
#if DRIVER_SPINDLE_ENABLE & SPINDLE_PWM
#define SPINDLE_PWM_PIN AUXOUTPUT0_PIN
#define SPINDLE_PWM_PORT AUXOUTPUT0_PORT // port+pin platforms
#endif
#if DRIVER_SPINDLE_ENABLE & SPINDLE_DIR
#define SPINDLE_DIRECTION_PIN AUXOUTPUT2_PIN
#endif
#if DRIVER_SPINDLE_ENABLE & SPINDLE_ENA
#define SPINDLE_ENABLE_PIN AUXOUTPUT1_PIN
#define SPINDLE_ENABLE_PORT EXPANDER_PORT // if on expander
#endif
On ESP32, when I2S is used and a spindle signal is not physically mapped, dummy pins are defined:
#define SPINDLE_ENABLE_DUMMY_PIN (I2S_OUT_PIN_BASE - 2)
#define SPINDLE_DIRECTION_DUMMY_PIN (I2S_OUT_PIN_BASE - 1)
Second Spindle (SPINDLE1 / PWM2)
A second PWM spindle is supported on some platforms. The DRIVER_SPINDLE1_ENABLE bitmask works identically to the primary:
RP2040 and iMXRT1062 (select boards):
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_PWM
#define SPINDLE1_PWM_PIN AUXOUTPUT2_PIN
#endif
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_ENA
#define SPINDLE1_ENABLE_PIN AUXOUTPUT1_PIN
#endif
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_DIR
#define SPINDLE1_DIRECTION_PIN AUXOUTPUT0_PIN
#endif
STM32F4xx (Nucleo Morpho, Longboard32):
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_ENA
#define SPINDLE1_ENABLE_PORT AUXOUTPUT6_PORT
#define SPINDLE1_ENABLE_PIN AUXOUTPUT6_PIN
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_PWM
#define SPINDLE1_PWM_PORT AUXOUTPUT4_PORT
#define SPINDLE1_PWM_PIN AUXOUTPUT4_PIN
#endif
Platforms known to support SPINDLE1: RP2040, ESP32, STM32F4xx, iMXRT1062. The SPINDLE1_ENABLE setting in my_machine.h selects the spindle type.
Spindle Encoder / Sync
Some boards support spindle encoder inputs for spindle synchronization:
#if SPINDLE_ENCODER_ENABLE
#define SPINDLE_PULSE_PIN 21 // Must be an odd pin on RP2040
#define SPINDLE_INDEX_PIN 22
#endif
10. Coolant Pins
Coolant uses a similar bitmask (COOLANT_ENABLE):
#if COOLANT_ENABLE & COOLANT_FLOOD
#define COOLANT_FLOOD_PIN AUXOUTPUT3_PIN
#define COOLANT_FLOOD_PORT AUXOUTPUT3_PORT
#endif
#if COOLANT_ENABLE & COOLANT_MIST
#define COOLANT_MIST_PIN AUXOUTPUT4_PIN
#define COOLANT_MIST_PORT AUXOUTPUT4_PORT
#endif
On the RP2040, coolant can be placed on the expander port:
#if COOLANT_ENABLE
#define COOLANT_PORT EXPANDER_PORT
#endif
#if COOLANT_ENABLE & COOLANT_FLOOD
#define COOLANT_FLOOD_PIN 12
#endif
11. Trinamic Driver Integration
When Trinamic stepper drivers are used, additional pins are needed for SPI chip select or UART communication.
Trinamic SPI (per-motor chip select)
#define TRINAMIC_SPI_ENABLE
#define MOTOR_CSX_PORT GPIOC
#define MOTOR_CSX_PIN 4
#define MOTOR_CSY_PORT GPIOD
#define MOTOR_CSY_PIN 11
#define MOTOR_CSZ_PORT GPIOC
#define MOTOR_CSZ_PIN 6
#ifdef M3_AVAILABLE
#define MOTOR_CSM3_PORT GPIOC
#define MOTOR_CSM3_PIN 7
#endif
Trinamic UART (single-wire UART per motor)
#define TRINAMIC_UART_ENABLE
#define MOTOR_UARTX_PORT GPIOC
#define MOTOR_UARTX_PIN 4
#define MOTOR_UARTY_PORT GPIOD
#define MOTOR_UARTY_PIN 11
#define MOTOR_UARTZ_PORT GPIOC
#define MOTOR_UARTZ_PIN 6
Trinamic Software SPI
Some boards use software SPI for Trinamic when hardware SPI is occupied:
#define TRINAMIC_SOFT_SPI
#define TRINAMIC_MOSI_PORT GPIOA
#define TRINAMIC_MOSI_PIN 7
#define TRINAMIC_SCK_PORT GPIOA
#define TRINAMIC_SCK_PIN 5
#define TRINAMIC_MISO_PORT GPIOA
#define TRINAMIC_MISO_PIN 6
Trinamic on I2S (ESP32)
On ESP32 I2S boards, Trinamic chip select pins are assigned from the I2S shift register bits:
#define X_CS_PIN I2SO(3) // Chip select on I2S bit 3
#define Y_CS_PIN I2SO(6)
#define Z_CS_PIN I2SO(9)
DAC Motor Current Control
Some boards (MKS SBASE, Smoothieboard on LPC176x) use I2C digital potentiometers for Trinamic current control:
// MKS SBASE: MCP44XX I2C digital pot for Vref
// Implemented in board_init() via mks_sbase.c
The Ooznest CNC (ESP32) uses an MCP4728 I2C DAC for the same purpose, programmed in board_init().
12. Step Pulse Generation Technologies
Each platform uses a different mechanism to generate step pulses:
| Platform | Mechanism |
|---|---|
| ESP32 (GPIO) | RMT peripheral (remote control module) |
| ESP32 (I2S) | I2S peripheral -> 74HCT595 shift register |
| RP2040 | PIO state machine |
| STM32F4/F7/H7 | GPIO direct write via BSRR register |
| iMXRT1062 | GPIO direct write via DR_SET/DR_CLEAR |
| LPC176x | GPIO direct write via FIOSET/FIOCLR |
| SAM3X8E | PIO direct write via PIO_SODR/PIO_CODR |
RMT Step Pulse Generation (ESP32)
When using direct GPIO stepping, the ESP32 RMT peripheral generates step pulses. Configuration is in driver.c:
// Each axis gets a dedicated RMT channel
// RMT configured for TX mode, carrier disabled, idle low
rmt_ll_tx_reset_pointer();
rmt_ll_tx_start();
All non-I2S ESP32 boards use RMT stepping automatically.
PIO Step Generation (RP2040)
The RP2040 PIO state machine generates step pulses in hardware. The PIO program (step_pulse.pio) is loaded at startup. STEP_PINS_BASE defines the first pin in a consecutive block used by the PIO.
GPIO Output Modes (RP2040)
The _OUTMODE macros control how multiple signals on the same GPIO port are written:
#define DIRECTION_OUTMODE GPIO_SHIFT5 // Direction bits shifted by 5
#define DIRECTION_OUTMODE GPIO_MAP // Direct register map
#define STEP_OUTMODE GPIO_BITBAND // Bit-band memory access
13. Platform-Specific Pin Features
ESP32 Input-Only Pins
GPIO 34-39 on ESP32 are input-only and cannot be used for output signals. The driver enforces this:
#if ((DIRECTION_MASK|STEPPERS_DISABLE_MASK|SPINDLE_MASK|COOLANT_MASK) & 0xC00000000ULL)
#error "Pins 34 - 39 are input only!"
#endif
RP2040 Odd-Pin Requirement for Encoder
Spindle encoder pulse inputs on RP2040 must be on an odd-numbered pin (PIO requirement):
#if SPINDLE_ENCODER_ENABLE
#define SPINDLE_PULSE_PIN 21 // Must be an odd pin
#define SPINDLE_INDEX_PIN 22
#endif
STM32 Pin Mode Configuration
STM32 platforms allow per-signal pin mode configuration:
#define STEP_PINMODE PINMODE_OUTPUT
#define DIRECTION_PINMODE PINMODE_OUTPUT
#define STEPPERS_ENABLE_PINMODE PINMODE_OUTPUT
#define STEPPERS_ENABLE_PINMODE PINMODE_OD // Open drain
iMXRT1062 Quadrature Encoder
iMXRT1062 boards that support encoder input define QEI pins:
#if ENCODER_ENABLE
#define QEI_A_PIN AUXINPUT1_PIN
#define QEI_B_PIN AUXINPUT2_PIN
#if (ENCODER_ENABLE & 1)
#define QEI_SELECT_PIN AUXINPUT3_PIN
#endif
#endif
14. Custom Board Configuration
Workflow
- Copy an existing
*_map.hfrom theboards/directory to a new file, e.g.my_machine_map.h - Edit the pin assignments to match your hardware
- In
my_machine.h, uncomment:#define BOARD_MY_MACHINE // Add my_machine_map.h in the boards directory before enabling this! - Build and flash
Required Defines
At minimum, a board map must define:
| Define | Purpose |
|---|---|
X_STEP_PIN |
X axis step pulse output |
X_DIRECTION_PIN |
X axis direction output |
Y_STEP_PIN |
Y axis step pulse output |
Y_DIRECTION_PIN |
Y axis direction output |
Z_STEP_PIN |
Z axis step pulse output |
Z_DIRECTION_PIN |
Z axis direction output |
STEPPERS_ENABLE_PIN or per-axis enables |
Motor enable |
X_LIMIT_PIN (or LIMIT_PORT + X_LIMIT_PIN) |
Limit inputs |
AUXOUTPUT0_PIN + spindle/coolant cross-references |
Spindle/coolant |
board_init() Custom Startup
If the board needs custom initialization (I2C DAC setup, pin repurposing, etc.), define HAS_BOARD_INIT in the map header and implement board_init() in a companion .c file:
// my_machine_map.h
#define HAS_BOARD_INIT
// my_machine.c
#include "driver.h"
void board_init (void)
{
// Custom hardware initialization
// Pin claiming, I2C setup, etc.
}
15. Platform-Specific Examples
ESP32 with I2S Shift Register (6-Axis)
#include "use_i2s_out.h"
#define I2S_OUT_BCK GPIO_NUM_22
#define I2S_OUT_WS GPIO_NUM_17
#define I2S_OUT_DATA GPIO_NUM_21
#define X_STEP_PIN I2SO(0)
#define X_DIRECTION_PIN I2SO(1)
#define Y_STEP_PIN I2SO(2)
#define Y_DIRECTION_PIN I2SO(3)
#if N_ABC_MOTORS >= 1
#define M3_AVAILABLE
#define M3_STEP_PIN I2SO(4)
#define M3_DIRECTION_PIN I2SO(5)
#endif
#define Z_STEP_PIN I2SO(6)
#define Z_DIRECTION_PIN I2SO(7)
ESP32 with RMT Step + GPIO Dir (Ooznest CNC)
#define X_STEP_PIN GPIO_NUM_38
#define X_DIRECTION_PIN GPIO_NUM_42
#define Y_STEP_PIN GPIO_NUM_39
#define Y_DIRECTION_PIN GPIO_NUM_45
#if N_ABC_MOTORS >= 1
#define M3_AVAILABLE
#define M3_STEP_PIN GPIO_NUM_40
#define M3_DIRECTION_PIN GPIO_NUM_46
#define M3_LIMIT_PIN GPIO_NUM_11
#endif
#define Z_STEP_PIN GPIO_NUM_41
#define Z_DIRECTION_PIN GPIO_NUM_47
ESP32 with HC595 Expander (Ooznest CNC)
#define USE_EXPANDERS
#define HC595_CS_PIN GPIO_NUM_8
#define STEPPERS_ENABLE_PORT EXPANDER_PORT
#define STEPPERS_ENABLE_PIN 3
#define SPINDLE_ENABLE_PORT EXPANDER_PORT
#define SPINDLE_ENABLE_PIN 0
// Second spindle (laser)
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_PWM
#define SPINDLE1_PWM_PIN AUXOUTPUT2_PIN
#endif
RP2040 with PIO Step + Shift Register Outputs
#define STEP_PORT GPIO_PIO
#define STEP_PINS_BASE 2
#define DIRECTION_PORT GPIO_OUTPUT
#define X_DIRECTION_PIN 5
#define Y_DIRECTION_PIN 6
#define Z_DIRECTION_PIN 7
#define USE_EXPANDERS
#define OUT_SHIFT_REGISTER 16
#define OUT_SR_DATA_PIN 17
#define OUT_SR_SCK_PIN 18
#define ENABLE_PORT EXPANDER_PORT
#define X_ENABLE_PIN 0
#define Y_ENABLE_PIN 1
#define Z_ENABLE_PIN 2
RP2040 with SR8 Step/Dir (PicoCNC)
#define SD_SHIFT_REGISTER 8
#define SD_SR_DATA_PIN 14
#define SD_SR_SCK_PIN 15
#define STEP_PORT GPIO_SR8
#define DIRECTION_PORT GPIO_SR8
#define ENABLE_PORT EXPANDER_PORT
#define OUT_SHIFT_REGISTER 16
#define OUT_SR_DATA_PIN 17
#define OUT_SR_SCK_PIN 18
#define X_ENABLE_PIN 0
#define Y_ENABLE_PIN 1
#define Z_ENABLE_PIN 2
STM32F4xx with GPIO Port+Pins (Nucleo Morpho)
#define STEP_PORT GPIOC
#define X_STEP_PIN 0
#define Y_STEP_PIN 5
#define Z_STEP_PIN 9
#define DIRECTION_PORT GPIOA
#define X_DIRECTION_PIN 0
#define Y_DIRECTION_PIN 4
#define Z_DIRECTION_PIN 11
#define X_ENABLE_PORT GPIOA
#define X_ENABLE_PIN 1
#define Y_ENABLE_PORT GPIOB
#define Y_ENABLE_PIN 12
#define AUXOUTPUT4_PORT GPIOA // Spindle PWM
#define AUXOUTPUT4_PIN 8
#define AUXOUTPUT5_PORT GPIOB // Spindle direction
#define AUXOUTPUT5_PIN 5
#define AUXOUTPUT6_PORT GPIOB // Spindle enable
#define AUXOUTPUT6_PIN 3
STM32F4xx with Dual Spindle (Longboard32)
// Primary spindle
#if DRIVER_SPINDLE_ENABLE & SPINDLE_PWM
#define SPINDLE_PWM_PIN AUXOUTPUT0_PIN
#endif
#if DRIVER_SPINDLE_ENABLE & SPINDLE_ENA
#define SPINDLE_ENABLE_PORT EXPANDER_PORT
#define SPINDLE_ENABLE_PIN AUXOUTPUT4_PIN
#endif
// Second spindle
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_PWM
#define SPINDLE1_PWM_PIN AUXOUTPUT8_PIN
#endif
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_DIR
#define SPINDLE1_DIRECTION_PIN AUXOUTPUT5_PIN
#endif
iMXRT1062 with Direct Pin Numbers (T41U5XBB)
#define X_STEP_PIN (2u)
#define X_DIRECTION_PIN (3u)
#define X_ENABLE_PIN (10u)
#define X_LIMIT_PIN (20u)
#define Y_STEP_PIN (4u)
#define Y_DIRECTION_PIN (5u)
#define M3_STEP_PIN (8u)
#define M3_DIRECTION_PIN (9u)
#define AUXOUTPUT3_PIN (12u) // Spindle enable
#define AUXOUTPUT5_PIN (13u) // Spindle PWM
#define AUXOUTPUT6_PIN (19u) // Coolant flood
// Second spindle
#if DRIVER_SPINDLE1_ENABLE & SPINDLE_PWM
#define SPINDLE1_PWM_PIN AUXOUTPUT2_PIN
#endif
iMXRT1062 with Quadrature Encoder (E5XMCS_T41)
#if ENCODER_ENABLE
#define QEI_A_PIN AUXINPUT1_PIN
#define QEI_B_PIN AUXINPUT2_PIN
#define QEI_SELECT_PIN AUXINPUT3_PIN
#endif
16. Per-Platform Reference
ESP32 Board Maps
main/boards/ in the ESP32 repo contains 32 board files:
| Board Map File | Stepping | N_ABC_MOTORS | Features |
|---|---|---|---|
generic_map.h |
RMT GPIO | 0 | Default 3-axis |
generic_s3_map.h |
RMT GPIO | 0 | ESP32-S3 variant |
generic_i2s_s3_map.h |
I2S | 6 | S3 with I2S shift register |
bdring_i2s_6_axis_map.h |
I2S | 6 | BDRING 6-axis with Trinamic SPI |
bdring_i2s_6x_v3_map.h |
I2S | 6 | Per-axis Trinamic CS, aux up to 7 |
btt_rodent_map.h |
I2S | 4 | BTT Rodent, TMC SPI chain |
cnc_boosterpack_map.h |
RMT GPIO | 0 | PCA9654E I2C expander |
jackpot_map.h |
I2S | 6 | Jackpot, TMC UART, Modbus |
mks_dlc32_2_0_map.h |
I2S | 5 | MKS DLC32, dual spindle |
ooznest_cnc_map.h |
RMT GPIO | 3 | HC595 expander, dual spindle |
root_cnc_pro_map.h |
I2S | 6 | I2SO up to index 31, aux up to 9 |
RP2040/RP2350 Board Maps
boards/ in the RP2040 repo contains 18 board files:
| Board Map File | Step Port | N_ABC_MOTORS | Features |
|---|---|---|---|
generic_map.h |
GPIO_PIO | 0 | Default 3-axis |
generic_map_4axis.h |
GPIO_PIO | 1 | 4-axis variant |
generic_map_8axis.h |
GPIO_PIO | 7 | 8-axis example |
picobob_map.h |
GPIO_PIO | 2 | PicoBOB |
picohal_map.h |
GPIO_PIO | 1 | PicoHAL with Modbus |
btt_skr_pico_10_map.h |
GPIO_PIO_1 | 1 | BTT SKR Pico, Trinamic UART |
pico_cnc_map.h |
GPIO_SR8 | 1 | Shift register step+dir+outputs |
flexihal2350_map.h |
GPIO_PIO_1 | 3 | RP2350, FlexGPIO expander |
RP2350B_5X_map.h |
GPIO_PIO | 2 | RP2350 5-axis |
STM32F4xx Board Maps
boards/ in the STM32F4xx repo contains 29 board files:
| Board Map File | N_ABC_MOTORS | Features |
|---|---|---|
generic_map.h |
1 | Default map |
blackpill_map.h |
1 | F411 BlackPill, dual spindle PWM |
btt_octopus_pro_map.h |
8 | 8 motors, Trinamic SPI/UART, CAN |
btt_skr_2.0_map.h |
2 | SKR 2.0, Trinamic SPI |
flexi_hal_map.h |
2 | F446, Trinamic mixed drivers |
st_morpho_map.h |
1 | Dual spindle, Trinamic headers |
longboard32_map.h |
0 | F412 Sienci, dual spindle, TMC2660, W5500 |
STM32H7xx Board Maps
boards/ in the STM32H7xx repo contains 10 board files:
| Board Map File | N_ABC_MOTORS | Features |
|---|---|---|
btt_octopus_max_map.h |
5 | 6 motors, Trinamic UART+SPI |
btt_octopus_pro_map.h |
7 | 8 motors, CAN, analog I/O |
btt_scylla_map.h |
3 | 4 motors, Trinamic SPI, ESP_AT |
dresco_octave_map.h |
7 | 8 motors, spindle encoder, CAN |
reference_map.h |
7 | 8 axes, dual spindle, WIZnet |
iMXRT1062 Board Maps
grblHAL_Teensy4/src/boards/ contains 8 board files:
| Board Map File | N_ABC_MOTORS | Features |
|---|---|---|
T41U5XBB_map.h |
2 | 5 axes, dual spindle, encoder |
T41BB5X_Pro_map.h |
2 | 5 axes, dual spindle, THCAD2 |
T40X101_map.h |
1 | 4 axes, Teensy 4.0 |
GRBLHAL2000_map.h |
2 | Spindle sync, THCAD2, HAS_BOARD_INIT |
E5XMCS_T41_map.h |
2 | 5 axes, 8 aux outputs, motor fault/warning |
generic_map.h |
2 | 3+2 ABC, QEI encoder |
cnc_boosterpack_map.h |
0 | CNC BoosterPack |
LPC176x Board Maps
src/boards/ in the LPC176x repo contains 10 board files:
| Board Map File | N_ABC_MOTORS | Features |
|---|---|---|
generic_map.h |
0 | Default, 5 ABC max |
ramps_1.6_map.h |
2 | RAMPS 1.6 on Re-ARM |
btt_skr_1.3_map.h |
2 | SKR 1.3, Trinamic SPI or UART |
btt_skr_1.4_turbo_map.h |
2 | SKR 1.4 Turbo, Trinamic |
btt_skr_e3_turbo_map.h |
2 | E3 Turbo, TMC2209 UART forced |
mks_sbase_map.h |
2 | MKS SBASE, I2C current control |
smoothieboard_map.h |
1 | Smoothieboard, I2C current control |
17. Step Output Mode Options
Port+Pin Platforms (STM32, LPC176x, SAM3X8E)
The STEP_OUTMODE and DIRECTION_OUTMODE macros control how GPIO writes are performed:
| Mode | Description |
|---|---|
GPIO_MAP |
Direct GPIO register write |
GPIO_BITBAND |
Bit-band memory access (atomic) |
GPIO_SHIFT12 |
Shifted register write |
RP2040
The DIRECTION_OUTMODE and LIMIT_INMODE macros select between GPIO access methods:
| Mode | Description |
|---|---|
GPIO_MAP |
Direct register write |
GPIO_SHIFTn |
Shifted by n bits |
GPIO_DIRECT |
Single pin write via gpio_put() |
18. Driver Capabilities Checklist
When designing a new board map, the following features should be considered. Each requires specific pin definitions in the map header and the corresponding #define FEATURE_ENABLE in my_machine.h:
| Feature | Required Defines |
|---|---|
| Spindle PWM | SPINDLE_PWM_PIN |
| Spindle Direction | SPINDLE_DIRECTION_PIN |
| Spindle Enable | SPINDLE_ENABLE_PIN |
| Coolant Flood | COOLANT_FLOOD_PIN |
| Coolant Mist | COOLANT_MIST_PIN |
| Probe | PROBE_PIN |
| Second Probe | PROBE2_PIN |
| Toolsetter | TOOLSETTER_PIN |
| Safety Door | SAFETY_DOOR_PIN |
| Motor Fault | MOTOR_FAULT_PIN |
| Motor Warning | MOTOR_WARNING_PIN |
| I2C Keypad | I2C_STROBE_PIN (plus I2C port) |
| MPG Mode | MPG_MODE_PIN |
| BLTouch | AUXOUTPUTn_PWM_PIN (servo capable) |
| Second Spindle (PWM2) | SPINDLE1_PWM_PIN + driver spindle |
| Spindle Encoder | SPINDLE_PULSE_PIN, SPINDLE_INDEX_PIN |
| Quadrature Encoder | QEI_A_PIN, QEI_B_PIN |
| SD Card | SD_CS_PIN + SPI pins |
| Ethernet | SPI_CS_PIN, SPI_IRQ_PIN + SPI |
If a pin is not defined in the board map, the corresponding feature is silently disabled with a compiler warning.
19. Troubleshooting Pin Assignments
Compile-Time Checks
The driver validates pin assignments at compile time:
// ESP32: input-only pins check
#if ((DIRECTION_MASK|STEPPERS_DISABLE_MASK|SPINDLE_MASK|COOLANT_MASK) & 0xC00000000ULL)
#error "Pins 34 - 39 are input only!"
#endif
// Feature availability warning
#if DRIVER_SPINDLE_PWM_ENABLE && !defined(SPINDLE_PWM_PIN)
#warning "PWM spindle is not supported by board map!"
#undef DRIVER_SPINDLE_PWM_ENABLE
#define DRIVER_SPINDLE_PWM_ENABLE 0
#endif
// Feature unavailable error
#if SAFETY_DOOR_ENABLE && !defined(SAFETY_DOOR_PIN)
#warning "Safety door input is not available!"
#undef SAFETY_DOOR_ENABLE
#define SAFETY_DOOR_ENABLE 0
#endif
// Board-specific errors
#if N_ABC_MOTORS > 2
#error "Axis configuration is not supported!"
#endif
Runtime Pin Inspection
Use the $PINS command to enumerate all pins with their assigned functions:
[PIN:PB12,Emergency stop]
[PIN:PC4,Probe]
[PIN:PA0,X step]
[PIN:PA1,X direction]
...
Use $PINSTATE for detailed state including mode, capabilities, and current logic level.
Silent Failures
Pin claiming is silent. If $PINS does not show a feature you enabled, the board map likely does not define a pin for it. Check that the corresponding #define FEATURE_PIN exists in the board map or that the AUXINPUT/AUXOUTPUT pool has a spare pin that can be claimed.