Added the rotary switch driver to the esp32 version

This commit is contained in:
Philip Gladstone 2024-02-02 21:47:10 -05:00
parent 8fbbcd6f3d
commit 82b4c81484
4 changed files with 111 additions and 196 deletions

View File

@ -38,7 +38,7 @@ typedef struct {
int last_recent_event_was_release : 1;
int timer_running : 1;
int possible_dbl_click : 1;
uint8_t id;
struct rotary_driver_handle *handle;
int click_delay_us;
int longpress_delay_us;
uint32_t last_event_time;
@ -46,7 +46,6 @@ typedef struct {
esp_timer_handle_t timer_handle;
} 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);
@ -59,10 +58,8 @@ static void callback_free_one(lua_State *L, int *cb_ptr)
}
}
static void callback_free(lua_State* L, unsigned int id, int mask)
static void callback_free(lua_State* L, DATA *d, int mask)
{
DATA *d = data[id];
if (d) {
int i;
for (i = 0; i < CALLBACK_COUNT; i++) {
@ -85,9 +82,8 @@ static int callback_setOne(lua_State* L, int *cb_ptr, int arg_number)
return -1;
}
static int callback_set(lua_State* L, int id, int mask, int arg_number)
static int callback_set(lua_State* L, DATA *d, int mask, int arg_number)
{
DATA *d = data[id];
int result = 0;
int i;
@ -120,35 +116,16 @@ static void callback_call(lua_State* L, DATA *d, int cbnum, int arg, uint32_t ti
}
}
int platform_rotary_exists( unsigned int id )
{
return (id < ROTARY_CHANNEL_COUNT);
}
// Lua: setup(id, phase_a, phase_b [, press])
// Lua: setup(phase_a, phase_b [, press])
static int lrotary_setup( lua_State* L )
{
unsigned int id;
int nargs = lua_gettop(L);
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 *) calloc(1, sizeof(DATA));
if (!data[id]) {
return -1;
}
}
DATA *d = data[id];
DATA *d = (DATA *)lua_newuserdata(L, sizeof(DATA));
if (!d) return luaL_error(L, "not enough memory");
memset(d, 0, sizeof(*d));
d->id = id;
luaL_getmetatable(L, "rotary.switch");
lua_setmetatable(L, -2);
esp_timer_create_args_t timer_args = {
.callback = lrotary_timer_done,
@ -167,90 +144,83 @@ static int lrotary_setup( lua_State* L )
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 phase_a = luaL_checkinteger(L, 1);
luaL_argcheck(L, platform_gpio_exists(phase_a) && phase_a > 0, 1, "Invalid pin");
int phase_b = luaL_checkinteger(L, 2);
luaL_argcheck(L, platform_gpio_exists(phase_b) && phase_b > 0, 2, "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");
if (nargs >= 3) {
press = luaL_checkinteger(L, 3);
luaL_argcheck(L, platform_gpio_exists(press) && press > 0, 3, "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 (nargs >= 4) {
d->longpress_delay_us = 1000 * luaL_checkinteger(L, 4);
luaL_argcheck(L, d->longpress_delay_us > 0, 4, "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 (nargs >= 5) {
d->click_delay_us = 1000 * luaL_checkinteger(L, 5);
luaL_argcheck(L, d->click_delay_us > 0, 5, "Invalid timeout");
}
if (rotary_setup(id, phase_a, phase_b, press, tasknumber)) {
d->handle = rotary_setup(phase_a, phase_b, press, tasknumber, d);
if (!d->handle) {
return luaL_error(L, "Unable to setup rotary switch.");
}
return 0;
return 1;
}
// Lua: close( id )
// Lua: close( )
static int lrotary_close( lua_State* L )
{
unsigned int id;
DATA *d = (DATA *)luaL_checkudata(L, 1, "rotary.switch");
id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( rotary, id );
callback_free(L, id, ROTARY_ALL);
if (d->handle) {
callback_free(L, d, ROTARY_ALL);
DATA *d = data[id];
if (d) {
data[id] = NULL;
free(d);
if (rotary_close( d->handle )) {
return luaL_error( L, "Unable to close switch." );
}
if (rotary_close( id )) {
return luaL_error( L, "Unable to close switch." );
d->handle = NULL;
}
return 0;
}
// Lua: on( id, mask[, cb] )
// Lua: on( mask[, cb] )
static int lrotary_on( lua_State* L )
{
unsigned int id;
id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( rotary, id );
DATA *d = (DATA *)luaL_checkudata(L, 1, "rotary.switch");
int mask = luaL_checkinteger(L, 2);
if (lua_gettop(L) >= 3) {
if (callback_set(L, id, mask, 3)) {
if (callback_set(L, d, mask, 3)) {
return luaL_error( L, "Unable to set callback." );
}
} else {
callback_free(L, id, mask);
callback_free(L, d, mask);
}
return 0;
}
// Lua: getpos( id ) -> pos, PRESS/RELEASE
// Lua: getpos( ) -> pos, PRESS/RELEASE
static int lrotary_getpos( lua_State* L )
{
unsigned int id;
id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( rotary, id );
DATA *d = (DATA *)luaL_checkudata(L, 1, "rotary.switch");
int pos = rotary_getpos(id);
int pos = rotary_getpos(d->handle);
if (pos == -1) {
return 0;
}
lua_pushinteger(L, (pos << 1) >> 1);
lua_pushinteger(L, (pos & 0x80000000) ? MASK(PRESS) : MASK(RELEASE));
lua_pushboolean(L, (pos & 0x80000000));
return 2;
}
@ -264,7 +234,7 @@ static bool lrotary_dequeue_single(lua_State* L, DATA *d)
// This chnnel is open
rotary_event_t result;
if (rotary_getevent(d->id, &result)) {
if (rotary_getevent(d->handle, &result)) {
int pos = result.pos;
lrotary_check_timer(d, result.time_us, 0);
@ -303,7 +273,8 @@ static bool lrotary_dequeue_single(lua_State* L, DATA *d)
d->lastpos = pos;
}
something_pending = rotary_has_queued_event(d->id);
rotary_event_handled(d->handle);
something_pending = rotary_has_queued_event(d->handle);
}
lrotary_check_timer(d, esp_timer_get_time(), 1);
@ -356,49 +327,28 @@ static void lrotary_check_timer(DATA *d, uint32_t time_us, bool dotimer)
static void lrotary_task(task_param_t param, task_prio_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];
DATA *d = (DATA *) param;
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);
task_post_medium(tasknumber, param);
}
}
static int rotary_open(lua_State *L)
{
tasknumber = task_get_id(lrotary_task);
if (rotary_driver_init() != ESP_OK) {
return luaL_error(L, "Initialization fail");
}
return 0;
}
// Module function map
LROT_BEGIN(rotary, NULL, 0)
LROT_FUNCENTRY( setup, lrotary_setup )
LROT_FUNCENTRY( close, lrotary_close )
LROT_FUNCENTRY( on, lrotary_on )
LROT_FUNCENTRY( getpos, lrotary_getpos )
LROT_NUMENTRY( TURN, MASK(TURN) )
LROT_NUMENTRY( PRESS, MASK(PRESS) )
LROT_NUMENTRY( RELEASE, MASK(RELEASE) )
@ -406,8 +356,22 @@ LROT_BEGIN(rotary, NULL, 0)
LROT_NUMENTRY( CLICK, MASK(CLICK) )
LROT_NUMENTRY( DBLCLICK, MASK(DBLCLICK) )
LROT_NUMENTRY( ALL, ROTARY_ALL )
LROT_END(rotary, NULL, 0)
// Module function map
LROT_BEGIN(rotary_switch, NULL, LROT_MASK_GC_INDEX)
LROT_FUNCENTRY(__gc, lrotary_close)
LROT_TABENTRY(__index, rotary_switch)
LROT_FUNCENTRY(on, lrotary_on)
LROT_FUNCENTRY(close, lrotary_close)
LROT_FUNCENTRY(getpos, lrotary_getpos)
LROT_END(rotary_switch, NULL, LROT_MASK_GC_INDEX)
static int rotary_open(lua_State *L) {
luaL_rometatable(L, "rotary.switch",
LROT_TABLEREF(rotary_switch)); // create metatable
tasknumber = task_get_id(lrotary_task);
return 0;
}
NODEMCU_MODULE(ROTARY, "rotary", rotary, rotary_open);

View File

@ -40,20 +40,18 @@
#define STATUS_IS_PRESSED(x) (((x) & 0x80000000) != 0)
typedef struct {
typedef struct rotary_driver_handle {
int8_t phase_a_pin;
int8_t phase_b_pin;
int8_t press_pin;
int8_t task_queued;
uint32_t read_offset; // Accessed by task
uint32_t write_offset; // Accessed by ISR
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;
void *callback_arg;
} *rotary_driver_handle_t;
static void set_gpio_mode(int pin, gpio_int_type_t intr)
{
@ -61,6 +59,7 @@ static void set_gpio_mode(int pin, gpio_int_type_t intr)
.pin_bit_mask = 1LL << pin,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = intr
};
@ -76,20 +75,12 @@ static void rotary_clear_pin(int pin)
}
// Just takes the channel number. Cleans up the resources used.
int rotary_close(uint32_t channel)
int rotary_close(rotary_driver_handle_t d)
{
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);
@ -102,7 +93,7 @@ int rotary_close(uint32_t channel)
static void rotary_interrupt(void *arg)
{
// This function runs with high priority
DATA *d = (DATA *) arg;
rotary_driver_handle_t d = (rotary_driver_handle_t)arg;
uint32_t last_status = GET_LAST_STATUS(d).pos;
@ -165,9 +156,9 @@ static void rotary_interrupt(void *arg)
|| 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, (task_param_t) &task_queued)) {
task_queued = 1;
if (!d->task_queued) {
if (task_post_medium(d->tasknumber, (task_param_t) d->callback_arg)) {
d->task_queued = 1;
}
}
} else {
@ -179,27 +170,21 @@ static void rotary_interrupt(void *arg)
}
}
void rotary_event_handled(rotary_driver_handle_t d)
{
d->task_queued = 0;
}
// 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 *) calloc(1, sizeof(DATA));
rotary_driver_handle_t rotary_setup(int phase_a, int phase_b, int press,
task_handle_t tasknumber, void *arg) {
rotary_driver_handle_t d = (rotary_driver_handle_t )calloc(1, sizeof(*d));
if (!d) {
return -1;
return NULL;
}
data[channel] = d;
d->tasknumber = tasknumber;
d->callback_arg = arg;
set_gpio_mode(phase_a, GPIO_INTR_ANYEDGE);
gpio_isr_handler_add(phase_a, rotary_interrupt, d);
@ -215,17 +200,11 @@ int rotary_setup(uint32_t channel, int phase_a, int phase_b, int press, task_han
}
d->press_pin = press;
return 0;
return d;
}
bool rotary_has_queued_event(uint32_t channel)
bool rotary_has_queued_event(rotary_driver_handle_t d)
{
if (channel >= sizeof(data) / sizeof(data[0])) {
return false;
}
DATA *d = data[channel];
if (!d) {
return false;
}
@ -234,16 +213,9 @@ bool rotary_has_queued_event(uint32_t channel)
}
// Get the oldest event in the queue and remove it (if possible)
bool rotary_getevent(uint32_t channel, rotary_event_t *resultp)
{
bool rotary_getevent(rotary_driver_handle_t d, 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;
}
@ -263,22 +235,10 @@ bool rotary_getevent(uint32_t channel, rotary_event_t *resultp)
return status;
}
int rotary_getpos(uint32_t channel)
{
if (channel >= sizeof(data) / sizeof(data[0])) {
return -1;
}
DATA *d = data[channel];
int rotary_getpos(rotary_driver_handle_t d) {
if (!d) {
return -1;
}
return GET_LAST_STATUS(d).pos;
}
esp_err_t rotary_driver_init()
{
return gpio_install_isr_service(ESP_INTR_FLAG_LOWMED);
}

View File

@ -6,23 +6,23 @@
#include <stdint.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);
struct rotary_driver_handle *rotary_setup(int phaseA,
int phaseB, int press,
task_handle_t tasknumber, void *arg);
bool rotary_getevent(uint32_t channel, rotary_event_t *result);
bool rotary_getevent(struct rotary_driver_handle *handle, rotary_event_t *result);
bool rotary_has_queued_event(uint32_t channel);
bool rotary_has_queued_event(struct rotary_driver_handle *handle);
int rotary_getpos(uint32_t channel);
int rotary_getpos(struct rotary_driver_handle *handle);
int rotary_close(uint32_t channel);
int rotary_close(struct rotary_driver_handle *handle);
int rotary_driver_init();
void rotary_event_handled(struct rotary_driver_handle *handle);
#endif

View File

@ -32,10 +32,9 @@ Note that the pins are named somewhat eccentrically, and I suspect that it reall
Initialize the nodemcu to talk to a rotary encoder switch.
#### Syntax
`rotary.setup(channel, pina, pinb[, pinpress[, longpress_time_ms[, dblclick_time_ms]]])`
`switch = rotary.setup(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.
@ -45,20 +44,18 @@ Initialize the nodemcu to talk to a rotary encoder switch.
#### 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)
switch = rotary.setup(5, 6, 7)
## rotary.on()
## switch:on()
Sets a callback on specific events.
#### Syntax
`rotary.on(channel, eventtype[, callback])`
`switch:on(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.
@ -73,8 +70,8 @@ in a 32-bit integer. Note that this wraps every hour or so.
#### Example
rotary.on(0, rotary.ALL, function (type, pos, when)
print "Position=" .. pos .. " event type=" .. type .. " time=" .. when
switch:on(rotary.ALL, function (type, pos, when)
print("Position=" .. pos .. " event type=" .. type .. " time=" .. when)
end)
#### Notes
@ -94,14 +91,11 @@ 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()
## switch:getpos()
Gets the current position and press status of the switch
#### Syntax
`pos, press = rotary.getpos(channel)`
#### Parameters
- `channel` The rotary module supports three switches. The channel is either 0, 1 or 2.
`pos, press = switch:getpos()`
#### Returns
- `pos` The current position of the switch.
@ -109,18 +103,15 @@ Gets the current position and press status of the switch
#### Example
print rotary.getpos(0)
print(switch:getpos())
## rotary.close()
## switch: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.
`switch:close()`
#### Example
rotary.close(0)
switch:close()