grblHAL-myversion/grblHAL_Teensy4/src/ioports.c

303 lines
8.7 KiB
C

/*
ioports.c - driver code for IMXRT1062 processor (on Teensy 4.1 board)
Part of grblHAL
Board by Phil Barrett: https://github.com/phil-barrett/grblHAL-teensy-4.x
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 "driver.h"
#include "mcp3221.h"
#ifdef HAS_IOPORTS
//#include "Arduino.h"
#include <math.h>
#include <string.h>
#include "grbl/protocol.h"
static uint_fast8_t aux_n_in, aux_n_out, analog_n_in;
static volatile uint32_t event_bits;
static volatile bool spin_lock = false;
static input_signal_t *aux_in;
static output_signal_t *aux_out;
static ioport_bus_t out = {0};
static char input_ports[56] = "", output_ports[56] = "";
static void aux_settings_load (void);
static status_code_t aux_set_invert_out (setting_id_t id, uint_fast16_t int_value);
static uint32_t aux_get_invert_out (setting_id_t setting);
static bool is_setting_available (const setting_detail_t *setting);
static const setting_group_detail_t aux_groups[] = {
{ Group_Root, Group_AuxPorts, "Aux ports"}
};
static const setting_detail_t aux_settings[] = {
{ Settings_IoPort_InvertIn, Group_AuxPorts, "Invert I/O Port inputs", NULL, Format_Bitfield, input_ports, NULL, NULL, Setting_NonCore, &settings.ioport.invert_in.mask, NULL, is_setting_available },
{ Settings_IoPort_InvertOut, Group_AuxPorts, "Invert I/O Port outputs", NULL, Format_Bitfield, output_ports, NULL, NULL, Setting_NonCoreFn, aux_set_invert_out, aux_get_invert_out, is_setting_available },
};
#ifndef NO_SETTINGS_DESCRIPTIONS
static const setting_descr_t aux_settings_descr[] = {
{ Settings_IoPort_InvertIn, "Invert IOPort inputs." },
{ Settings_IoPort_InvertOut, "Invert IOPort output." },
};
#endif
static setting_details_t details = {
.groups = aux_groups,
.n_groups = sizeof(aux_groups) / sizeof(setting_group_detail_t),
.settings = aux_settings,
.n_settings = sizeof(aux_settings) / sizeof(setting_detail_t),
#ifndef NO_SETTINGS_DESCRIPTIONS
.descriptions = aux_settings_descr,
.n_descriptions = sizeof(aux_settings_descr) / sizeof(setting_descr_t),
#endif
.load = aux_settings_load,
.save = settings_write_global
};
static bool is_setting_available (const setting_detail_t *setting)
{
bool available = false;
switch(setting->id) {
case Settings_IoPort_InvertIn:
case Settings_IoPort_Pullup_Disable:
available = aux_n_in > 0;
break;
case Settings_IoPort_InvertOut:
case Settings_IoPort_OD_Enable:
available = aux_n_out > 0;
break;
default:
break;
}
return available;
}
static setting_details_t *on_get_settings (void)
{
return &details;
}
static void aux_settings_load (void)
{
uint_fast8_t idx = aux_n_out;
if(aux_n_out) do {
idx--;
pinModeOutput(aux_out[idx].port, aux_out[idx].pin);
DIGITAL_OUT((*(aux_out[idx].port)), (settings.ioport.invert_out.mask >> idx) & 0x01);
} while(idx);
}
static status_code_t aux_set_invert_out (setting_id_t id, uint_fast16_t value)
{
ioport_bus_t invert;
invert.mask = (uint8_t)value & out.mask;
if(invert.mask != settings.ioport.invert_out.mask) {
uint_fast8_t idx = aux_n_out;
do {
idx--;
if(((settings.ioport.invert_out.mask >> idx) & 0x01) != ((invert.mask >> idx) & 0x01))
DIGITAL_OUT((*(aux_out[idx].port)), !DIGITAL_IN((*(aux_out[idx].port))));
} while(idx);
settings.ioport.invert_out.mask = invert.mask;
}
return Status_OK;
}
static uint32_t aux_get_invert_out (setting_id_t setting)
{
return settings.ioport.invert_out.mask;
}
static void digital_out (uint8_t port, bool on)
{
if(port < aux_n_out)
DIGITAL_OUT((*(aux_out[port].port)), ((settings.ioport.invert_out.mask >> port) & 0x01) ? !on : on);
}
inline static __attribute__((always_inline)) int32_t get_input (const input_signal_t *input, bool invert, wait_mode_t wait_mode, float timeout)
{
if(wait_mode == WaitMode_Immediate)
return DIGITAL_IN(input->gpio) ^ invert;
int32_t value = -1;
uint_fast16_t delay = (uint_fast16_t)ceilf((1000.0f / 50.0f) * timeout) + 1;
if(wait_mode == WaitMode_Rise || wait_mode == WaitMode_Fall) {
pin_irq_mode_t irq_mode = wait_mode == WaitMode_Rise ? IRQ_Mode_Rising : IRQ_Mode_Falling;
if(input->cap.irq_mode & irq_mode) {
event_bits &= ~input->gpio.bit;
pinEnableIRQ(input, irq_mode);
do {
if(event_bits & input->gpio.bit) {
value = DIGITAL_IN(input->gpio) ^ invert;
break;
}
if(delay) {
protocol_execute_realtime();
hal.delay_ms(50, NULL);
} else
break;
} while(--delay && !sys.abort);
pinEnableIRQ(input, IRQ_Mode_None); // Restore pin interrupt status
}
} else {
bool wait_for = wait_mode != WaitMode_Low;
do {
if((DIGITAL_IN(input->gpio) ^ invert) == wait_for) {
value = DIGITAL_IN(input->gpio);
break;
}
if(delay) {
protocol_execute_realtime();
hal.delay_ms(50, NULL);
} else
break;
} while(--delay && !sys.abort);
}
return value;
}
void ioports_event (input_signal_t *input)
{
spin_lock = true;
event_bits |= input->gpio.bit;
if(input->interrupt_callback)
input->interrupt_callback(input->id - Output_Aux0, !!(input->port->reg->DR & input->port->bit));
spin_lock = false;
}
static int32_t wait_on_input (bool digital, uint8_t port, wait_mode_t wait_mode, float timeout)
{
int32_t value = -1;
if(digital) {
if(port < aux_n_in)
value = get_input(&aux_in[port], (settings.ioport.invert_in.mask << port) & 0x01, wait_mode, timeout);
}
#if MCP3221_ENABLE
else if(port < analog_n_in)
value = (int32_t)MCP3221_read();
#endif
return value;
}
static bool register_interrupt_handler (uint8_t port, pin_irq_mode_t irq_mode, ioport_interrupt_callback_ptr interrupt_callback)
{
bool ok;
if((ok = port < aux_n_in && aux_in[port].cap.irq_mode != IRQ_Mode_None)) {
input_signal_t *input = &aux_in[port];
if((ok = (irq_mode & aux_in[port].cap.irq_mode) == irq_mode && interrupt_callback != NULL)) {
input->irq_mode = irq_mode;
input->interrupt_callback = interrupt_callback;
pinEnableIRQ(input, irq_mode);
}
if(irq_mode == IRQ_Mode_None || !ok) {
while(spin_lock);
pinEnableIRQ(input, IRQ_Mode_None);
input->irq_mode = IRQ_Mode_None;
input->interrupt_callback = NULL;
}
}
return ok;
}
static void set_pin_description (bool digital, bool output, uint8_t port, const char *s)
{
if(digital) {
if(!output && port < aux_n_in)
aux_in[port].description = s;
if(output && port < aux_n_out)
aux_out[port].description = s;
}
}
void ioports_init (pin_group_pins_t *aux_inputs, pin_group_pins_t *aux_outputs)
{
aux_in = aux_inputs->pins.inputs;
aux_out = aux_outputs->pins.outputs;
if((hal.port.num_digital_in = aux_n_in = aux_inputs->n_pins)) {
hal.port.wait_on_input = wait_on_input;
hal.port.register_interrupt_handler = register_interrupt_handler;
}
if((hal.port.num_digital_out = aux_n_out = aux_outputs->n_pins))
hal.port.digital_out = digital_out;
#if MCP3221_ENABLE
if(MCP3221_init())
hal.port.num_analog_in = analog_n_in = 1;
#endif
hal.port.set_pin_description = set_pin_description;
details.on_get_settings = grbl.on_get_settings;
grbl.on_get_settings = on_get_settings;
uint_fast8_t i;
for(i = 0; i < min(hal.port.num_digital_in, 8); i++) {
strcat(input_ports, i == 0 ? "Port " : ",Port ");
strcat(input_ports, uitoa(i));
}
for(i = 0; i < min(hal.port.num_digital_out, 8) ; i++) {
out.mask = (out.mask << 1) | 1;
strcat(output_ports, i == 0 ? "Port " : ",Port ");
strcat(output_ports, uitoa(i));
}
// analog_init();
}
#endif