From 5f16050312feb8f9030b112c66f66f83ff4c7705 Mon Sep 17 00:00:00 2001 From: odaki Date: Sat, 25 Apr 2020 21:16:16 +0900 Subject: [PATCH] Try to implement the I2S reset mechanism(not yet works) I started implementing the reset process for the I2S I/O expander. It's still not enough. --- .../Machines/esp32_printer_controller.h | 12 +- Grbl_Esp32/i2s_ioexpander.cpp | 411 ++++++++++++------ Grbl_Esp32/i2s_ioexpander.h | 15 +- Grbl_Esp32/motion_control.cpp | 3 + 4 files changed, 290 insertions(+), 151 deletions(-) diff --git a/Grbl_Esp32/Machines/esp32_printer_controller.h b/Grbl_Esp32/Machines/esp32_printer_controller.h index 9120c079..ad6e6319 100644 --- a/Grbl_Esp32/Machines/esp32_printer_controller.h +++ b/Grbl_Esp32/Machines/esp32_printer_controller.h @@ -94,11 +94,11 @@ // The 1 bits in LIMIT_MASK set the axes that have limit switches // For example, if the Y axis has no limit switches but the // X, Z, A and B axes do, the LIMIT_MASK value would be B11101 -#define LIMIT_MASK B0111 +//#define LIMIT_MASK B0111 -#define X_LIMIT_PIN GPIO_NUM_34 -#define Y_LIMIT_PIN GPIO_NUM_35 -#define Z_LIMIT_PIN GPIO_NUM_32 +//#define X_LIMIT_PIN GPIO_NUM_34 +//#define Y_LIMIT_PIN GPIO_NUM_35 +//#define Z_LIMIT_PIN GPIO_NUM_32 // Common enable for all steppers. If it is okay to leave // your drivers enabled at all times, you can leave @@ -125,7 +125,7 @@ // RESET, FEED_HOLD, and CYCLE_START can control GCode execution at // the push of a button. -// #define CONTROL_RESET_PIN GPIO_NUM_34 // labeled Reset, needs external pullup +#define CONTROL_RESET_PIN GPIO_NUM_34 // labeled Reset, needs external pullup // #define CONTROL_FEED_HOLD_PIN GPIO_NUM_36 // labeled Hold, needs external pullup // #define CONTROL_CYCLE_START_PIN GPIO_NUM_39 // labeled Start, needs external pullup @@ -185,7 +185,7 @@ // The control pins with names that begin with CONTROL_ are // ignored by default, to avoid noise problems. To make them // work, you must undefine IGNORE_CONTROL_PINS -// #undef IGNORE_CONTROL_PINS +#undef IGNORE_CONTROL_PINS // If some of the control pin switches are normally closed // (the default is normally open), you can invert some of them diff --git a/Grbl_Esp32/i2s_ioexpander.cpp b/Grbl_Esp32/i2s_ioexpander.cpp index 050b83f8..c674e2b8 100644 --- a/Grbl_Esp32/i2s_ioexpander.cpp +++ b/Grbl_Esp32/i2s_ioexpander.cpp @@ -91,10 +91,16 @@ static atomic_uint_least32_t i2s_port_data = ATOMIC_VAR_INIT(0); #define I2S_ENTER_CRITICAL() portENTER_CRITICAL(&i2s_spinlock) #define I2S_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_spinlock) +static int i2s_ioexpander_initialized = 0; + static volatile uint32_t i2s_ioexpander_pulse_period; static uint32_t i2s_ioexpander_remain_time_until_next_pulse; // Time remaining until the next pulse (μsec) static volatile i2s_ioexpander_pulse_phase_func_t i2s_ioexpander_pulse_phase_func; +static uint8_t i2s_ioexpander_ws_pin = -1; +static uint8_t i2s_ioexpander_bck_pin = -1; +static uint8_t i2s_ioexpander_data_pin = -1; + enum i2s_ioexpander_status_t { UNKNOWN = 0, RUNNING, @@ -103,7 +109,248 @@ enum i2s_ioexpander_status_t { static volatile i2s_ioexpander_status_t i2s_ioexpander_status; // -// External funtions (without init function) +// Internal functions +// +static inline void gpio_matrix_out_check(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv) { + //if pin = -1, do not need to configure + if (gpio != -1) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); + gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT); + gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv); + } +} + +static inline void i2s_reset_fifo_without_lock() { + I2S0.conf.rx_fifo_reset = 1; + I2S0.conf.rx_fifo_reset = 0; + I2S0.conf.tx_fifo_reset = 1; + I2S0.conf.tx_fifo_reset = 0; +} + +static void i2s_reset_fifo() { + I2S_ENTER_CRITICAL(); + i2s_reset_fifo_without_lock(); + I2S_EXIT_CRITICAL(); +} + +static int i2s_clear_dma_buffers() { + if (!i2s_ioexpander_initialized) { + return -1; + } + + for (int buf_idx = 0; buf_idx < DMA_BUF_COUNT; buf_idx++) { + // Clear the DMA buffer + uint32_t port_data = atomic_load(&i2s_port_data); + for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { + dma.buffers[buf_idx][i] = port_data; + } + // Initialize DMA descriptor + dma.desc[buf_idx]->owner = 1; + dma.desc[buf_idx]->eof = 1; // set to 1 will trigger the interrupt + dma.desc[buf_idx]->sosf = 0; + dma.desc[buf_idx]->length = DMA_BUF_LEN; + dma.desc[buf_idx]->size = DMA_BUF_LEN; + dma.desc[buf_idx]->buf = (uint8_t *) dma.buffers[buf_idx]; + dma.desc[buf_idx]->offset = 0; + dma.desc[buf_idx]->empty = (uint32_t)((buf_idx < (DMA_BUF_COUNT - 1)) ? (dma.desc[buf_idx + 1]) : dma.desc[0]); + } + return 0; +} + +static int i2s_gpio_attach(uint8_t ws, uint8_t bck, uint8_t data) { + // Route the i2s pins to the appropriate GPIO + gpio_matrix_out_check(data, I2S0O_DATA_OUT23_IDX, 0, 0); + gpio_matrix_out_check(bck, I2S0O_BCK_OUT_IDX, 0, 0); + gpio_matrix_out_check(ws, I2S0O_WS_OUT_IDX, 0, 0); + return 0; +} + +#define I2S_IOEXP_DETACH_PORT_IDX 0x100 + +static int i2s_gpio_detach(uint8_t ws, uint8_t bck, uint8_t data) { + // Route the i2s pins to the appropriate GPIO + gpio_matrix_out_check(ws, I2S_IOEXP_DETACH_PORT_IDX, 0, 0); + gpio_matrix_out_check(bck, I2S_IOEXP_DETACH_PORT_IDX, 0, 0); + gpio_matrix_out_check(data, I2S_IOEXP_DETACH_PORT_IDX, 0, 0); + return 0; +} + +static int i2s_gpio_shiftout(uint32_t port_data) { + digitalWrite(i2s_ioexpander_ws_pin, LOW); + for (int i = 0; i < 32; i++) { + // XXX do not use raw defines for machine + digitalWrite(i2s_ioexpander_data_pin, !!(port_data & (1 << (31 - i)))); + digitalWrite(i2s_ioexpander_bck_pin, HIGH); + digitalWrite(i2s_ioexpander_bck_pin, LOW); + } + digitalWrite(i2s_ioexpander_ws_pin, HIGH); // Latch + return 0; +} + +static int i2s_stop() { + I2S_ENTER_CRITICAL(); + // Stop FIFO DMA + I2S0.out_link.stop = 1; + + // Disconnect DMA from FIFO + I2S0.fifo_conf.dscr_en = 0; //Unset this bit to disable I2S DMA mode. (R/W) + + // stop TX module + I2S0.conf.tx_start = 0; + + // Force WS to LOW before detach + // This operation prevents unintended WS edge trigger when detach + digitalWrite(i2s_ioexpander_ws_pin, LOW); + + // Now, detach GPIO pin from I2S + i2s_gpio_detach(i2s_ioexpander_ws_pin, i2s_ioexpander_bck_pin, i2s_ioexpander_data_pin); + + // Force BCK to LOW + // After the TX module is stopped, BCK always seems to be in LOW. + // However, I'm going to do it manually to ensure the BCK's LOW. + digitalWrite(i2s_ioexpander_bck_pin, LOW); + + // Transmit recovery data to 74HC595 + uint32_t port_data = atomic_load(&i2s_port_data); // current expanded port value + i2s_gpio_shiftout(port_data); + + //clear pending interrupt + I2S0.int_clr.val = I2S0.int_st.val; + I2S_EXIT_CRITICAL(); + return 0; +} + +static int i2s_start() { + if (!i2s_ioexpander_initialized) { + return -1; + } + // Transmit recovery data to 74HC595 + uint32_t port_data = atomic_load(&i2s_port_data); // current expanded port value + i2s_gpio_shiftout(port_data); + + // Attach I2S to specified GPIO pin + i2s_gpio_attach(i2s_ioexpander_ws_pin, i2s_ioexpander_bck_pin, i2s_ioexpander_data_pin); + //start DMA link + I2S_ENTER_CRITICAL(); + i2s_reset_fifo_without_lock(); + //reset DMA + I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 0; + I2S0.lc_conf.out_rst = 1; + I2S0.lc_conf.out_rst = 0; + + I2S0.conf.tx_reset = 1; + I2S0.conf.tx_reset = 0; + I2S0.conf.rx_reset = 1; + I2S0.conf.rx_reset = 0; + + I2S0.out_link.addr = (uint32_t)dma.desc[0]; + + // Connect DMA to FIFO + I2S0.fifo_conf.dscr_en = 1; // Set this bit to enable I2S DMA mode. (R/W) + + I2S0.int_clr.val = 0xFFFFFFFF; + I2S0.out_link.start = 1; + I2S0.conf.tx_start = 1; + + I2S_EXIT_CRITICAL(); + + return 0; +} + +// +// I2S DMA Interrupts handler +// +static void IRAM_ATTR i2s_intr_handler_default(void *arg) { + lldesc_t *finish_desc; + portBASE_TYPE high_priority_task_awoken = pdFALSE; + + if (I2S0.int_st.out_eof) { + // Get the descriptor of the last item in the linkedlist + finish_desc = (lldesc_t*) I2S0.out_eof_des_addr; + + // If the queue is full it's because we have an underflow, + // more than buf_count isr without new data, remove the front buffer + if (xQueueIsQueueFullFromISR(dma.queue)) { + lldesc_t *front_desc; + // Remove a descriptor from the DMA complete event queue + xQueueReceiveFromISR(dma.queue, &front_desc, &high_priority_task_awoken); + uint32_t port_data = atomic_load(&i2s_port_data); + for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { + front_desc->buf[i] = port_data; + } + front_desc->length = DMA_BUF_LEN; + } + + // Send a DMA complete event to the I2S bitstreamer task with finished buffer + xQueueSendFromISR(dma.queue, &finish_desc, &high_priority_task_awoken); + } + + if (high_priority_task_awoken == pdTRUE) portYIELD_FROM_ISR(); + + // clear interrupt + I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt +} + +// +// I2S bitstream generator task +// +static void IRAM_ATTR i2sIOExpanderTask(void* parameter) { + lldesc_t *dma_desc; + while (1) { + // Wait a DMA complete event from I2S isr + // (Block until a DMA transfer has complete) + xQueueReceive(dma.queue, &dma_desc, portMAX_DELAY); + dma.current = (uint32_t*)(dma_desc->buf); + // It reuses the oldest (just transferred) buffer with the name "current" + // and fills the buffer for later DMA. + if (i2s_ioexpander_status == RUNNING) { + // + // Fillout the buffer for pulse + // + // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) + // is adjusted to be in a single buffer. + // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. + // Therefore, if a buffer is close to full and it is time to generate a pulse, + // the generation of the buffer is interrupted (the buffer length is shortened slightly) + // and the pulse generation is postponed until the next buffer is filled. + // + dma.rw_pos = 0; + while (dma.rw_pos < (DMA_SAMPLE_COUNT - SAMPLE_SAFE_COUNT)) { + // no data to read (buffer empty) + if (i2s_ioexpander_remain_time_until_next_pulse < I2S_IOEXP_USEC_PER_PULSE) { + // fillout future DMA buffer (tail of the DMA buffer chains) + if (i2s_ioexpander_pulse_phase_func != NULL) { + (*i2s_ioexpander_pulse_phase_func)(); // should be pushed into buffer max DMA_SAMPLE_SAFE_COUNT + i2s_ioexpander_remain_time_until_next_pulse = i2s_ioexpander_pulse_period; + continue; + } + } + // no pulse data in push buffer (pulse off or idle or callback is not defined) + dma.current[dma.rw_pos++] = atomic_load(&i2s_port_data); + if (i2s_ioexpander_remain_time_until_next_pulse >= I2S_IOEXP_USEC_PER_PULSE) { + i2s_ioexpander_remain_time_until_next_pulse -= I2S_IOEXP_USEC_PER_PULSE; + } else { + i2s_ioexpander_remain_time_until_next_pulse = 0; + } + } + // set filled length to the DMA descriptor + dma_desc->length = dma.rw_pos * I2S_SAMPLE_SIZE; + } else { + // Stepper paused unknown + // (just set current I/O port bits to the buffer) + uint32_t port_data = atomic_load(&i2s_port_data); + for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { + dma.current[i] = port_data; + } + dma.rw_pos = DMA_SAMPLE_COUNT; + dma_desc->length = DMA_BUF_LEN; + } + } +} + +// +// External funtions // void IRAM_ATTR i2s_ioexpander_write(uint8_t pin, uint8_t val) { uint32_t bit = 1UL << pin; @@ -153,158 +400,27 @@ int i2s_ioexpander_register_pulse_callback(i2s_ioexpander_pulse_phase_func_t fun return 0; } -// -// Internal functions (and init function) -// -static inline void gpio_matrix_out_check(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv) { - //if pin = -1, do not need to configure - if (gpio != -1) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); - gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT); - gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv); - } -} - -static inline void i2s_reset_fifo_without_lock() { - I2S0.conf.rx_fifo_reset = 1; - I2S0.conf.rx_fifo_reset = 0; - I2S0.conf.tx_fifo_reset = 1; - I2S0.conf.tx_fifo_reset = 0; -} - -static void i2s_reset_fifo() { - I2S_ENTER_CRITICAL(); - i2s_reset_fifo_without_lock(); - I2S_EXIT_CRITICAL(); -} - -static int i2s_start() { - //start DMA link - I2S_ENTER_CRITICAL(); - i2s_reset_fifo_without_lock(); - - //reset dma - I2S0.lc_conf.in_rst = 1; - I2S0.lc_conf.in_rst = 0; - I2S0.lc_conf.out_rst = 1; - I2S0.lc_conf.out_rst = 0; - - I2S0.conf.tx_reset = 1; - I2S0.conf.tx_reset = 0; - I2S0.conf.rx_reset = 1; - I2S0.conf.rx_reset = 0; - - I2S0.int_clr.val = 0xFFFFFFFF; - I2S0.out_link.start = 1; - I2S0.conf.tx_start = 1; - I2S_EXIT_CRITICAL(); - +int i2s_ioexpander_reset() { + i2s_stop(); + i2s_clear_dma_buffers(); + i2s_start(); return 0; } -// -// I2S DMA Interrupts handler -// -static void IRAM_ATTR i2s_intr_handler_default(void *arg) { - lldesc_t *finish_desc; - portBASE_TYPE high_priority_task_awoken = pdFALSE; - - if (I2S0.int_st.out_eof) { - // Get the descriptor of the last item in the linkedlist - finish_desc = (lldesc_t*) I2S0.out_eof_des_addr; - - // If the queue is full it's because we have an underflow, - // more than buf_count isr without new data, remove the front buffer - if (xQueueIsQueueFullFromISR(dma.queue)) { - uint32_t *finished_buffer; - xQueueReceiveFromISR(dma.queue, &finished_buffer, &high_priority_task_awoken); - // This will avoid any kind of noise that may get introduced due to transmission - // of previous data from tx descriptor on I2S line. - uint32_t port_data = atomic_load(&i2s_port_data); - for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { - finished_buffer[i] = port_data; - } - finish_desc->length = DMA_BUF_LEN; - } - // Send a DMA complete event to the I2S bitstreamer task with finished buffer - xQueueSendFromISR(dma.queue, &finish_desc, &high_priority_task_awoken); - } - - if (high_priority_task_awoken == pdTRUE) portYIELD_FROM_ISR(); - - // clear interrupt - I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt -} - -// -// I2S bitstream generator task -// -static void IRAM_ATTR i2sIOExpanderTask(void* parameter) { - lldesc_t *dma_desc; - while (1) { - // Wait a DMA complete event from I2S isr - // (Block until a DMA transfer has complete) - xQueueReceive(dma.queue, &dma_desc, portMAX_DELAY); - dma.current = (uint32_t*)(dma_desc->buf); - // It reuses the oldest (just transferred) buffer with the name "current" - // and fills the buffer for later DMA. - if (i2s_ioexpander_status == RUNNING) { - // - // Fillout the buffer for pulse - // - // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) - // is adjusted to be in a single buffer. - // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. - // Therefore, if a buffer is close to full and it is time to generate a pulse, - // the generation of the buffer is interrupted (the buffer length is shortened slightly) - // and the pulse generation is postponed until the next buffer is filled. - // - dma.rw_pos = 0; - while (dma.rw_pos < (DMA_SAMPLE_COUNT - SAMPLE_SAFE_COUNT)) { - // no data to read (buffer empty) - if (i2s_ioexpander_remain_time_until_next_pulse < I2S_IOEXP_USEC_PER_PULSE) { - // fillout future DMA buffer (tail of the DMA buffer chains) - if (i2s_ioexpander_pulse_phase_func != NULL) { - (*i2s_ioexpander_pulse_phase_func)(); // should be pushed into buffer max DMA_SAMPLE_SAFE_COUNT - i2s_ioexpander_remain_time_until_next_pulse = i2s_ioexpander_pulse_period; - continue; - } - } - // no pulse data in push buffer (pulse off or idle or callback is not defined) - dma.current[dma.rw_pos++] = atomic_load(&i2s_port_data); - if (i2s_ioexpander_remain_time_until_next_pulse >= I2S_IOEXP_USEC_PER_PULSE) { - i2s_ioexpander_remain_time_until_next_pulse -= I2S_IOEXP_USEC_PER_PULSE; - } else { - i2s_ioexpander_remain_time_until_next_pulse = 0; - } - } - // set filled length to the DMA descriptor - dma_desc->length = dma.rw_pos * I2S_SAMPLE_SIZE; - } else { - // Stepper not started or stopped - // (just set current I/O port bits to the buffer) - uint32_t port_data = atomic_load(&i2s_port_data); - for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { - dma.current[i] = port_data; - } - dma.rw_pos = DMA_SAMPLE_COUNT; - dma_desc->length = DMA_BUF_LEN; - } - } -} - // // Initialize funtion (external function) // int i2s_ioexpander_init(i2s_ioexpander_init_t &init_param) { + if (i2s_ioexpander_initialized) { + // already initialized + return -1; + } // To make sure hardware is enabled before any hardware register operations. periph_module_reset(PERIPH_I2S0_MODULE); periph_module_enable(PERIPH_I2S0_MODULE); // Route the i2s pins to the appropriate GPIO - gpio_matrix_out_check(init_param.data_pin, I2S0O_DATA_OUT23_IDX, 0, 0); - gpio_matrix_out_check(init_param.bck_pin, I2S0O_BCK_OUT_IDX, 0, 0); - gpio_matrix_out_check(init_param.ws_pin, I2S0O_WS_OUT_IDX, 0, 0); + i2s_gpio_attach(init_param.ws_pin, init_param.bck_pin, init_param.data_pin); /** * Each i2s transfer will take @@ -390,9 +506,9 @@ int i2s_ioexpander_init(i2s_ioexpander_init_t &init_param) { I2S0.conf.rx_reset = 0; //reset dma - I2S0.lc_conf.in_rst = 1; + I2S0.lc_conf.in_rst = 1; // Set this bit to reset in DMA FSM. (R/W) I2S0.lc_conf.in_rst = 0; - I2S0.lc_conf.out_rst = 1; + I2S0.lc_conf.out_rst = 1; // Set this bit to reset out DMA FSM. (R/W) I2S0.lc_conf.out_rst = 0; //Enable and configure DMA @@ -426,7 +542,7 @@ int i2s_ioexpander_init(i2s_ioexpander_init_t &init_param) { I2S0.conf.rx_start = 0; I2S0.conf.tx_msb_right = 1; // Set this bit to place right-channel data at the MSB in the transmit FIFO. - I2S0.conf.tx_right_first = 1; // Set this bit to transmit right-channel data first. + I2S0.conf.tx_right_first = 0; // Set this bit to transmit right-channel data first. I2S0.conf.tx_slave_mod = 0; // Master I2S0.fifo_conf.tx_fifo_mod_force_en = 1; //The bit should always be set to 1. @@ -484,9 +600,16 @@ int i2s_ioexpander_init(i2s_ioexpander_init_t &init_param) { esp_intr_alloc(ETS_I2S0_INTR_SOURCE, 0, i2s_intr_handler_default, nullptr, &i2s_isr_handle); esp_intr_enable(i2s_isr_handle); + // Remember GPIO pin numbers + i2s_ioexpander_ws_pin = init_param.ws_pin; + i2s_ioexpander_bck_pin = init_param.bck_pin; + i2s_ioexpander_data_pin = init_param.data_pin; + i2s_ioexpander_initialized = 1; + // Start the I2S peripheral i2s_start(); i2s_ioexpander_status = RUNNING; + return 0; } #endif diff --git a/Grbl_Esp32/i2s_ioexpander.h b/Grbl_Esp32/i2s_ioexpander.h index f15f592b..acf9fc23 100644 --- a/Grbl_Esp32/i2s_ioexpander.h +++ b/Grbl_Esp32/i2s_ioexpander.h @@ -93,7 +93,9 @@ typedef struct { bck _~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~_~... data vutsrqponmlkjihgfedcba9876543210vutsrqponmlkjihgfedcba9876543210____... XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - ^Latches the X bits when ws is switched to High + ^ + Latches the X bits when ws is switched to High + bit0:Extended GPIO 128, 1: Extended GPIO 129, ..., v: Extended GPIO 159 (data at LEFT Channel will ignored by shift-register IC) */ @@ -107,6 +109,8 @@ typedef struct { /* Initialize I2S and DMA for the stepper bitstreamer use I2S0, I2S0 isr, DMA, and FIFO(xQueue). + + return -1 ... already initialized */ int i2s_ioexpander_init(i2s_ioexpander_init_t &init_param); @@ -166,6 +170,15 @@ int i2s_ioexpander_set_pulse_period(uint32_t period); */ int i2s_ioexpander_register_pulse_callback(i2s_ioexpander_pulse_phase_func_t func); + +/* + Reset i2s I/O expander + - Stop ISR/DMA + - Clear DMA buffer with the current expanded GPIO bits + - Retart ISR/DMA + */ +int i2s_ioexpander_reset(); + #endif /* diff --git a/Grbl_Esp32/motion_control.cpp b/Grbl_Esp32/motion_control.cpp index dba7b022..1f701e2b 100644 --- a/Grbl_Esp32/motion_control.cpp +++ b/Grbl_Esp32/motion_control.cpp @@ -449,6 +449,9 @@ void mc_reset() { } #ifdef USE_GANGED_AXES ganged_mode = SQUARING_MODE_DUAL; // in case an error occurred during squaring +#endif +#ifdef USE_I2S_IOEXPANDER + i2s_ioexpander_reset(); #endif } }