From 5249bc66fa3034f22f0c363dc9dc87b2f334a918 Mon Sep 17 00:00:00 2001 From: Terje Io Date: Thu, 25 May 2023 08:57:10 +0200 Subject: [PATCH] Expanded ioports API with some configuration and PWM related functions. --- changelog.md | 12 +++++ crossbar.h | 10 ++-- grbl.h | 2 +- ioports.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++- ioports.h | 31 +++++++++++ 5 files changed, 198 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 02de89d..a9ac033 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,17 @@ ## grblHAL changelog +Build 20230525 + +Core: + +* Expanded ioports API with some configuration and PWM related functions. + +Drivers: + +* RP2040: added low-level ioports driver layer for analog output \(PWM\). NOTE: not yet used by any board map files. + +--- + 20230521 Drivers: diff --git a/crossbar.h b/crossbar.h index 6d1c750..3711615 100644 --- a/crossbar.h +++ b/crossbar.h @@ -343,6 +343,8 @@ typedef enum { PinGroup_StepperStep, PinGroup_StepperDir, PinGroup_AuxOutput, + PinGroup_AuxInputAnalog, + PinGroup_AuxOutputAnalog, PinGroup_SdCard, PinGroup_MotorChipSelect, PinGroup_MotorUART, @@ -409,9 +411,9 @@ typedef enum { #endif #define PINMODE_PULLUP (PullMode_Up<<3) #define PINMODE_PULLDOWN (PullMode_Down<<3) -#define PINMODE_REMAP (1U<<10) -#define PINMODE_PWM (1U<<11) -#define PINMODE_ANALOG (1U<<12) +#define PINMODE_PWM (1U<<10) +#define PINMODE_ANALOG (1U<<11) +#define PINMODE_REMAP (1U<<14) typedef union { uint16_t mask; @@ -433,6 +435,8 @@ typedef union { //! /a cfg_data argument to /a xbar_config_ptr for PWM pins typedef struct { float freq_hz; // + float min; + float max; float off_value; // percent of period float min_value; // percent of period float max_value; // percent of period diff --git a/grbl.h b/grbl.h index 6c95f17..f27cca3 100644 --- a/grbl.h +++ b/grbl.h @@ -42,7 +42,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_BUILD 20230519 +#define GRBL_BUILD 20230524 #define GRBL_URL "https://github.com/grblHAL" diff --git a/ioports.c b/ioports.c index faea7af..096cc15 100644 --- a/ioports.c +++ b/ioports.c @@ -27,9 +27,11 @@ * and advanced functionality simplifying plugin code that uses them. */ +#include + #include "hal.h" -static int16_t digital_in = -1, digital_out = -1, analog_in = -1, analog_out = -1; +static int16_t digital_in = -1, digital_out = -1, analog_in = -1, analog_out = -1; static uint8_t ioports_count (io_port_type_t type, io_port_direction_t dir) { @@ -126,3 +128,147 @@ bool ioport_can_claim_explicit (void) { return !(hal.port.claim == NULL || hal.port.get_pin_info == NULL); } + +/* experimental code follows */ + +static char *get_pnum (io_ports_data_t *ports, uint8_t port) +{ + return ports->pnum ? (ports->pnum + (port * 3) + (port > 9 ? port - 10 : 0)) : NULL; +} + +bool ioports_add (io_ports_data_t *ports, io_port_type_t type, uint8_t n_in, uint8_t n_out) +{ + uint_fast8_t n_ports; + + ports->get_pnum = get_pnum; + + if(type == Port_Digital) { + + if(n_in) { + ports->n_in_start = hal.port.num_digital_in; + hal.port.num_digital_in += (ports->n_in = n_in); + ports->in_map = malloc(ports->n_in * sizeof(ports->n_in)); + } + + if(n_out) { + ports->n_out_start = hal.port.num_digital_out; + hal.port.num_digital_out += (ports->n_out = n_out); + ports->out_map = malloc(ports->n_out * sizeof(ports->n_out)); + } + + } else { + + if(n_in) { + ports->n_in_start = hal.port.num_analog_in; + hal.port.num_analog_in += (ports->n_in = n_in); + ports->in_map = malloc(ports->n_in * sizeof(ports->n_in)); + } + + if(n_out) { + ports->n_out_start = hal.port.num_analog_out; + hal.port.num_analog_out += (ports->n_out = n_out); + ports->out_map = malloc(ports->n_out * sizeof(ports->n_out)); + } + } + + if((n_ports = max(ports->n_in, ports->n_out)) > 0) { + + char *pn; + uint_fast8_t i; + + if((ports->pnum = pn = malloc((3 * n_ports + (n_ports > 9 ? n_ports - 10 : 0)) + 1))) + for(i = 0; i < n_ports; i++) { + + if(pn) { + *pn = type == Port_Digital ? 'P' : 'E'; + strcpy(pn + 1, uitoa(i)); + } + + if(ports->n_in && i < ports->n_in) { + if(ports->in_map) + ports->in_map[i] = i; + if(i < 8) { + strcat(ports->input_ports, i == 0 ? "Aux " : ",Aux "); + strcat(ports->input_ports, uitoa(i)); + } + } + + if(ports->n_out && i < ports->n_out) { + if(ports->out_map) + ports->out_map[i] = i; + if(i < 8) { + ports->out.mask = (ports->out.mask << 1) + 1; + strcat(ports->output_ports, i == 0 ? "Aux " : ",Aux "); + strcat(ports->output_ports, uitoa(i)); + } + } + + if(pn) + pn += i > 9 ? 4 : 3; + } + } + + return n_ports > 0; +} + +/*! \brief calculate inverted pwm value if configured +\param pwm_data pointer t a \a spindle_pwm_t structure. +\param pwm_value non inverted PWM value. +\returns the inverted PWM value to use. +*/ +static inline uint_fast16_t invert_pwm (ioports_pwm_t *pwm_data, uint_fast16_t pwm_value) +{ + return pwm_data->invert_pwm ? pwm_data->period - pwm_value - 1 : pwm_value; +} + +/*! \brief Precompute PWM values for faster conversion. +\param config pointer to a \ref pwm_config_t structure. +\param pwm_data pointer to a \a ioports_pwm_t structure, to hold the precomputed values. +\param clock_hz timer clock frequency used for PWM generation. +\returns \a true if successful, \a false if no PWM range possible - driver should then revert to simple on/off control. +*/ +bool ioports_precompute_pwm_values (pwm_config_t *config, ioports_pwm_t *pwm_data, uint32_t clock_hz) +{ + if(config->max > config->min) { + pwm_data->min = config->min; + pwm_data->period = (uint_fast16_t)((float)clock_hz / config->freq_hz); + if(config->off_value == 0.0f) + pwm_data->off_value = pwm_data->invert_pwm ? pwm_data->period : 0; + else + pwm_data->off_value = invert_pwm(pwm_data, (uint_fast16_t)(pwm_data->period * config->off_value / 100.0f)); + pwm_data->min_value = (uint_fast16_t)(pwm_data->period * config->min_value / 100.0f); + pwm_data->max_value = (uint_fast16_t)(pwm_data->period * config->max_value / 100.0f); // + pwm_data->offset; + pwm_data->pwm_gradient = (float)(pwm_data->max_value - pwm_data->min_value) / (config->max - config->min); + pwm_data->always_on = config->off_value != 0.0f; + } + + return config->max > config->min; +} + +/*! \brief Analog value to PWM conversion. +\param pwm_data pointer to a \a ioports_pwm_t structure. +\param value analog value to be converted. +\returns the PWM value to use. + +__NOTE:__ \a ioports_precompute_pwm_values() must be called to precompute values before this function is called. +Typically this is done by the ioports initialization code. +*/ +uint_fast16_t ioports_compute_pwm_value (ioports_pwm_t *pwm_data, float value) +{ + uint_fast16_t pwm_value; + + if(value > pwm_data->min) { + + pwm_value = (uint_fast16_t)floorf((value - pwm_data->min) * pwm_data->pwm_gradient) + pwm_data->min_value; + + if(pwm_value >= pwm_data->max_value) + pwm_value = pwm_data->max_value; + else if(pwm_value < pwm_data->min_value) + pwm_value = pwm_data->min_value; + + pwm_value = invert_pwm(pwm_data, pwm_value); + } else + pwm_value = value == 0.0f ? pwm_data->off_value : invert_pwm(pwm_data, pwm_data->min_value); + + return pwm_value; +} diff --git a/ioports.h b/ioports.h index 98e0cd2..93074a5 100644 --- a/ioports.h +++ b/ioports.h @@ -124,4 +124,35 @@ uint8_t ioports_available (io_port_type_t type, io_port_direction_t dir); bool ioport_claim (io_port_type_t type, io_port_direction_t dir, uint8_t *port, const char *description); bool ioport_can_claim_explicit (void); +// + +struct io_ports_data; + +typedef struct io_ports_data { + char *pnum; + uint8_t n_in_start, n_in, n_out_start, n_out, *in_map, *out_map; + char input_ports[50], output_ports[50]; + ioport_bus_t out; + char *(*get_pnum)(struct io_ports_data *data, uint8_t port); +} io_ports_data_t; + +//!* \brief Precalculated values that may be set/used by HAL driver to speed up analog input to PWM conversions. */ +typedef struct { + uint_fast16_t period; + uint_fast16_t off_value; //!< NOTE: this value holds the inverted version if software PWM inversion is enabled by the driver. + uint_fast16_t min_value; + uint_fast16_t max_value; + float min; //!< Minimum analog input value. + float pwm_gradient; + bool invert_pwm; //!< NOTE: set (by driver) when inversion is done in code + bool always_on; +} ioports_pwm_t; + +bool ioports_add (io_ports_data_t *ports, io_port_type_t type, uint8_t n_in, uint8_t n_out); +#define iports_get_pnum(type, port) type.get_pnum(&type, port) +#define ioports_map_input(type, port) ( type.in_map ? type.in_map[port] : port ) +#define ioports_map_output(type, port) ( type.out_map ? type.out_map[port] : port ) +bool ioports_precompute_pwm_values (pwm_config_t *config, ioports_pwm_t *pwm_data, uint32_t clock_hz); +uint_fast16_t ioports_compute_pwm_value (ioports_pwm_t *pwm_data, float value); + /*EOF*/