From 9a5327cab10c51ea9074e6fe74e3be589df3cafd Mon Sep 17 00:00:00 2001 From: John Lauer Date: Sun, 27 Oct 2019 11:49:56 -0700 Subject: [PATCH] Add touch sensor module (#2863) * Touch module 1st checkin * ESP32. Check-in 2 for Touch sensor module * ESP32: Touch module. Sample Lua code. * ESP32: Latest YouTube vid * ESP32: Touch docs update * Added opt_* methods for value retrieval --- components/modules/Kconfig | 18 +- components/modules/touch.c | 636 ++++++++++++++++++ docs/img/touch_tutorial.jpg | Bin 0 -> 22672 bytes docs/modules/touch.md | 447 ++++++++++++ lua_examples/touch/touch_8pads_showlist.lua | 85 +++ .../touch/touch_8pads_showlist_test.lua | 4 + lua_examples/touch/touchjog_jog.lua | 255 +++++++ lua_examples/touch/touchjog_jog_drv8825.lua | 191 ++++++ lua_examples/touch/touchjog_main.lua | 94 +++ lua_examples/touch/touchjog_touch.lua | 67 ++ mkdocs.yml | 1 + 11 files changed, 1792 insertions(+), 6 deletions(-) create mode 100644 components/modules/touch.c create mode 100644 docs/img/touch_tutorial.jpg create mode 100644 docs/modules/touch.md create mode 100644 lua_examples/touch/touch_8pads_showlist.lua create mode 100644 lua_examples/touch/touch_8pads_showlist_test.lua create mode 100644 lua_examples/touch/touchjog_jog.lua create mode 100644 lua_examples/touch/touchjog_jog_drv8825.lua create mode 100644 lua_examples/touch/touchjog_main.lua create mode 100644 lua_examples/touch/touchjog_touch.lua diff --git a/components/modules/Kconfig b/components/modules/Kconfig index 6dcd9d0cc1..574f0bfdd2 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -173,6 +173,12 @@ config LUA_MODULE_OTAUPGRADE a partition table with at least two OTA partitions, plus the OTA data partition. See the IDF documentation for details. +config LUA_MODULE_PULSECNT + bool "Pulse counter module" + default "n" + help + Includes the pulse counter module to use ESP32's built-in pulse counting hardware. + config LUA_MODULE_QRCODEGEN bool "QR Code Generator module" default "n" @@ -224,6 +230,12 @@ config LUA_MODULE_TMR help Includes the timer module (recommended). +config LUA_MODULE_TOUCH + bool "Touch module" + default "n" + help + Includes the touch module to use ESP32's built-in touch sensor hardware. + config LUA_MODULE_U8G2 bool "U8G2 module" default "n" @@ -259,10 +271,4 @@ config LUA_MODULE_TIME help Includes the time module. -config LUA_MODULE_PULSECNT - bool "Pulse counter module" - default "n" - help - Includes the pulse counter module. - endmenu diff --git a/components/modules/touch.c b/components/modules/touch.c new file mode 100644 index 0000000000..b37d64af11 --- /dev/null +++ b/components/modules/touch.c @@ -0,0 +1,636 @@ +/* +Touch sensor module for ESP32 to allow interfacing from Lua +Authored by: ChiliPeppr (John Lauer) 2019 + +ESP-IDF docs for Touch Sensor +https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/touch_pad.html + +ESP32 can handle up to 10 capacitive touch pads / GPIOs. +The touch pad sensing process is under the control of a hardware-implemented finite-state +machine (FSM) which is initiated by software or a dedicated hardware timer. + +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 "common.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" +#include "task/task.h" +#include "driver/touch_pad.h" +#include "esp_log.h" +#include "lextra.h" +#include "soc/rtc_periph.h" +#include "soc/touch_channel.h" + +#include + +#define TOUCH_THRESH_NO_USE (0) + +static const char* TAG = "Touch"; + +typedef struct { + // uint8_t selfs_index; // Keep track of our own index in touch_selfs so on cleanup we can unallocate + int32_t cb_ref; // If a callback is provided, then we are using the ISR, otherwise we are just letting them poll + bool is_initted; + bool is_debug; + bool touch_pads[TOUCH_PAD_MAX]; // TOUCH_PAD_MAX=10. Array of bools representing what touch pads are in this object + uint16_t thres[TOUCH_PAD_MAX]; // Threshold values for each pad + uint32_t filterMs; + touch_high_volt_t hvolt; + touch_low_volt_t lvolt; + touch_volt_atten_t atten; + uint8_t slope; + bool is_intr; + touch_trigger_mode_t thresTrigger; +} touch_struct_t; +typedef touch_struct_t *touch_t; + +// array of 10 touch_struct_t pointers so we can reference by unit number +// this array gets filled in as we define touch_struct_t's during the create() method +// 10 pads are available, so leave enough room for 1 pad per create +static touch_t touch_self = NULL; // just allow 1 instance. used to allow 10, but changed approach. s[10] = {NULL}; +// static uint8_t touch_selfs_last_index = 0; // keep track of last index used on touch_selfs + +// Task ID to get ISR interrupt back into Lua callback +static task_handle_t touch_task_id; + +// Helper function to create Lua tables in C +void l_pushtableintkeyval(lua_State* L , int key , int value) { + lua_pushinteger(L, key); + lua_pushinteger(L, value); + lua_settable(L, -3); +} +void l_pushtableintkeybool(lua_State* L , int key , bool value) { + lua_pushinteger(L, key); + lua_pushboolean(L, value); + lua_settable(L, -3); +} + +/* This is the interrupt called by the hardware on a touch event. + * Decode what touch pad originated the interrupt + * and pass this information together with the event type + * the main program so it can execute in the Lua process. + */ +static void IRAM_ATTR touch_intr_handler(void *arg) +{ + + // Get the touch sensor status, usually used in ISR to decide which pads are ‘touched’. + uint32_t pad_intr = touch_pad_get_status(); + //clear interrupt + touch_pad_clear_status(); + + + // post using lua task posting technique + // on lua_open we set touch_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(touch_task_id, pad_intr ); + +} + +/* +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 touch pads that were triggered. +The format of the callback to your Lua code is: + function onTouch(arrayOfPadsTouched) +*/ +static void touch_task(task_param_t param, task_prio_t prio) +{ + (void)prio; + + // if (touch_self != NULL && touch_self->is_debug) ESP_LOGI(TAG, "Got interrupt param: %d", param); + + // Now see which touch_selfs has cb_refs + // For now just call all of them + lua_State *L = lua_getstate(); + + // old approach was to allow 10 objects. for now we are allowing 1, so go right to it. + // for (int i = 0; i < touch_selfs_last_index; i++) { + + // see if there's any self and a callback on self, if so, do the callback + if (touch_self != NULL) { + if (touch_self->cb_ref != LUA_NOREF) { + // we have a callback + lua_rawgeti (L, LUA_REGISTRYINDEX, touch_self->cb_ref); + const char* funcName = lua_tostring(L, -1); + + // create a table in c (it will be at the top of the stack) + lua_newtable(L); + + // bool s_pad_activated[TOUCH_PAD_MAX]; + for (int i = 0; i < TOUCH_PAD_MAX; i++) { + if ((param >> i) & 0x01) { + // s_pad_activated[i] = true; + l_pushtableintkeybool(L, i, true); + // if (touch_self->is_debug) ESP_LOGI(TAG, "Pushed key %d, bool %d", i, true); + + } else { + // don't push false values for now to reduce memory usage + // l_pushtableintkeybool(L, i, false); + // if (touch_self->is_debug) ESP_LOGI(TAG, "Pushed key %d, bool %d", i, false); + } + } + + // call the cb_ref with one argument + /* do the call (1 argument, 0 results) */ + if (lua_pcall(L, 1, 0, 0) != 0) { + ESP_LOGI(TAG, "error running callback function `f': %s", funcName); + } + } + } + + //} // old for loop + +} + + +/* +Lua sample code: +tp = touch.create({ + pad = {8,9}, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + cb = onTouch, -- Callback on touch event. Optional. If you do not provid you can just call touch:read(). If you do add a callback then TOUCH_FSM_MODE_TIMER is used. + intrInitAtStart = false, -- Turn on interrupt at start. Default to true. Set to false in case you want to config first. Turn it on later with tp:intrEnable() + thres = {[8]=300, [9]=500}, -- thres = 400 || {[8]=300, [9]=500} Pass in one uint16 (max 65536) or pass in table of pads with threshold value for each pad. Threshold of touchpad count. To get the right number, watch your touch pads for the high/low value. Run touch.test() to analyze all pads for their values. + thresTrigger = TOUCH_TRIGGER_BELOW, -- Touch interrupt will happen if counter value is below or above threshold. TOUCH_TRIGGER_BELOW or TOUCH_TRIGGER_ABOVE + slope = 4, -- Touch sensor charge / discharge speed, 0-7 where 0 is no slope (so counter will always be 0), 1 is slow (lower counter), 7 is fastest (higher counter). Run the touch.test(pad) to test all combinations to pick best one. + lvolt = TOUCH_LVOLT_0V5, -- Touch sensor low reference voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = TOUCH_HVOLT_2V7, -- Touch sensor high reference voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = TOUCH_HVOLT_ATTEN_1V, -- Touch sensor high reference voltage attenuation TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = true +}) +*/ +static int touch_create( lua_State *L ) { + + // Check if we are out of objects. There are only 10 pads, so should never get this, but check anyhow. + if (touch_self != NULL) { + luaL_error(L, "Only 1 touch pad object allowed in touch library."); + } + + // Presume we'll get a good create, so go ahead and make our touch_t object + touch_struct_t tpObj = {.cb_ref=LUA_NOREF, .is_initted=false, .is_debug=false}; + touch_t tp = &tpObj; + + // const int top = lua_gettop(L); + luaL_checkanytable (L, 1); + + tp->is_debug = opt_checkbool(L, "isDebug", false); + tp->filterMs = opt_checkint(L, "filterMs", 0); + tp->lvolt = opt_checkint_range(L, "lvolt", TOUCH_LVOLT_KEEP, TOUCH_LVOLT_KEEP, TOUCH_LVOLT_MAX); + tp->hvolt = opt_checkint_range(L, "hvolt", TOUCH_HVOLT_KEEP, TOUCH_HVOLT_KEEP, TOUCH_HVOLT_MAX); + tp->atten = opt_checkint_range(L, "atten", TOUCH_HVOLT_ATTEN_KEEP, TOUCH_HVOLT_ATTEN_KEEP, TOUCH_HVOLT_ATTEN_MAX); + tp->slope = opt_checkint_range(L, "slope", TOUCH_PAD_SLOPE_0, TOUCH_PAD_SLOPE_0, TOUCH_PAD_SLOPE_MAX); + tp->is_intr = opt_checkbool(L, "intrInitAtStart", true); + tp->thresTrigger = opt_checkint_range(L, "thresTrigger", TOUCH_TRIGGER_BELOW, TOUCH_TRIGGER_BELOW, TOUCH_TRIGGER_MAX); + + if (tp->is_debug) ESP_LOGI(TAG, "isDebug: %d, filterMs: %d, lvolt: %d, hvolt: %d, atten: %d, slope: %d, intrInitAtStart: %d, thresTrigger: %d", + tp->is_debug, tp->filterMs, tp->lvolt, tp->hvolt, tp->atten, tp->slope, tp->is_intr, tp->thresTrigger); + + // get the field pad. this can be passed in as int or table of ints. pad = 0 || {0,1,2,3,4,5,6,7,8,9} + lua_getfield(L, 1, "pad"); + int type = lua_type(L, -1); + if (type == LUA_TNUMBER) { + int touch_pad_num = lua_tointeger(L, -1); + luaL_argcheck(L, touch_pad_num >= 0 && touch_pad_num <= 9, -1, "The touch_pad_num allows 0 to 9"); + // opt_check(L, cond, name, extramsg) + tp->touch_pads[touch_pad_num] = true; + if (tp->is_debug) ESP_LOGI(TAG, "Set pad %d", touch_pad_num ); + } + else if (type == LUA_TTABLE) { + // else if (opt_get(L, "pad", LUA_TTABLE)) { + lua_pushnil (L); + while (lua_next(L, -2) != 0) + { + lua_pushvalue(L, -1); // copy, so lua_tonumber() doesn't break iter + int touch_pad_num = lua_tointeger(L, -1); + luaL_argcheck(L, touch_pad_num >= 0 && touch_pad_num <= 9, -1, "The touch_pad_num allows 0 to 9"); + tp->touch_pads[touch_pad_num] = true; + lua_pop (L, 2); // leave key + if (tp->is_debug) ESP_LOGI(TAG, "Set pad %d", touch_pad_num ); + } + } + else + return luaL_error (L, "missing/bad 'pad' field"); + + // See if they even gave us a callback + bool isCallback = true; + lua_getfield(L, 1, "cb"); + if lua_isnoneornil(L, -1) { + // user did not provide a callback. that's ok. just don't give them one. + isCallback = false; + if (tp->is_debug) ESP_LOGI(TAG, "No callback provided. Not turning on interrupt." ); + } else { + luaL_argcheck(L, lua_type(L, -1) == LUA_TFUNCTION || lua_type(L, -1) == LUA_TLIGHTFUNCTION, -1, "Cb must be function"); + + //get the lua function reference + luaL_unref(L, LUA_REGISTRYINDEX, tp->cb_ref); + lua_pushvalue(L, -1); + tp->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + if (tp->is_debug) ESP_LOGI(TAG, "Cb good." ); + } + + // Init + /*The default FSM mode is ‘TOUCH_FSM_MODE_SW’. If you want to use interrupt trigger mode, + then set it using function ‘touch_pad_set_fsm_mode’ to ‘TOUCH_FSM_MODE_TIMER’ after + calling ‘touch_pad_init’. */ + esp_err_t err = touch_pad_init(); + if (err == ESP_FAIL) { + ESP_LOGI(TAG, "Touch pad init error"); + return 0; + } else { + if (tp->is_debug) ESP_LOGI(TAG, "Initted touch pad"); + } + + // Set reference voltage for charging/discharging + // For example, the high reference valtage will be 2.7V - 1V = 1.7V + // The low reference voltage will be 0.5 + // The larger the range, the larger the pulse count value. + touch_pad_set_voltage(tp->hvolt, tp->lvolt, tp->atten); + if (tp->is_debug) ESP_LOGI(TAG, "Set voltage level hvolt: %d, lvolt: %d, atten: %d", tp->hvolt, tp->lvolt, tp->atten ); + + // If use interrupt trigger mode, should set touch sensor FSM mode at 'TOUCH_FSM_MODE_TIMER'. + if (isCallback == true) { + + if (tp->is_debug) ESP_LOGI(TAG, "Setting FSM mode since you provided a callback"); + err = touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + if (err == ESP_FAIL) { + ESP_LOGI(TAG, "Touch pad set fsm mode error"); + return 0; + } + + for (int padnum = 0; padnum< TOUCH_PAD_MAX; padnum++) { + // see if we have a pad by this number to config + if (tp->touch_pads[padnum] == true) { + //init RTC IO and mode for touch pad. + touch_pad_config(padnum, tp->thres[padnum]); + if (tp->is_debug) ESP_LOGI(TAG, "Did config for pad %d with thres %d", padnum, tp->thres[padnum]); + } + } + + // Initialize and start a software filter to detect slight change of capacitance. + if (tp->filterMs > 0) { + touch_pad_filter_start(tp->filterMs); + if (tp->is_debug) ESP_LOGI(TAG, "Set filter period to %d ms", tp->filterMs ); + } + + // Register touch interrupt ISR + touch_pad_isr_register(touch_intr_handler, NULL); + if (tp->is_debug) ESP_LOGI(TAG, "Registered ISR handler for callback" ); + + // set trigger mode + touch_pad_set_trigger_mode(tp->thresTrigger); + + if (tp->is_intr) { + touch_pad_intr_enable(); + if (tp->is_debug) ESP_LOGI(TAG, "Enabled interrupt" ); + } + + } else { + + // No callback mode. Just polling for values. + if (tp->is_debug) ESP_LOGI(TAG, "No callback provided, so no interrupt or threshold set." ); + touch_pad_intr_disable(); + err = touch_pad_set_fsm_mode(TOUCH_FSM_MODE_SW); + if (err == ESP_FAIL) { + ESP_LOGI(TAG, "Touch pad set fsm mode to sw error"); + return 0; + } else { + if (tp->is_debug) ESP_LOGI(TAG, "Touch pad set fsm mode to sw since no callback"); + } + + // set THRES_NO_USE since in polling mode + for (int padnum = 0; padnum< TOUCH_PAD_MAX; padnum++) { + // see if we have a pad by this number to config + if (tp->touch_pads[padnum] == true) { + //init RTC IO and mode for touch pad. + touch_pad_config(padnum, TOUCH_THRESH_NO_USE); + if (tp->is_debug) ESP_LOGI(TAG, "Did config for pad %d with thres %d", padnum, TOUCH_THRESH_NO_USE); + } + } + + // see if they want a filter, if so turn it on + // start touch pad filter function This API will start a filter to process the noise in order to + // prevent false triggering when detecting slight change of capacitance. Need to call + // touch_pad_filter_start before all touch filter APIs + if (tp->filterMs > 0) { + if (tp->is_debug) ESP_LOGI(TAG, "You provided a filter so turning on filter mode. filterMs: %d", tp->filterMs); + esp_err_t err = touch_pad_filter_start(tp->filterMs); + if (err == ESP_ERR_INVALID_ARG) { + ESP_LOGI(TAG, "Filter start parameter error"); + } else if (err == ESP_ERR_NO_MEM) { + ESP_LOGI(TAG, "Filter no memory for driver"); + } else if (err == ESP_ERR_INVALID_STATE) { + ESP_LOGI(TAG, "Filter driver state error"); + } + } + } + + // if (tp->is_debug) ESP_LOGI(TAG, "Created obj with callback ref of %d", tp->cb_ref ); + + // Now create our Lua version of this data to pass back + touch_t tp2 = (touch_t)lua_newuserdata(L, sizeof(touch_struct_t)); + if (!tp2) return luaL_error(L, "not enough memory"); + luaL_getmetatable(L, "touch.pctr"); + lua_setmetatable(L, -2); + tp2->cb_ref = tp->cb_ref; + // tp2->self_ref = tp->self_ref; + tp2->is_initted = tp->is_initted; + tp2->is_debug = tp->is_debug; + tp2->filterMs = tp->filterMs; + tp2->hvolt = tp->hvolt; + tp2->lvolt = tp->lvolt; + tp2->atten = tp->atten; + tp2->slope = tp->slope; + + for (int i = 0; i < 10; i++) { + tp2->touch_pads[i] = tp->touch_pads[i]; + } + + // We need to store this in self_refs so we have a way to look for the cb_ref from the IRAM interrupt + touch_self = tp2; + + return 1; +} + +// Get the touch.pctr object from the stack which is the struct touch_t +static touch_t touch_get( lua_State *L, int stack ) +{ + return (touch_t)luaL_checkudata(L, stack, "touch.pctr"); +} + +// Lua: touch:setTriggerMode(mode) -- TOUCH_TRIGGER_BELOW or TOUCH_TRIGGER_ABOVE +static int touch_setTriggerMode(lua_State* L) +{ + touch_t tp = touch_get(L, 1); + + int stack = 1; + + // Get mode -- first arg after self arg + int thresTrigger = luaL_checkinteger(L, ++stack); + luaL_argcheck(L, thresTrigger >= 0 && thresTrigger <= 1, -1, "The thresTrigger allows 0 or 1"); + + tp->thresTrigger = thresTrigger; + touch_pad_set_trigger_mode(tp->thresTrigger); + + if (tp->is_debug) ESP_LOGI(TAG, "Set thresTrigger to %d", tp->thresTrigger); + + return 0; +} + +// Lua: touch:setThres(padNum, thresVal) +// Set touch sensor interrupt threshold. +static int touch_setThres(lua_State* L) +{ + touch_t tp = touch_get(L, 1); + + int stack = 1; + + // Get padNum -- first arg after self arg + int padNum = luaL_checkinteger(L, ++stack); + luaL_argcheck(L, padNum >= 0 && padNum <= 10, stack, "The padNum number allows 0 to 10"); + + // Get thresVal -- first arg after self arg + int thresVal = luaL_checkinteger(L, ++stack); + luaL_argcheck(L, thresVal >= 0 && thresVal <= 65536, stack, "The thresVal number allows 0 to 65536"); + + touch_pad_set_thresh(padNum, thresVal); + + tp->thres[padNum] = thresVal; + + if (tp->is_debug) ESP_LOGI(TAG, "Set threshold for pad %d val %d", padNum, thresVal); + + return 0; +} + +// Lua: touch:intrEnable() +static int touch_intrEnable(lua_State* L) +{ + touch_t tp = touch_get(L, 1); + + tp->is_intr = true; + + touch_pad_intr_enable(); + + if (tp->is_debug) ESP_LOGI(TAG, "Turned on touch pad interrupt"); + + return 0; +} + +// Lua: touch:intrDisable() +static int touch_intrDisable(lua_State* L) +{ + touch_t tp = touch_get(L, 1); + + tp->is_intr = false; + + touch_pad_intr_disable(); + touch_pad_clear_status(); + + if (tp->is_debug) ESP_LOGI(TAG, "Turned off touch pad interrupt"); + + return 0; +} + +// Lua: tbl = touch:read() +// Get touch sensor counter value. Each touch sensor has a counter to count the +// number of charge/discharge cycles. When the pad is not ‘touched’, we can get +// a number of the counter. When the pad is ‘touched’, the value in counter will +// get smaller because of the larger equivalent capacitance. +static int touch_read(lua_State* L) +{ + touch_t tp = touch_get(L, 1); + + // return 1 parameter by default, unless in filter mode, then return 2 params + uint8_t numRetVals = 1; + + // see if we are in interrupt mode or polling mode + // see if we are in filter mode or non-filter + // if in filter mode, we need to do raw_read and filter_read + // in non-filter mode, we just need to do read + + if (tp->cb_ref == LUA_NOREF && tp->filterMs > 0) { + // we are in polling mode and we are in filter mode + // so do 2 params for raw and filter + + numRetVals = 2; + + // we will send back 2 params of format: + // pads will be {"0":rawval, "1":rawval, ...} + // filtervals will be {"0":filtval, "1":filtval, ...} + + // do raw vals + // create a table in c (it will be at the top of the stack) + lua_newtable(L); + + uint16_t touch_value = 0; + for (int i = 0; i < TOUCH_PAD_MAX; i++) { + + // see if this object wants a touch read for this pad + if (tp->touch_pads[i] == true) { + // they do want a touch read for this pad + esp_err_t err = touch_pad_read_raw_data((touch_pad_t)i, &touch_value); + + if (err == ESP_ERR_INVALID_ARG) { + luaL_error(L, "Touch pad parameter error"); + } else if (err == ESP_ERR_INVALID_STATE) { + luaL_error(L, "Touch pad hardware connection has an error, the value of touch_value is 0."); + } else if (err == ESP_FAIL) { + luaL_error(L, "Touch pad not initialized"); + } else { + + l_pushtableintkeyval(L, i, touch_value); + + if (tp->is_debug) ESP_LOGI(TAG, "Got touch raw val for pin %d of %d", i, touch_value ); + + } + + } + } + + // do filter vals + // create a table in c (it will be at the top of the stack) + lua_newtable(L); + + // uint16_t touch_value = 0; + for (int i = 0; i < TOUCH_PAD_MAX; i++) { + + // see if this object wants a touch read for this pad + if (tp->touch_pads[i] == true) { + // they do want a touch read for this pad + esp_err_t err = touch_pad_read_filtered((touch_pad_t)i, &touch_value); + + if (err == ESP_ERR_INVALID_ARG) { + luaL_error(L, "Touch pad parameter error"); + } else if (err == ESP_ERR_INVALID_STATE) { + luaL_error(L, "Touch pad hardware connection has an error, the value of touch_value is 0."); + } else if (err == ESP_FAIL) { + luaL_error(L, "Touch pad not initialized"); + } else { + + l_pushtableintkeyval(L, i, touch_value); + + if (tp->is_debug) ESP_LOGI(TAG, "Got touch filtered val for pin %d of %d", i, touch_value ); + + } + + } + } + + } else { + // we are in non-filter mode. do normal read and send 1 param back. + + // esp_err_t touch_pad_read(touch_pad_t touch_num, uint16_t *touch_value) + + // we will send back data in the format: {"0":val, "1":val} + // create a table in c (it will be at the top of the stack) + lua_newtable(L); + + uint16_t touch_value = 0; + for (int i = 0; i < TOUCH_PAD_MAX; i++) { + + // see if this object wants a touch read for this pad + if (tp->touch_pads[i] == true) { + // they do want a touch read for this pad + esp_err_t err = touch_pad_read((touch_pad_t)i, &touch_value); + + if (err == ESP_ERR_INVALID_ARG) { + luaL_error(L, "Touch pad parameter error"); + } else if (err == ESP_ERR_INVALID_STATE) { + luaL_error(L, "Touch pad hardware connection has an error, the value of touch_value is 0."); + } else if (err == ESP_FAIL) { + luaL_error(L, "Touch pad not initialized"); + } else { + + l_pushtableintkeyval(L, i, touch_value); + + if (tp->is_debug) ESP_LOGI(TAG, "Got touch val for pin %d of %d", i, touch_value ); + + } + + } + } + } + + // the table (or tables) is in the stack, it should get passed back + return numRetVals; +} + + +// Lua: touch:unregister( self ) +static int touch_unregister(lua_State* L) { + touch_t tp = touch_get(L, 1); + + // if there was a callback, turn off ISR + if (tp->cb_ref != LUA_NOREF) { + touch_intrDisable(L); + + touch_pad_isr_deregister(touch_intr_handler, NULL); + + luaL_unref(L, LUA_REGISTRYINDEX, tp->cb_ref); + tp->cb_ref = LUA_NOREF; + + } else { + // non-interrupt mode + if (tp->filterMs > 0) { + touch_pad_filter_stop(); + } + } + + touch_pad_deinit(); + touch_self = NULL; + + return 0; +} + +LROT_BEGIN(touch_dyn) + LROT_FUNCENTRY( read, touch_read ) + LROT_FUNCENTRY( intrEnable, touch_intrEnable ) + LROT_FUNCENTRY( intrDisable, touch_intrDisable ) + LROT_FUNCENTRY( setThres, touch_setThres ) + LROT_FUNCENTRY( setTriggerMode, touch_setTriggerMode ) + + // LROT_FUNCENTRY( __tostring, touch_tostring ) + LROT_FUNCENTRY( __gc, touch_unregister ) + LROT_TABENTRY ( __index, touch_dyn ) +LROT_END(touch_dyn, NULL, 0) + +LROT_BEGIN(touch) + LROT_FUNCENTRY( create, touch_create ) + LROT_NUMENTRY ( TOUCH_HVOLT_KEEP, TOUCH_HVOLT_KEEP ) + LROT_NUMENTRY ( TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V4 ) + LROT_NUMENTRY ( TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V5 ) + LROT_NUMENTRY ( TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V6 ) + LROT_NUMENTRY ( TOUCH_HVOLT_2V7, TOUCH_HVOLT_2V7 ) + LROT_NUMENTRY ( TOUCH_HVOLT_MAX, TOUCH_HVOLT_MAX ) + LROT_NUMENTRY ( TOUCH_LVOLT_KEEP, TOUCH_LVOLT_KEEP ) + LROT_NUMENTRY ( TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V5 ) + LROT_NUMENTRY ( TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V6 ) + LROT_NUMENTRY ( TOUCH_LVOLT_0V7, TOUCH_LVOLT_0V7 ) + LROT_NUMENTRY ( TOUCH_LVOLT_0V8, TOUCH_LVOLT_0V8 ) + LROT_NUMENTRY ( TOUCH_LVOLT_MAX, TOUCH_LVOLT_MAX ) + LROT_NUMENTRY ( TOUCH_HVOLT_ATTEN_KEEP, TOUCH_HVOLT_ATTEN_KEEP ) + LROT_NUMENTRY ( TOUCH_HVOLT_ATTEN_1V5, TOUCH_HVOLT_ATTEN_1V5 ) + LROT_NUMENTRY ( TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V ) + LROT_NUMENTRY ( TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_0V5 ) + LROT_NUMENTRY ( TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V ) + LROT_NUMENTRY ( TOUCH_TRIGGER_BELOW, TOUCH_TRIGGER_BELOW ) + LROT_NUMENTRY ( TOUCH_TRIGGER_ABOVE, TOUCH_TRIGGER_ABOVE ) +LROT_END(touch, NULL, 0) + +int luaopen_touch(lua_State *L) { + + luaL_rometatable(L, "touch.pctr", (void *)touch_dyn_map); + + touch_task_id = task_get_id(touch_task); + + return 0; +} + +NODEMCU_MODULE(TOUCH, "touch", touch, luaopen_touch); diff --git a/docs/img/touch_tutorial.jpg b/docs/img/touch_tutorial.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03f31531ed6212818d7b836c4bb27c3bf6d4ce2a GIT binary patch literal 22672 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 literal 0 HcmV?d00001 diff --git a/docs/modules/touch.md b/docs/modules/touch.md new file mode 100644 index 0000000000..1e0ae72a18 --- /dev/null +++ b/docs/modules/touch.md @@ -0,0 +1,447 @@ +# Touch Sensor Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2019-07-20 | [ChiliPeppr](https://github.com/chilipeppr) | John Lauer | [touch.c](../../components/modules/touch.c)| + +The touch sensor module enables you to easily interact with ESP32's built-in 10 touch sensors. The touch pad sensing process is under the control of a hardware-implemented finite-state machine (FSM) which is initiated by software (polling mode) or a dedicated hardware timer (interrupt mode). By using the interrupt mode you can offload the sensing away from the main CPU. There are several examples in the docs below on how to implement your code. + +For further information please refer to the ESP-IDF docs for Touch Sensor +https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/touch_pad.html + +Click the YouTube video below for a tutorial on how to use this touch library including sample touch sensors, how to set your touch counter thresholds, and what the touch counters look like in the touched/untouched state of a sensor pad. + +[![YouTube Touch Tutorial](../img/touch_tutorial.jpg "Walkthrough video")](https://youtu.be/6BxNFh3GaRA) + +The touch sensors are on the following GPIO pins + +| Touch Pad Number | GPIO | | Touch Pad Number | GPIO | +| :--------------- | :--- | - | :--------------- | :--- | +| 0 | GPIO4 | | 5 | GPIO12 | +| 1 | GPIO0 | | 6 | GPIO14 | +| 2 | GPIO2 | | 7 | GPIO27 | +| 3 | GPIO15 | | 8 | GPIO33 | +| 4 | GPIO13 | | 9 | GPIO32 | + +### Example Lua Code + +Example code showing how to configure 8 pads. +- Main run file [touch_8pads_showlist_test.lua](../../lua_examples/touch/touch_8pads_showlist_test.lua) +- Library [touch_8pads_showlist.lua](../../lua_examples/touch/touch_8pads_showlist.lua) + +Example code showing how to use 5 touch pads to jog a stepper motor at different frequencies depending on which pad is touched: +- Main run file [touchjog_main.lua](../../lua_examples/touch/touchjog_main.lua) +- Library [touchjog_touch.lua](../../lua_examples/touch/touchjog_touch.lua) +- Library [touchjog_jog.lua](../../lua_examples/touch/touchjog_jog.lua) +- Library [touchjog_jog_drv8825.lua](../../lua_examples/touch/touchjog_jog_drv8825.lua) + +## touch.create() + +Create the touch sensor object. You must call this method first. Only one touch object may be created since most settings on the touch driver are global in nature such as threshold trigger mode, interrupt callbacks, and reference voltages. + +### Syntax +```lua +tp = touch.create({ + pad = 0 || {0,1,2,3,4,5,6,7,8,9}, -- 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + cb = yourFunc, -- Callback on interrupt + intrInitAtStart = true || false, -- Turn on/off interrupt at start. + thres = 0..65536, -- All pads set to this thres. + thresTrigger = touch.TOUCH_TRIGGER_BELOW || touch.TOUCH_TRIGGER_ABOVE, + filterMs = 0..4294967295 -- Filter is only available in polling mode. + lvolt = touch.TOUCH_LVOLT_0V4 || touch.TOUCH_LVOLT_0V5 || touch.TOUCH_LVOLT_0V6 || touch.TOUCH_LVOLT_0V7, + hvolt = touch.TOUCH_HVOLT_2V4 || touch.TOUCH_HVOLT_2V5 || touch.TOUCH_HVOLT_2V6 || touch.TOUCH_HVOLT_2V7, + atten = touch.TOUCH_HVOLT_ATTEN_0V || touch.TOUCH_HVOLT_ATTEN_0V5 || touch.TOUCH_HVOLT_ATTEN_1V || touch.TOUCH_HVOLT_ATTEN_1V5 + isDebug = true || false +}) +``` + +### Parameters +List of values for configuration table: + +- `pad` Required. padNum || {table of padNums}. Specify one pad like `pad = 4`, or provide a table list of pads. For example use `pad = {0,1,2,3,4,5,6,7,8,9}` to specify all pads. Pads allowed are 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32. +- `cb` Optional. Your Lua method that gets called on touch event. `myfunction(pads)` will be called where `pads` is a table of pads that were touched. The key is the pad number while the value is true, i.e. `pads = {[2]=true, [3]=true}` if pad 2 and 3 were both touched at the same time. When you specify a callback, interrupt mode is automatically turned on. You can specify `intrInitAtState = false` to manually turn on the interrupt later. If no callback is provided or nil is specified, then polling mode is active where you must call `tp:read()` to get the touch pad values. +- `intrInitAtStart` Optional. Defaults to true. Turn on interrupt at start. Set to false to if you want to configure the touch sensors first and then manually turn on interrupts later with `tp:intrEnable()`. +- `thres` Optional. Defaults to 0. Range is 0 to 65536. Provide a threshold value to be set on all pads specified in the `pad` parameter. Typically you will set thresholds per pad since pad size/shape/wire legnth influences the counter value per pad and thus your threshold is usually differnt per pad. You can set thres later per pad with `tp:setThres(padNum, thres)`. +- `thresTrigger` Optional. Defaults to touch.TOUCH_TRIGGER_BELOW. + - touch.TOUCH_TRIGGER_BELOW + - touch.TOUCH_TRIGGER_ABOVE +- `filterMs` Optional. Range is 0 to 4294967295 milliseconds. Used in polling mode only (if you provide a callback polling mode is disabled). Will filter noise for this many ms to give more consistent counter results. When filterMs is specified you will receive a 2nd return value in the `raw, filter = tp:read()` call with the filtered values in a Lua table. +- `lvolt` Optional. Low reference voltage + - touch.TOUCH_LVOLT_0V4 + - touch.TOUCH_LVOLT_0V5 + - touch.TOUCH_LVOLT_0V6 + - touch.TOUCH_LVOLT_0V7 +- `hvolt` Optional. High reference voltage + - touch.TOUCH_HVOLT_2V4 + - touch.TOUCH_HVOLT_2V5 + - touch.TOUCH_HVOLT_2V6 + - touch.TOUCH_HVOLT_2V7 +- `atten` Optional. High reference voltage attenuation + - touch.TOUCH_HVOLT_ATTEN_0V + - touch.TOUCH_HVOLT_ATTEN_0V5 + - touch.TOUCH_HVOLT_ATTEN_1V + - touch.TOUCH_HVOLT_ATTEN_1V5 +- `isDebug` Optional. Defaults to false. Set to true to get debug information during development. The info returned while debug is on can be very helpful in understanding polling vs interrupts, configuration, and threshold settings. Set to false during production. + +### Returns +`touch` object + +### Example 1 - Polling +```lua +-- Touch sensor with 5 touch pads for polling counter state + +tp = touch.create({ + pad = {0,1,2,3,4}, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + lvolt = touch.TOUCH_LVOLT_0V5, -- Low ref voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = touch.TOUCH_HVOLT_2V7, -- High ref voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = touch.TOUCH_HVOLT_ATTEN_1V, -- High ref attenuation TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = true +}) + +function read() + raw = tp:read() + print("Pad", "RawVal") + for key, value in pairs(raw) do + print(key, value) + end +end + +read() +``` + +### Example 2 - Polling with Filtering +```lua +-- Touch sensor with 3 touch pads for polling with filtering + +tp = touch.create({ + pad = {4,5,6}, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + filterMs = 20, -- Polling mode only. Will filter noise for this many ms to give more consistent counter. + lvolt = touch.TOUCH_LVOLT_0V5, -- Low ref voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = touch.TOUCH_HVOLT_2V7, -- High ref voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = touch.TOUCH_HVOLT_ATTEN_1V, -- High ref attenuation TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = true +}) + +-- You will get 2 parameters from tp:read() when filterMs is specified in create() +function read() + raw, filter = tp:read() + -- Use sjson to make pretty output + print("Touch raw:", sjson.encode(raw)) + print("Touch filt:", sjson.encode(filter)) +end + +-- Filtered vals will be similar to raw vals on first read. +read() +-- Read a second later to see how filtered vals are more stable. +tmr.create():alarm(1000, tmr.ALARM_SINGLE, read) +``` + +### Example 3 - Interrupt Touch / Untouch +```lua +-- Touch sensor with 1 pad for touch / untouch using threshold trigger mode +-- Swap TOUCH_TRIGGER_BELOW / TOUCH_TRIGGER_ABOVE on each callback +-- NOTE: Can only use this technique with 1 pad + +pad = 6 -- 6=GPIO14 + +padState = 0 -- 0 means untouched +tp = nil -- Holds our touch sensor object + +function onTouch(pads) + + if padState == 0 then + -- we just got touched + -- swap trigger mode to TOUCH_TRIGGER_ABOVE so we get interrupt + -- on untouch + padState = 1 + tp:setTriggerMode(touch.TOUCH_TRIGGER_ABOVE) + print("Touch") + else + -- we just got untouched + -- swap trigger mode to TOUCH_TRIGGER_BELOW so we get interrupt + -- on touch + padState = 0 + tp:setTriggerMode(touch.TOUCH_TRIGGER_BELOW) + print("Untouch") + end + +end + +function init() + tp = touch.create({ + pad = pad, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + cb = onTouch, -- Callback will get Lua table of pads/bool(true) that were touched. + intrInitAtStart = false, -- Turn on interrupt at start. Default to true. Set to false to config first. Turn on later with tp:intrEnable() + thresTrigger = touch.TOUCH_TRIGGER_BELOW, -- TOUCH_TRIGGER_BELOW or TOUCH_TRIGGER_ABOVE. Touch interrupt happens if ctr value is below or above. + lvolt = touch.TOUCH_LVOLT_0V5, -- Low ref voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = touch.TOUCH_HVOLT_2V7, -- High ref voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = touch.TOUCH_HVOLT_ATTEN_1V, -- High ref attenuation TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = true + }) + +end + +function read() + local raw = tp:read() + print("Pad", "Val") + for key,value in pairs(raw) do + print(key,value) + end +end + +-- Do not touch during config +function config() + local raw = tp:read() + -- set threshold to 20% of baseline read state + local thres = raw[pad] - math.floor(raw[pad] * 0.2) + tp:setThres(pad, thres) + print("Pad is at " .. raw[pad] .. " when not touched") + print("Will trigger at thres: " .. thres) + tp:intrEnable() +end + +init() +read() +config() +``` + +### Example 4 - Interrupt Touch / Untouch with Timer +```lua +-- Touch sensor with 1 pad for touch / untouch using timer +-- Shows how to detect a touch and then an untouch + +m = {} + +m.pad = {8} -- 8=GPIO33 + +m._tp = nil -- will hold the touchpad obj +m._padState = 0 -- 0 means untouched +m._tmr = nil -- will hold the tmr obj +m._tmrWait = 50 -- ms to wait for no callback to indicate untouch + +-- We will get a callback every 8ms or so when touched +-- We will reset the tmr each time and after 50ms will presume untouched event +function m.onTouch(pads) + + if m._padState == 0 then + -- we just got touched + m._padState = 1 -- 1 means touched + m._tmr:start() + print("Touch") + else -- m._padState == 1 + -- we will get here on successive callbacks while touched + -- we get them about every 8ms + -- reset the tmr so it doesn't timeout yet + m._tmr:stop() + m._tmr:start() + end + +end + +function m.onTmr() + -- if we get the timer triggered, it means that we timed out + -- from touch callbacks, so we presume the touch ended + m._padState = 0 + print("Untouch") +end + +function m.init() + m._tp = touch.create({ + pad = m.pad, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + cb = m.onTouch, -- Callback will get Lua table of pads/bool(true) that were touched. + intrInitAtStart = false, -- Turn on interrupt at start. Default to true. Set to false in case you want to config first. Turn it on later with tp:intrEnable() + thresTrigger = touch.TOUCH_TRIGGER_BELOW, -- TOUCH_TRIGGER_BELOW or TOUCH_TRIGGER_ABOVE. Touch interrupt will happen if counter value is below or above threshold. + lvolt = touch.TOUCH_LVOLT_0V5, -- Low ref voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = touch.TOUCH_HVOLT_2V7, -- High ref voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = touch.TOUCH_HVOLT_ATTEN_1V, -- High ref attenuation TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = true + }) + + -- Setup the tmr for the callback process + m._tmr = tmr.create() + m._tmr:register(m._tmrWait, tmr.ALARM_SEMI, m.onTmr) + +end + +function m.read() + local raw = m._tp:read() + print("Pad", "Val") + for key,value in pairs(raw) do + print(key,value) + end +end + +-- Do not touch pad during config +function m.config() + local raw = m._tp:read() + -- set threshold to 20% of baseline read state + local thres = raw[m.pad] - math.floor(raw[m.pad] * 0.2) + m._tp:setThres(m.pad, thres) + print("Pad is at ", raw[m.pad], " as baseline") + print("Will trigger at thres: ", thres) + m._tp:intrEnable() +end + +m.init() +m.read() +m.config() +``` + +## touchObj:read() + +Read the touch sensor counter values for all pads configured in `touch.create()` method. + +### Syntax +`raw, filter = tp:read()` + +### Parameters +None + +### Returns +- `raw` Lua table of touch sensor counter values per pad +- `raw, filter` A 2nd Lua table of touch sensor filtered counter values per pad is returned if `filterMs` is specified during the touch.create() method. + +### Example 1 - Raw +```lua +raw = tp:read() +print("Pad", "Val") +for key,value in pairs(raw) do + print(key,value) +end +``` + +### Example 2 - Raw / Filter +```lua +-- You get a filter Lua table if you specified filterMs in touch.create() +raw, filter = tp:read() +print("Pad", "Raw", "Filt") +print("---", "---", "----") +for key,value in pairs(raw) do + print(key,value,filter[key]) +end +``` + +## touchObj:setThres(padNum, thresVal) + +Set touch sensor interrupt threshold per pad. The threshold only matters if you are in interrupt mode, which only activates if you specify a callback in the `touch.create()` configuration. + +### Syntax +`tp:setThres(padNum, thresVal)` + +### Parameters +- `padNum` Required. One pad number can be specified here. If you did multiple pads you must call this per pad. +- `thresVal` Required. The threshold value to set for the pad interrupt trigger. If you set touch.TOUCH_TRIGGER_BELOW then the interrupt occurs when the touch counter goes below this threshold value, or vice versa for touch.TOUCH_TRIGGER_ABOVE. + +### Returns +`nil` + +### Example 1 +```lua +-- Set threshold for pad 2 where baseline counter is around 800 and +-- when touched is around 200, so trigger at mid-point around 500 +tp:setThres(2, 500) +``` +### Example 2 +```lua +-- Configure by reading baseline, then setting threshold to 30% below base +local raw = tp:read() +print("Pad", "Base", "Thres") +for key,value in pairs(raw) do + if key ~= nil then + -- reduce by 30% + local thres = raw[key] - math.floor(raw[key] * 0.3) + tp:setThres(key, thres) + print(key, value, thres) + end +end +-- Now enable interrupts since our thresholds are correct +tp:intrEnable() +``` + +## touchObj:setTriggerMode() + +Set the trigger mode globally for all touch pads. The trigger mode only matters in interrupt mode where you can tell the hardware to give you an interrupt if the counter on the pad falls above or below the threshold you specify. + +### Syntax +`tp:setTriggerMode(mode)` + +### Parameters +- `mode` Required. touch.TOUCH_TRIGGER_BELOW or touch.TOUCH_TRIGGER_ABOVE can be specified. If your pad's baseline counter value is around 600 when not touched, and sits around 300 when touched, then you would set your threshold around 450. If you set touch.TOUCH_TRIGGER_BELOW then when the counter drops to 300 it would fall BELOW the threshold of 450, thus triggering the interrupt. This process works in the reverse for touch.TOUCH_TRIGGER_ABOVE. + +### Returns +`nil` + +### Example 1 +```lua +-- Trigger callback when pad counter goes above threshold value +tp:setTriggerMode(touch.TOUCH_TRIGGER_ABOVE) +-- Trigger callback when pad counter goes below threshold value +tp:setTriggerMode(touch.TOUCH_TRIGGER_BELOW) +``` + +### Example 2 +```lua +-- Configure touch hardware to callback on TOUCH_TRIGGER_BELOW during touch.create() +-- Then on first callback swap to TOUCH_TRIGGER_ABOVE when detecting touch +-- Then will get 2nd callback on untouch due to mode change +-- This only works with 1 pad since trigger mode is global for all pads + +padState = 0 -- Set start padState + +function onTouch(pads) + + if padState == 0 then + -- we just got touched + -- swap trigger mode to TOUCH_TRIGGER_ABOVE so we get interrupt + -- on untouch + padState = 1 + tp:setTriggerMode(touch.TOUCH_TRIGGER_ABOVE) + print("Got touch") + else + -- we just got untouched + -- swap trigger mode to TOUCH_TRIGGER_BELOW so we get interrupt + -- on touch + padState = 0 + tp:setTriggerMode(touch.TOUCH_TRIGGER_BELOW) + print("Got untouch") + end + + print("Pads:", sjson.encode(pads)) + +end +``` + +## touchObj:intrEnable() + +Enable interrupt on the touch sensor hardware. You can specify `intrInitAtStart=false` during `touch.create()` and thus you would want to call this method later on after configuring your pad thresholds. + +### Syntax +`tp:intrEnable()` + +### Parameters +None + +### Returns +`nil` + +### Example +```lua +tp:intrEnable() -- Enable interrupt +``` + +## touchObj:intrDisable() + +Disable interrupt on the touch sensor hardware. + +### Syntax +`tp:intrDisable()` + +### Parameters +None + +### Returns +`nil` + +### Example +```lua +tp:intrDisable() -- Disable interrupt +``` \ No newline at end of file diff --git a/lua_examples/touch/touch_8pads_showlist.lua b/lua_examples/touch/touch_8pads_showlist.lua new file mode 100644 index 0000000000..583e544bb1 --- /dev/null +++ b/lua_examples/touch/touch_8pads_showlist.lua @@ -0,0 +1,85 @@ +-- Touch sensor for 8 pads +-- Set threshold to 30% of untouched state +-- Print padNum list per callback + +-- To use: +-- tpad = require("touch_8pads_showlist") +-- tpad.init({isDebug=false}) +-- tpad.config() + +local m = {} + +m.pad = {2,3,4,5,6,7,8,9} -- 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + +m._tp = nil -- will hold the touchpad obj +m._isDebug = true + +-- We will get a callback every 8ms or so when touched +function m.onTouch(pads) + + print(m.pconcat(pads)) + +end + +function m.pconcat(tab) + local ctab = {} + local n = 1 + for k, v in pairs(tab) do + ctab[n] = k + n = n + 1 + end + return table.concat(ctab, ",") +end + +function m.init(tbl) + + if (tbl ~= nil) then + if (tbl.isDebug ~= nil) then m._isDebug = tbl.isDebug end + end + + m._tp = touch.create({ + pad = m.pad, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + cb = m.onTouch, -- Callback will get Lua table of pads/bool(true) that were touched. + intrInitAtStart = false, -- Turn on interrupt at start. Default to true. Set to false to config first. Turn on later with tp:intrEnable() + thresTrigger = touch.TOUCH_TRIGGER_BELOW, -- TOUCH_TRIGGER_BELOW or TOUCH_TRIGGER_ABOVE. Touch interrupt happens if counter is below or above. + lvolt = touch.TOUCH_LVOLT_0V5, -- Low ref voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = touch.TOUCH_HVOLT_2V7, -- High ref voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = touch.TOUCH_HVOLT_ATTEN_1V, -- TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = m._isDebug + }) + +end + +function m.read() + local raw = m._tp:read() + print("Pad", "Val") + for key,value in pairs(raw) do + print(key,value) + end +end + +function m.config() + local raw = m._tp:read() + + if (m._isDebug) then + print("Configuring...") + print("Pad", "Base", "Thres") + end + + for key,value in pairs(raw) do + if key ~= nil then + -- reduce by 30% + local thres = raw[key] - math.floor(raw[key] * 0.3) + m._tp:setThres(key, thres) + if (m._isDebug) then print(key, value, thres) end + end + end + m._tp:intrEnable() + if (m._isDebug) then print("You can now touch the sensors") end +end + +-- m.init() +-- m.read() +-- m.config() + +return m diff --git a/lua_examples/touch/touch_8pads_showlist_test.lua b/lua_examples/touch/touch_8pads_showlist_test.lua new file mode 100644 index 0000000000..bf032ffbdd --- /dev/null +++ b/lua_examples/touch/touch_8pads_showlist_test.lua @@ -0,0 +1,4 @@ +-- Test out the touch_8pads_showlist module +tpad = require("touch_8pads_showlist") +tpad.init({isDebug=true}) +tpad.config() \ No newline at end of file diff --git a/lua_examples/touch/touchjog_jog.lua b/lua_examples/touch/touchjog_jog.lua new file mode 100644 index 0000000000..9ab8623b48 --- /dev/null +++ b/lua_examples/touch/touchjog_jog.lua @@ -0,0 +1,255 @@ +-- Stepper Jog for DRV8825 +-- This library lets you jog your DRV8825 stepper using variable +-- frequency PWM, but it keeps track of your step counter so you +-- know your final position + +local m = {} +-- m = {} +m.motor = require("touchjog_jog_drv8825") + +-- These pins are set in touchjog_jog_drv8825.lua now +-- m.pinStep = 2 -- GPIO22, pin36 on esp32-wroom-32d, Orig 2 +-- m.pinDir = 14 -- pin33 on esp32-wroom-32d, Orig 14 +-- m.pinSleep = 0 -- ENN pin28 GPIO17 on esp32-wroom-32d, Orig 15 or 0 +-- m.pinPulseIn = 36 --2 -- 36 STEP Pulse In (Sens_VP), Pin 24 (Touch2) + +m.bits = 9 +m.dutyStart = 0 +m.dutyAtRun = 256 -- Since using 9bits 2^9=512, so 256 is 50% duty +m.freqStart = 100 + +m._freq = 100 +m._isOn = false + +m.isDebug = false + +-- You can pass in override settings to init including +-- stepper_ctrl_jog.init({IRUN=4}) +function m.init(tbl) + + print("initting...") + -- if tbl ~= nil and tbl.IRUN ~= nil then m.IRUN = tbl.IRUN end + + m.motor.init({ + initStepDirEnPins=true, + -- pinStep = m.pinStep, + -- pinDir = m.pinDir, + -- pinEn = m.pinSleep, + isDebug = false + }) + m.pinDir = m.motor.pinDir + m.pinStep = m.motor.pinStep + print("initted pins for drv8825") + + -- -- Start counting pulses before turning on pwm + -- m.initPulseCtr() + -- print("initted pulse ctr") + + -- After you init the pulsecnt library, you need to override + -- the gpio settings that it applies to the ctrl_gpio_num to + -- make it not just be an output pin + gpio.config( { gpio=m.pinDir, dir=gpio.IN_OUT, pull=gpio.PULL_UP } ) + + -- Start the PWM module, but at duty 0 so like it's off + m.start() + -- Pause it like what we do on joystick at center pos + m.pause() + + print("Initted drv8825_jog") + +end + +function m.round(num, numDecimalPlaces) + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +m._lastFreq = 0 +m._maxDelta = 20 +function m.setfreq(fr, isOverrideAccel) + + if fr < 1 then + print("Err on freq:", fr) + return + end + + -- Check last freq and don't let them change too fast + if isOverrideAccel ~= true then + + if fr > m._lastFreq then + -- they want faster + if fr - m._lastFreq > m._maxDelta then + local askedFr = fr + fr = m._lastFreq + m._maxDelta + print("Dampened accel from fr:",askedFr,"to fr:",fr) + end + elseif fr < m._lastFreq then + -- they want slower + if m._lastFreq - fr > m._maxDelta then + local askedFr = fr + fr = m._lastFreq - m._maxDelta + print("Dampened decel from fr:",askedFr,"to fr:",fr) + end + end + + end + + print("Setting freq to:", fr) + pcall(m._channel:setfreq(fr)) + m._lastFreq = fr +end + +function m.setduty(duty) + m._channel:setduty(duty) + -- print("Set duty:", duty) +end + +function m.getFreq() + return m._channel:getfreq() +end + +function m.getSteps() + local steps = m.motor.pcnt:getCnt() + print("Steps: "..steps) + return steps +end + + +function m.start() + m._channel = ledc.newChannel({ + gpio=m.pinStep, + bits=m.bits, + mode=ledc.HIGH_SPEED, + timer=ledc.TIMER_1, + channel=ledc.CHANNEL_1, + frequency=m.freqStart, -- Hz + -- 2^11 = 2048 + -- 2^9 = 512 + duty=m.dutyStart --512 -- 2**10 = 1024, so 50% duty is 512 + }); + m._isOn = true + print("Start... freq:", m.freqStart, "duty", m.dutyStart) +end + +function m.stop() + m._channel:stop(ledc.IDLE_LOW) + -- m.setfreq(m.freqStart) + -- m.setduty(0) + m._isOn = false + print("Stopped") +end + +m._isPaused = false +function m.pause() + -- m.setfreq(10, true) --override dampening + m.setduty(0) + m._isPaused = true + print("Paused") +end + +function m.resume() + if m._isOn == false then return end + if m._isPaused == false then return end + m.setduty(m.dutyAtRun) + m._isPaused = false + print("Resumed") +end + +function m.jogStart(freq) + print("jogStart. freq:", freq) + -- m.motor.dirFwd() + m.motor.enable() + m.setfreq(freq, true) --override dampen + m.resume() + -- m.motor.readDRV_STATUS() +end + +function m.jogStop() + m.pause() + m.motor.disable() + m.getSteps() + -- m.motor.readDRV_STATUS() +end + +m.testState = 0 -- 0 is run fwd, 1 is rev, 2 is pause +m.testLastState = nil +m.testFreq = 200 +m.testLenMs = 1000 +m.testPauseMs = 2000 + +-- Pass in tbl lenMs, pauseMs, freq +function m.testStart(tbl) + + if tbl.freq ~= nil then m.testFreq = tbl.freq end + if tbl.lenMs ~= nil then m.testLenMs = tbl.lenMs end + if tbl.pauseMs ~= nil then m.testPauseMs = tbl.pauseMs end + + -- Start test where we jog fwd for 1 second + -- Then we jog rev for 1 second + m.jogStart(m.testFreq) + m._testTmr = tmr.create() + + print("Starting out test at lenMs:", m.testLenMs, "pauseMs:", m.testPauseMs, "freq:", m.testFreq) + m._testTmr:alarm(m.testLenMs, tmr.ALARM_SEMI, function() + + -- print("Got tmr. testState:", m.testState, "testLastState:", m.testLastState) + + -- check state + if m.testState == 0 then + -- we were just going fwd + -- we are going to a pause state + print("We are going to pause for ms:", m.testPauseMs) + m.testLastState = 0 + m.testState = 2 + m.jogStop() + m._testTmr:interval(m.testPauseMs) + + elseif m.testState == 2 then + -- we were just on a pause + -- jog again + m.motor.dirToggle() + if m.testLastState == 0 then + m.testState = 1 + print("We are going to jog reverse at freq:", m.testFreq, "for ms:", m.testLenMs) + elseif m.testLastState == 1 then + m.testState = 0 + print("We are going to jog forward at freq:", m.testFreq, "for ms:", m.testLenMs) + end + + m.jogStart(m.testFreq) + m._testTmr:interval(m.testLenMs) + + elseif m.testState == 1 then + -- we were just in rev + -- we are going to a pause state + print("We are going to pause for ms:", m.testPauseMs) + m.testLastState = 1 + m.testState = 2 + m.jogStop() + m._testTmr:interval(m.testPauseMs) + + end + + -- restart timer + m._testTmr:start() + end) +end + +function m.testStop() + m.jogStop() + if m._testTmr then + local running, mode = m._testTmr:state() + if running then + m._testTmr:unregister() + end + end +end + +-- m.init() +-- m.jogStart(100) +-- m.testStart() +-- localTime = time.getlocal() +-- print(string.format("%02d:%02d:%02d", localTime["hour"], localTime["min"], localTime["sec"])) + +return m + diff --git a/lua_examples/touch/touchjog_jog_drv8825.lua b/lua_examples/touch/touchjog_jog_drv8825.lua new file mode 100644 index 0000000000..5300273d0e --- /dev/null +++ b/lua_examples/touch/touchjog_jog_drv8825.lua @@ -0,0 +1,191 @@ +-- DRV8825 driver +-- Provides gpio init, enable, disable, direction, and pulse counting for steps +-- The DVR8825 runs over normal GPIO + +local m = {} + +m.pinStep = 4 +m.pinDir = 0 -- it is 0 cuz that is bootstrap pin which inconsequential on direction pin +m.pinSleep = 16 + +-- Loopback pulse in from m.pinStep +-- You need to connect a physical wire from m.pinStep 4 to pin 36 for step counting to work +m.pinPulseIn = 36 -- 36 (sens_vp) + +-- Microsteps pins in case you want to manage microsteps from gpio +m.pinM0 = 17 +m.pinM1 = 18 +m.pinM2 = 19 + +-- Min/max steps allowed 32768 + or - is allowed +m.stepMax = 32000 +m.stepMin = -32000 + +m.isDebug = false + +-- Pulse counter object +m.pcnt = nil + +m._isInitted = false +m._onLimit = nil + +-- Pass in a table of settings +-- @param tbl.initStepDirEnPins Defaults to false +-- @param tbl.pinStep Defaults to 4 +-- @param tbl.pinDir Defaults to 0 +-- @param tbl.pinEn Defaults to 16 +-- @param tbl.isDebug Defaults to false. Turn on for extra logging. +-- @param tbl.onLimit Callback when stepMax or stepMin is hit +-- @param tbl.stepLimitMax Defaults to 32000 +-- @param tbl.stepLimitMin Defaults to -32000 +-- Example motor.init({initStepDirEnPins=true, }) +function m.init(tbl) + + if m._isInitted then + print("DRV8825 already initted") + return + end + + m._isInitted = true + + if tbl.pinStep ~= nil then m.pinStep = tbl.pinStep end + if tbl.pinDir ~= nil then m.pinDir = tbl.pinDir end + if tbl.pinEn ~= nil then m.pinSleep = tbl.pinEn end + if tbl.isDebug == true then m.isDebug = true end + if tbl.onLimit ~= nil then m._onLimit = tbl.onLimit end + if tbl.stepLimitMax ~= nil then m.stepMax = tbl.stepLimitMax end + if tbl.stepLimitMin ~= nil then m.stepMin = tbl.stepLimitMin end + + -- defaults to false + if tbl.initStepDirEnPins == true then + + + gpio.config({ + gpio= { + m.pinStep, m.pinSleep, m.pinDir, + m.pinM0, m.pinM1, m.pinM2 + }, + dir=gpio.IN_OUT, + }) + + gpio.write(m.pinStep, 0) + + m.disable() + + m.dirFwd() + + -- for full steps, all low + -- for max micro-stepping, all high + gpio.write(m.pinM0, 0) + gpio.write(m.pinM1, 0) + gpio.write(m.pinM2, 0) + + end + + -- Start counting pulses on the loopback signal + m.initPulseCtr() + print("initted pulse ctr") + +end + +function m.initPulseCtr() + + -- Setup the pulse counter to watch the steps + -- Adhere to the direction pin as well to know automatically fwd/rev + -- so our steps are accurate to where the motor is + m.pcnt = pulsecnt.create(0, m.onPulseCnt, false) + m.pcnt:chan0Config( + m.pinPulseIn, -- 36 (sens_vp), 39 (sens_vn), m.pinStep, m.pinPulseIn, --pinPulseIn --pulse_gpio_num + m.pinDir, --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 PCNT_MODE_KEEP, PCNT_MODE_REVERSE, PCNT_MODE_DISABLE + pulsecnt.PCNT_MODE_KEEP, --hctrl_mode PCNT_MODE_KEEP, PCNT_MODE_REVERSE, PCNT_MODE_DISABLE + -32768, --counter_l_lim + 32767 --counter_h_lim + ) + m.pcnt:setThres(m.stepMin, m.stepMax) + m.pcnt:clear() + +end + +function m.onPulseCnt(unit, isThr0, isThr1, isLLim, isHLim, isZero) + + print("Got pulse counter.") + print("unit:", unit, "isThr0:", isThr0, "isThr1:", isThr1) + print("isLLim:", isLLim, "isHLim:", isHLim, "isZero:", isZero) + + if isThr0 or isThr1 then + m.disable() + + -- if callback from user, then call it + if m.onLimit ~= nil then + m.onLimit(isThr0, isThr1) + end + + -- m.pause() + -- m.stop() + if isThr0 then + print("Hit endstop in negative direction") + else + print("Hit endstop in positive direction") + end + end + +end + + +m.DIR_FWD = 1 +m.DIR_REV = 0 +m._dir = nil +function m.setDir(dir) + if dir == m.DIR_FWD then + if m._dir == m.DIR_FWD then + -- already set. ignore. + if m.isDebug then print("Dir fwd already set. Ignoring.") end + return + end + gpio.write(m.pinDir,1) + m._dir = m.DIR_FWD + if m.isDebug then print("Set dir fwd") end + else + if m._dir == m.DIR_REV then + -- already set. ignore. + if m.isDebug then print("Dir rev already set. Ignoring.") end + return + end + gpio.write(m.pinDir,0) + m._dir = m.DIR_REV + if m.isDebug then print("Set dir rev") end + end +end + +function m.dirFwd() + m.setDir(m.DIR_FWD) +end + +function m.dirRev() + m.setDir(m.DIR_REV) +end + +function m.dirToggle() + if m._dir == m.DIR_FWD then + m.dirRev() + else + m.dirFwd() + end +end + +function m.disable() + -- drv8825 low makes sleep / high make active + gpio.write(m.pinSleep, 0) + if m.isDebug then print("Sleeping motor (disable)") end +end + +function m.enable() + -- drv8825 low makes sleep / high make active + gpio.write(m.pinSleep, 1) + if m.isDebug then print("Waking motor (enable)") end +end + +return m \ No newline at end of file diff --git a/lua_examples/touch/touchjog_main.lua b/lua_examples/touch/touchjog_main.lua new file mode 100644 index 0000000000..8fad1c0fc5 --- /dev/null +++ b/lua_examples/touch/touchjog_main.lua @@ -0,0 +1,94 @@ +-- Touch Jog Example +-- Shows how to jog a stepper motor (DRV8825) using 5 touch pads +-- where each touch pad jogs at a difference frequency, slow to fast, +-- when touching the pads. When you release from the pads the jogging +-- is stopped. + +-- File hierarchy: +-- touchjog_main.lua +-- - touchjog_touch.lua +-- - touchjog_jog.lua +-- - touchjog_jog_drv8825.lua + +-- You need to connect the following pins: +-- Touch pads use pins 2, 12, 13, 14, 15, 27, 33, 32 +-- Stepper motor uses pins: + -- m.pinStep = 4 + -- m.pinDir = 0 + -- m.pinSleep = 16 + -- -- Loopback pulse in from m.pinStep (use wire to connect) + -- m.pinPulseIn = 36 -- 36 (sens_vp) + +m = {} +m.jog = require("touchjog_jog") +m.touch = require("touchjog_touch") + +m.testFreq = 100 +m._isJogging = false + +function m.init() + + -- Init jog + m.jog.init() + + -- Init touch pads + m.touch.init({ + cb=m.onTouch, -- our callback on interrupt + }) + m.touch.config() + m.touch.read() + + -- Setup the tmr for the callback process + m._tmr = tmr.create() + m._tmr:register(150, tmr.ALARM_SEMI, m.onTmr) + + +end + +m._padState = 0 -- 0 means untouched +m._tmr = nil -- will hold the tmr obj + +-- We will get a callback every 5ms or so when touched +-- We will reset the tmr each time and after 20ms we will throw +-- untouched event +function m.onTouch(pads) + + -- print("state:", m._padState) + + if m._padState == 0 then + -- we just got touched + m._padState = 1 -- 1 means touched + m._tmr:start() + print("Got touch") + m.jog.jogStart(100) + else -- m._padState == 1 + -- we will get here on successive callbacks while touched + -- we get them about every 8ms + -- so reset the tmr so it doesn't timeout yet + m._tmr:stop() + m._tmr:start() + end + + -- pads {2,3,4,5,6} + local runfreq = 100 + if pads[6] then runfreq = 20 + elseif pads[5] then runfreq = 70 + elseif pads[4] then runfreq = 150 + elseif pads[3] then runfreq = 300 + elseif pads[2] then runfreq = 450 + end + + m.jog.setfreq(runfreq,false) + +end + +function m.onTmr() + -- if we get the timer triggered, it means that we timed out + -- from touch callbacks, so we presume the touch ended + m._padState = 0 + print("Got untouch") + m.jog.jogStop() +end + +m.init() + diff --git a/lua_examples/touch/touchjog_touch.lua b/lua_examples/touch/touchjog_touch.lua new file mode 100644 index 0000000000..1e7461adcf --- /dev/null +++ b/lua_examples/touch/touchjog_touch.lua @@ -0,0 +1,67 @@ +-- Touch module for 5 pads +-- Configures 5 pads at 90% threshold and gives callback + +-- Using pins 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, +-- 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + +local m = {} + +-- Touch uses pins 2, 12, 13, 14, 15, 27, 33, 32 +m.pads = {2,3,4,5,6} -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + +-- Public object for touchpad +m.tp = nil + +m._cb = nil +m._isInitted = false + +-- Pass in tbl +-- @param cb This is your callback when pads touched +function m.init(tbl) + + if m._isInitted then + print("Already initted") + return + end + + if tbl ~= nil then + if tbl.cb ~= nil then m._cb = tbl.cb end + end + + m._tp = touch.create({ + pad = m.pads, -- pad = 0 || {0,1,2,3,4,5,6,7,8,9} 0=GPIO4, 1=GPIO0, 2=GPIO2, 3=GPIO15, 4=GPIO13, 5=GPIO12, 6=GPIO14, 7=GPIO27, 8=GPIO33, 9=GPIO32 + cb = m._cb, -- Callback will get Lua table of pads/bool(true) that were touched. + intrInitAtStart = false, -- Turn on interrupt at start. Default to true. Set to false in case you want to config first. Turn it on later with tp:intrEnable() + thres = 0, -- Start with thres 0 so no callbacks, then set thres later with tp:setThres(padNum, thres) + thresTrigger = touch.TOUCH_TRIGGER_BELOW, -- Touch interrupt if counter value is below or above threshold. TOUCH_TRIGGER_BELOW or TOUCH_TRIGGER_ABOVE + lvolt = touch.TOUCH_LVOLT_0V5, -- Low ref voltage TOUCH_LVOLT_0V4, TOUCH_LVOLT_0V5, TOUCH_LVOLT_0V6, TOUCH_LVOLT_0V7 + hvolt = touch.TOUCH_HVOLT_2V7, -- High ref voltage TOUCH_HVOLT_2V4, TOUCH_HVOLT_2V5, TOUCH_HVOLT_2V6, TOUCH_HVOLT_2V7 + atten = touch.TOUCH_HVOLT_ATTEN_1V, -- High ref atten TOUCH_HVOLT_ATTEN_0V, TOUCH_HVOLT_ATTEN_0V5, TOUCH_HVOLT_ATTEN_1V, TOUCH_HVOLT_ATTEN_1V5 + isDebug = true + }) + +end + +function m.read() + local raw = m._tp:read() + print("Pad", "Val") + for key,value in pairs(raw) do + print(key,value) + end +end + +function m.config() + local raw = m._tp:read() + print("Configuring trigger to:") + print("Pad", "Val", "Thres") + for key,value in pairs(raw) do + -- reduce thres to 90% of untouched counter value + local thres = math.floor(raw[key] * 0.9) + m._tp:setThres(key, thres) + print(key, raw[key], thres) + end + + m._tp:intrEnable() +end + +return m diff --git a/mkdocs.yml b/mkdocs.yml index ff041129c2..fb44555a5e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,6 +62,7 @@ pages: - 'struct': 'modules/struct.md' - 'time': 'modules/time.md' - 'tmr': 'modules/tmr.md' + - 'touch': 'modules/touch.md' - 'u8g2': 'modules/u8g2.md' - 'uart': 'modules/uart.md' - 'ucg': 'modules/ucg.md'