Commit 9a5327ca authored by John Lauer's avatar John Lauer Committed by Marcel Stör
Browse files

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
parent b0558d5b
......@@ -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
This diff is collapsed.
# 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
-- 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
-- Test out the touch_8pads_showlist module
tpad = require("touch_8pads_showlist")
tpad.init({isDebug=true})
tpad.config()
\ No newline at end of file
-- 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
-- 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
-- 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()
-- 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
......@@ -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'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment