# Touch Sensor Low Level Driver
`touch_sensor_lowlevel` is a singleton wrapper around the ESP-IDF touch sensor
driver. It owns channel setup, sampling, polling reads, callbacks, and the
Linux replay backend behind one API.
Version 1.0.1 supports ESP-IDF 5.5, 6.0, and 6.1. ESP-IDF 5.5 and 6.0 cover
ESP32-S2, ESP32-S3, ESP32-P4, and Linux. ESP-IDF 6.1 also covers ESP32-H4 and
ESP32-S31 when those targets are available in the SDK.
This README is the application-facing contract: use it for public APIs, input
units, valid ranges, default resolution, ownership, and lifecycle. The
[lowlevel design guide](../../design_guide/touch_sensor_lowlevel_design.md)
explains the internal configuration-loading order, scan budget, calibration,
and frame-delivery algorithms. The guide does not add another public API.
## Features
- Polling and callback access to touch data.
- Configurable channel order, channel type, scan period, and sample count.
- Effective topology and timing discovery through
`touch_sensor_lowlevel_get_info()`.
- Best-effort hardware startup alignment using the first configured channel.
- Linux replay support with application-provided data providers.
## Quick Start
```c
#include <inttypes.h>
#include "esp_check.h"
#include "touch_sensor_lowlevel.h"
void app_main(void)
{
uint32_t channels[] = {8, 12};
touch_lowlevel_config_t config = {
.channel_num = 2,
.channel_list = channels,
.channel_type = NULL,
.sample_period_ms = 50,
.sample_cfg_num = 0,
};
ESP_ERROR_CHECK(touch_sensor_lowlevel_create(&config));
/* On hardware, start performs bounded best-effort startup alignment. */
ESP_ERROR_CHECK(touch_sensor_lowlevel_start());
touch_lowlevel_info_t info = {0};
ESP_ERROR_CHECK(touch_sensor_lowlevel_get_info(&info));
uint32_t data[SOC_TOUCH_SAMPLE_CFG_NUM] = {0};
ESP_ERROR_CHECK(touch_sensor_lowlevel_get_data(info.channel_list[0], data));
for (uint32_t i = 0; i < info.sample_cfg_num; i++) {
printf("channel %" PRIu32 " sample[%" PRIu32 "]: %" PRIu32 "\n",
info.channel_list[0], i, data[i]);
}
ESP_ERROR_CHECK(touch_sensor_lowlevel_stop());
ESP_ERROR_CHECK(touch_sensor_lowlevel_delete());
}
```
`touch_sensor_lowlevel_create()` creates the single low-level instance. The
channel arrays are copied during creation and may be released afterwards.
Delete the instance before creating it again.
## Runtime Configuration
`touch_lowlevel_config_t` is passed to
`touch_sensor_lowlevel_create()`:
```c
touch_lowlevel_config_t config = {
.channel_num = 3,
.channel_list = (uint32_t[]){8, 12, 10},
.channel_type = NULL,
.sample_period_ms = 0,
.sample_cfg_num = 0,
};
```
### Field ranges and default resolution
| Field | Valid input | Default or normalization | Ownership and effect |
| --- | --- | --- | --- |
| `channel_num` | `1..SOC_TOUCH_SENSOR_NUM` | Required; no implicit channel list | Number of configured channels. |
| `channel_list` | Supported channel IDs, unique within the list | Required; copied by `create()` | Order is preserved; the first channel is the hardware startup-alignment reference. |
| `channel_type` | One valid enum value per channel, or `NULL` | `NULL` means all channels are `TOUCH_LOWLEVEL_TYPE_TOUCH` | Proximity is available only on targets exposing `TOUCH_LOWLEVEL_TYPE_PROXIMITY`. Mixed ordinary/proximity startup is rejected. |
| `sample_period_ms` | `0` or a positive full-scan period in milliseconds | `0` resolves to 11 ms for `channel_num <= 8`, otherwise 17 ms | The complete scan must fit this period; the effective value is returned by `get_info()`. |
| `sample_cfg_num` | `0` or `1..SOC_TOUCH_SAMPLE_CFG_NUM` | `0` resolves to `TOUCH_LOWLEVEL_SAMPLE_CFG_NUM`; zero or an out-of-range resolved value is normalized to `SOC_TOUCH_SAMPLE_CFG_NUM` | Number of valid frequency/sample entries in every frame. Query the effective value instead of assuming a target default. |
| `proximity_count` | Ordinary touch: ignored. Proximity: V2 `1..255`; V3 `1..floor(255 / effective_sample_cfg_num)` | No default for proximity; it must be nonzero when proximity is configured | Number of scans accumulated into one proximity frame. It is validated at `start()`. |
`channel_list` and `channel_type` are copied during `create()`; the caller may
release its arrays after the call returns. The component is a singleton, so a
second `create()` requires a preceding `delete()`. For proximity, configure
all channels as proximity; ordinary raw startup alignment is skipped and
`NEW_DATA` is delivered only after an accumulation completes.
When `sample_period_ms` is zero, the built-in default is 11 ms for up to eight
channels and 17 ms for larger channel lists. The effective period and sample
count are available through `touch_sensor_lowlevel_get_info()`.
## Built-in tuning defaults
The public runtime fields above are the normal application configuration. The
following development-time defaults are selected from the private default
headers and can be overridden in `main/touch_sensor_lowlevel_config.h`. They
are listed here so a capture can be reproduced without reading private source
files; the algorithm and timing consequences are described in the
[design guide](../../design_guide/touch_sensor_lowlevel_design.md).
| Macro family | Built-in default | Valid range or meaning |
| --- | ---: | --- |
| `TOUCH_LOWLEVEL_SAMPLE_CFG_NUM` | `SOC_TOUCH_SAMPLE_CFG_NUM` | Effective value is normalized to `1..SOC_TOUCH_SAMPLE_CFG_NUM`. |
| `TOUCH_LOWLEVEL_DYNAMIC_SAMPLE_RATE_ENABLED` | `0` | `0` disabled, `1` enabled. |
| `TOUCH_LOWLEVEL_DYNAMIC_SAMPLE_RATE_JITTER_US` | `0 us` | `0` disables period jitter; a positive value is the jitter bound. |
| `TOUCH_LOWLEVEL_FILTER_MODE` | `0` | SDK benchmark filter enum; `0` is the default `IIR_4` mode. |
| `TOUCH_LOWLEVEL_JITTER_STEP` | `4` | Driver jitter-filter step, `0..15`; used only by the jitter filter. |
| `TOUCH_LOWLEVEL_DENOISE_LVL` | `1` | Driver denoise level, `0..4`. |
| `TOUCH_LOWLEVEL_SMOOTH_FILTER` | `0` | SDK no-filter value. |
| `TOUCH_LOWLEVEL_ACTIVE_HYSTERESIS` | `2` | SDK driver hysteresis delta. |
| `TOUCH_LOWLEVEL_DEBOUNCE_CNT` | `2` | SDK driver consecutive-sample count. |
| `TOUCH_LOWLEVEL_CALIBRATION_TARGET_RAW` | `5000` | Positive ordinary-touch startup reference count. |
V2-specific defaults are charge times `200`, estimated cycle `4 us`, voltage
limits `0/11`, idle connection `1`, bias type `1`, power-on wait `256 us`,
measurement interval `32 us`, maximum measurement time `0` (disabled), charge
speed `7`, and initial charge voltage `2`. The driver-defined ranges are
voltage limits `0..15`, idle/bias selectors `0..1`, charge speed `0..7`, and
initial charge voltage `0..2`.
V3-specific defaults are power-on wait `33 us`, measurement interval `32 us`,
maximum measurement time `0` (disabled), output mode `1`, divider `4`, bias
voltage `15` (`0..15`), active threshold `0xFFFF`, and initial charge times
`200`. Its three starting sample presets use resistance `3` (`0..3`),
capacitance `125` (`0..127`), and coarse/fine tuning pairs `8/5`, `17/11`,
and `19/7`. The physical oscillator frequency is SDK/HAL dependent; these
presets are not published as fixed MHz values.
`CONFIG_TOUCH_SENSOR_LOWLEVEL_DEBUG_CONFIG` defaults to disabled. When it is
enabled, the debug set/get configuration APIs in the public header are
available for local validation; they are not required by normal applications
and should not be used as a production configuration ABI.
## Startup Alignment
On hardware, `touch_sensor_lowlevel_start()` performs bounded best-effort
startup alignment for ordinary touch using the first channel in `channel_list`. It measures all
effective frequencies and keeps the best valid measured charge-time table,
with a default target of 5000 and a 50 permille tolerance. Reaching that
tolerance is an early-success condition; a valid candidate that fits the
requested period can still be used when the target is not reached. The
operation is internal to `start()`; there is no separate public alignment
configuration, execution, or result-query API.
The startup sample count is fixed internally. If a finger is already on the
reference channel at power-on, those samples become the initial operating
point. The driver does not re-align in the background or promise to return to
5000; the raw baseline and upper-layer FSM may take time to recover after the
finger is released. Keep the reference electrode untouched when a known
no-touch operating point is required.
Keep the reference electrode untouched and the board stable while the driver
starts. A stability check can reject an unstable measurement window, but it
cannot guarantee detection of a finger that remains on the electrode during
startup. If no valid candidate can be measured, the requested period cannot be
met, or the hardware/read path fails, `touch_sensor_lowlevel_start()` returns
the corresponding `esp_err_t`; do not open the upper-layer event path until it
returns `ESP_OK`.
Linux has no hardware charge-time control. Its `start()` path begins replay
without changing the supplied raw values, and the same create/start calls are
used by applications on that target.
### Proximity data
An all-proximity configuration keeps its configured charge times and skips
ordinary raw alignment: accumulated proximity counts are not a 5000-count raw
measurement. Startup still measures scan time and rejects an impossible scan
period. Mixed ordinary-touch/proximity configurations are unsupported.
The driver configures SDK proximity sensing and delivers `NEW_DATA` only on a
completed accumulation, using `TOUCH_CHAN_DATA_TYPE_PROXIMITY`. It does not
repeat the cached accumulation at every ordinary scan. For V2, the completed
frame interval is the effective scan period multiplied by `proximity_count`;
upper layers must use that interval for their time-based processing.
`get_info().proximity_count` reports the configured accumulation count, so a
wrapper reusing an existing driver can derive the actual frame period. Proximity
startup and interrupt routing have mock coverage; physical proximity behavior
still requires validation on a supported board.
## Sample Data
The buffer passed to `touch_sensor_lowlevel_get_data()` and to a callback must
have `SOC_TOUCH_SAMPLE_CFG_NUM` entries. Only the first `sample_cfg_num`
entries reported by `touch_sensor_lowlevel_get_info()` are valid.
```c
touch_lowlevel_info_t info = {0};
ESP_ERROR_CHECK(touch_sensor_lowlevel_get_info(&info));
uint32_t data[SOC_TOUCH_SAMPLE_CFG_NUM] = {0};
ESP_ERROR_CHECK(touch_sensor_lowlevel_get_data(info.channel_list[0], data));
for (uint32_t i = 0; i < info.sample_cfg_num; i++) {
printf("%" PRIu32 "\n", data[i]);
}
```
## Polling and Callback Modes
Polling reads the latest values:
```c
uint32_t data[SOC_TOUCH_SAMPLE_CFG_NUM] = {0};
ESP_ERROR_CHECK(touch_sensor_lowlevel_get_data(channel, data));
```
For callbacks, register one callback for each channel that should receive new
data. The callback data pointer is valid while the callback runs; copy values
that must be retained.
```c
static void touch_cb(uint32_t channel, touch_lowlevel_state_t state,
void *state_values, void *arg)
{
if (state != TOUCH_LOWLEVEL_STATE_NEW_DATA) {
return;
}
const touch_lowlevel_info_t *info = arg;
const uint32_t *data = state_values;
for (uint32_t i = 0; i < info->sample_cfg_num; i++) {
printf("channel %" PRIu32 " sample[%" PRIu32 "]: %" PRIu32 "\n",
channel, i, data[i]);
}
}
touch_lowlevel_info_t info = {0};
ESP_ERROR_CHECK(touch_sensor_lowlevel_get_info(&info));
touch_lowlevel_handle_t handle = NULL;
ESP_ERROR_CHECK(touch_sensor_lowlevel_register(info.channel_list[0], touch_cb,
&info, &handle));
```
Unregister callbacks before deleting the driver when they are no longer
needed. Stop the driver before deleting it.
## Application Defaults and Kconfig
Normal applications only need `touch_lowlevel_config_t`; target-specific
defaults are selected automatically. An application may provide
`main/touch_sensor_lowlevel_config.h` for product-specific development
overrides. Runtime diagnostic configuration APIs are disabled by default and
are intended for development builds.
## Linux Replay Data
On Linux, built-in replay data is used unless the application supplies strong
implementations of the weak provider functions. Single-frequency rows use:
```c
const uint32_t (*weak_get_data_buffer(uint32_t channel))[2];
uint32_t weak_get_data_buffer_size(uint32_t channel);
```
Each row is `{timestamp_ms, raw_value}`. Grouped multi-frequency rows use:
```c
const uint32_t (*weak_get_multi_freq_data_buffer(uint32_t channel))
[SOC_TOUCH_SAMPLE_CFG_NUM];
uint32_t weak_get_multi_freq_data_buffer_size(uint32_t channel);
```
Each row contains all raw values for one sample point. Return `NULL` or `0` to
fall back to the built-in replay data.
## API Summary
- `touch_sensor_lowlevel_create()`: Create the singleton instance.
- `touch_sensor_lowlevel_start()`: Run hardware startup alignment when
applicable and start continuous scanning.
- `touch_sensor_lowlevel_get_info()`: Read effective topology, timing, and
sample layout.
- `touch_sensor_lowlevel_stop()`: Stop continuous scanning.
- `touch_sensor_lowlevel_delete()`: Delete the singleton instance.
- `touch_sensor_lowlevel_get_data()`: Poll current channel data.
- `touch_sensor_lowlevel_get_frequency()`: Estimate V3 ordinary-touch
oscillator frequency from the latest complete raw frame when the documented
hardware preconditions are met.
- `touch_sensor_lowlevel_register()`: Register a new-data callback.
- `touch_sensor_lowlevel_unregister()`: Unregister a callback.
The lifecycle is `create -> start -> get/register -> stop -> delete`. A
callback receives a borrowed `SOC_TOUCH_SAMPLE_CFG_NUM`-entry buffer; only the
first effective `sample_cfg_num` values are valid and any retained data must
be copied before the callback returns. The `touch_lowlevel_info_t` channel
arrays are also owned by lowlevel and remain valid only until the next
`create()` or `delete()`.
idf.py add-dependency "espressif/touch_sensor_lowlevel^1.0.1"