2016-02-28 20:48:41 +01:00
|
|
|
/*
|
|
|
|
* Driver for interfacing to cheap rotary switches that
|
|
|
|
* have a quadrature output with an optional press button
|
|
|
|
*
|
|
|
|
* This sets up the relevant gpio as interrupt and then keeps track of
|
|
|
|
* the position of the switch in software. Changes are enqueued to task
|
|
|
|
* level and a task message posted when required. If the queue fills up
|
|
|
|
* then moves are ignored, but the last press/release will be included.
|
|
|
|
*
|
|
|
|
* Philip Gladstone, N1DQ
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "platform.h"
|
|
|
|
#include "c_types.h"
|
2016-05-26 10:36:20 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
2016-02-28 20:48:41 +01:00
|
|
|
#include "driver/rotary.h"
|
|
|
|
#include "user_interface.h"
|
Initial pass at switching to RTOS SDK.
This compiles, links, and starts the RTOS without crashing and burning.
Lua environment does not yet start due to the different task architecture.
Known pain points:
- task implementation needs to be rewritten for RTOS (next up on my TODO)
- secure espconn does not exist, all secure espconn stuff has been #if 0'd
- lwip now built from within the RTOS SDK, but does not appear to include
MDNS support. Investigation needed.
- there is no access to FRC1 NMI, not sure if we ever actually used that
however. Also #if 0'd out for now.
- new timing constraints introduced by the RTOS, all use of ets_delay_us()
and os_delay_us() needs to be reviewed (the tsl2561 driver in particular).
- even more confusion with ets_ vs os_ vs c_ vs non-prefixed versions.
In the long run everything should be switched to non-prefixed versions.
- system_set_os_print() not available, needs to be reimplemented
- all the RTOS rodata is loaded into RAM, as it apparently uses some
constants while the flash isn't mapped, so our exception handler can't
work its magic. This should be narrowed down to the minimum possible
at some point.
- with each task having its own stack in RTOS, we probably need change
flash-page buffers from the stack to the heap in a bunch of places.
A single, shared, page buffer *might* be possible if we limit ourselves
to running NodeMCU in a single task.
- there's a ton of junk in the sdk-overrides now; over time the core code
should be updated to not need those shims
2016-05-24 07:05:01 +02:00
|
|
|
#include "esp_system.h"
|
2016-02-28 20:48:41 +01:00
|
|
|
#include "task/task.h"
|
|
|
|
#include "ets_sys.h"
|
|
|
|
|
|
|
|
//
|
|
|
|
// Queue is empty if read == write.
|
|
|
|
// However, we always want to keep the previous value
|
|
|
|
// so writing is only allowed if write - read < QUEUE_SIZE - 1
|
|
|
|
|
|
|
|
#define QUEUE_SIZE 8
|
|
|
|
|
|
|
|
#define GET_LAST_STATUS(d) (d->queue[(d->write_offset-1) & (QUEUE_SIZE - 1)])
|
|
|
|
#define GET_PREV_STATUS(d) (d->queue[(d->write_offset-2) & (QUEUE_SIZE - 1)])
|
|
|
|
#define HAS_QUEUED_DATA(d) (d->read_offset < d->write_offset)
|
|
|
|
#define HAS_QUEUE_SPACE(d) (d->read_offset + QUEUE_SIZE - 1 > d->write_offset)
|
|
|
|
|
|
|
|
#define REPLACE_STATUS(d, x) (d->queue[(d->write_offset-1) & (QUEUE_SIZE - 1)] = (rotary_event_t) { (x), system_get_time() })
|
|
|
|
#define QUEUE_STATUS(d, x) (d->queue[(d->write_offset++) & (QUEUE_SIZE - 1)] = (rotary_event_t) { (x), system_get_time() })
|
|
|
|
|
|
|
|
#define GET_READ_STATUS(d) (d->queue[d->read_offset & (QUEUE_SIZE - 1)])
|
|
|
|
#define ADVANCE_IF_POSSIBLE(d) if (d->read_offset < d->write_offset) { d->read_offset++; }
|
|
|
|
|
|
|
|
#define STATUS_IS_PRESSED(x) ((x & 0x80000000) != 0)
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
int8_t phase_a_pin;
|
|
|
|
int8_t phase_b_pin;
|
|
|
|
int8_t press_pin;
|
|
|
|
uint32_t read_offset; // Accessed by task
|
|
|
|
uint32_t write_offset; // Accessed by ISR
|
|
|
|
uint32_t pin_mask;
|
|
|
|
uint32_t phase_a;
|
|
|
|
uint32_t phase_b;
|
|
|
|
uint32_t press;
|
|
|
|
uint32_t last_press_change_time;
|
|
|
|
int tasknumber;
|
|
|
|
rotary_event_t queue[QUEUE_SIZE];
|
|
|
|
} DATA;
|
|
|
|
|
|
|
|
static DATA *data[ROTARY_CHANNEL_COUNT];
|
|
|
|
|
|
|
|
static uint8_t task_queued;
|
|
|
|
|
|
|
|
static void set_gpio_bits(void);
|
|
|
|
|
|
|
|
static void rotary_clear_pin(int pin)
|
|
|
|
{
|
|
|
|
if (pin >= 0) {
|
|
|
|
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[pin]), GPIO_PIN_INTR_DISABLE);
|
|
|
|
platform_gpio_mode(pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Just takes the channel number. Cleans up the resources used.
|
|
|
|
int rotary_close(uint32_t channel)
|
|
|
|
{
|
|
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DATA *d = data[channel];
|
|
|
|
|
|
|
|
if (!d) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
data[channel] = NULL;
|
|
|
|
|
|
|
|
rotary_clear_pin(d->phase_a_pin);
|
|
|
|
rotary_clear_pin(d->phase_b_pin);
|
|
|
|
rotary_clear_pin(d->press_pin);
|
|
|
|
|
2016-05-26 10:36:20 +02:00
|
|
|
free(d);
|
2016-02-28 20:48:41 +01:00
|
|
|
|
|
|
|
set_gpio_bits();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t ICACHE_RAM_ATTR rotary_interrupt(uint32_t ret_gpio_status)
|
|
|
|
{
|
|
|
|
// This function really is running at interrupt level with everything
|
|
|
|
// else masked off. It should take as little time as necessary.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
// This gets the set of pins which have changed status
|
|
|
|
uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(data) / sizeof(data[0]); i++) {
|
|
|
|
DATA *d = data[i];
|
|
|
|
if (!d || (gpio_status & d->pin_mask) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & d->pin_mask);
|
|
|
|
|
|
|
|
uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS);
|
|
|
|
|
|
|
|
uint32_t last_status = GET_LAST_STATUS(d).pos;
|
|
|
|
|
|
|
|
uint32_t now = system_get_time();
|
|
|
|
|
|
|
|
uint32_t new_status;
|
|
|
|
|
|
|
|
new_status = last_status & 0x80000000;
|
|
|
|
|
|
|
|
// This is the debounce logic for the press switch. We ignore changes
|
|
|
|
// for 10ms after a change.
|
|
|
|
if (now - d->last_press_change_time > 10 * 1000) {
|
|
|
|
new_status = (bits & d->press) ? 0 : 0x80000000;
|
|
|
|
if (STATUS_IS_PRESSED(new_status ^ last_status)) {
|
|
|
|
d->last_press_change_time = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A B
|
|
|
|
// 1 1 => 0
|
|
|
|
// 1 0 => 1
|
|
|
|
// 0 0 => 2
|
|
|
|
// 0 1 => 3
|
|
|
|
|
|
|
|
int micropos = 2;
|
|
|
|
if (bits & d->phase_b) {
|
|
|
|
micropos = 3;
|
|
|
|
}
|
|
|
|
if (bits & d->phase_a) {
|
|
|
|
micropos ^= 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t rotary_pos = last_status;
|
|
|
|
|
|
|
|
switch ((micropos - last_status) & 3) {
|
|
|
|
case 0:
|
|
|
|
// No change, nothing to do
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
// Incremented by 1
|
|
|
|
rotary_pos++;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
// Decremented by 1
|
|
|
|
rotary_pos--;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// We missed an interrupt
|
|
|
|
// We will ignore... but mark it.
|
|
|
|
rotary_pos += 1000000;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
new_status |= rotary_pos & 0x7fffffff;
|
|
|
|
|
|
|
|
if (last_status != new_status) {
|
|
|
|
// Either we overwrite the status or we add a new one
|
|
|
|
if (!HAS_QUEUED_DATA(d)
|
|
|
|
|| STATUS_IS_PRESSED(last_status ^ new_status)
|
|
|
|
|| STATUS_IS_PRESSED(last_status ^ GET_PREV_STATUS(d).pos)) {
|
|
|
|
if (HAS_QUEUE_SPACE(d)) {
|
|
|
|
QUEUE_STATUS(d, new_status);
|
|
|
|
if (!task_queued) {
|
2016-05-26 10:36:20 +02:00
|
|
|
if (task_post_medium(d->tasknumber, (task_param_t) &task_queued)) {
|
2016-02-28 20:48:41 +01:00
|
|
|
task_queued = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
REPLACE_STATUS(d, new_status);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
REPLACE_STATUS(d, new_status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret_gpio_status &= ~(d->pin_mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret_gpio_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The pin numbers are actual platform GPIO numbers
|
|
|
|
int rotary_setup(uint32_t channel, int phase_a, int phase_b, int press, task_handle_t tasknumber )
|
|
|
|
{
|
|
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data[channel]) {
|
|
|
|
if (rotary_close(channel)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-26 10:36:20 +02:00
|
|
|
DATA *d = (DATA *) zalloc(sizeof(DATA));
|
2016-02-28 20:48:41 +01:00
|
|
|
if (!d) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
data[channel] = d;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
d->tasknumber = tasknumber;
|
|
|
|
|
|
|
|
d->phase_a = 1 << pin_num[phase_a];
|
|
|
|
platform_gpio_mode(phase_a, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
|
|
|
|
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[phase_a]), GPIO_PIN_INTR_ANYEDGE);
|
|
|
|
d->phase_a_pin = phase_a;
|
|
|
|
|
|
|
|
d->phase_b = 1 << pin_num[phase_b];
|
|
|
|
platform_gpio_mode(phase_b, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
|
|
|
|
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[phase_b]), GPIO_PIN_INTR_ANYEDGE);
|
|
|
|
d->phase_b_pin = phase_b;
|
|
|
|
|
|
|
|
if (press >= 0) {
|
|
|
|
d->press = 1 << pin_num[press];
|
|
|
|
platform_gpio_mode(press, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
|
|
|
|
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[press]), GPIO_PIN_INTR_ANYEDGE);
|
|
|
|
}
|
|
|
|
d->press_pin = press;
|
|
|
|
|
|
|
|
d->pin_mask = d->phase_a | d->phase_b | d->press;
|
|
|
|
|
|
|
|
set_gpio_bits();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_gpio_bits()
|
|
|
|
{
|
|
|
|
uint32_t bits = 0;
|
|
|
|
for (int i = 0; i < ROTARY_CHANNEL_COUNT; i++) {
|
|
|
|
DATA *d = data[i];
|
|
|
|
|
|
|
|
if (d) {
|
|
|
|
bits = bits | d->pin_mask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
platform_gpio_register_intr_hook(bits, rotary_interrupt);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool rotary_has_queued_event(uint32_t channel)
|
|
|
|
{
|
|
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
DATA *d = data[channel];
|
|
|
|
|
|
|
|
if (!d) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return HAS_QUEUED_DATA(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the oldest event in the queue and remove it (if possible)
|
|
|
|
bool rotary_getevent(uint32_t channel, rotary_event_t *resultp)
|
|
|
|
{
|
|
|
|
rotary_event_t result = { 0 };
|
|
|
|
|
|
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
DATA *d = data[channel];
|
|
|
|
|
|
|
|
if (!d) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
ETS_GPIO_INTR_DISABLE();
|
|
|
|
|
|
|
|
bool status = FALSE;
|
|
|
|
|
|
|
|
if (HAS_QUEUED_DATA(d)) {
|
|
|
|
result = GET_READ_STATUS(d);
|
|
|
|
d->read_offset++;
|
|
|
|
status = TRUE;
|
|
|
|
} else {
|
|
|
|
result = GET_LAST_STATUS(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
ETS_GPIO_INTR_ENABLE();
|
|
|
|
|
|
|
|
*resultp = result;
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rotary_getpos(uint32_t channel)
|
|
|
|
{
|
|
|
|
if (channel >= sizeof(data) / sizeof(data[0])) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
DATA *d = data[channel];
|
|
|
|
|
|
|
|
if (!d) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return GET_LAST_STATUS(d).pos;
|
|
|
|
}
|