446 lines
13 KiB
C++
446 lines
13 KiB
C++
#include <math.h>
|
|
#include <cfloat>
|
|
#include <climits>
|
|
#include "hardware/irq.h"
|
|
#include "hardware/clocks.h"
|
|
#include "encoder.hpp"
|
|
#include "encoder.pio.h"
|
|
|
|
#define LAST_STATE(state) ((state) & 0b0011)
|
|
#define CURR_STATE(state) (((state) & 0b1100) >> 2)
|
|
|
|
namespace encoder {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// STATICS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
Encoder* Encoder::encoders[][NUM_PIO_STATE_MACHINES] = { { nullptr, nullptr, nullptr, nullptr }, { nullptr, nullptr, nullptr, nullptr } };
|
|
uint8_t Encoder::claimed_sms[] = { 0x0, 0x0 };
|
|
uint Encoder::pio_program_offset[] = { 0, 0 };
|
|
|
|
|
|
Encoder::Capture::Capture()
|
|
: captured_count(0), captured_delta(0), captured_frequency(0.0f), counts_per_rev(INT32_MAX) {
|
|
}
|
|
|
|
Encoder::Capture::Capture(int32_t count, int32_t delta, float frequency, float counts_per_rev)
|
|
: captured_count(count), captured_delta(delta), captured_frequency(frequency)
|
|
, counts_per_rev(MAX(counts_per_rev, FLT_EPSILON)) { //Clamp counts_per_rev to avoid potential NaN
|
|
}
|
|
|
|
int32_t Encoder::Capture::count() const {
|
|
return captured_count;
|
|
}
|
|
|
|
int32_t Encoder::Capture::delta() const {
|
|
return captured_delta;
|
|
}
|
|
|
|
float Encoder::Capture::frequency() const {
|
|
return captured_frequency;
|
|
}
|
|
|
|
float Encoder::Capture::revolutions() const {
|
|
return (float)captured_count / counts_per_rev;
|
|
}
|
|
|
|
float Encoder::Capture::degrees() const {
|
|
return revolutions() * 360.0f;
|
|
}
|
|
|
|
float Encoder::Capture::radians() const {
|
|
return revolutions() * M_TWOPI;
|
|
}
|
|
|
|
float Encoder::Capture::revolutions_delta() const {
|
|
return (float)captured_delta / counts_per_rev;
|
|
}
|
|
|
|
float Encoder::Capture::degrees_delta() const {
|
|
return revolutions_delta() * 360.0f;
|
|
}
|
|
|
|
float Encoder::Capture::radians_delta() const {
|
|
return revolutions_delta() * M_TWOPI;
|
|
}
|
|
|
|
float Encoder::Capture::revolutions_per_second() const {
|
|
return captured_frequency / counts_per_rev;
|
|
}
|
|
|
|
float Encoder::Capture::revolutions_per_minute() const {
|
|
return revolutions_per_second() * 60.0f;
|
|
}
|
|
|
|
float Encoder::Capture::degrees_per_second() const {
|
|
return revolutions_per_second() * 360.0f;
|
|
}
|
|
|
|
float Encoder::Capture::radians_per_second() const {
|
|
return revolutions_per_second() * M_TWOPI;
|
|
}
|
|
|
|
Encoder::Encoder(PIO pio, uint sm, const pin_pair &pins, uint common_pin, Direction direction,
|
|
float counts_per_rev, bool count_microsteps, uint16_t freq_divider)
|
|
: pio(pio)
|
|
, sm(sm)
|
|
, enc_pins(pins)
|
|
, enc_common_pin(common_pin)
|
|
, enc_direction(direction)
|
|
, enc_counts_per_rev(MAX(counts_per_rev, FLT_EPSILON))
|
|
, count_microsteps(count_microsteps)
|
|
, freq_divider(freq_divider)
|
|
, clocks_per_time((float)(clock_get_hz(clk_sys) / (ENC_LOOP_CYCLES * freq_divider))) {
|
|
}
|
|
|
|
Encoder::~Encoder() {
|
|
if(initialised) {
|
|
pio_sm_set_enabled(pio, sm, false);
|
|
pio_sm_unclaim(pio, sm);
|
|
|
|
uint pio_idx = pio_get_index(pio);
|
|
encoders[pio_idx][sm] = nullptr;
|
|
claimed_sms[pio_idx] &= ~(1u << sm);
|
|
|
|
hw_clear_bits(&pio->inte1, PIO_IRQ1_INTE_SM0_RXNEMPTY_BITS << sm);
|
|
|
|
//If there are no more SMs using the encoder program, then we can remove it from the PIO
|
|
if(claimed_sms[pio_idx] == 0) {
|
|
pio_remove_program(pio, &encoder_program, pio_program_offset[pio_idx]);
|
|
|
|
if(pio_idx == 0) {
|
|
irq_remove_handler(PIO0_IRQ_1, pio0_interrupt_handler);
|
|
}
|
|
else {
|
|
irq_remove_handler(PIO1_IRQ_1, pio1_interrupt_handler);
|
|
}
|
|
}
|
|
|
|
// Reset all the pins this PWM will control back to an unused state
|
|
gpio_set_function(enc_pins.a, GPIO_FUNC_NULL);
|
|
gpio_set_function(enc_pins.b, GPIO_FUNC_NULL);
|
|
|
|
if(enc_common_pin != PIN_UNUSED) {
|
|
gpio_set_function(enc_common_pin, GPIO_FUNC_NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Encoder::pio_interrupt_handler(uint pio_idx) {
|
|
// Go through each SM on the PIO to see which triggered this interrupt,
|
|
// and if there's an associated encoder, have it update its state
|
|
for(uint8_t sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) {
|
|
if(encoders[pio_idx][sm] != nullptr) {
|
|
encoders[pio_idx][sm]->process_steps();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Encoder::pio0_interrupt_handler() {
|
|
pio_interrupt_handler(0);
|
|
}
|
|
|
|
void Encoder::pio1_interrupt_handler() {
|
|
pio_interrupt_handler(1);
|
|
}
|
|
|
|
bool Encoder::init() {
|
|
if(!initialised && !pio_sm_is_claimed(pio, sm)) {
|
|
// Are the pins we want to use actually valid?
|
|
if((enc_pins.a < NUM_BANK0_GPIOS) && (enc_pins.b < NUM_BANK0_GPIOS)) {
|
|
|
|
// If a Pin C was defined, and valid, set it as a GND to pull the other two pins down
|
|
if((enc_common_pin != PIN_UNUSED) && (enc_common_pin < NUM_BANK0_GPIOS)) {
|
|
gpio_init(enc_common_pin);
|
|
gpio_set_dir(enc_common_pin, GPIO_OUT);
|
|
gpio_put(enc_common_pin, false);
|
|
}
|
|
|
|
pio_sm_claim(pio, sm);
|
|
uint pio_idx = pio_get_index(pio);
|
|
|
|
// If this is the first time using an encoder on this PIO, add the program to the PIO memory
|
|
if(claimed_sms[pio_idx] == 0) {
|
|
pio_program_offset[pio_idx] = pio_add_program(pio, &encoder_program);
|
|
}
|
|
|
|
// Initialise the A and B pins of this encoder
|
|
pio_gpio_init(pio, enc_pins.a);
|
|
pio_gpio_init(pio, enc_pins.b);
|
|
gpio_pull_up(enc_pins.a);
|
|
gpio_pull_up(enc_pins.b);
|
|
|
|
// Set their default direction
|
|
pio_sm_set_consecutive_pindirs(pio, sm, enc_pins.a, 1, false);
|
|
pio_sm_set_consecutive_pindirs(pio, sm, enc_pins.b, 1, false);
|
|
|
|
pio_sm_config c = encoder_program_get_default_config(pio_program_offset[pio_idx]);
|
|
sm_config_set_jmp_pin(&c, enc_pins.a);
|
|
sm_config_set_in_pins(&c, enc_pins.b);
|
|
|
|
sm_config_set_in_shift(&c, false, false, 1);
|
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
|
|
|
|
sm_config_set_clkdiv_int_frac(&c, freq_divider, 0);
|
|
|
|
pio_sm_init(pio, sm, pio_program_offset[pio_idx], &c);
|
|
|
|
hw_set_bits(&pio->inte1, PIO_IRQ1_INTE_SM0_RXNEMPTY_BITS << sm);
|
|
if(claimed_sms[pio_idx] == 0) {
|
|
// Configure the processor to run pio_handler() when PIO IRQ 0 is asserted
|
|
if(pio_idx == 0) {
|
|
irq_add_shared_handler(PIO0_IRQ_1, pio0_interrupt_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
|
irq_set_enabled(PIO0_IRQ_1, true);
|
|
}
|
|
else {
|
|
irq_add_shared_handler(PIO1_IRQ_1, pio1_interrupt_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
|
irq_set_enabled(PIO1_IRQ_1, true);
|
|
}
|
|
}
|
|
|
|
//Keep a record of this encoder for the interrupt callback
|
|
encoders[pio_idx][sm] = this;
|
|
claimed_sms[pio_idx] |= 1u << sm;
|
|
|
|
enc_state_a = gpio_get(enc_pins.a);
|
|
enc_state_b = gpio_get(enc_pins.b);
|
|
pio_sm_exec(pio, sm, pio_encode_set(pio_x, (uint)enc_state_a << 1 | (uint)enc_state_b));
|
|
pio_sm_set_enabled(pio, sm, true);
|
|
|
|
initialised = true;
|
|
}
|
|
}
|
|
|
|
return initialised;
|
|
}
|
|
|
|
pin_pair Encoder::pins() const {
|
|
return enc_pins;
|
|
}
|
|
|
|
uint Encoder::common_pin() const {
|
|
return enc_common_pin;
|
|
}
|
|
|
|
bool_pair Encoder::state() const {
|
|
return bool_pair(enc_state_a, enc_state_b);
|
|
}
|
|
|
|
int32_t Encoder::count() const {
|
|
return enc_count;
|
|
}
|
|
|
|
int32_t Encoder::delta() {
|
|
int32_t count = enc_count; // Store a local copy of enc_count to avoid two reads
|
|
|
|
// Determine the change in counts since the last time this function was performed
|
|
int32_t change = count - last_count;
|
|
last_count = count;
|
|
|
|
return change;
|
|
}
|
|
|
|
void Encoder::zero() {
|
|
enc_count = 0;
|
|
enc_cumulative_time = 0;
|
|
enc_step = 0;
|
|
enc_turn = 0;
|
|
|
|
microstep_time = 0;
|
|
step_dir = NO_DIR; // may not be wanted?
|
|
|
|
last_count = 0;
|
|
last_capture_count = 0;
|
|
}
|
|
|
|
int16_t Encoder::step() const {
|
|
return enc_step;
|
|
}
|
|
|
|
int16_t Encoder::turn() const {
|
|
return enc_turn;
|
|
}
|
|
|
|
float Encoder::revolutions() const {
|
|
return (float)count() / enc_counts_per_rev;
|
|
}
|
|
|
|
float Encoder::degrees() const {
|
|
return revolutions() * 360.0f;
|
|
}
|
|
|
|
float Encoder::radians() const {
|
|
return revolutions() * M_TWOPI;
|
|
}
|
|
|
|
Direction Encoder::direction() const {
|
|
return enc_direction;
|
|
}
|
|
|
|
void Encoder::direction(Direction direction) {
|
|
enc_direction = direction;
|
|
}
|
|
|
|
float Encoder::counts_per_rev() const {
|
|
return enc_counts_per_rev;
|
|
}
|
|
|
|
void Encoder::counts_per_rev(float counts_per_rev) {
|
|
enc_counts_per_rev = MAX(counts_per_rev, FLT_EPSILON);
|
|
}
|
|
|
|
Encoder::Capture Encoder::capture() {
|
|
// Take a capture of the current values
|
|
int32_t count = enc_count;
|
|
int32_t cumulative_time = enc_cumulative_time;
|
|
enc_cumulative_time = 0;
|
|
|
|
// Determine the change in counts since the last capture was taken
|
|
int32_t change = count - last_capture_count;
|
|
last_capture_count = count;
|
|
|
|
// Calculate the average frequency of steps
|
|
float frequency = 0.0f;
|
|
if(change != 0 && cumulative_time != INT32_MAX) {
|
|
frequency = (clocks_per_time * (float)change) / (float)cumulative_time;
|
|
}
|
|
|
|
return Capture(count, change, frequency, enc_counts_per_rev);
|
|
}
|
|
|
|
void Encoder::process_steps() {
|
|
while(pio->ints1 & (PIO_IRQ1_INTS_SM0_RXNEMPTY_BITS << sm)) {
|
|
uint32_t received = pio_sm_get(pio, sm);
|
|
|
|
// Extract the current and last encoder states from the received value
|
|
enc_state_a = (bool)(received & STATE_A_MASK);
|
|
enc_state_b = (bool)(received & STATE_B_MASK);
|
|
uint8_t states = (received & STATES_MASK) >> 28;
|
|
|
|
// Extract the time (in cycles) it has been since the last received
|
|
int32_t time_received = (received & TIME_MASK) + ENC_DEBOUNCE_TIME;
|
|
|
|
// For rotary encoders, only every fourth step is cared about, causing an inaccurate time value
|
|
// To address this we accumulate the times received and zero it when a step is counted
|
|
if(!count_microsteps) {
|
|
if(time_received + microstep_time < time_received) // Check to avoid integer overflow
|
|
time_received = INT32_MAX;
|
|
else
|
|
time_received += microstep_time;
|
|
microstep_time = time_received;
|
|
}
|
|
|
|
bool up = (enc_direction == NORMAL_DIR);
|
|
|
|
// Determine what step occurred
|
|
switch(LAST_STATE(states)) {
|
|
//--------------------------------------------------
|
|
case MICROSTEP_0:
|
|
switch(CURR_STATE(states)) {
|
|
// A ____|‾‾‾‾
|
|
// B _________
|
|
case MICROSTEP_1:
|
|
if(count_microsteps)
|
|
microstep(time_received, up);
|
|
break;
|
|
|
|
// A _________
|
|
// B ____|‾‾‾‾
|
|
case MICROSTEP_3:
|
|
if(count_microsteps)
|
|
microstep(time_received, !up);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
//--------------------------------------------------
|
|
case MICROSTEP_1:
|
|
switch(CURR_STATE(states)) {
|
|
// A ‾‾‾‾‾‾‾‾‾
|
|
// B ____|‾‾‾‾
|
|
case MICROSTEP_2:
|
|
if(count_microsteps || step_dir == INCREASING)
|
|
microstep(time_received, up);
|
|
|
|
step_dir = NO_DIR; // Finished increasing
|
|
break;
|
|
|
|
// A ‾‾‾‾|____
|
|
// B _________
|
|
case MICROSTEP_0:
|
|
if(count_microsteps)
|
|
microstep(time_received, !up);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
//--------------------------------------------------
|
|
case MICROSTEP_2:
|
|
switch(CURR_STATE(states)) {
|
|
// A ‾‾‾‾|____
|
|
// B ‾‾‾‾‾‾‾‾‾
|
|
case MICROSTEP_3:
|
|
if(count_microsteps)
|
|
microstep(time_received, up);
|
|
|
|
step_dir = INCREASING; // Started increasing
|
|
break;
|
|
|
|
// A ‾‾‾‾‾‾‾‾‾
|
|
// B ‾‾‾‾|____
|
|
case MICROSTEP_1:
|
|
if(count_microsteps)
|
|
microstep(time_received, !up);
|
|
|
|
step_dir = DECREASING; // Started decreasing
|
|
break;
|
|
}
|
|
break;
|
|
|
|
//--------------------------------------------------
|
|
case MICROSTEP_3:
|
|
switch(CURR_STATE(states)) {
|
|
// A _________
|
|
// B ‾‾‾‾|____
|
|
case MICROSTEP_0:
|
|
if(count_microsteps)
|
|
microstep(time_received, up);
|
|
break;
|
|
|
|
// A ____|‾‾‾‾
|
|
// B ‾‾‾‾‾‾‾‾‾
|
|
case MICROSTEP_2:
|
|
if(count_microsteps || step_dir == DECREASING)
|
|
microstep(time_received, !up);
|
|
|
|
step_dir = NO_DIR; // Finished decreasing
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Encoder::microstep(int32_t time, bool up) {
|
|
if(up) {
|
|
enc_count++;
|
|
if(++enc_step >= (int16_t)enc_counts_per_rev) {
|
|
enc_step -= (int16_t)enc_counts_per_rev;
|
|
enc_turn++;
|
|
}
|
|
}
|
|
else {
|
|
enc_count--;
|
|
if(--enc_step < 0) {
|
|
enc_step += (int16_t)enc_counts_per_rev;
|
|
enc_turn--;
|
|
}
|
|
}
|
|
microstep_time = 0;
|
|
|
|
if(time + enc_cumulative_time < time) // Check to avoid integer overflow
|
|
enc_cumulative_time = INT32_MAX;
|
|
else
|
|
enc_cumulative_time += time;
|
|
}
|
|
} |