ESP32: Pulse counter module released (#2739)
* ESP32: Added pulsecnt module The pulsecnt module let's you use the ESP32's pulse counter capabilities from Lua. * ESP32: Pulsecnt module. Better/faster callback. Reduced the amount of callback variables to speed things up and shift more logic to Lua than in the C code. * ESP32: Completed docs for pulsecnt * ESP32: Final release of pulsecnt * ESP32: Production release of pulsecnt * ESP32: Release (tweaked docs) * ESP32: Pulse Counter Release. Cleaned up .gitignore * ESP32: Pulse counter release (changed ch1 gpio to int to match ch0)
This commit is contained in:
parent
6d0e45793f
commit
8b9794b99d
|
@ -13,3 +13,4 @@ tools/toolchains
|
||||||
.cproject
|
.cproject
|
||||||
.project
|
.project
|
||||||
.settings/
|
.settings/
|
||||||
|
.vscode/**
|
|
@ -271,4 +271,10 @@ config LUA_MODULE_TIME
|
||||||
help
|
help
|
||||||
Includes the time module.
|
Includes the time module.
|
||||||
|
|
||||||
|
config LUA_MODULE_PULSECNT
|
||||||
|
bool "Pulse counter module"
|
||||||
|
default "n"
|
||||||
|
help
|
||||||
|
Includes the pulse counter module.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -0,0 +1,732 @@
|
||||||
|
/*
|
||||||
|
Pulse counter module for ESP32 to allow interfacing from Lua
|
||||||
|
Authored by: ChiliPeppr (John Lauer) 2019
|
||||||
|
|
||||||
|
ESP-IDF docs for Pulse Counter
|
||||||
|
https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/pcnt.html
|
||||||
|
|
||||||
|
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||||
|
Make modifications at will and freely.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, this
|
||||||
|
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "module.h"
|
||||||
|
#include "lauxlib.h"
|
||||||
|
#include "lmem.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "task/task.h"
|
||||||
|
#include "driver/pcnt.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lextra.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
pcnt_isr_handle_t user_isr_handle = NULL; //user's ISR service handle
|
||||||
|
|
||||||
|
/* A sample structure to pass events from the PCNT
|
||||||
|
* interrupt handler to the main program.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t unit; // the PCNT unit that originated an interrupt
|
||||||
|
uint32_t status; // information on the event type that caused the interrupt
|
||||||
|
} pcnt_evt_t;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
// PulsecntHandle_t pcnt;
|
||||||
|
int32_t cb_ref, self_ref;
|
||||||
|
uint8_t unit; //=0, -- Defaults to 0. ESP32 has 0 thru 7 units to count pulses on
|
||||||
|
bool is_initted;
|
||||||
|
bool is_debug;
|
||||||
|
int ch0_pulse_gpio_num; // needs to be signed to support PCNT_PIN_NOT_USED of -1
|
||||||
|
int ch0_ctrl_gpio_num; // needs to be signed to support PCNT_PIN_NOT_USED of -1
|
||||||
|
bool ch0_is_defined;
|
||||||
|
uint8_t ch0_pos_mode;
|
||||||
|
uint8_t ch0_neg_mode;
|
||||||
|
uint8_t ch0_lctrl_mode;
|
||||||
|
uint8_t ch0_hctrl_mode;
|
||||||
|
int16_t ch0_counter_l_lim;
|
||||||
|
int16_t ch0_counter_h_lim;
|
||||||
|
bool ch1_is_defined;
|
||||||
|
int ch1_pulse_gpio_num; // needs to be signed to support PCNT_PIN_NOT_USED of -1
|
||||||
|
int ch1_ctrl_gpio_num; // needs to be signed to support PCNT_PIN_NOT_USED of -1
|
||||||
|
uint8_t ch1_pos_mode;
|
||||||
|
uint8_t ch1_neg_mode;
|
||||||
|
uint8_t ch1_lctrl_mode;
|
||||||
|
uint8_t ch1_hctrl_mode;
|
||||||
|
int16_t ch1_counter_l_lim;
|
||||||
|
int16_t ch1_counter_h_lim;
|
||||||
|
int16_t thresh0; // thresh0 is for the unit, not the channel
|
||||||
|
int16_t thresh1; // thresh1 is for the unit, not the channel
|
||||||
|
uint32_t counter;
|
||||||
|
} pulsecnt_struct_t;
|
||||||
|
typedef pulsecnt_struct_t *pulsecnt_t;
|
||||||
|
|
||||||
|
// array of 8 pulsecnt_struct_t pointers so we can reference by unit number
|
||||||
|
// this array gets filled in as we define pulsecnt_struct_t's during the create() method
|
||||||
|
static pulsecnt_t pulsecnt_selfs[8];
|
||||||
|
|
||||||
|
// Task ID to get ISR interrupt back into Lua callback
|
||||||
|
static task_handle_t pulsecnt_task_id;
|
||||||
|
|
||||||
|
/* Decode what PCNT's unit originated an interrupt
|
||||||
|
* and pass this information together with the event type
|
||||||
|
* the main program.
|
||||||
|
*/
|
||||||
|
static void IRAM_ATTR pulsecnt_intr_handler(void *arg)
|
||||||
|
{
|
||||||
|
uint32_t intr_status = PCNT.int_st.val;
|
||||||
|
uint8_t i;
|
||||||
|
pcnt_evt_t evt;
|
||||||
|
// portBASE_TYPE HPTaskAwoken = pdFALSE;
|
||||||
|
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
if (intr_status & (BIT(i))) {
|
||||||
|
evt.unit = i;
|
||||||
|
/* Save the PCNT event type that caused an interrupt
|
||||||
|
to pass it to the main program */
|
||||||
|
evt.status = PCNT.status_unit[i].val;
|
||||||
|
PCNT.int_clr.val = BIT(i);
|
||||||
|
|
||||||
|
|
||||||
|
// post using lua task posting technique
|
||||||
|
// on lua_open we set pulsecnt_task_id as a method which gets called
|
||||||
|
// by Lua after task_post_high with reference to this self object and then we can steal the
|
||||||
|
// callback_ref and then it gets called by lua_call where we get to add our args
|
||||||
|
task_post_high(pulsecnt_task_id, (evt.status << 8) | evt.unit );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This method gets called from the IRAM interuppt method via Lua's task queue. That lets the interrupt
|
||||||
|
run clean while this method gets called at a lower priority to not break the IRAM interrupt high priority.
|
||||||
|
We will do the actual callback here for the user with the fully decoded state of the pulse count.
|
||||||
|
The format of the callback to your Lua code is:
|
||||||
|
function onPulseCnt(unit, isThr0, isThr1, isLLim, isHLim, isZero)
|
||||||
|
*/
|
||||||
|
static void pulsecnt_task(task_param_t param, task_prio_t prio)
|
||||||
|
{
|
||||||
|
(void)prio;
|
||||||
|
|
||||||
|
// we bit packed the unit number and status into 1 int in the IRAM interrupt so need to unpack here
|
||||||
|
uint32_t unit = (uint32_t)param & 0xffu;
|
||||||
|
int status = ((uint32_t)param >> 8);
|
||||||
|
|
||||||
|
// int16_t cur_count, evt_count = 0;
|
||||||
|
// pcnt_get_counter_value(unit, &cur_count);
|
||||||
|
|
||||||
|
// try to get the pulsecnt_struct_t from the pulsecnt_selfs array
|
||||||
|
pulsecnt_t pc = pulsecnt_selfs[unit];
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Cb for unit %d, gpio: %d, ctrl_gpio: %d, pos_mode: %d, neg_mode: %d, lctrl_mode: %d, hctrl_mode: %d, counter_l_lim: %d, counter_h_lim: %d", pc->unit, pc->ch0_pulse_gpio_num, pc->ch0_ctrl_gpio_num, pc->ch0_pos_mode, pc->ch0_neg_mode, pc->ch0_lctrl_mode, pc->ch0_hctrl_mode, pc->ch0_counter_l_lim, pc->ch0_counter_h_lim );
|
||||||
|
|
||||||
|
|
||||||
|
bool thr1 = false;
|
||||||
|
bool thr0 = false;
|
||||||
|
bool l_lim = false;
|
||||||
|
bool h_lim = false;
|
||||||
|
bool zero = false;
|
||||||
|
// char evt_str[20]; // "-32768 or 32768" is 15 chars long and is the max string len
|
||||||
|
// bool is_multi_lim = false;
|
||||||
|
|
||||||
|
/*0: positive value to zero; 1: negative value to zero; 2: counter value negative ; 3: counter value positive*/
|
||||||
|
// uint8_t moving_to = status & 0x00000003u; // get first two bits
|
||||||
|
|
||||||
|
if (status & PCNT_STATUS_THRES1_M) {
|
||||||
|
// printf("THRES1 EVT\n");
|
||||||
|
thr1 = true;
|
||||||
|
// evt_count = pc->thresh1;
|
||||||
|
}
|
||||||
|
if (status & PCNT_STATUS_THRES0_M) {
|
||||||
|
// printf("THRES0 EVT\n");
|
||||||
|
thr0 = true;
|
||||||
|
// evt_count = pc->thresh0;
|
||||||
|
}
|
||||||
|
if (status & PCNT_STATUS_L_LIM_M) {
|
||||||
|
// printf("L_LIM EVT\n");
|
||||||
|
l_lim = true;
|
||||||
|
/*
|
||||||
|
// see if there is a ch0 and ch1 limit. if so then pass back string. otherwise pass back just one int.
|
||||||
|
if (pc->ch0_is_defined && pc->ch1_is_defined) {
|
||||||
|
// we need to pass back both because it's indeterminate which limit triggered this and there is
|
||||||
|
// no way to know from the ESP32 API
|
||||||
|
is_multi_lim = true;
|
||||||
|
sprintf(evt_str, "%d or %d", pc->ch0_counter_l_lim, pc->ch1_counter_l_lim);
|
||||||
|
} else if (pc->ch0_is_defined) {
|
||||||
|
// we have a ch0 item, so use its val
|
||||||
|
evt_count = pc->ch0_counter_l_lim;
|
||||||
|
} else if (pc->ch1_is_defined) {
|
||||||
|
// we have a ch1 item, so use its val
|
||||||
|
evt_count = pc->ch1_counter_l_lim;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (status & PCNT_STATUS_H_LIM_M) {
|
||||||
|
// printf("H_LIM EVT\n");
|
||||||
|
h_lim = true;
|
||||||
|
/*
|
||||||
|
// see if there is a ch0 and ch1 limit. if so then pass back string. otherwise pass back just one int.
|
||||||
|
if (pc->ch0_is_defined && pc->ch1_is_defined) {
|
||||||
|
// we need to pass back both because it's indeterminate which limit triggered this and there is
|
||||||
|
// no way to know from the ESP32 API
|
||||||
|
is_multi_lim = true;
|
||||||
|
sprintf(evt_str, "%d or %d", pc->ch0_counter_h_lim, pc->ch1_counter_h_lim);
|
||||||
|
} else if (pc->ch0_is_defined) {
|
||||||
|
// we have a ch0 item, so use its val
|
||||||
|
evt_count = pc->ch0_counter_h_lim;
|
||||||
|
} else if (pc->ch1_is_defined) {
|
||||||
|
// we have a ch1 item, so use its val
|
||||||
|
evt_count = pc->ch1_counter_h_lim;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (status & PCNT_STATUS_ZERO_M) {
|
||||||
|
// printf("ZERO EVT\n");
|
||||||
|
zero = true;
|
||||||
|
// evt_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// at the start of turning on the pulse counter you get a stat=255 which is like a
|
||||||
|
// 1st time callback saying it's alive
|
||||||
|
// if (status == 255) {
|
||||||
|
// evt_count = -1;
|
||||||
|
// }
|
||||||
|
|
||||||
|
lua_State *L = lua_getstate ();
|
||||||
|
if (pc->cb_ref != LUA_NOREF)
|
||||||
|
{
|
||||||
|
// lua_rawgeti (L, LUA_REGISTRYINDEX, pulsecnt_cb_refs[unit]);
|
||||||
|
lua_rawgeti (L, LUA_REGISTRYINDEX, pc->cb_ref);
|
||||||
|
lua_pushinteger (L, unit);
|
||||||
|
// if (is_multi_lim) {
|
||||||
|
// lua_pushstring(L, evt_str);
|
||||||
|
// } else {
|
||||||
|
// lua_pushinteger (L, evt_count);
|
||||||
|
// }
|
||||||
|
// lua_pushinteger (L, cur_count);
|
||||||
|
lua_pushboolean (L, thr0);
|
||||||
|
lua_pushboolean (L, thr1);
|
||||||
|
lua_pushboolean (L, l_lim);
|
||||||
|
lua_pushboolean (L, h_lim);
|
||||||
|
lua_pushboolean (L, zero);
|
||||||
|
// lua_pushinteger (L, moving_to);
|
||||||
|
// lua_pushinteger (L, status);
|
||||||
|
lua_call (L, 6, 0);
|
||||||
|
} else {
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Could not find cb for unit %d with ptr %d", unit, pc->cb_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the pulsecnt.pctr object from the stack which is the struct pulsecnt_t
|
||||||
|
static pulsecnt_t pulsecnt_get( lua_State *L, int stack )
|
||||||
|
{
|
||||||
|
return (pulsecnt_t)luaL_checkudata(L, stack, "pulsecnt.pctr");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pc:setFilter(clkCyclesToIgnore)
|
||||||
|
// Example: pc:setFilter(100) -- Ignore any signal shorter than 100 clock cycles. 80Mhz clock.
|
||||||
|
// You can ignore from 0 to 1023 clock cycles
|
||||||
|
static int pulsecnt_set_filter( lua_State *L ) {
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// when we're called from an object the stack index 1 has our self ref
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, ++stack);
|
||||||
|
|
||||||
|
// Get clkCyclesToIgnore -- first arg after self arg
|
||||||
|
int clkCyclesToIgnore = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, clkCyclesToIgnore >= 0 && clkCyclesToIgnore <= 1023, stack, "The clkCyclesToIgnore number allows 0 to 1023");
|
||||||
|
|
||||||
|
pcnt_set_filter_value(pc->unit, clkCyclesToIgnore);
|
||||||
|
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Setup filter for unit %d with clkCyclesToIgnore %d", pc->unit, clkCyclesToIgnore);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pc:setThres(thresh0_val, thresh1_val)
|
||||||
|
// Example: pc:setThres(-5, 5)
|
||||||
|
// When you set the threshold, the pulse counter will be reset and the callback will be attached.
|
||||||
|
static int pulsecnt_set_thres( lua_State *L ) {
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// when we're called from an object the stack index 1 has our self ref
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, ++stack);
|
||||||
|
|
||||||
|
// Get thresh0_val -- first arg after self arg
|
||||||
|
int thresh0_val = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, thresh0_val >= -32768 && thresh0_val <= 32767, stack, "The thresh0_val number allows -32768 to 32767");
|
||||||
|
|
||||||
|
// Get thresh0_val -- first arg after self arg
|
||||||
|
int thresh1_val = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, thresh1_val >= -32768 && thresh1_val <= 32767, stack, "The thresh1_val number allows -32768 to 32767");
|
||||||
|
|
||||||
|
// store it so we have it during callback
|
||||||
|
pc->thresh0 = thresh0_val;
|
||||||
|
pc->thresh1 = thresh1_val;
|
||||||
|
|
||||||
|
/* Set threshold 0 and 1 values and enable events to watch */
|
||||||
|
pcnt_set_event_value(pc->unit, PCNT_EVT_THRES_0, thresh0_val);
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_THRES_0);
|
||||||
|
pcnt_set_event_value(pc->unit, PCNT_EVT_THRES_1, thresh1_val);
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_THRES_1);
|
||||||
|
/* Enable events on zero, maximum and minimum limit values */
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_ZERO);
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_H_LIM);
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_L_LIM);
|
||||||
|
|
||||||
|
/* Initialize PCNT's counter */
|
||||||
|
pcnt_counter_pause(pc->unit);
|
||||||
|
pcnt_counter_clear(pc->unit);
|
||||||
|
|
||||||
|
// check if there's a callback otherwise don't trigger interrupt, instead they may just be polling
|
||||||
|
if (pc->cb_ref != LUA_NOREF) {
|
||||||
|
/* Register ISR handler and enable interrupts for PCNT unit */
|
||||||
|
pcnt_isr_register(pulsecnt_intr_handler, NULL, 0, &user_isr_handle);
|
||||||
|
pcnt_intr_enable(pc->unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Everything is set up, now go to counting */
|
||||||
|
pcnt_counter_resume(pc->unit);
|
||||||
|
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Setup threshold for unit %d with thr0 %d, thr1 %d", pc->unit, thresh0_val, thresh1_val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Lua: pc:rawSetEventVal(enumEventItem, val)
|
||||||
|
// enumEventItem can be pulsecnt.PCNT_EVT_L_LIM, PCNT_EVT_H_LIM, PCNT_EVT_THRES_0, PCNT_EVT_THRES_1, PCNT_EVT_ZERO
|
||||||
|
// Example: pc:rawSetEventVal(pulsecnt.PCNT_EVT_THRES_1, 100)
|
||||||
|
// The pulse counter is not cleared using this method so you can make the change on-the-fly, however, in practice
|
||||||
|
// it appears the pulse counter module does not pay attention to on-the-fly changes for the threshold value.
|
||||||
|
static int pulsecnt_set_event_value( lua_State *L ) {
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// when we're called from an object the stack index 1 has our self ref
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, ++stack);
|
||||||
|
|
||||||
|
|
||||||
|
// Get enum -- first arg after self arg
|
||||||
|
int enumEventItem = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, enumEventItem >= -1 && enumEventItem <= 15, stack, "The enumEventItem number allows -1 to 15");
|
||||||
|
|
||||||
|
// Get val -- 2nd arg after self arg
|
||||||
|
int val = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, val >= -32768 && val <= 32767, stack, "The val number allows -32768 to 32767");
|
||||||
|
|
||||||
|
/* Set value for this unit */
|
||||||
|
pcnt_set_event_value(pc->unit, enumEventItem, val);
|
||||||
|
|
||||||
|
// store it so we have it during callback and reset event or ESP32 won't accept in new val
|
||||||
|
if (enumEventItem == PCNT_EVT_THRES_0) {
|
||||||
|
pc->thresh0 = val;
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_THRES_0);
|
||||||
|
} else if (enumEventItem == PCNT_EVT_THRES_1) {
|
||||||
|
pc->thresh1 = val;
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_THRES_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there's a callback otherwise don't trigger interrupt, instead they may just be polling
|
||||||
|
if (pc->cb_ref != LUA_NOREF) {
|
||||||
|
// even though we likely have the interrupt enabled, this re-reads the vals we just set?
|
||||||
|
pcnt_intr_enable(pc->unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Set enumEventItem %d for unit %d with val %d", enumEventItem, pc->unit, val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: retVal = pc:rawGetEventVal(enumEventItem)
|
||||||
|
// enumEventItem can be pulsecnt.PCNT_EVT_L_LIM, PCNT_EVT_H_LIM, PCNT_EVT_THRES_0, PCNT_EVT_THRES_1, PCNT_EVT_ZERO
|
||||||
|
// Example: retVal = pc:rawGetEventVal(pulsecnt.PCNT_EVT_THRES_1)
|
||||||
|
static int pulsecnt_get_event_value( lua_State *L ) {
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// when we're called from an object the stack index 1 has our self ref
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, ++stack);
|
||||||
|
|
||||||
|
// Get enum -- first arg after self arg
|
||||||
|
int enumEventItem = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, enumEventItem >= -1 && enumEventItem <= 15, stack, "The enumEventItem number allows -1 to 15");
|
||||||
|
|
||||||
|
/* Set threshold 0 and 1 values and enable events to watch */
|
||||||
|
// pcnt_get_event_value(pcnt_unit_tunit, pcnt_evt_type_tevt_type, int16_t *value)
|
||||||
|
int16_t val;
|
||||||
|
pcnt_get_event_value(pc->unit, enumEventItem, &val);
|
||||||
|
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Get enumEventItem %d for unit %d with val %d", enumEventItem, pc->unit, val);
|
||||||
|
|
||||||
|
lua_pushinteger (L, val);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Not implemented yet.
|
||||||
|
// Lua example call:
|
||||||
|
// pc:config({
|
||||||
|
// ch0: {
|
||||||
|
// pulse_gpio_num = 4,
|
||||||
|
// ctrl_gpio_num = 5,
|
||||||
|
// },
|
||||||
|
// ch1: {
|
||||||
|
// pulse_gpio_num = 12,
|
||||||
|
// ctrl_gpio_num = 14,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
static int pulsecnt_config(lua_State *L) {
|
||||||
|
|
||||||
|
// We are passed in a complex Lua table
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// when we're called from an object the stack index 1 has our self ref
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, ++stack);
|
||||||
|
|
||||||
|
// get and set a self reference if we don't have one (which we likely won't have until this call occurs)
|
||||||
|
if (pc->self_ref == LUA_NOREF) {
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
pc->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to make sure the next stack item is a Lua table
|
||||||
|
int top = lua_gettop( L );
|
||||||
|
|
||||||
|
for (int stack = 1; stack <= top; stack++) {
|
||||||
|
if (lua_type( L, stack ) == LUA_TNIL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (lua_type( L, stack ) != LUA_TTABLE) {
|
||||||
|
// ws2812_cleanup( L, 0 );
|
||||||
|
luaL_checktype( L, stack, LUA_TTABLE ); // trigger error
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// retrieve ch0
|
||||||
|
//
|
||||||
|
lua_getfield( L, stack, "ch0" );
|
||||||
|
if (lua_type( L, stack ) != LUA_TTABLE) {
|
||||||
|
// ws2812_cleanup( L, 1 );
|
||||||
|
return luaL_argerror( L, stack, "ch0 needs to be a table of settings" );
|
||||||
|
}
|
||||||
|
// int gpio_num = luaL_checkint( L, -1 );
|
||||||
|
lua_pop( L, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called internally, not from Lua
|
||||||
|
static int pulsecnt_channel_config( lua_State *L, uint8_t channel ) {
|
||||||
|
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// when we're called from an object the stack index 1 has our self ref
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, ++stack);
|
||||||
|
|
||||||
|
// get and set a self reference if we don't have one (which we likely won't have until this call occurs)
|
||||||
|
if (pc->self_ref == LUA_NOREF) {
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
pc->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get pulse_gpio_num -- first arg after self arg
|
||||||
|
int pulse_gpio_num = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, pulse_gpio_num >= -1 && pulse_gpio_num <= 40, stack, "The pulse_gpio_num number allows -1 to 40");
|
||||||
|
|
||||||
|
// Get ctrl_gpio_num -- 2nd arg
|
||||||
|
int ctrl_gpio_num = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, ctrl_gpio_num >= -1 && ctrl_gpio_num <= 40, stack, "The ctrl_gpio_num number allows -1 to 40");
|
||||||
|
|
||||||
|
// Get pos_mode -- 3rd arg
|
||||||
|
int pos_mode = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, pos_mode >= 0 && pos_mode <= 2, stack, "The pos_mode number allows 0, 1, or 2");
|
||||||
|
|
||||||
|
// Get neg_mode -- 4th arg
|
||||||
|
int neg_mode = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, neg_mode >= 0 && neg_mode <= 2, stack, "The neg_mode number allows 0, 1, or 2");
|
||||||
|
|
||||||
|
// Get lctrl_mode -- 5th arg
|
||||||
|
int lctrl_mode = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, lctrl_mode >= 0 && lctrl_mode <= 2, stack, "The lctrl_mode number allows 0, 1, or 2");
|
||||||
|
|
||||||
|
// Get hctrl_mode -- 6th arg
|
||||||
|
int hctrl_mode = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, hctrl_mode >= 0 && hctrl_mode <= 2, stack, "The hctrl_mode number allows 0, 1, or 2");
|
||||||
|
|
||||||
|
// Get counter_l_lim -- 7th arg. Defaults to -32767. Range int16 [-32768 : 32767]
|
||||||
|
int counter_l_lim = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, counter_l_lim >= -32768 && counter_l_lim <= 32767, stack, "The counter_l_lim number allows -32768 to 32767");
|
||||||
|
|
||||||
|
// Get counter_l_lim -- 7th arg. Defaults to -32767. Range int16 [-32768 : 32767]
|
||||||
|
int counter_h_lim = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, counter_h_lim >= -32768 && counter_h_lim <= 32767, stack, "The counter_h_lim number allows -32768 to 32767");
|
||||||
|
|
||||||
|
if (channel == 0) {
|
||||||
|
pc->ch0_is_defined = true;
|
||||||
|
pc->ch0_pulse_gpio_num = pulse_gpio_num;
|
||||||
|
pc->ch0_ctrl_gpio_num = ctrl_gpio_num;
|
||||||
|
pc->ch0_pos_mode = pos_mode;
|
||||||
|
pc->ch0_neg_mode = neg_mode;
|
||||||
|
pc->ch0_lctrl_mode = lctrl_mode;
|
||||||
|
pc->ch0_hctrl_mode = hctrl_mode;
|
||||||
|
pc->ch0_counter_l_lim = counter_l_lim;
|
||||||
|
pc->ch0_counter_h_lim = counter_h_lim;
|
||||||
|
} else {
|
||||||
|
pc->ch1_is_defined = true;
|
||||||
|
pc->ch1_pulse_gpio_num = pulse_gpio_num;
|
||||||
|
pc->ch1_ctrl_gpio_num = ctrl_gpio_num;
|
||||||
|
pc->ch1_pos_mode = pos_mode;
|
||||||
|
pc->ch1_neg_mode = neg_mode;
|
||||||
|
pc->ch1_lctrl_mode = lctrl_mode;
|
||||||
|
pc->ch1_hctrl_mode = hctrl_mode;
|
||||||
|
pc->ch1_counter_l_lim = counter_l_lim;
|
||||||
|
pc->ch1_counter_h_lim = counter_h_lim;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize PCNT functions:
|
||||||
|
* - configure and initialize PCNT
|
||||||
|
* - set up the input filter
|
||||||
|
* - set up the counter events to watch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Prepare configuration for the PCNT unit */
|
||||||
|
pcnt_config_t pcnt_config = {
|
||||||
|
// Set PCNT input signal and control GPIOs
|
||||||
|
.pulse_gpio_num = pulse_gpio_num,
|
||||||
|
.ctrl_gpio_num = ctrl_gpio_num,
|
||||||
|
.channel = channel,
|
||||||
|
.unit = pc->unit, // PCNT_TEST_UNIT
|
||||||
|
// What to do on the positive / negative edge of pulse input?
|
||||||
|
.pos_mode = pos_mode, //PCNT_COUNT_INC, // Count up on the positive edge
|
||||||
|
.neg_mode = neg_mode, //PCNT_COUNT_DIS, // Keep the counter value on the negative edge
|
||||||
|
// What to do when control input is low or high?
|
||||||
|
.lctrl_mode = lctrl_mode, //PCNT_MODE_REVERSE, // Reverse counting direction if low
|
||||||
|
.hctrl_mode = hctrl_mode, //PCNT_MODE_KEEP, // Keep the primary counter mode if high
|
||||||
|
// Set the maximum and minimum limit values to watch
|
||||||
|
.counter_l_lim = counter_l_lim, //PCNT_L_LIM_VAL,
|
||||||
|
.counter_h_lim = counter_h_lim, //PCNT_H_LIM_VAL,
|
||||||
|
};
|
||||||
|
/* Initialize PCNT unit */
|
||||||
|
pcnt_unit_config(&pcnt_config);
|
||||||
|
|
||||||
|
/* Enable events on zero, maximum and minimum limit values */
|
||||||
|
if (pc->cb_ref != LUA_NOREF) { // if they didn't give callback, don't setup pcnt_isr_register
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_ZERO);
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_H_LIM);
|
||||||
|
pcnt_event_enable(pc->unit, PCNT_EVT_L_LIM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize PCNT's counter */
|
||||||
|
pcnt_counter_pause(pc->unit);
|
||||||
|
pcnt_counter_clear(pc->unit);
|
||||||
|
|
||||||
|
/* Register ISR handler and enable interrupts for PCNT unit */
|
||||||
|
if (pc->cb_ref != LUA_NOREF) { // if they didn't give callback, don't setup pcnt_isr_register
|
||||||
|
pcnt_isr_register(pulsecnt_intr_handler, NULL, 0, &user_isr_handle);
|
||||||
|
pcnt_intr_enable(pc->unit);
|
||||||
|
}
|
||||||
|
/* Everything is set up, now go to counting */
|
||||||
|
pcnt_counter_resume(pc->unit);
|
||||||
|
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Channel %d config for unit %d, gpio: %d, ctrl_gpio: %d, chn: %d, pos_mode: %d, neg_mode: %d, lctrl_mode: %d, hctrl_mode: %d, counter_l_lim: %d, counter_h_lim: %d", channel, pc->unit, pulse_gpio_num, ctrl_gpio_num, PCNT_CHANNEL_0, pos_mode, neg_mode, lctrl_mode, hctrl_mode, counter_l_lim, counter_h_lim );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pc:chan0Config(pulse_gpio_num, ctrl_gpio_num, pos_mode, neg_mode, lctrl_mode, hctrl_mode, counter_l_lim, counter_h_lim)
|
||||||
|
// Example: pc:chan0Config(2, 4, pulsecnt.PCNT_COUNT_INC, pulsecnt.PCNT_COUNT_DIS, pulsecnt.PCNT_MODE_REVERSE, pulsecnt.PCNT_MODE_KEEP, -100, 100)
|
||||||
|
static int pulsecnt_channel0_config( lua_State *L, uint8_t channel ) {
|
||||||
|
return pulsecnt_channel_config(L, 0);
|
||||||
|
}
|
||||||
|
// Lua: pc:chan1Config(pulse_gpio_num, ctrl_gpio_num, pos_mode, neg_mode, lctrl_mode, hctrl_mode, counter_l_lim, counter_h_lim)
|
||||||
|
// Example: pc:chan1Config(2, 4, pulsecnt.PCNT_COUNT_INC, pulsecnt.PCNT_COUNT_DIS, pulsecnt.PCNT_MODE_REVERSE, pulsecnt.PCNT_MODE_KEEP, -100, 100)
|
||||||
|
static int pulsecnt_channel1_config( lua_State *L, uint8_t channel ) {
|
||||||
|
return pulsecnt_channel_config(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pc = pulsecnt.create(unit, callback)
|
||||||
|
static int pulsecnt_create( lua_State *L ) {
|
||||||
|
|
||||||
|
int stack = 0;
|
||||||
|
|
||||||
|
// Get unit number -- first arg
|
||||||
|
int unit = luaL_checkinteger(L, ++stack);
|
||||||
|
luaL_argcheck(L, unit >= 0 && unit <= 7, stack, "The unit number allows 0 to 7");
|
||||||
|
|
||||||
|
// Get callback method -- 2nd arg
|
||||||
|
++stack;
|
||||||
|
// See if they even gave us a callback
|
||||||
|
bool isCallback = true;
|
||||||
|
if lua_isnoneornil(L, stack) {
|
||||||
|
// user did not provide a callback. that's ok. just don't give them one.
|
||||||
|
isCallback = false;
|
||||||
|
} else {
|
||||||
|
luaL_argcheck(L, lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION, stack, "Must be function");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok, we have our unit number which is required. good. now create our object
|
||||||
|
pulsecnt_t pc = (pulsecnt_t)lua_newuserdata(L, sizeof(pulsecnt_struct_t));
|
||||||
|
if (!pc) return luaL_error(L, "not enough memory");
|
||||||
|
luaL_getmetatable(L, "pulsecnt.pctr");
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
pc->cb_ref = LUA_NOREF;
|
||||||
|
pc->self_ref = LUA_NOREF;
|
||||||
|
pc->is_initted = false;
|
||||||
|
pc->is_debug = false;
|
||||||
|
pc->counter = 99;
|
||||||
|
pc->unit = unit; // default to 0
|
||||||
|
|
||||||
|
//get the lua function reference
|
||||||
|
if (isCallback) {
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, pc->cb_ref);
|
||||||
|
lua_pushvalue(L, stack);
|
||||||
|
pc->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store in our global static pulsecnt_selfs array for later reference during callback
|
||||||
|
// where we only know the unit number
|
||||||
|
pulsecnt_selfs[unit] = pc;
|
||||||
|
|
||||||
|
// Get is_debug -- 3rd arg optional
|
||||||
|
++stack;
|
||||||
|
bool is_debug = luaL_optbool(L, stack, false);
|
||||||
|
if (is_debug == 1) {
|
||||||
|
pc->is_debug = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Created obj for unit %d with callback ref of %d", pc->unit, pc->cb_ref );
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pulsecnt:testCb( )
|
||||||
|
// This tests the callback where you can mimic in your code a sample callback as
|
||||||
|
// part of your debugging process. Make sure to set your callback during the create() call.
|
||||||
|
static int pulsecnt_testCb(lua_State* L)
|
||||||
|
{
|
||||||
|
// return 0;
|
||||||
|
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, 1);
|
||||||
|
|
||||||
|
// get a self reference if we don't have one
|
||||||
|
if (pc->self_ref == LUA_NOREF) {
|
||||||
|
lua_pushvalue(L, 1);
|
||||||
|
pc->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc->cb_ref == LUA_NOREF) {
|
||||||
|
luaL_error(L, "Callback not registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
// post using lua task posting technique
|
||||||
|
// on lua_open we set pulsecnt_task_id as a method which gets called
|
||||||
|
// by Lua after task_post_high with reference to this self object and then we can steal the
|
||||||
|
// callback_ref and then it gets called by lua_call where we get to add our args
|
||||||
|
// task_post_low(pulsecnt_task_id, (task_param_t)pc);
|
||||||
|
task_post_low(pulsecnt_task_id, ( (0xffu) << 8) | (pc->unit) );
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pulsecnt:getCnt( )
|
||||||
|
// Get's the pulse counter for a unit.
|
||||||
|
static int pulsecnt_getCnt(lua_State* L)
|
||||||
|
{
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, 1);
|
||||||
|
|
||||||
|
int16_t count = 0;
|
||||||
|
pcnt_get_counter_value(pc->unit, &count);
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Got ctr val for unit %d with count of %d", pc->unit, count );
|
||||||
|
pc->counter = count;
|
||||||
|
|
||||||
|
lua_pushinteger(L, pc->counter);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pulsecnt:clear( )
|
||||||
|
// Clear the pulse counter, i.e. set it back to 0.
|
||||||
|
static int pulsecnt_clear(lua_State* L)
|
||||||
|
{
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, 1);
|
||||||
|
pcnt_counter_clear(pc->unit);
|
||||||
|
int16_t count = 0;
|
||||||
|
pcnt_get_counter_value(pc->unit, &count);
|
||||||
|
if (pc->is_debug) ESP_LOGI("pulsecnt", "Cleared ctr for unit %d with new count of %d", pc->unit, count );
|
||||||
|
pc->counter = count;
|
||||||
|
lua_pushinteger(L, pc->counter);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua: pulsecnt:unregister( self )
|
||||||
|
static int pulsecnt_unregister(lua_State* L){
|
||||||
|
pulsecnt_t pc = pulsecnt_get(L, 1);
|
||||||
|
|
||||||
|
lua_pushinteger(L, pc->unit);
|
||||||
|
if (pc->self_ref != LUA_REFNIL) {
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, pc->self_ref);
|
||||||
|
pc->self_ref = LUA_NOREF;
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, pc->cb_ref);
|
||||||
|
pc->cb_ref = LUA_NOREF;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LUA_REG_TYPE pulsecnt_dyn_map[] =
|
||||||
|
{
|
||||||
|
{ LSTRKEY( "getCnt" ), LFUNCVAL( pulsecnt_getCnt )},
|
||||||
|
{ LSTRKEY( "clear" ), LFUNCVAL( pulsecnt_clear )},
|
||||||
|
{ LSTRKEY( "testCb" ), LFUNCVAL( pulsecnt_testCb )},
|
||||||
|
{ LSTRKEY( "chan0Config" ), LFUNCVAL( pulsecnt_channel0_config )},
|
||||||
|
{ LSTRKEY( "chan1Config" ), LFUNCVAL( pulsecnt_channel1_config )},
|
||||||
|
{ LSTRKEY( "config" ), LFUNCVAL( pulsecnt_config )},
|
||||||
|
|
||||||
|
{ LSTRKEY( "setThres" ), LFUNCVAL( pulsecnt_set_thres )},
|
||||||
|
{ LSTRKEY( "setFilter" ), LFUNCVAL( pulsecnt_set_filter )},
|
||||||
|
{ LSTRKEY( "rawSetEventVal" ), LFUNCVAL( pulsecnt_set_event_value )},
|
||||||
|
{ LSTRKEY( "rawGetEventVal" ), LFUNCVAL( pulsecnt_get_event_value )},
|
||||||
|
|
||||||
|
// { LSTRKEY( "__tostring" ), LFUNCVAL( pulsecnt_tostring )},
|
||||||
|
{ LSTRKEY( "__gc" ), LFUNCVAL( pulsecnt_unregister ) },
|
||||||
|
{ LSTRKEY( "__index" ), LROVAL( pulsecnt_dyn_map ) },
|
||||||
|
|
||||||
|
{ LNILKEY, LNILVAL}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const LUA_REG_TYPE pulsecnt_map[] =
|
||||||
|
{
|
||||||
|
{ LSTRKEY( "create" ), LFUNCVAL( pulsecnt_create )},
|
||||||
|
{ LSTRKEY( "PCNT_MODE_KEEP" ), LNUMVAL( 0 ) }, /*pcnt_ctrl_mode_t.PCNT_MODE_KEEP*/
|
||||||
|
{ LSTRKEY( "PCNT_MODE_REVERSE" ), LNUMVAL( 1 ) }, /*pcnt_ctrl_mode_t.PCNT_MODE_REVERSE*/
|
||||||
|
{ LSTRKEY( "PCNT_MODE_DISABLE" ), LNUMVAL( 2 ) }, /*pcnt_ctrl_mode_t.PCNT_MODE_DISABLE*/
|
||||||
|
{ LSTRKEY( "PCNT_COUNT_DIS" ), LNUMVAL( 0 ) }, /*pcnt_count_mode_t.PCNT_COUNT_DIS*/
|
||||||
|
{ LSTRKEY( "PCNT_COUNT_INC" ), LNUMVAL( 1 ) }, /*pcnt_count_mode_t.PCNT_COUNT_INC*/
|
||||||
|
{ LSTRKEY( "PCNT_COUNT_DEC" ), LNUMVAL( 2 ) }, /*pcnt_count_mode_t.PCNT_COUNT_DEC*/
|
||||||
|
// { LSTRKEY( "PCNT_COUNT_MAX " ), LNUMVAL( pcnt_count_mode_t.PCNT_COUNT_MAX ) },
|
||||||
|
{ LSTRKEY( "PCNT_H_LIM_VAL" ), LNUMVAL( 32767 ) },
|
||||||
|
{ LSTRKEY( "PCNT_L_LIM_VAL" ), LNUMVAL( -32768 ) },
|
||||||
|
{ LSTRKEY( "PCNT_EVT_L_LIM" ), LNUMVAL( 0 ) },
|
||||||
|
{ LSTRKEY( "PCNT_EVT_H_LIM" ), LNUMVAL( 1 ) },
|
||||||
|
{ LSTRKEY( "PCNT_EVT_THRES_0" ), LNUMVAL( 2 ) },
|
||||||
|
{ LSTRKEY( "PCNT_EVT_THRES_1" ), LNUMVAL( 3 ) },
|
||||||
|
{ LSTRKEY( "PCNT_EVT_ZERO" ), LNUMVAL( 4 ) },
|
||||||
|
{ LSTRKEY( "PCNT_PIN_NOT_USED" ), LNUMVAL( -1 ) }, /*PCNT_PIN_NOT_USED*/
|
||||||
|
{ LNILKEY, LNILVAL}
|
||||||
|
};
|
||||||
|
|
||||||
|
int luaopen_pulsecnt(lua_State *L) {
|
||||||
|
|
||||||
|
luaL_rometatable(L, "pulsecnt.pctr", (void *)pulsecnt_dyn_map);
|
||||||
|
|
||||||
|
pulsecnt_task_id = task_get_id(pulsecnt_task);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODEMCU_MODULE(PULSECNT, "pulsecnt", pulsecnt_map, luaopen_pulsecnt);
|
|
@ -0,0 +1,350 @@
|
||||||
|
# Pulse Counter Module
|
||||||
|
| Since | Origin / Contributor | Maintainer | Source |
|
||||||
|
| :----- | :-------------------- | :---------- | :------ |
|
||||||
|
| 2019-04-07 | [ChiliPeppr](https://github.com/chilipeppr) | John Lauer | [pulsecnt.c](../../components/modules/pulsecnt.c)|
|
||||||
|
|
||||||
|
The pulsecnt library handles sophisticated and automatic pulse counting using the built-in hardware on ESP32.
|
||||||
|
The pulsecnt library gives you a means to not rely on GPIO triggers to do your pulse counting and instead offload the work to independent hardware. You gain the ability to count pulses up to 80Mhz (speed of APB clock).
|
||||||
|
You can get a callback to Lua when different counting thresholds are reached, or when upper or lower counting limits are hit. You can count pulses on all GPIO ports. There is also a way to provide
|
||||||
|
a control GPIO for ignoring or decrementing pulses when the control signal is high or low.
|
||||||
|
|
||||||
|
[Youtube video of examples using the pulsecnt](https://youtu.be/vk5QZnWdlAA) library including push button counting, stepper motor step counting, and hall effect sensor counting.
|
||||||
|
|
||||||
|
## pulsecnt.create()
|
||||||
|
|
||||||
|
Create the pulse counter object.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecnt.create(unit, callbackOnEvents)`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
- `unit` Required. ESP32 has 0 thru 7 units to count pulses on.
|
||||||
|
- `callbackOnEvents` Optional. Your Lua method to call on event. myfunction(unit, isThr0, isThr1, isLLim, isHLim, isZero) will be called. Event will be PCNT_EVT_THRES_0 (Threshold 0 hit), PCNT_EVT_THRES_1 (Threshold 1 hit), PCNT_EVT_L_LIM (Minimum counter value), PCNT_EVT_H_LIM (Maximum counter value), or PCNT_EVT_ZERO (counter value zero event)
|
||||||
|
- `isDebug` Optional. Turn on extra logging by passing in true.
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`pulsecnt` object
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
-- Example Pulse Counter for 1 channel with polling of pulse count
|
||||||
|
|
||||||
|
pinPulseInput = 2
|
||||||
|
|
||||||
|
llim = -32768
|
||||||
|
hlim = 32767
|
||||||
|
|
||||||
|
pcnt = pulsecnt.create(7) -- Use unit 7 (0-7 are allowed)
|
||||||
|
|
||||||
|
pcnt:chan0Config(
|
||||||
|
pinPulseInput, --pulse_gpio_num
|
||||||
|
pulsecnt.PCNT_PIN_NOT_USED, --ctrl_gpio_num If no control is desired specify PCNT_PIN_NOT_USED
|
||||||
|
pulsecnt.PCNT_COUNT_INC, --pos_mode PCNT positive edge count mode
|
||||||
|
pulsecnt.PCNT_COUNT_DIS, --neg_mode PCNT negative edge count mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --lctrl_mode Ctrl low PCNT_MODE_KEEP, PCNT_MODE_REVERSE, PCNT_MODE_DISABLE
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --hctrl_mode Ctrl high PCNT_MODE_KEEP, PCNT_MODE_REVERSE, PCNT_MODE_DISABLE
|
||||||
|
llim, --counter_l_lim [Range -32768 to 32767]
|
||||||
|
hlim --counter_h_lim [Range -32768 to 32767]
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Clear counting
|
||||||
|
pcnt:clear()
|
||||||
|
|
||||||
|
-- Poll the pulse counter
|
||||||
|
print("Current pulse counter val:" .. pcnt:getCnt())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
-- Example Pulse Counter for 1 channel with callback
|
||||||
|
|
||||||
|
pinPulseInput = 2
|
||||||
|
thr0 = 2
|
||||||
|
thr1 = 4
|
||||||
|
llim = -6
|
||||||
|
hlim = 6
|
||||||
|
|
||||||
|
-- Callback on pulsecnt events
|
||||||
|
function onPulseCnt(unit, isThr0, isThr1, isLLim, isHLim, isZero)
|
||||||
|
|
||||||
|
print("unit:", unit, "isThr0:", isThr0, "isThr1:", isThr1)
|
||||||
|
print("isLLim:", isLLim, "isHLim:", isHLim, "isZero:", isZero)
|
||||||
|
|
||||||
|
-- Figure out what counter triggered this
|
||||||
|
if isThr0 then print("thr0 "..thr0.." triggered event") end
|
||||||
|
if isThr1 then print("thr1 "..thr1.." triggered event") end
|
||||||
|
if isLLim then print("llim "..llim.." triggered event") end
|
||||||
|
if isHLim then print("hlim "..hlim.." triggered event") end
|
||||||
|
if isZero then print("zero 0 triggered event") end
|
||||||
|
|
||||||
|
-- Get current count
|
||||||
|
print("Current pulse counter val:" .. pcnt:getCnt())
|
||||||
|
end
|
||||||
|
|
||||||
|
pcnt = pulsecnt.create(0, onPulseCnt) -- Use unit 0
|
||||||
|
|
||||||
|
pcnt:chan0Config(
|
||||||
|
pinPulseInput, --pulse_gpio_num
|
||||||
|
pulsecnt.PCNT_PIN_NOT_USED, --ctrl_gpio_num If no control is desired specify PCNT_PIN_NOT_USED
|
||||||
|
pulsecnt.PCNT_COUNT_INC, --pos_mode PCNT positive edge count mode PCNT_COUNT_DIS, PCNT_COUNT_INC, PCNT_COUNT_DEC
|
||||||
|
pulsecnt.PCNT_COUNT_DIS, --neg_mode PCNT negative edge count mode PCNT_COUNT_DIS, PCNT_COUNT_INC, PCNT_COUNT_DEC
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --lctrl_mode When Ctrl low PCNT_MODE_KEEP, PCNT_MODE_REVERSE, PCNT_MODE_DISABLE
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --hctrl_mode When Ctrl high PCNT_MODE_KEEP, PCNT_MODE_REVERSE, PCNT_MODE_DISABLE
|
||||||
|
llim, --counter_l_lim [Range -32768 to 32767]
|
||||||
|
hlim --counter_h_lim [Range -32768 to 32767]
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Clear counting
|
||||||
|
pcnt:clear()
|
||||||
|
|
||||||
|
-- Set counter threshold for low and high
|
||||||
|
pcnt:setThres(thr0, thr1)
|
||||||
|
|
||||||
|
-- Get current pulse counter. Should be zero at start.
|
||||||
|
print("Current pulse counter val:" .. pcnt:getCnt())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
-- Example Pulse Counter for 2 push buttons
|
||||||
|
-- Button 1 increments the counter (chan0)
|
||||||
|
-- Button 2 decrements the counter (chan1)
|
||||||
|
-- The pulsecnt filter is used to debounce
|
||||||
|
|
||||||
|
pinPulseInput = 2
|
||||||
|
pinPulseInput2 = 25
|
||||||
|
|
||||||
|
thr0 = 2
|
||||||
|
thr1 = 4
|
||||||
|
llim = -6
|
||||||
|
hlim = 6
|
||||||
|
|
||||||
|
-- Example callback
|
||||||
|
function onPulseCnt(unit, isThr0, isThr1, isLLim, isHLim, isZero)
|
||||||
|
|
||||||
|
print("unit:", unit, "isThr0:", isThr0, "isThr1:", isThr1)
|
||||||
|
print("isLLim:", isLLim, "isHLim:", isHLim, "isZero:", isZero)
|
||||||
|
|
||||||
|
-- Figure out what counter triggered this
|
||||||
|
if isThr0 then print("thr0 "..thr0.." triggered event") end
|
||||||
|
if isThr1 then print("thr1 "..thr1.." triggered event") end
|
||||||
|
if isLLim then print("llim "..llim.." triggered event") end
|
||||||
|
if isHLim then print("hlim "..hlim.." triggered event") end
|
||||||
|
if isZero then print("zero 0 triggered event") end
|
||||||
|
|
||||||
|
-- Get current count
|
||||||
|
print("Current pulse counter val:" .. pcnt:getCnt())
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
pcnt = pulsecnt.create(0, onPulseCnt) -- Use unit 0
|
||||||
|
|
||||||
|
-- Set clock cycles for filter. Any pulses lasting shorter than
|
||||||
|
-- this will be ignored when the filter is enabled. So, pulse
|
||||||
|
-- needs to be high or low for longer than this filter clock
|
||||||
|
-- cycle. Clock is 80Mhz APB clock, so one cycle is
|
||||||
|
-- 1000/80,000,000 = 0.0000125 ms. 1023 cycles = 0.0127875 ms
|
||||||
|
-- so not much of a debounce for manual push buttons.
|
||||||
|
pcnt:setFilter(1023)
|
||||||
|
|
||||||
|
-- Button 1
|
||||||
|
pcnt:chan0Config(
|
||||||
|
pinPulseInput, --pulse_gpio_num
|
||||||
|
pulsecnt.PCNT_PIN_NOT_USED, --ctrl_gpio_num If no control is desired specify PCNT_PIN_NOT_USED
|
||||||
|
pulsecnt.PCNT_COUNT_INC, --pos_mode PCNT positive edge count mode
|
||||||
|
pulsecnt.PCNT_COUNT_DIS, --neg_mode PCNT negative edge count mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --lctrl_mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --hctrl_mode
|
||||||
|
llim, --counter_l_lim
|
||||||
|
hlim --counter_h_lim
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Button 2
|
||||||
|
pcnt:chan1Config(
|
||||||
|
pinPulseInput2, --pulse_gpio_num
|
||||||
|
pulsecnt.PCNT_PIN_NOT_USED, --ctrl_gpio_num If no control is desired specify PCNT_PIN_NOT_USED
|
||||||
|
pulsecnt.PCNT_COUNT_INC, --pos_mode PCNT positive edge count mode
|
||||||
|
pulsecnt.PCNT_COUNT_DIS, --neg_mode PCNT negative edge count mode
|
||||||
|
pulsecnt.PCNT_MODE_REVERSE, --lctrl_mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --hctrl_mode
|
||||||
|
llim, --counter_l_lim
|
||||||
|
hlim --counter_h_lim
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Clear counting
|
||||||
|
pcnt:clear()
|
||||||
|
|
||||||
|
-- Set counter threshold for low and high
|
||||||
|
pcnt:setThres(thr0, thr1)
|
||||||
|
|
||||||
|
-- Buttons are now setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## pulsecntObj:chan0Config()
|
||||||
|
|
||||||
|
Configure channel 0 of the pulse counter object you created from the create() method.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecntObj:chan0Config(pulse_gpio_num, ctrl_gpio_num, pos_mode, neg_mode, lctrl_mode, hctrl_mode, counter_l_lim, counter_h_lim)`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
- `pulse_gpio_num` Required. Any GPIO pin can be used.
|
||||||
|
- `ctrl_gpio_num` Required (although you can specify pulsecnt.PIN_NOT_USED to ignore). Any GPIO pin can be used. If you are trying to use a pin you use as gpio.OUT in other parts of your code, you can instead configure the pin as gpio.IN_OUT and write high or low to it to achieve your gpio.OUT task while still enabling the pulse counter to successfully read the pin state.
|
||||||
|
- `pos_mode` Required. Positive rising edge count mode, i.e. count the pulse when the rising edge occurs.
|
||||||
|
- pulsecnt.PCNT_COUNT_DIS = 0 Counter mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- pulsecnt.PCNT_COUNT_INC = 1 Counter mode: Increase counter value.
|
||||||
|
- pulsecnt.PCNT_COUNT_DEC = 2 Counter mode: Decrease counter value.
|
||||||
|
- `neg_mode` Required. Negative falling edge count mode, i.e. count the pulse when the falling edge occurs.
|
||||||
|
- pulsecnt.PCNT_COUNT_DIS = 0 Counter mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- pulsecnt.PCNT_COUNT_INC = 1 Counter mode: Increase counter value.
|
||||||
|
- pulsecnt.PCNT_COUNT_DEC = 2 Counter mode: Decrease counter value.
|
||||||
|
- `lctrl_mode` Required. When `ctrl_gpio_num` is low how should the counter be influenced.
|
||||||
|
- pulsecnt.PCNT_MODE_KEEP = 0 Control mode: will not change counter mode.
|
||||||
|
- pulsecnt.PCNT_MODE_REVERSE = 1 Control mode: invert counter mode (increase -> decrease, decrease -> increase).
|
||||||
|
- pulsecnt.PCNT_MODE_DISABLE = 2 Control mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- `hctrl_mode` Required. When `ctrl_gpio_num` is high how should the counter be influenced.
|
||||||
|
- pulsecnt.PCNT_MODE_KEEP = 0 Control mode: will not change counter mode.
|
||||||
|
- pulsecnt.PCNT_MODE_REVERSE = 1 Control mode: invert counter mode (increase -> decrease, decrease -> increase).
|
||||||
|
- pulsecnt.PCNT_MODE_DISABLE = 2 Control mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- `counter_l_lim` Required. Range -32768 to 32767. The lower limit counter. You get a callback at this count and the counter is reset to zero after this lower limit is hit.
|
||||||
|
- `counter_h_lim` Required. Range -32768 to 32767. The high limit counter. You get a callback at this count and the counter is reset to zero after this high limit is hit.
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`nil`
|
||||||
|
|
||||||
|
## pulsecntObj:chan1Config()
|
||||||
|
|
||||||
|
Configure channel 1 of the pulse counter object you created from the create() method.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecntObj:chan1Config(pulse_gpio_num, ctrl_gpio_num, pos_mode, neg_mode, lctrl_mode, hctrl_mode, counter_l_lim, counter_h_lim)`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
- `pulse_gpio_num` Required. Any GPIO pin can be used.
|
||||||
|
- `ctrl_gpio_num` Required (although you can specify pulsecnt.PIN_NOT_USED to ignore). Any GPIO pin can be used. If you are trying to use a pin you use as gpio.OUT in other parts of your code, you can instead configure the pin as gpio.IN and toggle gpio.PULL_UP or gpio.PULL_DOWN to achieve your gpio.OUT task while still enabling the pulse counter to successfully read the pin state.
|
||||||
|
- `pos_mode` Required. Positive rising edge count mode, i.e. count the pulse when the rising edge occurs.
|
||||||
|
- pulsecnt.PCNT_COUNT_DIS = 0 Counter mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- pulsecnt.PCNT_COUNT_INC = 1 Counter mode: Increase counter value.
|
||||||
|
- pulsecnt.PCNT_COUNT_DEC = 2 Counter mode: Decrease counter value.
|
||||||
|
- `neg_mode` Required. Negative falling edge count mode, i.e. count the pulse when the falling edge occurs.
|
||||||
|
- pulsecnt.PCNT_COUNT_DIS = 0 Counter mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- pulsecnt.PCNT_COUNT_INC = 1 Counter mode: Increase counter value.
|
||||||
|
- pulsecnt.PCNT_COUNT_DEC = 2 Counter mode: Decrease counter value.
|
||||||
|
- `lctrl_mode` Required. When `ctrl_gpio_num` is low how should the counter be influenced.
|
||||||
|
- pulsecnt.PCNT_MODE_KEEP = 0 Control mode: will not change counter mode.
|
||||||
|
- pulsecnt.PCNT_MODE_REVERSE = 1 Control mode: invert counter mode (increase -> decrease, decrease -> increase).
|
||||||
|
- pulsecnt.PCNT_MODE_DISABLE = 2 Control mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- `hctrl_mode` Required. When `ctrl_gpio_num` is high how should the counter be influenced.
|
||||||
|
- pulsecnt.PCNT_MODE_KEEP = 0 Control mode: will not change counter mode.
|
||||||
|
- pulsecnt.PCNT_MODE_REVERSE = 1 Control mode: invert counter mode (increase -> decrease, decrease -> increase).
|
||||||
|
- pulsecnt.PCNT_MODE_DISABLE = 2 Control mode: Inhibit counter (counter value will not change in this condition).
|
||||||
|
- `counter_l_lim` Required. Range -32768 to 32767. The lower limit counter. You get a callback at this count and the counter is reset to zero after this lower limit is hit.
|
||||||
|
- `counter_h_lim` Required. Range -32768 to 32767. The high limit counter. You get a callback at this count and the counter is reset to zero after this high limit is hit.
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`nil`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
-- Button 1 increment counter
|
||||||
|
pcnt:chan0Config(
|
||||||
|
pinPulseInput, --pulse_gpio_num
|
||||||
|
pulsecnt.PCNT_PIN_NOT_USED, --ctrl_gpio_num If no control is desired specify PCNT_PIN_NOT_USED
|
||||||
|
pulsecnt.PCNT_COUNT_INC, --pos_mode PCNT positive edge count mode
|
||||||
|
pulsecnt.PCNT_COUNT_DIS, --neg_mode PCNT negative edge count mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --lctrl_mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --hctrl_mode
|
||||||
|
llim, --counter_l_lim
|
||||||
|
hlim --counter_h_lim
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Button 2 decrement counter
|
||||||
|
pcnt:chan1Config(
|
||||||
|
pinPulseInput2, --pulse_gpio_num
|
||||||
|
pulsecnt.PCNT_PIN_NOT_USED, --ctrl_gpio_num If no control is desired specify PCNT_PIN_NOT_USED
|
||||||
|
pulsecnt.PCNT_COUNT_INC, --pos_mode PCNT positive edge count mode
|
||||||
|
pulsecnt.PCNT_COUNT_DIS, --neg_mode PCNT negative edge count mode
|
||||||
|
pulsecnt.PCNT_MODE_REVERSE, --lctrl_mode
|
||||||
|
pulsecnt.PCNT_MODE_KEEP, --hctrl_mode
|
||||||
|
llim, --counter_l_lim
|
||||||
|
hlim --counter_h_lim
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## pulsecntObj:setThres()
|
||||||
|
|
||||||
|
Set the threshold values to establish watchpoints for getting callbacks on.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecntObj:setThres(thr0, thr1)`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
- `thr0` Required. Threshold 0 value. Range -32768 to 32767. This is a watchpoint value to get a callback with isThr0 set to true on this count being reached.
|
||||||
|
- `thr1` Required. Threshold 1 value. Range -32768 to 32767. This is a watchpoint value to get a callback with isThr1 set to true on this count being reached.
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`nil`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
pcnt:setThres(-2000, 2000) -- get callbacks when counter is -2000 or 2000
|
||||||
|
```
|
||||||
|
```lua
|
||||||
|
pcnt:setThres(500, 10000) -- get callbacks when counter is 500 or 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
## pulsecntObj:setFilter()
|
||||||
|
|
||||||
|
Set a filter to ignore pulses on the `pulse_gpio_num` pin and the `ctrl_gpio_num` to avoid short glitches. Any pulses lasting shorter than this will be ignored.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecntObj:setFilter(clkCycleCnt)`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
- `clkCycleCnt` Required. 0 to 1023 allowd. Any pulses lasting shorter than this will be ignored. A pulse needs to be high or low for longer than this filter clock cycle. Clock is 80Mhz APB clock, so one cycle is 1000/80,000,000 = 0.0000125 ms. The longest value of 1023 cycles = 0.0127875 ms.
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`nil`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
pcnt:setFilter(1023) -- set max filter clock cylce count to ignore pulses shorter than 12.7us
|
||||||
|
```
|
||||||
|
|
||||||
|
## pulsecntObj:clear()
|
||||||
|
|
||||||
|
Clear the counter. Sets it back to zero.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecntObj:clear()`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
None
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`nil`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
pcnt:clear() -- set counter back to zero
|
||||||
|
```
|
||||||
|
|
||||||
|
## pulsecntObj:getCnt()
|
||||||
|
|
||||||
|
Get the current pulse counter.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
`pulsecntObj:getCnt()`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
None
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
`integer`
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```lua
|
||||||
|
val = pcnt:getCnt() -- get counter
|
||||||
|
print("Pulse counter:", val)
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in New Issue