Squashed commit of the following:
commit 2c7c3fc3985cc32866e8af496abea9971eaee90a Merge: 9179dae41022c3
Author: philip <philip@gladstonefamily.net> Date: Sun Feb 28 14:47:47 2016 -0500 Merge remote-tracking branch 'upstream/dev' into rotary_2 commit 9179dae0824e6b35ad09e5113aacc26dc91692c0 Author: philip <philip@gladstonefamily.net> Date: Fri Feb 26 20:53:27 2016 -0500 Review comments commit 67741170e20ccb2b636e701f0664feff2aafbb4c Author: philip <philip@gladstonefamily.net> Date: Fri Feb 26 20:59:49 2016 -0500 Squashed commit of the following: commit 8c9a64731c4a8b9aedda18a399b433b173d2199f Merge: 085935f19d3c1d
Author: philip <philip@gladstonefamily.net> Date: Fri Feb 26 20:58:10 2016 -0500 Merge remote-tracking branch 'upstream/dev' into rotarymod Conflicts: app/platform/platform.c commit 085935fc56986d607ff5e05d1663970331959c34 Author: philip <philip@gladstonefamily.net> Date: Fri Feb 26 20:53:27 2016 -0500 Review comment commit 7732fd2d1044f28b8fcf5b0aa0f76d76fe80f449 Author: philip <philip@gladstonefamily.net> Date: Sat Feb 20 12:10:38 2016 -0500 Module to handle rotary decoders Eliminate ROTARY_DEBUG Remove unused file Eliminate a malloc call Cleaned up the register code. Now 0x114 bytes Fix bug with clearing bits in one case Fix the type in the #define name
This commit is contained in:
parent
41022c3b6a
commit
d40ee50a8e
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* 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"
|
||||
#include "../libc/c_stdlib.h"
|
||||
#include "../libc/c_stdio.h"
|
||||
#include "driver/rotary.h"
|
||||
#include "user_interface.h"
|
||||
#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);
|
||||
|
||||
c_free(d);
|
||||
|
||||
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) {
|
||||
if (task_post_medium(d->tasknumber, (os_param_t) &task_queued)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
DATA *d = (DATA *) c_zalloc(sizeof(DATA));
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Definitions to access the Rotary driver
|
||||
*/
|
||||
#ifndef __ROTARY_H__
|
||||
#define __ROTARY_H__
|
||||
|
||||
#include "c_types.h"
|
||||
|
||||
#define ROTARY_CHANNEL_COUNT 3
|
||||
|
||||
typedef struct {
|
||||
uint32_t pos;
|
||||
uint32_t time_us;
|
||||
} rotary_event_t;
|
||||
|
||||
int rotary_setup(uint32_t channel, int phaseA, int phaseB, int press, task_handle_t tasknumber);
|
||||
|
||||
bool rotary_getevent(uint32_t channel, rotary_event_t *result);
|
||||
|
||||
bool rotary_has_queued_event(uint32_t channel);
|
||||
|
||||
int rotary_getpos(uint32_t channel);
|
||||
|
||||
int rotary_close(uint32_t channel);
|
||||
|
||||
#endif
|
|
@ -42,6 +42,7 @@
|
|||
#endif /* NODE_ERROR */
|
||||
|
||||
#define GPIO_INTERRUPT_ENABLE
|
||||
#define GPIO_INTERRUPT_HOOK_ENABLE
|
||||
// #define GPIO_SAFE_NO_INTR_ENABLE
|
||||
|
||||
#define ICACHE_STORE_TYPEDEF_ATTR __attribute__((aligned(4),packed))
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
//#define LUA_USE_MODULES_PERF
|
||||
#define LUA_USE_MODULES_PWM
|
||||
#define LUA_USE_MODULES_RC
|
||||
//#define LUA_USE_MODULES_ROTARY
|
||||
#define LUA_USE_MODULES_RTCFIFO
|
||||
#define LUA_USE_MODULES_RTCMEM
|
||||
#define LUA_USE_MODULES_RTCTIME
|
||||
|
|
|
@ -0,0 +1,405 @@
|
|||
/*
|
||||
* Module for interfacing with cheap rotary switches that
|
||||
* are much used in the automtive industry as the cntrols for
|
||||
* CD players and the like.
|
||||
*
|
||||
* Philip Gladstone, N1DQ
|
||||
*/
|
||||
|
||||
#include "module.h"
|
||||
#include "lauxlib.h"
|
||||
#include "platform.h"
|
||||
#include "c_types.h"
|
||||
#include "user_interface.h"
|
||||
#include "driver/rotary.h"
|
||||
#include "../libc/c_stdlib.h"
|
||||
|
||||
#define MASK(x) (1 << ROTARY_ ## x ## _INDEX)
|
||||
|
||||
#define ROTARY_PRESS_INDEX 0
|
||||
#define ROTARY_LONGPRESS_INDEX 1
|
||||
#define ROTARY_RELEASE_INDEX 2
|
||||
#define ROTARY_TURN_INDEX 3
|
||||
#define ROTARY_CLICK_INDEX 4
|
||||
#define ROTARY_DBLCLICK_INDEX 5
|
||||
|
||||
#define ROTARY_ALL 0x3f
|
||||
|
||||
#define LONGPRESS_DELAY_US 500000
|
||||
#define CLICK_DELAY_US 500000
|
||||
|
||||
#define CALLBACK_COUNT 6
|
||||
|
||||
#ifdef LUA_USE_MODULES_ROTARY
|
||||
#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
|
||||
#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using ROTARY module
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int lastpos;
|
||||
int last_recent_event_was_press : 1;
|
||||
int last_recent_event_was_release : 1;
|
||||
int timer_running : 1;
|
||||
int possible_dbl_click : 1;
|
||||
uint8_t id;
|
||||
int click_delay_us;
|
||||
int longpress_delay_us;
|
||||
uint32_t last_event_time;
|
||||
int callback[CALLBACK_COUNT];
|
||||
ETSTimer timer;
|
||||
} DATA;
|
||||
|
||||
static DATA *data[ROTARY_CHANNEL_COUNT];
|
||||
static task_handle_t tasknumber;
|
||||
static void lrotary_timer_done(void *param);
|
||||
static void lrotary_check_timer(DATA *d, uint32_t time_us, bool dotimer);
|
||||
|
||||
static void callback_free_one(lua_State *L, int *cb_ptr)
|
||||
{
|
||||
if (*cb_ptr != LUA_NOREF) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, *cb_ptr);
|
||||
*cb_ptr = LUA_NOREF;
|
||||
}
|
||||
}
|
||||
|
||||
static void callback_free(lua_State* L, unsigned int id, int mask)
|
||||
{
|
||||
DATA *d = data[id];
|
||||
|
||||
if (d) {
|
||||
int i;
|
||||
for (i = 0; i < CALLBACK_COUNT; i++) {
|
||||
if (mask & (1 << i)) {
|
||||
callback_free_one(L, &d->callback[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int callback_setOne(lua_State* L, int *cb_ptr, int arg_number)
|
||||
{
|
||||
if (lua_type(L, arg_number) == LUA_TFUNCTION || lua_type(L, arg_number) == LUA_TLIGHTFUNCTION) {
|
||||
lua_pushvalue(L, arg_number); // copy argument (func) to the top of stack
|
||||
callback_free_one(L, cb_ptr);
|
||||
*cb_ptr = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int callback_set(lua_State* L, int id, int mask, int arg_number)
|
||||
{
|
||||
DATA *d = data[id];
|
||||
int result = 0;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < CALLBACK_COUNT; i++) {
|
||||
if (mask & (1 << i)) {
|
||||
result |= callback_setOne(L, &d->callback[i], arg_number);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void callback_callOne(lua_State* L, int cb, int mask, int arg, uint32_t time)
|
||||
{
|
||||
if (cb != LUA_NOREF) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, cb);
|
||||
|
||||
lua_pushinteger(L, mask);
|
||||
lua_pushinteger(L, arg);
|
||||
lua_pushinteger(L, time);
|
||||
|
||||
lua_call(L, 3, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void callback_call(lua_State* L, DATA *d, int cbnum, int arg, uint32_t time)
|
||||
{
|
||||
if (d) {
|
||||
callback_callOne(L, d->callback[cbnum], 1 << cbnum, arg, time);
|
||||
}
|
||||
}
|
||||
|
||||
int platform_rotary_exists( unsigned int id )
|
||||
{
|
||||
return (id < ROTARY_CHANNEL_COUNT);
|
||||
}
|
||||
|
||||
// Lua: setup(id, phase_a, phase_b [, press])
|
||||
static int lrotary_setup( lua_State* L )
|
||||
{
|
||||
unsigned int id;
|
||||
|
||||
id = luaL_checkinteger( L, 1 );
|
||||
MOD_CHECK_ID( rotary, id );
|
||||
|
||||
if (rotary_close(id)) {
|
||||
return luaL_error( L, "Unable to close switch." );
|
||||
}
|
||||
callback_free(L, id, ROTARY_ALL);
|
||||
|
||||
if (!data[id]) {
|
||||
data[id] = (DATA *) c_zalloc(sizeof(DATA));
|
||||
if (!data[id]) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
DATA *d = data[id];
|
||||
memset(d, 0, sizeof(*d));
|
||||
|
||||
os_timer_setfn(&d->timer, lrotary_timer_done, (void *) d);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < CALLBACK_COUNT; i++) {
|
||||
d->callback[i] = LUA_NOREF;
|
||||
}
|
||||
|
||||
d->click_delay_us = CLICK_DELAY_US;
|
||||
d->longpress_delay_us = LONGPRESS_DELAY_US;
|
||||
|
||||
int phase_a = luaL_checkinteger(L, 2);
|
||||
luaL_argcheck(L, platform_gpio_exists(phase_a) && phase_a > 0, 2, "Invalid pin");
|
||||
int phase_b = luaL_checkinteger(L, 3);
|
||||
luaL_argcheck(L, platform_gpio_exists(phase_b) && phase_b > 0, 3, "Invalid pin");
|
||||
int press;
|
||||
if (lua_gettop(L) >= 4) {
|
||||
press = luaL_checkinteger(L, 4);
|
||||
luaL_argcheck(L, platform_gpio_exists(press) && press > 0, 4, "Invalid pin");
|
||||
} else {
|
||||
press = -1;
|
||||
}
|
||||
|
||||
if (lua_gettop(L) >= 5) {
|
||||
d->longpress_delay_us = 1000 * luaL_checkinteger(L, 5);
|
||||
luaL_argcheck(L, d->longpress_delay_us > 0, 5, "Invalid timeout");
|
||||
}
|
||||
|
||||
if (lua_gettop(L) >= 6) {
|
||||
d->click_delay_us = 1000 * luaL_checkinteger(L, 6);
|
||||
luaL_argcheck(L, d->click_delay_us > 0, 6, "Invalid timeout");
|
||||
}
|
||||
|
||||
if (rotary_setup(id, phase_a, phase_b, press, tasknumber)) {
|
||||
return luaL_error(L, "Unable to setup rotary switch.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Lua: close( id )
|
||||
static int lrotary_close( lua_State* L )
|
||||
{
|
||||
unsigned int id;
|
||||
|
||||
id = luaL_checkinteger( L, 1 );
|
||||
MOD_CHECK_ID( rotary, id );
|
||||
callback_free(L, id, ROTARY_ALL);
|
||||
|
||||
DATA *d = data[id];
|
||||
if (d) {
|
||||
data[id] = NULL;
|
||||
c_free(d);
|
||||
}
|
||||
|
||||
if (rotary_close( id )) {
|
||||
return luaL_error( L, "Unable to close switch." );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Lua: on( id, mask[, cb] )
|
||||
static int lrotary_on( lua_State* L )
|
||||
{
|
||||
unsigned int id;
|
||||
id = luaL_checkinteger( L, 1 );
|
||||
MOD_CHECK_ID( rotary, id );
|
||||
|
||||
int mask = luaL_checkinteger(L, 2);
|
||||
|
||||
if (lua_gettop(L) >= 3) {
|
||||
if (callback_set(L, id, mask, 3)) {
|
||||
return luaL_error( L, "Unable to set callback." );
|
||||
}
|
||||
} else {
|
||||
callback_free(L, id, mask);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Lua: getpos( id ) -> pos, PRESS/RELEASE
|
||||
static int lrotary_getpos( lua_State* L )
|
||||
{
|
||||
unsigned int id;
|
||||
id = luaL_checkinteger( L, 1 );
|
||||
MOD_CHECK_ID( rotary, id );
|
||||
|
||||
int pos = rotary_getpos(id);
|
||||
|
||||
if (pos == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
lua_pushnumber(L, (pos << 1) >> 1);
|
||||
lua_pushnumber(L, (pos & 0x80000000) ? MASK(PRESS) : MASK(RELEASE));
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Returns TRUE if there maybe/is more stuff to do
|
||||
static bool lrotary_dequeue_single(lua_State* L, DATA *d)
|
||||
{
|
||||
bool something_pending = FALSE;
|
||||
|
||||
if (d) {
|
||||
// This chnnel is open
|
||||
rotary_event_t result;
|
||||
|
||||
if (rotary_getevent(d->id, &result)) {
|
||||
int pos = result.pos;
|
||||
|
||||
lrotary_check_timer(d, result.time_us, 0);
|
||||
|
||||
if (pos != d->lastpos) {
|
||||
// We have something to enqueue
|
||||
if ((pos ^ d->lastpos) & 0x7fffffff) {
|
||||
// Some turning has happened
|
||||
callback_call(L, d, ROTARY_TURN_INDEX, (pos << 1) >> 1, result.time_us);
|
||||
}
|
||||
if ((pos ^ d->lastpos) & 0x80000000) {
|
||||
// pressing or releasing has happened
|
||||
callback_call(L, d, (pos & 0x80000000) ? ROTARY_PRESS_INDEX : ROTARY_RELEASE_INDEX, (pos << 1) >> 1, result.time_us);
|
||||
if (pos & 0x80000000) {
|
||||
// Press
|
||||
if (d->last_recent_event_was_release && result.time_us - d->last_event_time < d->click_delay_us) {
|
||||
d->possible_dbl_click = 1;
|
||||
}
|
||||
d->last_recent_event_was_press = 1;
|
||||
d->last_recent_event_was_release = 0;
|
||||
} else {
|
||||
// Release
|
||||
d->last_recent_event_was_press = 0;
|
||||
if (d->possible_dbl_click) {
|
||||
callback_call(L, d, ROTARY_DBLCLICK_INDEX, (pos << 1) >> 1, result.time_us);
|
||||
d->possible_dbl_click = 0;
|
||||
// Do this to suppress the CLICK event
|
||||
d->last_recent_event_was_release = 0;
|
||||
} else {
|
||||
d->last_recent_event_was_release = 1;
|
||||
}
|
||||
}
|
||||
d->last_event_time = result.time_us;
|
||||
}
|
||||
|
||||
d->lastpos = pos;
|
||||
}
|
||||
|
||||
something_pending = rotary_has_queued_event(d->id);
|
||||
}
|
||||
|
||||
lrotary_check_timer(d, system_get_time(), 1);
|
||||
}
|
||||
|
||||
return something_pending;
|
||||
}
|
||||
|
||||
static void lrotary_timer_done(void *param)
|
||||
{
|
||||
DATA *d = (DATA *) param;
|
||||
|
||||
d->timer_running = 0;
|
||||
|
||||
lrotary_check_timer(d, system_get_time(), 1);
|
||||
}
|
||||
|
||||
static void lrotary_check_timer(DATA *d, uint32_t time_us, bool dotimer)
|
||||
{
|
||||
uint32_t delay = time_us - d->last_event_time;
|
||||
if (d->timer_running) {
|
||||
os_timer_disarm(&d->timer);
|
||||
d->timer_running = 0;
|
||||
}
|
||||
|
||||
int timeout = -1;
|
||||
|
||||
if (d->last_recent_event_was_press) {
|
||||
if (delay > d->longpress_delay_us) {
|
||||
callback_call(lua_getstate(), d, ROTARY_LONGPRESS_INDEX, (d->lastpos << 1) >> 1, d->last_event_time + d->longpress_delay_us);
|
||||
d->last_recent_event_was_press = 0;
|
||||
} else {
|
||||
timeout = (d->longpress_delay_us - delay) / 1000;
|
||||
}
|
||||
}
|
||||
if (d->last_recent_event_was_release) {
|
||||
if (delay > d->click_delay_us) {
|
||||
callback_call(lua_getstate(), d, ROTARY_CLICK_INDEX, (d->lastpos << 1) >> 1, d->last_event_time + d->click_delay_us);
|
||||
d->last_recent_event_was_release = 0;
|
||||
} else {
|
||||
timeout = (d->click_delay_us - delay) / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (dotimer && timeout >= 0) {
|
||||
d->timer_running = 1;
|
||||
os_timer_arm(&d->timer, timeout + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void lrotary_task(os_param_t param, uint8_t prio)
|
||||
{
|
||||
(void) param;
|
||||
(void) prio;
|
||||
|
||||
uint8_t *task_queue_ptr = (uint8_t*) param;
|
||||
if (task_queue_ptr) {
|
||||
// Signal that new events may need another task post
|
||||
*task_queue_ptr = 0;
|
||||
}
|
||||
|
||||
int id;
|
||||
bool need_to_post = FALSE;
|
||||
lua_State *L = lua_getstate();
|
||||
|
||||
for (id = 0; id < ROTARY_CHANNEL_COUNT; id++) {
|
||||
DATA *d = data[id];
|
||||
if (d) {
|
||||
if (lrotary_dequeue_single(L, d)) {
|
||||
need_to_post = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_to_post) {
|
||||
// If there is pending stuff, queue another task
|
||||
task_post_medium(tasknumber, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int rotary_open(lua_State *L)
|
||||
{
|
||||
tasknumber = task_get_id(lrotary_task);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Module function map
|
||||
static const LUA_REG_TYPE rotary_map[] = {
|
||||
{ LSTRKEY( "setup" ), LFUNCVAL( lrotary_setup ) },
|
||||
{ LSTRKEY( "close" ), LFUNCVAL( lrotary_close ) },
|
||||
{ LSTRKEY( "on" ), LFUNCVAL( lrotary_on ) },
|
||||
{ LSTRKEY( "getpos" ), LFUNCVAL( lrotary_getpos) },
|
||||
{ LSTRKEY( "TURN" ), LNUMVAL( MASK(TURN) ) },
|
||||
{ LSTRKEY( "PRESS" ), LNUMVAL( MASK(PRESS) ) },
|
||||
{ LSTRKEY( "RELEASE" ), LNUMVAL( MASK(RELEASE) ) },
|
||||
{ LSTRKEY( "LONGPRESS" ),LNUMVAL( MASK(LONGPRESS) ) },
|
||||
{ LSTRKEY( "CLICK" ), LNUMVAL( MASK(CLICK) ) },
|
||||
{ LSTRKEY( "DBLCLICK" ), LNUMVAL( MASK(DBLCLICK)) },
|
||||
{ LSTRKEY( "ALL" ), LNUMVAL( ROTARY_ALL ) },
|
||||
|
||||
{ LNILKEY, LNILVAL }
|
||||
};
|
||||
|
||||
NODEMCU_MODULE(ROTARY, "rotary", rotary_map, rotary_open);
|
|
@ -14,6 +14,24 @@
|
|||
#include "driver/uart.h"
|
||||
#include "driver/sigma_delta.h"
|
||||
|
||||
#ifdef GPIO_INTERRUPT_ENABLE
|
||||
static task_handle_t gpio_task_handle;
|
||||
|
||||
#ifdef GPIO_INTERRUPT_HOOK_ENABLE
|
||||
struct gpio_hook_entry {
|
||||
platform_hook_function func;
|
||||
uint32_t bits;
|
||||
};
|
||||
struct gpio_hook {
|
||||
struct gpio_hook_entry *entry;
|
||||
uint32_t all_bits;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
static struct gpio_hook platform_gpio_hook;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static void pwms_init();
|
||||
|
||||
int platform_init()
|
||||
|
@ -152,16 +170,23 @@ int platform_gpio_read( unsigned pin )
|
|||
}
|
||||
|
||||
#ifdef GPIO_INTERRUPT_ENABLE
|
||||
static task_handle_t gpio_task_handle;
|
||||
|
||||
static void ICACHE_RAM_ATTR platform_gpio_intr_dispatcher (void *dummy){
|
||||
uint32 j=0;
|
||||
uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
|
||||
UNUSED(dummy);
|
||||
|
||||
#ifdef GPIO_INTERRUPT_HOOK_ENABLE
|
||||
if (gpio_status & platform_gpio_hook.all_bits) {
|
||||
for (j = 0; j < platform_gpio_hook.count; j++) {
|
||||
if (gpio_status & platform_gpio_hook.entry[j].bits)
|
||||
gpio_status = (platform_gpio_hook.entry[j].func)(gpio_status);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* gpio_status is a bit map where bit 0 is set if unmapped gpio pin 0 (pin3) has
|
||||
* triggered the ISR. bit 1 if unmapped gpio pin 1 (pin10=U0TXD), etc. Since this
|
||||
* in the ISR, it makes sense to optimize this by doing a fast scan of the status
|
||||
* is the ISR, it makes sense to optimize this by doing a fast scan of the status
|
||||
* and reverse mapping any set bits.
|
||||
*/
|
||||
for (j = 0; gpio_status>0; j++, gpio_status >>= 1) {
|
||||
|
@ -188,6 +213,90 @@ void platform_gpio_init( task_handle_t gpio_task )
|
|||
get_pin_map();
|
||||
ETS_GPIO_INTR_ATTACH(platform_gpio_intr_dispatcher, NULL);
|
||||
}
|
||||
|
||||
#ifdef GPIO_INTERRUPT_HOOK_ENABLE
|
||||
/*
|
||||
* Register an ISR hook to be called from the GPIO ISR for a given GPIO bitmask.
|
||||
* This routine is only called a few times so has been optimised for size and
|
||||
* the unregister is a special case when the bits are 0.
|
||||
*
|
||||
* Each hook function can only be registered once. If it is re-registered
|
||||
* then the hooked bits are just updated to the new value.
|
||||
*/
|
||||
int platform_gpio_register_intr_hook(uint32_t bits, platform_hook_function hook)
|
||||
{
|
||||
struct gpio_hook nh, oh = platform_gpio_hook;
|
||||
int i, j;
|
||||
|
||||
if (!hook) {
|
||||
// Cannot register or unregister null hook
|
||||
return 0;
|
||||
}
|
||||
|
||||
int delete_slot = -1;
|
||||
|
||||
// If hook already registered, just update the bits
|
||||
for (i=0; i<oh.count; i++) {
|
||||
if (hook == oh.entry[i].func) {
|
||||
if (!bits) {
|
||||
// Unregister if move to zero bits
|
||||
delete_slot = i;
|
||||
break;
|
||||
}
|
||||
if (bits & (oh.all_bits & ~oh.entry[i].bits)) {
|
||||
// Attempt to hook an already hooked bit
|
||||
return 0;
|
||||
}
|
||||
// Update the hooked bits (in the right order)
|
||||
uint32_t old_bits = oh.entry[i].bits;
|
||||
*(volatile uint32_t *) &oh.entry[i].bits = bits;
|
||||
*(volatile uint32_t *) &oh.all_bits = (oh.all_bits & ~old_bits) | bits;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// This must be the register new hook / delete old hook
|
||||
|
||||
if (delete_slot < 0) {
|
||||
if (bits & oh.all_bits) {
|
||||
return 0; // Attempt to hook already hooked bits
|
||||
}
|
||||
nh.count = oh.count + 1; // register a new hook
|
||||
} else {
|
||||
nh.count = oh.count - 1; // unregister an old hook
|
||||
}
|
||||
|
||||
// These return NULL if the count = 0 so only error check if > 0)
|
||||
nh.entry = c_malloc( nh.count * sizeof(*(nh.entry)) );
|
||||
if (nh.count && !(nh.entry)) {
|
||||
return 0; // Allocation failure
|
||||
}
|
||||
|
||||
for (i=0, j=0; i<oh.count; i++) {
|
||||
// Don't copy if this is the entry to delete
|
||||
if (i != delete_slot) {
|
||||
nh.entry[j++] = oh.entry[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (delete_slot < 0) { // for a register add the hook to the tail and set the all bits
|
||||
nh.entry[j].bits = bits;
|
||||
nh.entry[j].func = hook;
|
||||
nh.all_bits = oh.all_bits | bits;
|
||||
} else { // for an unregister clear the matching all bits
|
||||
nh.all_bits = oh.all_bits & (~oh.entry[delete_slot].bits);
|
||||
}
|
||||
|
||||
ETS_GPIO_INTR_DISABLE();
|
||||
// This is a structure copy, so interrupts need to be disabled
|
||||
platform_gpio_hook = nh;
|
||||
ETS_GPIO_INTR_ENABLE();
|
||||
|
||||
c_free(oh.entry);
|
||||
return 1;
|
||||
}
|
||||
#endif // GPIO_INTERRUPT_HOOK_ENABLE
|
||||
|
||||
/*
|
||||
* Initialise GPIO interrupt mode. Optionally in RAM because interrupts are dsabled
|
||||
*/
|
||||
|
|
|
@ -37,12 +37,20 @@ uint8_t platform_key_led( uint8_t level);
|
|||
#define PLATFORM_GPIO_HIGH 1
|
||||
#define PLATFORM_GPIO_LOW 0
|
||||
|
||||
typedef uint32_t (* platform_hook_function)(uint32_t bitmask);
|
||||
|
||||
static inline int platform_gpio_exists( unsigned pin ) { return pin < NUM_GPIO; }
|
||||
int platform_gpio_mode( unsigned pin, unsigned mode, unsigned pull );
|
||||
int platform_gpio_write( unsigned pin, unsigned level );
|
||||
int platform_gpio_read( unsigned pin );
|
||||
void platform_gpio_init( task_handle_t gpio_task );
|
||||
|
||||
// Note that these functions will not be compiled in unless GPIO_INTERRUPT_ENABLE and
|
||||
// GPIO_INTERRUPT_HOOK_ENABLE are defined.
|
||||
int platform_gpio_register_intr_hook(uint32_t gpio_bits, platform_hook_function hook);
|
||||
#define platform_gpio_unregister_intr_hook(hook) \
|
||||
platform_gpio_register_intr_hook(0, hook);
|
||||
void platform_gpio_intr_init( unsigned pin, GPIO_INT_TYPE type );
|
||||
void platform_gpio_init( task_handle_t gpio_task );
|
||||
// *****************************************************************************
|
||||
// Timer subsection
|
||||
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
# rotary Module
|
||||
|
||||
This module can read the state of cheap rotary encoder switches. These are available at
|
||||
all the standard places for a dollar or two. They are five pin devices where three are used
|
||||
for a gray code encoder for rotation, and two are used for the push switch. These switches
|
||||
are commonly used in car audio systems.
|
||||
|
||||
These switches do not have absolute positioning, but only encode the number of positions
|
||||
rotated clockwise / anticlockwise. To make use of this module, connect the common pin on the quadrature
|
||||
encoder to ground and the A and B phases to the nodemcu. One pin of the push switch should
|
||||
also be grounded and the other pin connected to the nodemcu.
|
||||
|
||||
## Sources for parts
|
||||
|
||||
- Amazon: This [search](http://www.amazon.com/s/ref=nb_sb_noss_1?url=search-alias%3Dindustrial&field-keywords=rotary+encoder+push+button&rh=n%3A16310091%2Ck%3Arotary+encoder+push+button) shows a variety.
|
||||
- Ebay: Somewhat cheaper in this [search](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2050601.m570.l1313.TR0.TRC0.H0.Xrotary+encoder+push+button.TRS0&_nkw=rotary+encoder+push+button&_sacat=0)
|
||||
- Adafruit: [rotary encoder](https://www.adafruit.com/products/377)
|
||||
- Aliexpress: This [search](http://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20160217173657&SearchText=rotary+encoder+push+button) reveals all sorts of shapes and sizes.
|
||||
|
||||
There is also a switch mounted on a board with standard 0.1" pins.
|
||||
This is the KY-040, and can also be found at [lots of places](https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=ky-040%20rotary%20encoder).
|
||||
Note that the pins are named somewhat eccentrically, and I suspect that it really does need the VCC connected.
|
||||
|
||||
## Constants
|
||||
- `rotary.PRESS = 1` The eventtype for the switch press.
|
||||
- `rotary.LONGPRESS = 2` The eventtype for a long press.
|
||||
- `rotary.RELEASE = 4` The eventtype for the switch release.
|
||||
- `rotary.TURN = 8` The eventtype for the switch rotation.
|
||||
- `rotary.CLICK = 16` The eventtype for a single click (after release)
|
||||
- `rotary.DBLCLICK = 32` The eventtype for a double click (after second release)
|
||||
- `rotary.ALL = 63` All event types.
|
||||
|
||||
## rotary.setup()
|
||||
Initialize the nodemcu to talk to a rotary encoder switch.
|
||||
|
||||
#### Syntax
|
||||
`rotary.setup(channel, pina, pinb[, pinpress[, longpress_time_ms[, dblclick_time_ms]]])`
|
||||
|
||||
#### Parameters
|
||||
- `channel` The rotary module supports three switches. The channel is either 0, 1 or 2.
|
||||
- `pina` This is a GPIO number (excluding 0) and connects to pin phase A on the rotary switch.
|
||||
- `pinb` This is a GPIO number (excluding 0) and connects to pin phase B on the rotary switch.
|
||||
- `pinpress` (optional) This is a GPIO number (excluding 0) and connects to the press switch.
|
||||
- `longpress_time_ms` (optional) The number of milliseconds (default 500) of press to be considered a long press.
|
||||
- `dblclick_time_ms` (optional) The number of milliseconds (default 500) between a release and a press for the next release to be considered a double click.
|
||||
|
||||
#### Returns
|
||||
Nothing. If the arguments are in error, or the operation cannot be completed, then an error is thrown.
|
||||
|
||||
For all API calls, if the channel number is out of range, then an error will be thrown.
|
||||
|
||||
#### Example
|
||||
|
||||
rotary.setup(0, 5,6, 7)
|
||||
|
||||
## rotary.on()
|
||||
Sets a callback on specific events.
|
||||
|
||||
#### Syntax
|
||||
`rotary.on(channel, eventtype[, callback])`
|
||||
|
||||
#### Parameters
|
||||
- `channel` The rotary module supports three switches. The channel is either 0, 1 or 2.
|
||||
- `eventtype` This defines the type of event being registered. This is the logical or of one or more of `PRESS`, `LONGPRESS`, `RELEASE`, `TURN`, `CLICK` or `DBLCLICK`.
|
||||
- `callback` This is a function that will be invoked when the specified event happens.
|
||||
|
||||
If the callback is None or omitted, then the registration is cancelled.
|
||||
|
||||
The callback will be invoked with three arguments when the event happens. The first argument is the
|
||||
current position of the rotary switch, the second is the eventtype, and the third is the time when the event happened.
|
||||
|
||||
The position is tracked
|
||||
and is represented as a signed 32-bit integer. Increasing values indicate clockwise motion. The time is the number of microseconds represented
|
||||
in a 32-bit integer. Note that this wraps every hour or so.
|
||||
|
||||
#### Example
|
||||
|
||||
rotary.on(0, rotary.ALL, function (pos, type, when)
|
||||
print "Position=" .. pos .. " event type=" .. type .. " time=" .. when
|
||||
end)
|
||||
|
||||
#### Notes
|
||||
|
||||
Events will be delivered in order, but there may be missing TURN events. If there is a long
|
||||
queue of events, then PRESS and RELEASE events may also be missed. Multiple pending TURN events
|
||||
are typically dispatched as one TURN callback with the final position as its parameter.
|
||||
|
||||
Some switches have 4 steps per detent. This means that, in practice, the application
|
||||
should divide the position by 4 and use that to determine the number of clicks. It is
|
||||
unlikely that a switch will ever reach 30 bits of rotation in either direction -- some
|
||||
are rated for under 50,000 revolutions.
|
||||
|
||||
The `CLICK` and `LONGPRESS` events are delivered on a timeout. The `DBLCLICK` event is delivered after a `PRESS`, `RELEASE`, `PRESS`, `RELEASE` sequence
|
||||
where this is a short time gap between the middle `RELEASE` and `PRESS`.
|
||||
|
||||
#### Errors
|
||||
If an invalid `eventtype` is supplied, then an error will be thrown.
|
||||
|
||||
## rotary.getpos()
|
||||
Gets the current position and press status of the switch
|
||||
|
||||
#### Syntax
|
||||
`pos, press, queue = rotary.getpos(channel)`
|
||||
|
||||
#### Parameters
|
||||
- `channel` The rotary module supports three switches. The channel is either 0, 1 or 2.
|
||||
|
||||
#### Returns
|
||||
- `pos` The current position of the switch.
|
||||
- `press` A boolean indicating if the switch is currently pressed.
|
||||
- `queue` The number of undelivered callbacks (normally 0).
|
||||
|
||||
#### Example
|
||||
|
||||
print rotary.getpos(0)
|
||||
|
||||
## rotary.close()
|
||||
Releases the resources associated with the rotary switch.
|
||||
|
||||
#### Syntax
|
||||
`rotary.close(channel)`
|
||||
|
||||
#### Parameters
|
||||
- `channel` The rotary module supports three switches. The channel is either 0, 1 or 2.
|
||||
|
||||
#### Example
|
||||
|
||||
rotary.close(0)
|
||||
|
|
@ -57,6 +57,7 @@ pages:
|
|||
- 'ow (1-Wire)': 'en/modules/ow.md'
|
||||
- 'perf': 'en/modules/perf.md'
|
||||
- 'pwm' : 'en/modules/pwm.md'
|
||||
- 'rotary' : 'en/modules/rotary.md'
|
||||
- 'rtcmem': 'en/modules/rtcmem.md'
|
||||
- 'rtctime': 'en/modules/rtctime.md'
|
||||
- 'rtcfifo': 'en/modules/rtcfifo.md'
|
||||
|
|
Loading…
Reference in New Issue