Squashed commit of the following:

commit 2c7c3fc3985cc32866e8af496abea9971eaee90a
Merge: 9179dae 41022c3
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: 085935f 19d3c1d
    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:
philip 2016-02-28 14:48:41 -05:00
parent 41022c3b6a
commit d40ee50a8e
9 changed files with 1004 additions and 4 deletions

320
app/driver/rotary.c Normal file
View File

@ -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;
}

View File

@ -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

View File

@ -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))

View File

@ -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

405
app/modules/rotary.c Normal file
View File

@ -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);

View File

@ -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
*/

View File

@ -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

129
docs/en/modules/rotary.md Normal file
View File

@ -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)

View File

@ -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'