grblHAL-myversion/grblHAL_Teensy4/src/driver.c

2568 lines
73 KiB
C

/*
driver.c - driver code for IMXRT1062 processor (on Teensy 4.0/4.1 board)
Part of grblHAL
Copyright (c) 2020-2021 Terje Io
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "uart.h"
#include "driver.h"
#include "grbl/protocol.h"
#include "grbl/limits.h"
#ifdef I2C_PORT
#include "i2c.h"
#endif
#if EEPROM_ENABLE
#include "eeprom/eeprom.h"
#else
#include "avr/eeprom.h"
#endif
#if IOPORTS_ENABLE
#include "ioports.h"
#endif
#if SDCARD_ENABLE
#include "sdcard/sdcard.h"
#endif
#if KEYPAD_ENABLE
#include "keypad/keypad.h"
#endif
#if PLASMA_ENABLE
#include "plasma/thc.h"
#endif
#if PPI_ENABLE
#include "laser/ppi.h"
static void ppi_timeout_isr (void);
#endif
#if ODOMETER_ENABLE
#include "odometer/odometer.h"
#endif
#if ETHERNET_ENABLE
#include "enet.h"
#if TELNET_ENABLE
#include "networking/TCPStream.h"
#endif
#if WEBSOCKET_ENABLE
#include "networking/WsStream.h"
#endif
#endif
#if USB_SERIAL_CDC == 1
#include "usb_serial_ard.h"
#elif USB_SERIAL_CDC == 2
#include "usb_serial_pjrc.h"
#endif
#if defined(ENABLE_SAFETY_DOOR_INPUT_PIN) && defined(SAFETY_DOOR_PIN)
#define SAFETY_DOOR_ENABLE 1
#else
#define SAFETY_DOOR_ENABLE 0
#ifdef SAFETY_DOOR_PIN
//#define LIMITS_OVERRIDE_PIN SAFETY_DOOR_PIN
#endif
#endif
#define DEBOUNCE_QUEUE 8 // Must be a power of 2
#define F_BUS_MHZ (F_BUS_ACTUAL / 1000000)
#if X_AUTO_SQUARE || Y_AUTO_SQUARE || Z_AUTO_SQUARE
#define SQUARING_ENABLED
#endif
#if defined(X2_LIMIT_PIN) || defined(Y2_LIMIT_PIN) || defined(Z2_LIMIT_PIN) || defined(A2_LIMIT_PIN) || defined(B2_LIMIT_PIN)
#define DUAL_LIMIT_SWITCHES
#else
#ifdef SQUARING_ENABLED
#error "Squaring requires at least one axis with dual switch inputs!"
#endif
#endif
typedef struct {
volatile uint_fast8_t head;
volatile uint_fast8_t tail;
input_signal_t *signal[DEBOUNCE_QUEUE];
} debounce_queue_t;
#if QEI_ENABLE
#define QEI_DEBOUNCE 3
#define QEI_VELOCITY_TIMEOUT 100
typedef union {
uint_fast8_t pins;
struct {
uint_fast8_t a :1,
b :1;
};
} qei_state_t;
typedef struct {
encoder_t encoder;
int32_t count;
int32_t vel_count;
uint_fast16_t state;
volatile uint32_t dbl_click_timeout;
volatile uint32_t vel_timeout;
uint32_t vel_timestamp;
} qei_t;
static qei_t qei = {0};
#endif
static debounce_queue_t debounce_queue = {0};
// Standard inputs
static gpio_t Reset, FeedHold, CycleStart, Probe, LimitX, LimitY, LimitZ;
// Standard outputs
static gpio_t Mist, Flood, stepX, stepY, stepZ, dirX, dirY, dirZ;
#if !VFD_SPINDLE
static gpio_t spindleEnable, spindleDir;
#endif
// Optional I/O
#if SAFETY_DOOR_ENABLE
static gpio_t SafetyDoor;
#endif
#ifdef LIMITS_OVERRIDE_PIN
static gpio_t LimitsOverride;
#endif
#ifdef A_AXIS
static gpio_t stepA, dirA, LimitA;
#ifdef A_ENABLE_PIN
static gpio_t enableA;
#endif
#endif
#ifdef B_AXIS
static gpio_t stepB, dirB, LimitB;
#ifdef B_ENABLE_PIN
static gpio_t enableB;
#endif
#endif
#ifdef C_AXIS
static gpio_t stepC, dirC;
#ifdef C_ENABLE_PIN
static gpio_t enableC;
#endif
#ifdef C_LIMIT_PIN
static gpio_t LimitC;
#endif
#endif
#ifdef STEPPERS_ENABLE_PIN
static gpio_t steppersEnable;
#endif
#ifdef X_ENABLE_PIN
static gpio_t enableX;
#endif
#ifdef Y_ENABLE_PIN
static gpio_t enableY;
#endif
#ifdef Z_ENABLE_PIN
static gpio_t enableZ;
#endif
#if KEYPAD_ENABLE
static gpio_t KeypadStrobe;
#endif
#if MPG_MODE_ENABLE
static gpio_t ModeSelect;
#endif
#if QEI_ENABLE
static bool qei_enable = false;
static gpio_t QEI_A, QEI_B;
#ifdef QEI_SELECT_PIN
#define QEI_SELECT_ENABLED 1
static gpio_t QEI_Select;
#endif
#ifdef QEI_INDEX_PIN
#define QEI_INDEX_ENABLED 1
static gpio_t QEI_Index;
#endif
#endif
#ifdef X2_STEP_PIN
static gpio_t stepX2;
#endif
#ifdef X2_DIRECTION_PIN
static gpio_t dirX2;
#endif
#ifdef X2_ENABLE_PIN
static gpio_t enableX2;
#endif
#ifdef X2_LIMIT_PIN
static gpio_t LimitX2;
#endif
#ifdef Y2_STEP_PIN
static gpio_t stepY2;
#endif
#ifdef Y2_DIRECTION_PIN
static gpio_t dirY2;
#endif
#ifdef Y2_ENABLE_PIN
static gpio_t enableY2;
#endif
#ifdef Y2_LIMIT_PIN
static gpio_t LimitY2;
#endif
#ifdef Z2_STEP_PIN
static gpio_t stepZ2;
#endif
#ifdef Z2_DIRECTION_PIN
static gpio_t dirZ2;
#endif
#ifdef Z2_ENABLE_PIN
static gpio_t enableZ2;
#endif
#ifdef Z2_LIMIT_PIN
static gpio_t LimitZ2;
#endif
#ifdef SPINDLE_INDEX_PIN
static gpio_t SpindleIndex;
#endif
#ifdef AUXINPUT0_PIN
static gpio_t AuxIn0;
#endif
#ifdef AUXINPUT1_PIN
static gpio_t AuxIn1;
#endif
#ifdef AUXINPUT2_PIN
static gpio_t AuxIn2;
#endif
#ifdef AUXINPUT3_PIN
static gpio_t AuxIn3;
#endif
input_signal_t inputpin[] = {
#if ESTOP_ENABLE
{ .id = Input_EStop, .port = &Reset, .pin = RESET_PIN, .group = PinGroup_Control },
#else
{ .id = Input_Reset, .port = &Reset, .pin = RESET_PIN, .group = PinGroup_Control },
#endif
{ .id = Input_FeedHold, .port = &FeedHold, .pin = FEED_HOLD_PIN, .group = PinGroup_Control },
{ .id = Input_CycleStart, .port = &CycleStart, .pin = CYCLE_START_PIN, .group = PinGroup_Control },
#if SAFETY_DOOR_ENABLE
{ .id = Input_SafetyDoor, .port = &SafetyDoor, .pin = SAFETY_DOOR_PIN, .group = PinGroup_Control },
#endif
#if defined(LIMITS_OVERRIDE_PIN)
{ .id = Input_LimitsOverride, .port = &LimitsOverride, .pin = LIMITS_OVERRIDE_PIN, .group = PinGroup_Control },
#endif
{ .id = Input_Probe, .port = &Probe, .pin = PROBE_PIN, .group = PinGroup_Probe },
// Limit input pins must be consecutive
{ .id = Input_LimitX, .port = &LimitX, .pin = X_LIMIT_PIN, .group = PinGroup_Limit },
#ifdef X2_LIMIT_PIN
{ .id = Input_LimitX_Max, .port = &LimitX2, .pin = X2_LIMIT_PIN, .group = PinGroup_Limit },
#endif
{ .id = Input_LimitY, .port = &LimitY, .pin = Y_LIMIT_PIN, .group = PinGroup_Limit },
#ifdef Y2_LIMIT_PIN
{ .id = Input_LimitY_Max, .port = &LimitY2, .pin = Y2_LIMIT_PIN, .group = PinGroup_Limit },
#endif
{ .id = Input_LimitZ, .port = &LimitZ, .pin = Z_LIMIT_PIN, .group = PinGroup_Limit }
#ifdef Z2_LIMIT_PIN
, { .id = Input_LimitZ_Max, .port = &LimitZ2, .pin = Z2_LIMIT_PIN, .group = PinGroup_Limit }
#endif
#ifdef A_LIMIT_PIN
, { .id = Input_LimitA, .port = &LimitA, .pin = A_LIMIT_PIN, .group = PinGroup_Limit }
#endif
#ifdef B_LIMIT_PIN
, { .id = Input_LimitB, .port = &LimitB, .pin = B_LIMIT_PIN, .group = PinGroup_Limit }
#endif
#ifdef C_LIMIT_PIN
, { .id = Input_LimitC, .port = &LimitC, .pin = C_LIMIT_PIN, .group = PinGroup_Limit }
#endif
// End limit pin definitions
#if MPG_MODE_ENABLE
, { .id = Input_ModeSelect, .port = &ModeSelect, .pin = MODE_PIN, .group = PinGroup_MPG }
#endif
#if KEYPAD_ENABLE && defined(KEYPAD_STROBE_PIN)
, { .id = Input_KeypadStrobe, .port = &KeypadStrobe, .pin = KEYPAD_STROBE_PIN, .group = PinGroup_Keypad }
#endif
#ifdef SPINDLE_INDEX_PIN
, { .id = Input_SpindleIndex, .port = &SpindleIndex, .pin = SPINDLE_INDEX_PIN, .group = PinGroup_SpindleIndex }
#endif
#if QEI_ENABLE
, { .id = Input_QEI_A, .port = &QEI_A, .pin = QEI_A_PIN, .group = PinGroup_QEI }
, { .id = Input_QEI_B, .port = &QEI_B, .pin = QEI_B_PIN, .group = PinGroup_QEI }
#if QEI_SELECT_ENABLED
, { .id = Input_QEI_Select, .port = &QEI_Select, .pin = QEI_SELECT_PIN, .group = PinGroup_QEI_Select }
#endif
#if QEI_INDEX_ENABLED
, { .id = Input_QEI_Index, .port = &QEI_Index, .pin = QEI_INDEX_PIN, .group = PinGroup_QEI }
#endif
#endif
// Aux input pins must be consecutive
#ifdef AUXINPUT0_PIN
, { .id = Input_Aux0, .port = &AuxIn0, .pin = AUXINPUT0_PIN, .group = PinGroup_AuxInput }
#endif
#ifdef AUXINPUT1_PIN
, { .id = Input_Aux1, .port = &AuxIn1, .pin = AUXINPUT1_PIN, .group = PinGroup_AuxInput }
#endif
#ifdef AUXINPUT2_PIN
, { .id = Input_Aux2, .port = &AuxIn2, .pin = AUXINPUT2_PIN, .group = PinGroup_AuxInput }
#endif
#ifdef AUXINPUT3_PIN
, { .id = Input_Aux3, .port = &AuxIn3, .pin = AUXINPUT3_PIN, .group = PinGroup_AuxInput }
#endif
};
static pin_group_pins_t aux_inputs = {0}, limit_inputs = {0};
#if USB_SERIAL_CDC || QEI_ENABLE
#define ADD_MSEVENT 1
static volatile bool ms_event = false;
#else
#define ADD_MSEVENT 0
#endif
static bool IOInitDone = false;
static uint16_t pulse_length, pulse_delay;
static axes_signals_t next_step_outbits;
static delay_t grbl_delay = { .ms = 0, .callback = NULL };
static probe_state_t probe = {
.connected = On
};
#ifdef SQUARING_ENABLED
static axes_signals_t motors_1 = {AXES_BITMASK}, motors_2 = {AXES_BITMASK};
#endif
#if !VFD_SPINDLE
static bool pwmEnabled = false;
static spindle_pwm_t spindle_pwm;
static void spindle_set_speed (uint_fast16_t pwm_value);
#endif
#if MODBUS_ENABLE
static modbus_stream_t modbus_stream = {0};
#endif
#if SPINDLE_SYNC_ENABLE
#include "grbl/spindle_sync.h"
static spindle_data_t spindle_data;
static spindle_encoder_t spindle_encoder = {
.tics_per_irq = 4
};
static spindle_sync_t spindle_tracker;
static volatile bool spindleLock = false;
static void stepperPulseStartSynchronized (stepper_t *stepper);
static void spindleDataReset (void);
static spindle_data_t *spindleGetData (spindle_data_request_t request);
static void spindle_pulse_isr (void);
#endif
#if ETHERNET_ENABLE
static network_services_t services = {0};
static void enetStreamWriteS (const char *data)
{
#if TELNET_ENABLE
if(services.telnet)
TCPStreamWriteS(data);
#endif
#if WEBSOCKET_ENABLE
if(services.websocket)
WsStreamWriteS(data);
#endif
#if USB_SERIAL_CDC
if(!(services.telnet || services.websocket)) // TODO: check if usb connection is up?
usb_serialWriteS(data);
#else
serialWriteS(data);
#endif
}
#if TELNET_ENABLE
const io_stream_t ethernet_stream = {
.type = StreamType_Telnet,
.read = TCPStreamGetC,
.write = TCPStreamWriteS,
.write_all = enetStreamWriteS,
.get_rx_buffer_available = TCPStreamRxFree,
.reset_read_buffer = TCPStreamRxFlush,
.cancel_read_buffer = TCPStreamRxCancel,
.suspend_read = TCPStreamSuspendInput,
.enqueue_realtime_command = protocol_enqueue_realtime_command
};
#endif
#if WEBSOCKET_ENABLE
const io_stream_t websocket_stream = {
.type = StreamType_WebSocket,
.read = WsStreamGetC,
.write = WsStreamWriteS,
.write_all = enetStreamWriteS,
.get_rx_buffer_available = WsStreamRxFree,
.reset_read_buffer = WsStreamRxFlush,
.cancel_read_buffer = WsStreamRxCancel,
.suspend_read = WsStreamSuspendInput,
.enqueue_realtime_command = protocol_enqueue_realtime_command
};
#endif
#endif // ETHERNET_ENABLE
#if USB_SERIAL_CDC
const io_stream_t serial_stream = {
.type = StreamType_Serial,
.read = usb_serialGetC,
.write = usb_serialWriteS,
#if ETHERNET_ENABLE
.write_all = enetStreamWriteS,
#else
.write_all = usb_serialWriteS,
#endif
.get_rx_buffer_available = usb_serialRxFree,
.reset_read_buffer = usb_serialRxFlush,
.cancel_read_buffer = usb_serialRxCancel,
.suspend_read = usb_serialSuspendInput,
.enqueue_realtime_command = protocol_enqueue_realtime_command
};
#else
const io_stream_t serial_stream = {
.type = StreamType_Serial,
.read = serialGetC,
.write = serialWriteS,
#if ETHERNET_ENABLE
.write_all = enetStreamWriteS,
#else
.write_all = serialWriteS,
#endif
.get_rx_buffer_available = serialRxFree,
.reset_read_buffer = serialRxFlush,
.cancel_read_buffer = serialRxCancel,
.suspend_read = serialSuspendInput,
.enqueue_realtime_command = protocol_enqueue_realtime_command
};
#endif
// Interrupt handler prototypes
// Interrupt handlers needs to be registered, possibly by modifying a system specific startup file.
// It is possible to relocate the interrupt dispatch table from flash to RAM and programatically attach handlers.
// See the driver for SAMD21 for an example, relocation is done in the driver_init() function.
// Also, if a MCU specific driver library is used this might have functions to programatically attach handlers.
static void stepper_driver_isr (void);
static void stepper_pulse_isr (void);
static void stepper_pulse_isr_delayed (void);
static void gpio_isr (void);
static void debounce_isr (void);
static void systick_isr (void);
static void (*systick_isr_org)(void) = NULL;
// Millisecond resolution delay function
// Will return immediately if a callback function is provided
static void driver_delay_ms (uint32_t ms, delay_callback_ptr callback)
{
if(ms) {
grbl_delay.ms = ms;
if(!(grbl_delay.callback = callback))
while(grbl_delay.ms);
} else {
if(grbl_delay.ms) {
grbl_delay.callback = NULL;
grbl_delay.ms = 1;
}
if(callback)
callback();
}
}
void selectStream (stream_type_t stream)
{
static stream_type_t active_stream = StreamType_Serial;
switch(stream) {
#if TELNET_ENABLE
case StreamType_Telnet:
hal.stream.write_all("[MSG:TELNET STREAM ACTIVE]" ASCII_EOL);
memcpy(&hal.stream, &ethernet_stream, sizeof(io_stream_t));
services.telnet = On;
break;
#endif
#if WEBSOCKET_ENABLE
case StreamType_WebSocket:
hal.stream.write_all("[MSG:WEBSOCKET STREAM ACTIVE]" ASCII_EOL);
memcpy(&hal.stream, &websocket_stream, sizeof(io_stream_t));
services.websocket = On;
break;
#endif
case StreamType_Serial:
memcpy(&hal.stream, &serial_stream, sizeof(io_stream_t));
#if ETHERNET_ENABLE
services.mask = 0;
#endif
if(active_stream != StreamType_Serial)
hal.stream.write_all("[MSG:SERIAL STREAM ACTIVE]" ASCII_EOL);
break;
default:
break;
}
active_stream = stream;
}
// Set stepper pulse output pins.
// step_outbits.value (or step_outbits.mask) are: bit0 -> X, bit1 -> Y...
// Individual step bits can be accessed by step_outbits.x, step_outbits.y, ...
#ifdef SQUARING_ENABLED
inline static __attribute__((always_inline)) void set_step_outputs (axes_signals_t step_outbits_1)
{
axes_signals_t step_outbits_2;
step_outbits_2.mask = (step_outbits_1.mask & motors_2.mask) ^ settings.steppers.step_invert.mask;
step_outbits_1.mask = (step_outbits_1.mask & motors_1.mask) ^ settings.steppers.step_invert.mask;
DIGITAL_OUT(stepX, step_outbits_1.x);
#ifdef X2_STEP_PIN
DIGITAL_OUT(stepX2, step_outbits_2.x);
#endif
DIGITAL_OUT(stepY, step_outbits_1.y);
#ifdef Y2_STEP_PIN
DIGITAL_OUT(stepY2, step_outbits_2.y);
#endif
DIGITAL_OUT(stepZ, step_outbits_1.z);
#ifdef Z2_STEP_PIN
DIGITAL_OUT(stepZ2, step_outbits_2.z);
#endif
#ifdef A_AXIS
DIGITAL_OUT(stepA, step_outbits_1.a);
#endif
#ifdef B_AXIS
DIGITAL_OUT(stepB, step_outbits_1.b);
#endif
}
#else
inline static __attribute__((always_inline)) void set_step_outputs (axes_signals_t step_outbits)
{
step_outbits.value ^= settings.steppers.step_invert.mask;
DIGITAL_OUT(stepX, step_outbits.x);
#ifdef X2_STEP_PIN
DIGITAL_OUT(stepX2, step_outbits.x);
#endif
DIGITAL_OUT(stepY, step_outbits.y);
#ifdef Y2_STEP_PIN
DIGITAL_OUT(stepY2, step_outbits.y);
#endif
DIGITAL_OUT(stepZ, step_outbits.z);
#ifdef Z2_STEP_PIN
DIGITAL_OUT(stepZ2, step_outbits.z);
#endif
#ifdef A_AXIS
DIGITAL_OUT(stepA, step_outbits.a);
#endif
#ifdef B_AXIS
DIGITAL_OUT(stepB, step_outbits.b);
#endif
#ifdef C_AXIS
DIGITAL_OUT(stepC, step_outbits.c);
#endif
}
#endif
// Set stepper direction ouput pins.
// dir_outbits.value (or dir_outbits.mask) are: bit0 -> X, bit1 -> Y...
// Individual direction bits can be accessed by dir_outbits.x, dir_outbits.y, ...
inline static __attribute__((always_inline)) void set_dir_outputs (axes_signals_t dir_outbits)
{
dir_outbits.value ^= settings.steppers.dir_invert.mask;
DIGITAL_OUT(dirX, dir_outbits.x);
#ifdef X2_DIRECTION_PIN
DIGITAL_OUT(dirX2, dir_outbits.x);
#endif
DIGITAL_OUT(dirY, dir_outbits.y);
#ifdef Y2_DIRECTION_PIN
DIGITAL_OUT(dirY2, dir_outbits.y);
#endif
DIGITAL_OUT(dirZ, dir_outbits.z);
#ifdef Z2_DIRECTION_PIN
DIGITAL_OUT(dirZ2, dir_outbits.z);
#endif
#ifdef A_AXIS
DIGITAL_OUT(dirA, dir_outbits.a);
#endif
#ifdef B_AXIS
DIGITAL_OUT(dirB, dir_outbits.b);
#endif
#ifdef C_AXIS
DIGITAL_OUT(dirC, dir_outbits.c);
#endif
}
// Enable steppers.
// enable.value (or enable.mask) are: bit0 -> X, bit1 -> Y...
// Individual enable bits can be accessed by enable.x, enable.y, ...
// NOTE: if a common signal is used to enable all drivers enable.x should be used to set the signal.
static void stepperEnable (axes_signals_t enable)
{
enable.value ^= settings.steppers.enable_invert.mask;
#ifdef STEPPERS_ENABLE_PIN
DIGITAL_OUT(steppersEnable, enable.x)
#endif
#ifdef X_ENABLE_PIN
DIGITAL_OUT(enableX, enable.x)
#endif
#ifdef X2_ENABLE_PIN
DIGITAL_OUT(enableX2, enable.x)
#endif
#ifdef Y_ENABLE_PIN
DIGITAL_OUT(enableY, enable.y)
#endif
#ifdef Y2_ENABLE_PIN
DIGITAL_OUT(enableY2, enable.y)
#endif
#ifdef Z_ENABLE_PIN
DIGITAL_OUT(enableZ, enable.z)
#endif
#ifdef Z2_ENABLE_PIN
DIGITAL_OUT(enableZ2, enable.z)
#endif
#ifdef A_ENABLE_PIN
DIGITAL_OUT(enableA, enable.a)
#endif
#ifdef B_ENABLE_PIN
DIGITAL_OUT(enableB, enable.b)
#endif
#ifdef C_ENABLE_PIN
DIGITAL_OUT(enableC, enable.c)
#endif
}
// Starts stepper driver timer and forces a stepper driver interrupt callback.
static void stepperWakeUp (void)
{
// Enable stepper drivers.
stepperEnable((axes_signals_t){AXES_BITMASK});
PIT_LDVAL0 = 5000;
PIT_TFLG0 |= PIT_TFLG_TIF;
PIT_TCTRL0 |= (PIT_TCTRL_TIE|PIT_TCTRL_TEN);
}
// Disables stepper driver interrupts and reset outputs.
static void stepperGoIdle (bool clear_signals)
{
PIT_TCTRL0 &= ~(PIT_TCTRL_TIE|PIT_TCTRL_TEN);
if(clear_signals) {
set_step_outputs((axes_signals_t){0});
set_dir_outputs((axes_signals_t){0});
}
}
// Sets up stepper driver interrupt timeout.
// Called at the start of each segment.
// NOTE: If a 32-bit timer is used it is advisable to limit max step time to about 2 seconds
// in order to avoid excessive delays on completion of motions
// NOTE: If a 16 bit timer is used it may be neccesary to adjust the timer clock frequency (prescaler)
// to cover the needed range. Refer to actual drivers for code examples.
static void stepperCyclesPerTick (uint32_t cycles_per_tick)
{
PIT_TCTRL0 &= ~PIT_TCTRL_TEN;
PIT_LDVAL0 = cycles_per_tick < (1UL << 20) ? cycles_per_tick : 0x000FFFFFUL;
PIT_TFLG0 |= PIT_TFLG_TIF;
PIT_TCTRL0 |= PIT_TCTRL_TEN;
}
// Start a stepper pulse, no delay version.
// stepper_t struct is defined in grbl/stepper.h
static void stepperPulseStart (stepper_t *stepper)
{
#if SPINDLE_SYNC_ENABLE
if(stepper->new_block && stepper->exec_segment->spindle_sync) {
spindle_tracker.stepper_pulse_start_normal = hal.stepper.pulse_start;
hal.stepper.pulse_start = stepperPulseStartSynchronized;
hal.stepper.pulse_start(stepper);
return;
}
#endif
if(stepper->dir_change)
set_dir_outputs(stepper->dir_outbits);
if(stepper->step_outbits.value) {
set_step_outputs(stepper->step_outbits);
TMR4_CTRL0 |= TMR_CTRL_CM(0b001);
}
}
// Start a stepper pulse, delay version.
// Note: delay is only added when there is a direction change and a pulse to be output.
// In the delayed step pulse interrupt handler the pulses are output and
// normal (no delay) operation is resumed.
// stepper_t struct is defined in grbl/stepper.h
static void stepperPulseStartDelayed (stepper_t *stepper)
{
#if SPINDLE_SYNC_ENABLE
if(stepper->new_block && stepper->exec_segment->spindle_sync) {
spindle_tracker.stepper_pulse_start_normal = hal.stepper.pulse_start;
hal.stepper.pulse_start = stepperPulseStartSynchronized;
hal.stepper.pulse_start(stepper);
return;
}
#endif
if(stepper->dir_change) {
set_dir_outputs(stepper->dir_outbits);
if(stepper->step_outbits.value) {
next_step_outbits = stepper->step_outbits; // Store out_bits
attachInterruptVector(IRQ_QTIMER4, stepper_pulse_isr_delayed);
TMR4_COMP10 = pulse_delay;
TMR4_CTRL0 |= TMR_CTRL_CM(0b001);
}
return;
}
if(stepper->step_outbits.value) {
set_step_outputs(stepper->step_outbits);
TMR4_CTRL0 |= TMR_CTRL_CM(0b001);
}
}
#if SPINDLE_SYNC_ENABLE
// Spindle sync version: sets stepper direction and pulse pins and starts a step pulse.
// Switches back to "normal" version if spindle synchronized motion is finished.
// TODO: add delayed pulse handling...
static void stepperPulseStartSynchronized (stepper_t *stepper)
{
static bool sync = false;
static float block_start;
if(stepper->new_block) {
if(!stepper->exec_segment->spindle_sync) {
hal.stepper.pulse_start = spindle_tracker.stepper_pulse_start_normal;
hal.stepper.pulse_start(stepper);
return;
}
sync = true;
set_dir_outputs(stepper->dir_outbits);
spindle_tracker.programmed_rate = stepper->exec_block->programmed_rate;
spindle_tracker.steps_per_mm = stepper->exec_block->steps_per_mm;
spindle_tracker.segment_id = 0;
spindle_tracker.prev_pos = 0.0f;
block_start = spindleGetData(SpindleData_AngularPosition)->angular_position * spindle_tracker.programmed_rate;
pidf_reset(&spindle_tracker.pid);
#ifdef PID_LOG
sys.pid_log.idx = 0;
sys.pid_log.setpoint = 100.0f;
#endif
}
if(stepper->step_outbits.value) {
set_step_outputs(stepper->step_outbits);
TMR4_CTRL0 |= TMR_CTRL_CM(0b001);
}
if(spindle_tracker.segment_id != stepper->exec_segment->id) {
spindle_tracker.segment_id = stepper->exec_segment->id;
if(!stepper->new_block) { // adjust this segments total time for any positional error since last segment
float actual_pos;
if(stepper->exec_segment->cruising) {
float dt = (float)hal.f_step_timer / (float)(stepper->exec_segment->cycles_per_tick * stepper->exec_segment->n_step);
actual_pos = spindleGetData(SpindleData_AngularPosition)->angular_position * spindle_tracker.programmed_rate;
if(sync) {
spindle_tracker.pid.sample_rate_prev = dt;
// block_start += (actual_pos - spindle_tracker.block_start) - spindle_tracker.prev_pos;
// block_start += spindle_tracker.prev_pos;
sync = false;
}
actual_pos -= block_start;
int32_t step_delta = (int32_t)(pidf(&spindle_tracker.pid, spindle_tracker.prev_pos, actual_pos, dt) * spindle_tracker.steps_per_mm);
int32_t ticks = (((int32_t)stepper->step_count + step_delta) * (int32_t)stepper->exec_segment->cycles_per_tick) / (int32_t)stepper->step_count;
stepper->exec_segment->cycles_per_tick = (uint32_t)max(ticks, spindle_tracker.min_cycles_per_tick);
stepperCyclesPerTick(stepper->exec_segment->cycles_per_tick);
} else
actual_pos = spindle_tracker.prev_pos;
#ifdef PID_LOG
if(sys.pid_log.idx < PID_LOG) {
sys.pid_log.target[sys.pid_log.idx] = spindle_tracker.prev_pos;
sys.pid_log.actual[sys.pid_log.idx] = actual_pos; // - spindle_tracker.prev_pos;
// spindle_tracker.log[sys.pid_log.idx] = STEPPER_TIMER->BGLOAD << stepper->amass_level;
// spindle_tracker.pos[sys.pid_log.idx] = stepper->exec_segment->cycles_per_tick stepper->amass_level;
// spindle_tracker.pos[sys.pid_log.idx] = stepper->exec_segment->cycles_per_tick * stepper->step_count;
// STEPPER_TIMER->BGLOAD = STEPPER_TIMER->LOAD;
// spindle_tracker.pos[sys.pid_log.idx] = spindle_tracker.prev_pos;
sys.pid_log.idx++;
}
#endif
}
spindle_tracker.prev_pos = stepper->exec_segment->target_position;
}
}
#endif
#if PLASMA_ENABLE
#if PLASMA_ENABLE
static void output_pulse_isr(void);
#endif
static axes_signals_t pulse_output = {0};
void stepperOutputStep (axes_signals_t step_outbits, axes_signals_t dir_outbits)
{
pulse_output = step_outbits;
dir_outbits.value ^= settings.steppers.dir_invert.mask;
DIGITAL_OUT(dirZ, dir_outbits.z);
TMR2_CTRL0 |= TMR_CTRL_CM(0b001);
}
#endif
#ifdef DUAL_LIMIT_SWITCHES
// Returns limit state as an axes_signals_t variable.
// Each bitfield bit indicates an axis limit, where triggered is 1 and not triggered is 0.
// Dual limit switch inputs per axis version. Only one needs to be dual input!
inline static limit_signals_t limitsGetState()
{
limit_signals_t signals = {0};
signals.min.mask = signals.min2.mask = settings.limits.invert.mask;
signals.min.x = (LimitX.reg->DR & LimitX.bit) != 0;
#ifdef X2_LIMIT_PIN
signals.min2.x = (LimitX2.reg->DR & LimitX2.bit) != 0;
#endif
signals.min.y = (LimitY.reg->DR & LimitY.bit) != 0;
#ifdef Y2_LIMIT_PIN
signals.min2.y = (LimitY2.reg->DR & LimitY2.bit) != 0;
#endif
signals.min.z = (LimitZ.reg->DR & LimitZ.bit) != 0;
#ifdef Z2_LIMIT_PIN
signals.min2.z = (LimitZ2.reg->DR & LimitZ2.bit) != 0;
#endif
#ifdef A_LIMIT_PIN
signals.min.a = (LimitA.reg->DR & LimitA.bit) != 0;
#endif
#ifdef B_LIMIT_PIN
signals.min.b = (LimitB.reg->DR & LimitB.bit) != 0;
#endif
#ifdef C_LIMIT_PIN
signals.min.c = (LimitC.reg->DR & LimitC.bit) != 0;
#endif
if(settings.limits.invert.mask) {
signals.min.value ^= settings.limits.invert.mask;
signals.min2.value ^= settings.limits.invert.mask;
}
return signals;
}
#else // SINGLE INPUT LIMIT SWITCHES
// Returns limit state as an axes_signals_t bitmap variable.
// signals.value (or signals.mask) are: bit0 -> X, bit1 -> Y...
// Individual signals bits can be accessed by signals.x, signals.y, ...
// Each bit indicates a limit signal, where triggered is 1 and not triggered is 0.
// axes_signals_t is defined in grbl/nuts_bolts.h.
inline static limit_signals_t limitsGetState()
{
limit_signals_t signals = {0};
signals.min.mask = settings.limits.invert.mask;
signals.min.x = (LimitX.reg->DR & LimitX.bit) != 0;
signals.min.y = (LimitY.reg->DR & LimitY.bit) != 0;
signals.min.z = (LimitZ.reg->DR & LimitZ.bit) != 0;
#ifdef A_LIMIT_PIN
signals.min.a = (LimitA.reg->DR & LimitA.bit) != 0;
#endif
#ifdef B_LIMIT_PIN
signals.min.b = (LimitB.reg->DR & LimitB.bit) != 0;
#endif
#ifdef C_LIMIT_PIN
signals.min.c = (LimitC.reg->DR & LimitC.bit) != 0;
#endif
if (settings.limits.invert.mask)
signals.min.value ^= settings.limits.invert.mask;
return signals;
}
#endif
#ifdef SQUARING_ENABLED
static axes_signals_t getAutoSquaredAxes (void)
{
axes_signals_t ganged = {0};
#if X_AUTO_SQUARE
ganged.x = On;
#endif
#if Y_AUTO_SQUARE
ganged.y = On;
#endif
#if Z_AUTO_SQUARE
ganged.z = On;
#endif
return ganged;
}
// Enable/disable motors for auto squaring of ganged axes
static void StepperDisableMotors (axes_signals_t axes, squaring_mode_t mode)
{
motors_1.mask = (mode == SquaringMode_A || mode == SquaringMode_Both ? axes.mask : 0) ^ AXES_BITMASK;
motors_2.mask = (mode == SquaringMode_B || mode == SquaringMode_Both ? axes.mask : 0) ^ AXES_BITMASK;
}
#endif
// Enable/disable limit pins interrupt.
// NOTE: the homing parameter is indended for configuring advanced
// stepper drivers for sensorless homing.
static void limitsEnable (bool on, bool homing)
{
uint32_t i = limit_inputs.n_pins;
on &= settings.limits.flags.hard_enabled;
do {
i--;
limit_inputs.pins[i].gpio.reg->ISR = limit_inputs.pins[i].gpio.bit; // Clear interrupt.
if(on)
limit_inputs.pins[i].gpio.reg->IMR |= limit_inputs.pins[i].gpio.bit; // Enable interrupt.
else
limit_inputs.pins[i].gpio.reg->IMR &= ~limit_inputs.pins[i].gpio.bit; // Disable interrupt.
} while(i);
}
// Returns system state as a control_signals_t bitmap variable.
// signals.value (or signals.mask) are: bit0 -> reset, bit1 -> feed_hold, ...
// Individual enable bits can be accessed by signals.reset, signals.feed_hold, ...
// Each bit indicates a control signal, where triggered is 1 and not triggered is 0.
// axes_signals_t is defined in grbl/system.h.
inline static control_signals_t systemGetState (void)
{
control_signals_t signals;
signals.value = settings.control_invert.value;
#if ESTOP_ENABLE
signals.e_stop = (Reset.reg->DR & Reset.bit) != 0;
#else
signals.reset = (Reset.reg->DR & Reset.bit) != 0;
#endif
signals.feed_hold = (FeedHold.reg->DR & FeedHold.bit) != 0;
signals.cycle_start = (CycleStart.reg->DR & CycleStart.bit) != 0;
#if SAFETY_DOOR_ENABLE
signals.safety_door_ajar = (SafetyDoor.reg->DR & SafetyDoor.bit) != 0;
#endif
if(settings.control_invert.value)
signals.value ^= settings.control_invert.value;
#ifdef LIMITS_OVERRIDE_PIN
signals.limits_override = (LimitsOverride.reg->DR & LimitsOverride.bit) == 0;
#endif
return signals;
}
// Sets up the probe pin invert mask to
// appropriately set the pin logic according to setting for normal-high/normal-low operation
// and the probing cycle modes for toward-workpiece/away-from-workpiece.
static void probeConfigure(bool is_probe_away, bool probing)
{
probe.triggered = Off;
probe.is_probing = probing;
probe.inverted = is_probe_away ? !settings.probe.invert_probe_pin : settings.probe.invert_probe_pin;
}
// Returns the probe connected and triggered pin states.
probe_state_t probeGetState (void)
{
probe_state_t state = {0};
state.connected = probe.connected;
state.triggered = !!(Probe.reg->DR & Probe.bit) ^ probe.inverted;
return state;
}
#if !VFD_SPINDLE
// Static spindle (off, on cw & on ccw)
inline static void spindle_off ()
{
DIGITAL_OUT(spindleEnable, settings.spindle.invert.on);
}
inline static void spindle_on ()
{
DIGITAL_OUT(spindleEnable, !settings.spindle.invert.on);
#if SPINDLE_SYNC_ENABLE
spindleDataReset();
#endif
}
inline static void spindle_dir (bool ccw)
{
DIGITAL_OUT(spindleDir, ccw ^ settings.spindle.invert.ccw);
}
// Start or stop spindle.
static void spindleSetState (spindle_state_t state, float rpm)
{
if (!state.on)
spindle_off();
else {
if(hal.driver_cap.spindle_dir)
spindle_dir(state.ccw);
spindle_on();
}
}
// Variable spindle control functions
// Set spindle speed.
static void spindle_set_speed (uint_fast16_t pwm_value)
{
if (pwm_value == spindle_pwm.off_value) {
if(settings.spindle.flags.pwm_action == SpindleAction_DisableWithZeroSPeed)
spindle_off();
pwmEnabled = false;
if(spindle_pwm.always_on) {
#if SPINDLEPWMPIN == 12
TMR1_COMP21 = spindle_pwm.off_value;
TMR1_CMPLD11 = spindle_pwm.period - spindle_pwm.off_value;
TMR1_CTRL1 |= TMR_CTRL_CM(0b001);
#else // 13
TMR2_COMP20 = spindle_pwm.off_value;
TMR2_CMPLD10 = spindle_pwm.period - spindle_pwm.off_value;
TMR2_CTRL0 |= TMR_CTRL_CM(0b001);
#endif
} else {
#if SPINDLEPWMPIN == 12
TMR1_CTRL1 &= ~TMR_CTRL_CM(0b111);
TMR1_SCTRL1 &= ~TMR_SCTRL_VAL; // set TMR_SCTRL_VAL || TMR_SCTRL_OPS if inverted PWM
TMR1_SCTRL1 |= TMR_SCTRL_FORCE;
#else // 13
TMR2_CTRL0 &= ~TMR_CTRL_CM(0b111);
TMR2_SCTRL0 &= ~TMR_SCTRL_VAL; // set TMR_SCTRL_VAL || TMR_SCTRL_OPS if inverted PWM
TMR2_SCTRL0 |= TMR_SCTRL_FORCE;
#endif
}
} else {
if(!pwmEnabled) {
spindle_on();
pwmEnabled = true;
}
#if SPINDLEPWMPIN == 12
TMR1_COMP21 = pwm_value;
TMR1_CMPLD11 = spindle_pwm.period - pwm_value;
TMR1_CTRL1 |= TMR_CTRL_CM(0b001);
#else // 13
TMR2_COMP20 = pwm_value;
TMR2_CMPLD10 = spindle_pwm.period - pwm_value;
TMR2_CTRL0 |= TMR_CTRL_CM(0b001);
#endif
}
}
#ifdef SPINDLE_PWM_DIRECT
// Convert spindle speed to PWM value.
static uint_fast16_t spindleGetPWM (float rpm)
{
return spindle_compute_pwm_value(&spindle_pwm, rpm, false);
}
#else
// Update spindle speed.
static void spindleUpdateRPM (float rpm)
{
spindle_set_speed(spindle_compute_pwm_value(&spindle_pwm, rpm, false));
}
#endif
// Start or stop spindle.
static void spindleSetStateVariable (spindle_state_t state, float rpm)
{
if (!state.on || rpm == 0.0f) {
spindle_set_speed(spindle_pwm.off_value);
spindle_off();
} else {
if(hal.driver_cap.spindle_dir)
spindle_dir(state.ccw);
spindle_set_speed(spindle_compute_pwm_value(&spindle_pwm, rpm, false));
}
#if SPINDLE_SYNC_ENABLE
if(settings.spindle.at_speed_tolerance > 0.0f) {
float tolerance = rpm * settings.spindle.at_speed_tolerance / 100.0f;
spindle_data.rpm_low_limit = rpm - tolerance;
spindle_data.rpm_high_limit = rpm + tolerance;
}
spindle_data.rpm_programmed = spindle_data.rpm = rpm;
#endif
}
// Returns spindle state in a spindle_state_t variable.
// spindle_state_t is defined in grbl/spindle_control.h
static spindle_state_t spindleGetState (void)
{
spindle_state_t state = {settings.spindle.invert.mask};
state.on = (spindleEnable.reg->DR & spindleEnable.bit) != 0;
if(hal.driver_cap.spindle_dir)
state.ccw = (spindleDir.reg->DR & spindleDir.bit) != 0;
state.value ^= settings.spindle.invert.mask;
if(pwmEnabled)
state.on |= pwmEnabled;
#if SPINDLE_SYNC_ENABLE
float rpm = spindleGetData(SpindleData_RPM)->rpm;
state.at_speed = settings.spindle.at_speed_tolerance <= 0.0f || (rpm >= spindle_data.rpm_low_limit && rpm <= spindle_data.rpm_high_limit);
#endif
return state;
}
#if PPI_ENABLE
static void spindlePulseOn (uint_fast16_t pulse_length)
{
static uint_fast16_t plen = 0;
if(plen != pulse_length) {
plen = pulse_length;
PPI_TIMER.CH[0].COMP1 = (uint16_t)((pulse_length * F_BUS_MHZ) / 128);
}
spindle_on();
PPI_TIMER.CH[0].CTRL |= TMR_CTRL_CM(0b001);
}
#endif
#if SPINDLE_SYNC_ENABLE
static spindle_data_t *spindleGetData (spindle_data_request_t request)
{
bool stopped;
uint32_t pulse_length, rpm_timer_delta;
spindle_encoder_counter_t encoder;
__disable_irq();
memcpy(&encoder, &spindle_encoder.counter, sizeof(spindle_encoder_counter_t));
pulse_length = spindle_encoder.timer.pulse_length / spindle_encoder.tics_per_irq;
rpm_timer_delta = GPT1_CNT - spindle_encoder.timer.last_pulse;
__enable_irq();
// If no (4) spindle pulses during last 250 ms assume RPM is 0
if((stopped = ((pulse_length == 0) || (rpm_timer_delta > spindle_encoder.maximum_tt)))) {
spindle_data.rpm = 0.0f;
rpm_timer_delta = (GPT2_CNT - spindle_encoder.counter.last_count) * pulse_length;
}
switch(request) {
case SpindleData_Counters:
spindle_data.pulse_count = GPT2_CNT;
spindle_data.index_count = encoder.index_count;
spindle_data.error_count = spindle_encoder.error_count;
break;
case SpindleData_RPM:
if(!stopped)
spindle_data.rpm = spindle_encoder.rpm_factor / (float)pulse_length;
break;
case SpindleData_AngularPosition:;
while(spindleLock);
int32_t d = encoder.last_count - encoder.last_index;
spindle_data.angular_position = (float)encoder.index_count +
((float)(d) +
(pulse_length == 0 ? 0.0f : (float)rpm_timer_delta / (float)pulse_length)) *
spindle_encoder.pulse_distance;
break;
}
return &spindle_data;
}
static void spindleDataReset (void)
{
while(spindleLock);
uint32_t timeout = millis() + 1000; // 1 second
uint32_t index_count = spindle_data.index_count + 2;
if(spindleGetData(SpindleData_RPM)->rpm > 0.0f) { // wait for index pulse if running
while(index_count != spindle_data.index_count && millis() <= timeout);
// if(uwTick > timeout)
// alarm?
}
GPT2_CR &= ~GPT_CR_EN; // Reset timer
GPT1_CR &= ~GPT_CR_EN; // Reset timer
GPT1_PR = 24;
GPT1_CR |= GPT_CR_EN;
spindle_encoder.timer.last_pulse =
spindle_encoder.timer.last_index = GPT1_CNT;
spindle_encoder.timer.pulse_length =
spindle_encoder.counter.last_count =
spindle_encoder.counter.last_index =
spindle_encoder.counter.pulse_count =
spindle_encoder.counter.index_count =
spindle_encoder.error_count = 0;
// Spindle pulse counter
GPT2_OCR1 = spindle_encoder.tics_per_irq;
GPT2_CR |= GPT_CR_EN;
}
#endif
// end spindle code
#endif
// Start/stop coolant (and mist if enabled).
// coolant_state_t is defined in grbl/coolant_control.h.
static void coolantSetState (coolant_state_t mode)
{
mode.value ^= settings.coolant_invert.mask;
DIGITAL_OUT(Flood, mode.flood);
DIGITAL_OUT(Mist, mode.mist);
}
// Returns coolant state in a coolant_state_t variable.
// coolant_state_t is defined in grbl/coolant_control.h.
static coolant_state_t coolantGetState (void)
{
coolant_state_t state = {0};
state.flood = (Flood.reg->DR & Flood.bit) != 0;
state.mist = (Mist.reg->DR & Mist.bit) != 0;
state.value ^= settings.coolant_invert.mask;
return state;
}
#if ETHERNET_ENABLE
static void reportIP (bool newopt)
{
if(!newopt && (services.telnet || services.websocket)) {
hal.stream.write("[NETCON:");
hal.stream.write(services.telnet ? "Telnet" : "Websocket");
hal.stream.write("]" ASCII_EOL);
}
}
#endif
// Helper functions for setting/clearing/inverting individual bits atomically (uninterruptable)
static void bitsSetAtomic (volatile uint_fast16_t *ptr, uint_fast16_t bits)
{
__disable_irq();
*ptr |= bits;
__enable_irq();
}
static uint_fast16_t bitsClearAtomic (volatile uint_fast16_t *ptr, uint_fast16_t bits)
{
__disable_irq();
uint_fast16_t prev = *ptr;
*ptr &= ~bits;
__enable_irq();
return prev;
}
static uint_fast16_t valueSetAtomic (volatile uint_fast16_t *ptr, uint_fast16_t value)
{
__disable_irq();
uint_fast16_t prev = *ptr;
*ptr = value;
__enable_irq();
return prev;
}
static void enable_irq (void)
{
__enable_irq();
}
static void disable_irq (void)
{
__disable_irq();
}
// Configures perhipherals when settings are initialized or changed
static void settings_changed (settings_t *settings)
{
if(IOInitDone) {
stepperEnable(settings->steppers.deenergize);
#ifdef SQUARING_ENABLED
hal.stepper.disable_motors((axes_signals_t){0}, SquaringMode_Both);
#endif
#if !PLASMA_ENABLE && !defined(SPINDLE_RPM_CONTROLLED)
if(hal.driver_cap.variable_spindle && spindle_precompute_pwm_values(&spindle_pwm, F_BUS_ACTUAL / 2)) {
#if SPINDLEPWMPIN == 12
TMR1_COMP11 = spindle_pwm.period;
TMR1_CMPLD11 = spindle_pwm.period;
#else // 13
TMR2_COMP10 = spindle_pwm.period;
TMR2_CMPLD10 = spindle_pwm.period;
#endif
hal.spindle.set_state = spindleSetStateVariable;
} else
hal.spindle.set_state = spindleSetState;
#elif !VFD_SPINDLE
hal.spindle.set_state = spindleSetState;
#endif
#if SPINDLE_SYNC_ENABLE
if((hal.spindle.get_data = settings->spindle.ppr > 0 ? spindleGetData : NULL) && spindle_encoder.ppr != settings->spindle.ppr) {
hal.spindle.reset_data = spindleDataReset;
hal.spindle.set_state((spindle_state_t){0}, 0.0f);
pidf_init(&spindle_tracker.pid, &settings->position.pid);
float timer_resolution = 1.0f / 1000000.0f; // 1 us resolution
spindle_tracker.min_cycles_per_tick = (int32_t)ceilf(settings->steppers.pulse_microseconds * 2.0f + settings->steppers.pulse_delay_microseconds);
spindle_encoder.ppr = settings->spindle.ppr;
spindle_encoder.tics_per_irq = max(1, spindle_encoder.ppr / 32);
spindle_encoder.pulse_distance = 1.0f / spindle_encoder.ppr;
spindle_encoder.maximum_tt = (uint32_t)(2.0f / timer_resolution) / spindle_encoder.tics_per_irq;
spindle_encoder.rpm_factor = 60.0f / ((timer_resolution * (float)spindle_encoder.ppr));
spindleDataReset();
}
#endif
// Stepper pulse timeout setup.
TMR4_CSCTRL0 &= ~(TMR_CSCTRL_TCF1|TMR_CSCTRL_TCF2);
pulse_length = (uint16_t)((float)F_BUS_MHZ * (settings->steppers.pulse_microseconds - STEP_PULSE_LATENCY));
if(hal.driver_cap.step_pulse_delay && settings->steppers.pulse_delay_microseconds > 0.0f) {
float delay = settings->steppers.pulse_delay_microseconds - STEP_PULSE_LATENCY;
if(delay <= STEP_PULSE_LATENCY)
delay = STEP_PULSE_LATENCY + 0.2f;
pulse_delay = (uint16_t)((float)F_BUS_MHZ * delay);
hal.stepper.pulse_start = stepperPulseStartDelayed;
} else
hal.stepper.pulse_start = stepperPulseStart;
TMR4_COMP10 = pulse_length;
TMR4_CSCTRL0 &= ~TMR_CSCTRL_TCF2EN;
TMR4_CTRL0 &= ~TMR_CTRL_OUTMODE(0b000);
attachInterruptVector(IRQ_QTIMER4, stepper_pulse_isr);
#if PLASMA_ENABLE
TMR2_CSCTRL0 &= ~(TMR_CSCTRL_TCF1|TMR_CSCTRL_TCF2);
TMR2_COMP10 = pulse_length;
TMR2_CSCTRL0 &= ~TMR_CSCTRL_TCF2EN;
TMR2_CTRL0 &= ~TMR_CTRL_OUTMODE(0b000);
#endif
/****************************************
* Control, limit & probe pins config *
****************************************/
control_signals_t control_fei;
control_fei.mask = settings->control_disable_pullup.mask ^ settings->control_invert.mask;
axes_signals_t limit_fei;
limit_fei.mask = settings->limits.disable_pullup.mask ^ settings->limits.invert.mask;
bool pullup;
uint32_t i = sizeof(inputpin) / sizeof(input_signal_t);
input_signal_t *signal;
NVIC_DISABLE_IRQ(IRQ_GPIO6789);
do {
pullup = true;
signal = &inputpin[--i];
signal->irq_mode = IRQ_Mode_None;
switch(signal->id) {
#if ESTOP_ENABLE
case Input_EStop:
pullup = !settings->control_disable_pullup.e_stop;
signal->debounce = hal.driver_cap.software_debounce;
signal->irq_mode = control_fei.e_stop ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#else
case Input_Reset:
pullup = !settings->control_disable_pullup.reset;
signal->debounce = hal.driver_cap.software_debounce;
signal->irq_mode = control_fei.reset ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#endif
case Input_FeedHold:
pullup = !settings->control_disable_pullup.feed_hold;
signal->debounce = hal.driver_cap.software_debounce;
signal->irq_mode = control_fei.feed_hold ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
case Input_CycleStart:
pullup = !settings->control_disable_pullup.cycle_start;
signal->debounce = hal.driver_cap.software_debounce;
signal->irq_mode = control_fei.cycle_start ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#if SAFETY_DOOR_ENABLE
case Input_SafetyDoor:
pullup = !settings->control_disable_pullup.safety_door_ajar;
signal->debounce = hal.driver_cap.software_debounce;
signal->irq_mode = control_fei.safety_door_ajar ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#endif
#ifdef LIMITS_OVERRIDE_PIN
case Input_LimitsOverride:
pullup = true;
signal->debounce = false;
break;
#endif
case Input_Probe:
pullup = hal.driver_cap.probe_pull_up;
break;
case Input_LimitX:
case Input_LimitX_Max:
pullup = !settings->limits.disable_pullup.x;
signal->debounce = hal.driver_cap.software_debounce;
signal->irq_mode = limit_fei.x ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
case Input_LimitY:
case Input_LimitY_Max:
pullup = !settings->limits.disable_pullup.y;
signal->irq_mode = limit_fei.y ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
case Input_LimitZ:
case Input_LimitZ_Max:
pullup = !settings->limits.disable_pullup.z;
signal->irq_mode = limit_fei.z ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#ifdef A_LIMIT_PIN
case Input_LimitA:
case Input_LimitA_Max:
pullup = !settings->limits.disable_pullup.a;
signal->irq_mode = limit_fei.a ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#endif
#ifdef B_LIMIT_PIN
case Input_LimitB:
case Input_LimitB_Max:
pullup = !settings->limits.disable_pullup.b;
signal->irq_mode = limit_fei.b ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#endif
#ifdef C_LIMIT_PIN
case Input_LimitC:
case Input_LimitC_Max:
pullup = !settings->limits.disable_pullup.b;
signal->irq_mode = limit_fei.b ? IRQ_Mode_Falling : IRQ_Mode_Rising;
break;
#endif
#if MPG_MODE_ENABLE
case Input_ModeSelect:
signal->irq_mode = IRQ_Mode_Change;
break;
#endif
#if KEYPAD_ENABLE
case Input_KeypadStrobe:
pullup = true;
signal->irq_mode = IRQ_Mode_Change;
break;
#endif
#ifdef SPINDLE_INDEX_PIN
case Input_SpindleIndex:
pullup = false;
signal->irq_mode = IRQ_Mode_Rising;
break;
#endif
#if QEI_ENABLE
case Input_QEI_A:
if(qei_enable)
signal->irq_mode = IRQ_Mode_Change;
break;
case Input_QEI_B:
if(qei_enable)
signal->irq_mode = IRQ_Mode_Change;
break;
#if QEI_INDEX_ENABLED
case Input_QEI_Index:
if(qei_enable)
signal->irq_mode = IRQ_Mode_None;
break;
#endif
#if QEI_SELECT_ENABLED
case Input_QEI_Select:
signal->debounce = hal.driver_cap.software_debounce;
if(qei_enable)
signal->irq_mode = IRQ_Mode_Falling;
break;
#endif
#endif
default:
break;
}
pinMode(signal->pin, pullup ? INPUT_PULLUP : INPUT_PULLDOWN);
signal->gpio.reg = (gpio_reg_t *)digital_pin_to_info_PGM[signal->pin].reg;
signal->gpio.bit = digital_pin_to_info_PGM[signal->pin].mask;
if(signal->port != NULL)
memcpy(signal->port, &signal->gpio, sizeof(gpio_t));
if(signal->irq_mode != IRQ_Mode_None) {
if(signal->gpio.reg == (gpio_reg_t *)&GPIO6_DR)
signal->offset = 0;
else if(signal->gpio.reg == (gpio_reg_t *)&GPIO7_DR)
signal->offset = 1;
else if(signal->gpio.reg == (gpio_reg_t *)&GPIO8_DR)
signal->offset = 2;
else
signal->offset = 3;
if(signal->irq_mode == IRQ_Mode_Change)
signal->gpio.reg->EDGE_SEL |= signal->gpio.bit;
else {
signal->gpio.reg->EDGE_SEL &= ~signal->gpio.bit;
uint32_t iopin = __builtin_ctz(signal->gpio.bit);
if(iopin < 16) {
uint32_t shift = iopin << 1;
signal->gpio.reg->ICR1 = (signal->gpio.reg->ICR1 & ~(0b11 << shift)) | (signal->irq_mode << shift);
} else {
uint32_t shift = (iopin - 16) << 1;
signal->gpio.reg->ICR2 = (signal->gpio.reg->ICR2 & ~(0b11 << shift)) | (signal->irq_mode << shift);
}
}
signal->gpio.reg->ISR = signal->gpio.bit; // Clear interrupt.
if(signal->group != PinGroup_Limit) // If pin is not a limit pin
signal->gpio.reg->IMR |= signal->gpio.bit; // enable interrupt
signal->active = (signal->gpio.reg->DR & signal->gpio.bit) != 0;
if(signal->irq_mode != IRQ_Mode_Change)
signal->active = signal->active ^ (signal->irq_mode == IRQ_Mode_Falling ? 0 : 1);
}
} while(i);
NVIC_ENABLE_IRQ(IRQ_GPIO6789);
}
}
void pinModeOutput (gpio_t *gpio, uint8_t pin)
{
pinMode(pin, OUTPUT);
gpio->reg = (gpio_reg_t *)digital_pin_to_info_PGM[pin].reg;
gpio->bit = digital_pin_to_info_PGM[pin].mask;
}
#if QEI_ENABLE
static void qei_update (void)
{
const uint8_t encoder_valid_state[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
uint_fast8_t idx;
qei_state_t state = {0};
state.a = (QEI_A.reg->DR & QEI_A.bit) != 0;
state.b = (QEI_B.reg->DR & QEI_B.bit) != 0;
idx = (((qei.state << 2) & 0x0F) | state.pins);
if(encoder_valid_state[idx] ) {
qei.state = ((qei.state << 4) | idx) & 0xFF;
if (qei.state == 0x42 || qei.state == 0xD4 || qei.state == 0x2B || qei.state == 0xBD) {
qei.count--;
if(qei.vel_timeout == 0) {
qei.encoder.event.position_changed = hal.encoder.on_event != NULL;
hal.encoder.on_event(&qei.encoder, qei.count);
}
} else if(qei.state == 0x81 || qei.state == 0x17 || qei.state == 0xE8 || qei.state == 0x7E) {
qei.count++;
if(qei.vel_timeout == 0) {
qei.encoder.event.position_changed = hal.encoder.on_event != NULL;
hal.encoder.on_event(&qei.encoder, qei.count);
}
}
}
}
static void qei_reset (uint_fast8_t id)
{
qei.vel_timeout = 0;
qei.count = qei.vel_count = 0;
qei.vel_timestamp = millis();
qei.vel_timeout = qei.encoder.axis != 0xFF ? QEI_VELOCITY_TIMEOUT : 0;
}
// dummy handler, called on events if plugin init fails
static void encoder_event (encoder_t *encoder, int32_t position)
{
UNUSED(position);
encoder->event.events = 0;
}
#endif
// Initializes MCU peripherals for Grbl use
static bool driver_setup (settings_t *settings)
{
#if TRINAMIC_ENABLE && defined(BOARD_CNC_BOOSTERPACK) // Trinamic BoosterPack does not support mixed drivers
driver_settings.trinamic.driver_enable.mask = AXES_BITMASK;
#endif
/******************
* Stepper init *
******************/
PIT_MCR = 0x00;
CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);
attachInterruptVector(IRQ_PIT, stepper_driver_isr);
NVIC_SET_PRIORITY(IRQ_PIT, 2);
NVIC_ENABLE_IRQ(IRQ_PIT);
TMR4_ENBL = 0;
TMR4_LOAD0 = 0;
TMR4_CTRL0 = TMR_CTRL_PCS(0b1000) | TMR_CTRL_ONCE | TMR_CTRL_LENGTH;
TMR4_CSCTRL0 = TMR_CSCTRL_TCF1EN;
attachInterruptVector(IRQ_QTIMER4, stepper_pulse_isr);
NVIC_SET_PRIORITY(IRQ_QTIMER4, 0);
NVIC_ENABLE_IRQ(IRQ_QTIMER4);
TMR4_ENBL = 1;
#if PLASMA_ENABLE
TMR2_ENBL = 0;
TMR2_LOAD0 = 0;
TMR2_CTRL0 = TMR_CTRL_PCS(0b1000) | TMR_CTRL_ONCE | TMR_CTRL_LENGTH;
TMR2_CSCTRL0 = TMR_CSCTRL_TCF1EN;
attachInterruptVector(IRQ_QTIMER2, output_pulse_isr);
NVIC_SET_PRIORITY(IRQ_QTIMER2, 0);
NVIC_ENABLE_IRQ(IRQ_QTIMER2);
TMR2_ENBL = 1;
#endif
pinModeOutput(&stepX, X_STEP_PIN);
pinModeOutput(&dirX, X_DIRECTION_PIN);
#ifdef X_ENABLE_PIN
pinModeOutput(&enableX, X_ENABLE_PIN);
#endif
#ifdef X2_STEP_PIN
pinModeOutput(&stepX2, X2_STEP_PIN);
#endif
#ifdef X2_DIRECTION_PIN
pinModeOutput(&dirX2, X2_DIRECTION_PIN);
#endif
#ifdef X2_ENABLE_PIN
pinModeOutput(&enableX2, X2_ENABLE_PIN);
#endif
pinModeOutput(&stepY, Y_STEP_PIN);
pinModeOutput(&dirY, Y_DIRECTION_PIN);
#ifdef Y_ENABLE_PIN
pinModeOutput(&enableY, Y_ENABLE_PIN);
#endif
#ifdef Y2_STEP_PIN
pinModeOutput(&stepY2, Y2_STEP_PIN);
#endif
#ifdef Y2_DIRECTION_PIN
pinModeOutput(&dirY2, Y2_DIRECTION_PIN);
#endif
#ifdef Y2_ENABLE_PIN
pinModeOutput(&enableY2, Y2_ENABLE_PIN);
#endif
pinModeOutput(&stepZ, Z_STEP_PIN);
pinModeOutput(&dirZ, Z_DIRECTION_PIN);
#ifdef Z_ENABLE_PIN
pinModeOutput(&enableZ, Z_ENABLE_PIN);
#endif
#ifdef Z2_STEP_PIN
pinModeOutput(&stepZ2, Z2_STEP_PIN);
#endif
#ifdef Z2_DIRECTION_PIN
pinModeOutput(&dirZ2, Z2_DIRECTION_PIN);
#endif
#ifdef Z2_ENABLE_PIN
pinModeOutput(&enableZ2, Z2_ENABLE_PIN);
#endif
#ifdef A_AXIS
pinModeOutput(&stepA, A_STEP_PIN);
pinModeOutput(&dirA, A_DIRECTION_PIN);
#ifdef A_ENABLE_PIN
pinModeOutput(&enableA, A_ENABLE_PIN);
#endif
#endif
#ifdef B_AXIS
pinModeOutput(&stepB, B_STEP_PIN);
pinModeOutput(&dirB, B_DIRECTION_PIN);
#ifdef B_ENABLE_PIN
pinModeOutput(&enableB, B_ENABLE_PIN);
#endif
#endif
#ifdef C_AXIS
pinModeOutput(&stepC, C_STEP_PIN);
pinModeOutput(&dirC, C_DIRECTION_PIN);
#ifdef C_ENABLE_PIN
pinModeOutput(&enableC, C_ENABLE_PIN);
#endif
#endif
#ifdef STEPPERS_ENABLE_PIN
pinModeOutput(&steppersEnable, STEPPERS_ENABLE_PIN);
#endif
/****************************
* Software debounce init *
****************************/
if(hal.driver_cap.software_debounce) {
TMR3_ENBL = 0;
TMR3_LOAD0 = 0;
TMR3_CTRL0 = TMR_CTRL_PCS(0b1111) | TMR_CTRL_ONCE | TMR_CTRL_LENGTH;
TMR3_COMP10 = (uint16_t)((40000UL * F_BUS_MHZ) / 128); // 150 MHz -> 40ms
TMR3_CSCTRL0 = TMR_CSCTRL_TCF1EN;
attachInterruptVector(IRQ_QTIMER3, debounce_isr);
NVIC_SET_PRIORITY(IRQ_QTIMER3, 4);
NVIC_ENABLE_IRQ(IRQ_QTIMER3);
TMR3_ENBL = 1;
}
/***********************
* Control pins init *
***********************/
attachInterruptVector(IRQ_GPIO6789, gpio_isr);
/***********************
* Coolant pins init *
***********************/
pinModeOutput(&Flood, COOLANT_FLOOD_PIN);
pinModeOutput(&Mist, COOLANT_MIST_PIN);
#if !VFD_SPINDLE
/******************
* Spindle init *
******************/
pinModeOutput(&spindleEnable, SPINDLE_ENABLE_PIN);
pinModeOutput(&spindleDir, SPINDLE_DIRECTION_PIN);
#if !PLASMA_ENABLE
#if SPINDLEPWMPIN == 12
TMR1_ENBL = 0;
TMR1_LOAD1 = 0;
TMR1_CTRL1 = TMR_CTRL_PCS(0b1001) | TMR_CTRL_OUTMODE(0b100) | TMR_CTRL_LENGTH;
TMR1_SCTRL1 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; // set TMR_SCTRL_VAL || TMR_SCTRL_OPS if inverted PWM
TMR1_ENBL = 1 << 1;
#else // 13
TMR2_ENBL = 0;
TMR2_LOAD0 = 0;
TMR2_CTRL0 = TMR_CTRL_PCS(0b1001) | TMR_CTRL_OUTMODE(0b100) | TMR_CTRL_LENGTH;
TMR2_SCTRL0 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; // set TMR_SCTRL_VAL || TMR_SCTRL_OPS if inverted PWM
TMR2_ENBL = 1;
#endif
*(portConfigRegister(SPINDLEPWMPIN)) = 1;
#if SPINDLE_SYNC_ENABLE
CCM_CCGR1 |= CCM_CCGR1_GPT1_BUS(CCM_CCGR_ON);
CCM_CMEOR |= CCM_CMEOR_MOD_EN_OV_GPT;
// Free running timer
GPT1_CR = 0;
GPT1_CR |= GPT_CR_SWR;
while(GPT1_CR & GPT_CR_SWR);
GPT1_CR = GPT_CR_CLKSRC(1);
GPT1_CR |= GPT_CR_FRR|GPT_CR_ENMOD|GPT_CR_EN;
GPT1_PR = 150;
#if SPINDLE_PULSE_PIN == 14
CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON);
IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8;
IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02 = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
// IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02 = 0x001d0b0;
IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;
// Spindle pulse counter
GPT2_CR = 0;
GPT2_CR |= GPT_CR_SWR;
while(GPT2_CR & GPT_CR_SWR);
GPT2_CR = GPT_CR_CLKSRC(3);
GPT2_CR |= GPT_CR_ENMOD;
GPT2_CR |= GPT_CR_FRR|GPT_CR_EN;
GPT2_OCR1 = spindle_encoder.tics_per_irq;
GPT2_IR = GPT_IR_OF1IE;
attachInterruptVector(IRQ_GPT2, spindle_pulse_isr);
NVIC_SET_PRIORITY(IRQ_GPT2, 1);
NVIC_ENABLE_IRQ(IRQ_GPT2);
#endif
#endif // SPINDLE_SYNC_ENABLE
#if PPI_ENABLE
PPI_TIMER.ENBL = 0;
PPI_TIMER.CH[0].LOAD = 0;
PPI_TIMER.CH[0].COMP1 = (uint16_t)((1500UL * F_BUS_MHZ) / 128);
PPI_TIMER.CH[0].CTRL = TMR_CTRL_PCS(0b1111) | TMR_CTRL_ONCE | TMR_CTRL_LENGTH;
PPI_TIMER.CH[0].CSCTRL = TMR_CSCTRL_TCF1EN;
attachInterruptVector(PPI_TIMERIRQ, ppi_timeout_isr);
NVIC_SET_PRIORITY(PPI_TIMERIRQ, 3);
NVIC_ENABLE_IRQ(PPI_TIMERIRQ);
PPI_TIMER.ENBL = 1;
ppi_init();
#endif // PPI_ENABLE
#endif // !PLASMA_ENABLE
#endif // !VFD_SPINDLE
// Set defaults
IOInitDone = settings->version == 19;
hal.settings_changed(settings);
hal.stepper.go_idle(true);
#if IOPORTS_ENABLE
ioports_init();
#endif
#if SDCARD_ENABLE
sdcard_init();
#endif
#if ETHERNET_ENABLE
grbl_enet_start();
#endif
#if QEI_ENABLE
if(qei_enable)
encoder_start(&qei.encoder);
#endif
return IOInitDone;
}
#if EEPROM_ENABLE == 0
// EEPROM emulation - stores settings in flash
bool nvsRead (uint8_t *dest)
{
// assert size ? E2END
eeprom_read_block(dest, 0, hal.nvs.size);
return true; //?;
}
bool nvsWrite (uint8_t *source)
{
eeprom_write_block(source, 0, hal.nvs.size);
return true; //?;
}
// End EEPROM emulation
#endif
#if ETHERNET_ENABLE || ADD_MSEVENT
static void execute_realtime (uint_fast16_t state)
{
#if ADD_MSEVENT
if(ms_event) {
ms_event = false;
#if USB_SERIAL_CDC
usb_execute_realtime(state);
#endif
#if QEI_ENABLE
if(qei.vel_timeout && !(--qei.vel_timeout)) {
qei.encoder.velocity = abs(qei.count - qei.vel_count) * 1000 / (millis() - qei.vel_timestamp);
qei.vel_timestamp = millis();
qei.vel_timeout = QEI_VELOCITY_TIMEOUT;
if((qei.encoder.event.position_changed = !qei.dbl_click_timeout || qei.encoder.velocity == 0))
hal.encoder.on_event(&qei.encoder, qei.count);
qei.vel_count = qei.count;
}
if(qei.dbl_click_timeout && !(--qei.dbl_click_timeout)) {
qei.encoder.event.click = On;
hal.encoder.on_event(&qei.encoder, qei.count);
}
#endif
}
#endif // ADD_MSEVENT
#if ETHERNET_ENABLE
grbl_enet_poll();
#endif
}
#endif
#ifdef DEBUGOUT
void debugOut (bool on)
{
digitalWrite(13, on); // LED
}
#endif
#ifdef UART_DEBUG
void uart_debug_write (char *s)
{
serialWriteS(s);
while(serialTxCount()); // Wait until message is delivered
}
#endif
// Cold restart (T4.x has no reset button)
static void reboot (void)
{
SCB_AIRCR = 0x05FA0004;
}
// Initialize HAL pointers, setup serial comms and enable EEPROM.
// NOTE: Grbl is not yet configured (from EEPROM data), driver_setup() will be called when done.
bool driver_init (void)
{
static char options[30];
// Chain our systick isr to the Arduino handler
if(systick_isr_org == NULL)
systick_isr_org = _VectorsRam[15];
_VectorsRam[15] = systick_isr;
// Enable lazy stacking of FPU registers here if a FPU is available.
// FPU->FPCCR = (FPU->FPCCR & ~FPU_FPCCR_LSPEN_Msk) | FPU_FPCCR_ASPEN_Msk; // enable lazy stacking
options[0] = '\0';
#if USB_SERIAL_CDC == 1
strcat(options, "USB.1 ");
#endif
#if USB_SERIAL_CDC == 2
strcat(options, "USB.2 ");
#endif
if(*options != '\0')
options[strlen(options) - 1] = '\0';
hal.info = "iMXRT1062";
hal.driver_version = "210414";
#ifdef BOARD_NAME
hal.board = BOARD_NAME;
#endif
hal.driver_options = *options == '\0' ? NULL : options;
hal.driver_setup = driver_setup;
hal.f_step_timer = 24000000;
hal.rx_buffer_size = RX_BUFFER_SIZE;
hal.delay_ms = driver_delay_ms;
hal.settings_changed = settings_changed;
hal.stepper.wake_up = stepperWakeUp;
hal.stepper.go_idle = stepperGoIdle;
hal.stepper.enable = stepperEnable;
hal.stepper.cycles_per_tick = stepperCyclesPerTick;
hal.stepper.pulse_start = stepperPulseStart;
#ifdef SQUARING_ENABLED
hal.stepper.get_auto_squared = getAutoSquaredAxes;
hal.stepper.disable_motors = StepperDisableMotors;
#endif
hal.limits.enable = limitsEnable;
hal.limits.get_state = limitsGetState;
hal.homing.get_state = limitsGetState;
hal.coolant.set_state = coolantSetState;
hal.coolant.get_state = coolantGetState;
hal.probe.configure = probeConfigure;
hal.probe.get_state = probeGetState;
#if !VFD_SPINDLE
hal.spindle.set_state = spindleSetState;
hal.spindle.get_state = spindleGetState;
#ifdef SPINDLE_PWM_DIRECT
hal.spindle.get_pwm = spindleGetPWM;
hal.spindle.update_pwm = spindle_set_speed;
#else
hal.spindle.update_rpm = spindleUpdateRPM;
#endif
#if PPI_ENABLE
hal.spindle.pulse_on = spindlePulseOn;
#endif
#endif
#if SPINDLE_SYNC_ENABLE
hal.driver_cap.spindle_sync = On;
hal.driver_cap.spindle_at_speed = On;
#endif
hal.control.get_state = systemGetState;
#if ETHERNET_ENABLE
grbl.on_report_options = reportIP;
#endif
#if USB_SERIAL_CDC
usb_serialInit();
#else
serialInit(115200);
#endif
selectStream(StreamType_Serial);
#ifdef I2C_PORT
i2c_init();
#endif
#if EEPROM_ENABLE
i2c_eeprom_init();
#else // use Arduino emulated EEPROM in flash
eeprom_initialize();
hal.nvs.type = NVS_Flash;
hal.nvs.memcpy_from_flash = nvsRead;
hal.nvs.memcpy_to_flash = nvsWrite;
#endif
hal.reboot = reboot;
hal.irq_enable = enable_irq;
hal.irq_disable = disable_irq;
hal.set_bits_atomic = bitsSetAtomic;
hal.clear_bits_atomic = bitsClearAtomic;
hal.set_value_atomic = valueSetAtomic;
hal.get_elapsed_ticks = millis;
#if ETHERNET_ENABLE || ADD_MSEVENT
grbl.on_execute_realtime = execute_realtime;
#endif
#if QEI_ENABLE
hal.encoder.reset = qei_reset;
hal.encoder.on_event = encoder_event;
#endif
#if MODBUS_ENABLE
modbus_stream.write = serialWrite;
modbus_stream.read = serialGetC;
modbus_stream.flush_rx_buffer = serialRxFlush;
modbus_stream.flush_tx_buffer = serialTxFlush;
modbus_stream.get_rx_buffer_count = serialRxCount;
modbus_stream.get_tx_buffer_count = serialTxCount;
modbus_stream.set_baud_rate = serialSetBaudRate;
bool modbus = modbus_init(&modbus_stream);
#if SPINDLE_HUANYANG
if(modbus)
huanyang_init(&modbus_stream);
#endif
#endif
// Driver capabilities, used for announcing and negotiating (with Grbl) driver functionality.
// See driver_cap_t union i grbl/hal.h for available flags.
#if ESTOP_ENABLE
hal.signals_cap.e_stop = On;
#endif
#if SAFETY_DOOR_ENABLE
hal.signals_cap.safety_door_ajar = On;
#endif
#ifdef LIMITS_OVERRIDE_PIN
hal.signals_cap.limits_override = On;
#endif
#if !VFD_SPINDLE && !PLASMA_ENABLE
#ifdef SPINDLE_DIRECTION_PIN
hal.driver_cap.spindle_dir = On;
#endif
hal.driver_cap.variable_spindle = On;
#endif
#ifdef COOLANT_MIST_PIN
hal.driver_cap.mist_control = On;
#endif
hal.driver_cap.software_debounce = On;
hal.driver_cap.step_pulse_delay = On;
hal.driver_cap.amass_level = 3;
hal.driver_cap.control_pull_up = On;
hal.driver_cap.limits_pull_up = On;
hal.driver_cap.probe_pull_up = On;
#ifdef DEBUGOUT
hal.debug_out = debugOut;
#endif
#ifdef UART_DEBUG
serialInit(115200);
uart_debug_write(ASCII_EOL "UART Debug:" ASCII_EOL);
#endif
uint32_t i;
input_signal_t *signal;
for(i = 0 ; i < sizeof(inputpin) / sizeof(input_signal_t); i++) {
signal = &inputpin[i];
if(signal->group == PinGroup_AuxInput) {
if(aux_inputs.pins == NULL)
aux_inputs.pins = signal;
aux_inputs.n_pins++;
}
if(signal->group == PinGroup_Limit) {
if(limit_inputs.pins == NULL)
limit_inputs.pins = signal;
limit_inputs.n_pins++;
}
}
#ifdef HAS_BOARD_INIT
board_init(&aux_inputs);
#endif
#if ETHERNET_ENABLE
grbl_enet_init();
#endif
#if KEYPAD_ENABLE
keypad_init();
#endif
#if QEI_ENABLE
qei_enable = encoder_init(QEI_ENABLE);
#endif
#if PLASMA_ENABLE
hal.stepper.output_step = stepperOutputStep;
plasma_init();
#endif
my_plugin_init();
#if ODOMETER_ENABLE
odometer_init(); // NOTE: this *must* be last plugin to be initialized as it claims storage at the end of NVS.
#endif
// No need to move version check before init.
// Compiler will fail any signature mismatch for existing entries.
return hal.version == 8;
}
/* interrupt handlers */
// Main stepper driver.
static void stepper_driver_isr (void)
{
if(PIT_TFLG0 & PIT_TFLG_TIF) {
PIT_TFLG0 |= PIT_TFLG_TIF;
hal.stepper.interrupt_callback();
}
}
/* The Stepper Port Reset Interrupt: This interrupt handles the falling edge of the step
pulse. This should always trigger before the next general stepper driver interrupt and independently
finish, if stepper driver interrupts is disabled after completing a move.
NOTE: Interrupt collisions between the serial and stepper interrupts can cause delays by
a few microseconds, if they execute right before one another. Not a big deal, but can
cause issues at high step rates if another high frequency asynchronous interrupt is
added to Grbl.
*/
// This interrupt is enabled when Grbl sets the motor port bits to execute
// a step. This ISR resets the motor port after a short period (settings.pulse_microseconds)
// completing one step cycle.
static void stepper_pulse_isr (void)
{
TMR4_CSCTRL0 &= ~TMR_CSCTRL_TCF1;
set_step_outputs((axes_signals_t){0});
}
static void stepper_pulse_isr_delayed (void)
{
TMR4_CSCTRL0 &= ~TMR_CSCTRL_TCF1;
set_step_outputs(next_step_outbits);
attachInterruptVector(IRQ_QTIMER4, stepper_pulse_isr);
TMR4_COMP10 = pulse_length;
TMR4_CTRL0 |= TMR_CTRL_CM(0b001);
}
#if SPINDLE_SYNC_ENABLE && SPINDLE_PULSE_PIN == 14
static void spindle_pulse_isr (void)
{
uint32_t tval = GPT1_CNT;
GPT2_SR |= GPT_SR_OF1; // clear interrupt flag
GPT2_OCR1 += spindle_encoder.tics_per_irq;
spindleLock = true;
spindle_encoder.counter.pulse_count = GPT2_CNT;
spindle_encoder.counter.last_count = spindle_encoder.counter.pulse_count;
spindle_encoder.timer.pulse_length = tval - spindle_encoder.timer.last_pulse;
spindle_encoder.timer.last_pulse = tval;
spindleLock = false;
}
#endif
#if PLASMA_ENABLE
static void output_pulse_isr(void)
{
axes_signals_t output = {pulse_output.mask ^ settings.steppers.dir_invert.mask};
TMR2_CSCTRL0 &= ~TMR_CSCTRL_TCF1;
DIGITAL_OUT(stepZ, output.z);
if(pulse_output.value) {
pulse_output.value = 0;
TMR2_CTRL0 |= TMR_CTRL_CM(0b001);
}
}
#endif
#if PPI_ENABLE
// Switches off the spindle (laser) after laser.pulse_length time has elapsed
static void ppi_timeout_isr (void)
{
PPI_TIMER.CH[0].CSCTRL &= ~TMR_CSCTRL_TCF1;
spindle_off();
}
#endif
inline static bool enqueue_debounce (input_signal_t *signal)
{
bool ok;
uint_fast8_t bptr = (debounce_queue.head + 1) & (DEBOUNCE_QUEUE - 1);
if((ok = bptr != debounce_queue.tail)) {
debounce_queue.signal[debounce_queue.head] = signal;
debounce_queue.head = bptr;
}
return ok;
}
// Returns NULL if no debounce checks enqueued
inline static input_signal_t *get_debounce (void)
{
input_signal_t *signal = NULL;
uint_fast8_t bptr = debounce_queue.tail;
if(bptr != debounce_queue.head) {
signal = debounce_queue.signal[bptr++];
debounce_queue.tail = bptr & (DEBOUNCE_QUEUE - 1);
}
return signal;
}
static void debounce_isr (void)
{
uint8_t grp = 0;
input_signal_t *signal;
TMR3_CSCTRL0 &= ~TMR_CSCTRL_TCF1;
while((signal = get_debounce())) {
signal->gpio.reg->IMR |= signal->gpio.bit;
if(((signal->gpio.reg->DR & signal->gpio.bit) != 0) == (signal->irq_mode == IRQ_Mode_Falling ? 0 : 1))
grp |= signal->group;
}
if(grp & PinGroup_Limit)
hal.limits.interrupt_callback(limitsGetState());
if(grp & PinGroup_Control)
hal.control.interrupt_callback(systemGetState());
#if QEI_SELECT_ENABLED
if(grp & PinGroup_QEI_Select) {
if(!qei.dbl_click_timeout)
qei.dbl_click_timeout = qei.encoder.settings->dbl_click_window;
else if(qei.dbl_click_timeout < qei.encoder.settings->dbl_click_window - 40) {
qei.dbl_click_timeout = 0;
qei.encoder.event.dbl_click = On;
hal.encoder.on_event(&qei.encoder, qei.count);
}
}
#endif
}
//GPIO intr process
static void gpio_isr (void)
{
bool debounce = false;
uint8_t grp = 0;
uint32_t intr_status[4];
// Get masked interrupt status
intr_status[0] = ((gpio_reg_t *)&GPIO6_DR)->ISR & ((gpio_reg_t *)&GPIO6_DR)->IMR;
intr_status[1] = ((gpio_reg_t *)&GPIO7_DR)->ISR & ((gpio_reg_t *)&GPIO7_DR)->IMR;
intr_status[2] = ((gpio_reg_t *)&GPIO8_DR)->ISR & ((gpio_reg_t *)&GPIO8_DR)->IMR;
intr_status[3] = ((gpio_reg_t *)&GPIO9_DR)->ISR & ((gpio_reg_t *)&GPIO9_DR)->IMR;
// Clear interrupts
((gpio_reg_t *)&GPIO6_DR)->ISR = intr_status[0];
((gpio_reg_t *)&GPIO7_DR)->ISR = intr_status[1];
((gpio_reg_t *)&GPIO8_DR)->ISR = intr_status[2];
((gpio_reg_t *)&GPIO9_DR)->ISR = intr_status[3];
uint32_t i = sizeof(inputpin) / sizeof(input_signal_t);
do {
if(inputpin[--i].irq_mode != IRQ_Mode_None) {
if(intr_status[inputpin[i].offset] & inputpin[i].gpio.bit) {
inputpin[i].active = true;
if(inputpin[i].debounce && enqueue_debounce(&inputpin[i])) {
inputpin[i].gpio.reg->IMR &= ~inputpin[i].gpio.bit;
debounce = true;
} else {
#if QEI_ENABLE
if(inputpin[i].group & PinGroup_QEI) {
qei_update();
/*
QEI_A.reg->IMR &= ~QEI_A.bit; // Switch off
QEI_B.reg->IMR &= ~QEI_B.bit; // encoder interrupts.
qei.iflags.a = inputpin[i].port == &QEI_A;
qei.iflags.b = inputpin[i].port == &QEI_B;
qei.debounce = QEI_DEBOUNCE;
qei.initial_debounce = true;
*/
} else
#endif
#if SPINDLE_SYNC_ENABLE && defined(SPINDLE_INDEX_PIN)
if(inputpin[i].group & PinGroup_SpindleIndex) {
spindleLock = true;
spindle_encoder.counter.index_count++;
spindle_encoder.counter.last_index = GPT2_CNT;
spindle_encoder.timer.last_index = GPT1_CNT;
spindleLock = false;
} else
#endif
grp |= inputpin[i].group;
}
}
}
} while(i);
if(debounce) {
TMR3_CTRL0 |= TMR_CTRL_CM(0b001);
}
if(grp & PinGroup_Limit) {
limit_signals_t state = limitsGetState();
if(limit_signals_merge(state).value) //TODO: add check for limit switches having same state as when limit_isr were invoked?
hal.limits.interrupt_callback(state);
}
if(grp & PinGroup_Control)
hal.control.interrupt_callback(systemGetState());
#if QEI_SELECT_ENABLED
if(grp & PinGroup_QEI_Select) {
if(!qei.dbl_click_timeout)
qei.dbl_click_timeout = qei.encoder.settings->dbl_click_window;
else if(qei.dbl_click_timeout < qei.encoder.settings->dbl_click_window - 40) {
qei.dbl_click_timeout = 0;
qei.encoder.event.dbl_click = On;
hal.encoder.on_event(&qei.encoder, qei.count);
}
}
#endif
#if MPG_MODE_ENABLE
static bool mpg_mutex = false;
if((grp & PinGroup_MPG) && !mpg_mutex) {
mpg_mutex = true;
modeChange();
// hal.delay_ms(50, modeChange);
mpg_mutex = false;
}
#endif
#if KEYPAD_ENABLE
if(grp & PinGroup_Keypad)
keypad_keyclick_handler(!(KeypadStrobe.reg->DR & KeypadStrobe.bit));
#endif
}
// Interrupt handler for 1 ms interval timer
static void systick_isr (void)
{
systick_isr_org();
#if ADD_MSEVENT
ms_event = true;
#endif
if(grbl_delay.ms && !(--grbl_delay.ms)) {
if(grbl_delay.callback) {
grbl_delay.callback();
grbl_delay.callback = NULL;
}
}
}