Here's a simple touch read in assembly, without using the macros. Parts of HULP have been included. the files in this arduino project. Files: esp32_ulp_touch: esp32_ulp_touch.ino, stuff.h, touch.h, touch.c, ulp.s, ulp_main.h
.
#include "driver/rtc_io.h"
#include "esp32/ulp.h"
// include ulp header you will create
#include "ulp_main.h"
// include ulptool binary load function
#include "ulptool.h"
#include "driver/gpio.h"
#include "soc/rtc.h"
#include "esp_sleep.h"
#include "touch.h"
#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 5 /* Time ESP32 will go to sleep (in seconds) */
// this MUST be the same as PIN_TOUCH in the ulp.S file.
//#define PIN_TOUCH GPIO_NUM_32
#define PIN_TOUCH GPIO_NUM_4
#define ULP_TOUCH_INTERVAL_MS 2000
/**
* ULP sleep duration in ms.
*/
#define ULP_SLEEP_PERIOD 1000
enum {
LBL_TOUCH_INTERVAL,
LBL_HALT,
};
// Unlike the esp-idf always use these binary blob names
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");
// do some blinking too.
gpio_num_t ulp_blink_pin = GPIO_NUM_25; // this is for the Heltec Wireless Stick Lite
void initBlinkPin() {
Serial.println("initBlinkPin");
rtc_gpio_init(ulp_blink_pin);
//rtc_gpio_pulldown_dis(ulp_blink_pin); // disable VCOM pulldown (saves 80Β΅A). what's this about?
rtc_gpio_set_direction(ulp_blink_pin, RTC_GPIO_MODE_OUTPUT_ONLY);
// blink once:
rtc_gpio_set_level(ulp_blink_pin, 0);
delay(500);
rtc_gpio_set_level(ulp_blink_pin, 1);
delay(500);
rtc_gpio_set_level(ulp_blink_pin, 0);
}
static void init_run_ulp(uint32_t usec);
void setup() {
Serial.begin(115200);
delay(200);
// put your setup code here, to run once:
initBlinkPin();
init_run_ulp(ULP_TOUCH_INTERVAL_MS * 1000); // 1000 msec
const hulp_touch_controller_config_t controller_config = HULP_TOUCH_CONTROLLER_CONFIG_DEFAULT();
ESP_ERROR_CHECK(hulp_configure_touch_controller(&controller_config));
const hulp_touch_pin_config_t pin_config = HULP_TOUCH_PIN_CONFIG_DEFAULT();
ESP_ERROR_CHECK(hulp_configure_touch_pin(PIN_TOUCH, &pin_config));
}
void loop() {
// put your main code here, to run repeatedly:
delay(ULP_TOUCH_INTERVAL_MS);
Serial.printf("touch val: %u ", ulp_touch_val & 0xFFFF);
}
/*
* usec: sleep period.
*/
static void init_run_ulp(uint32_t usec) {
// initialize ulp variable
ulp_set_wakeup_period(0, usec);
esp_err_t err = ulptool_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
// ulp coprocessor will run on its own now
ulp_count = 0;
err = ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t));
if (err) Serial.println("Error Starting ULP Coprocessor");
}
// from https://github.com/espressif/esp-idf/blob/166c30e7b2ed1dcaae56179329540a862915208a/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_gpio.h
#ifndef ONCE_ONLY
#define ONCE_ONLY
typedef enum {
RTCIO_MODE_OUTPUT = 0,
RTCIO_MODE_OUTPUT_OD = 1,
} rtc_io_out_mode_t;
#define SOC_TOUCH_SENSOR_NUM (15)
// from https://github.com/espressif/esp-idf/blob/1cb31e50943bb757966ca91ed7f4852692a5b0ed/components/soc/esp32s3/include/soc/touch_sensor_caps.h
// from https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/touch_sensor_periph.c
/* Store IO number corresponding to the Touch Sensor channel number. */
const int touch_sensor_channel_io_map[SOC_TOUCH_SENSOR_NUM] = {
TOUCH_PAD_NUM0_GPIO_NUM,
TOUCH_PAD_NUM1_GPIO_NUM,
TOUCH_PAD_NUM2_GPIO_NUM,
TOUCH_PAD_NUM3_GPIO_NUM,
TOUCH_PAD_NUM4_GPIO_NUM,
TOUCH_PAD_NUM5_GPIO_NUM,
TOUCH_PAD_NUM6_GPIO_NUM,
TOUCH_PAD_NUM7_GPIO_NUM,
TOUCH_PAD_NUM8_GPIO_NUM,
TOUCH_PAD_NUM9_GPIO_NUM
};
/********************************/
#define TOUCH_PAD_BIT_MASK_ALL ((1<<SOC_TOUCH_SENSOR_NUM)-1)
#define TOUCH_PAD_SLOPE_DEFAULT (TOUCH_PAD_SLOPE_7)
#define TOUCH_PAD_TIE_OPT_DEFAULT (TOUCH_PAD_TIE_OPT_LOW)
// #define TOUCH_PAD_BIT_MASK_MAX (TOUCH_PAD_BIT_MASK_ALL)
#define TOUCH_PAD_HIGH_VOLTAGE_THRESHOLD (TOUCH_HVOLT_2V7)
#define TOUCH_PAD_LOW_VOLTAGE_THRESHOLD (TOUCH_LVOLT_0V5)
#define TOUCH_PAD_ATTEN_VOLTAGE_THRESHOLD (TOUCH_HVOLT_ATTEN_0V5)
#define TOUCH_PAD_IDLE_CH_CONNECT_DEFAULT (TOUCH_PAD_CONN_GND)
#define TOUCH_PAD_THRESHOLD_MAX (SOC_TOUCH_PAD_THRESHOLD_MAX) /*!<If set touch threshold max value, The touch sensor can't be in touched status */
#endif // ONCE_ONLY
#ifndef HULP_TOUCH_H
#define HULP_TOUCH_H
#include "driver/gpio.h"
#include "driver/touch_pad.h"
#include "touch_sensor_types.h"
#include "stuff.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SWAPPED_TOUCH_INDEX(x) ((x) == TOUCH_PAD_NUM9 ? TOUCH_PAD_NUM8 : ((x) == TOUCH_PAD_NUM8 ? TOUCH_PAD_NUM9 : (x)))
typedef struct {
uint16_t fastclk_meas_cycles;
touch_high_volt_t high_voltage;
touch_low_volt_t low_voltage;
touch_volt_atten_t attenuation;
} hulp_touch_controller_config_t;
#define HULP_TOUCH_CONTROLLER_CONFIG_DEFAULT() { \
.fastclk_meas_cycles = TOUCH_PAD_MEASURE_CYCLE_DEFAULT, \
.high_voltage = TOUCH_HVOLT_2V4, \
.low_voltage = TOUCH_LVOLT_0V8, \
.attenuation = TOUCH_HVOLT_ATTEN_1V5, \
}
/**
* Prepare touch controller for ULP control.
* Do this once, then configure each pin using hulp_configure_touch_pin.
* fastclk_meas_cycles: measurement time in fastclk (8MHz) cycles. (65535 = 8.19ms)
* Shorter = lower power consumption; Longer = higher counts (better possible signal/noise filtering)
*/
esp_err_t hulp_configure_touch_controller(const hulp_touch_controller_config_t *config);
typedef struct {
touch_cnt_slope_t slope;
touch_tie_opt_t tie_opt;
} hulp_touch_pin_config_t;
#define HULP_TOUCH_PIN_CONFIG_DEFAULT() { \
.slope = TOUCH_PAD_SLOPE_DEFAULT, \
.tie_opt = TOUCH_PAD_TIE_OPT_DEFAULT, \
}
/**
* Initialise and configure a pin for touch function.
*/
esp_err_t hulp_configure_touch_pin(gpio_num_t touch_gpio, const hulp_touch_pin_config_t *config);
#define I_TOUCH_GET_PAD_THRESHOLD(touch_num) \
I_RD_REG((SENS_SAR_TOUCH_THRES1_REG + (4 * ((uint8_t)SWAPPED_TOUCH_INDEX(touch_num)/2))), (uint8_t)(SWAPPED_TOUCH_INDEX(touch_num)%2 ? 0 : 16), (uint8_t)(SWAPPED_TOUCH_INDEX(touch_num)%2 ? 15 : 31))
#define I_TOUCH_GET_GPIO_THRESHOLD(gpio_num) \
I_TOUCH_GET_PAD_THRESHOLD(hulp_touch_get_pad_num(gpio_num))
#define I_TOUCH_GET_PAD_VALUE(touch_num) \
I_RD_REG((SENS_SAR_TOUCH_OUT1_REG + (4 * ((uint8_t)SWAPPED_TOUCH_INDEX(touch_num)/2))), (uint8_t)((SWAPPED_TOUCH_INDEX(touch_num)%2) ? 0 : 16), (uint8_t)((SWAPPED_TOUCH_INDEX(touch_num)%2) ? 15 : 31))
#define I_TOUCH_GET_GPIO_VALUE(gpio_num) \
I_TOUCH_GET_PAD_VALUE(hulp_touch_get_pad_num(gpio_num))
#define I_TOUCH_GET_DONE_BIT() \
I_RD_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_MEAS_DONE_S)
#define M_TOUCH_WAIT_DONE() \
I_TOUCH_GET_DONE_BIT(), \
I_BL(-1,1)
#define M_TOUCH_BEGIN() \
I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 0), \
I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 1)
//Junk:
/**
* Internal. Do not use directly.
*/
int hulp_touch_get_pad_num(gpio_num_t pin);
#ifdef __cplusplus
}
#endif
#endif // HULP_TOUCH_H
#include "esp_log.h"
#include "driver/touch_pad.h"
#include "stuff.h"
#include "touch.h"
static const char* TAG = "HULP-TCH";
int hulp_touch_get_pad_num(gpio_num_t pin)
{
for(int i = 0; i < SOC_TOUCH_SENSOR_NUM; ++i)
{
if(touch_sensor_channel_io_map[i] == pin) return i;
}
ESP_LOGW(TAG, "no touch pad for gpio %d", pin);
return -1;
}
esp_err_t hulp_configure_touch_controller(const hulp_touch_controller_config_t *config)
{
if(!config)
{
return ESP_ERR_INVALID_ARG;
}
esp_err_t err;
if(
ESP_OK != (err = touch_pad_init()) ||
ESP_OK != (err = touch_pad_set_fsm_mode(TOUCH_FSM_MODE_SW)) ||
ESP_OK != (err = touch_pad_set_voltage(config->high_voltage, config->low_voltage, config->attenuation)) ||
//sw control so sleep_cycle is irrelevant here; set to default
ESP_OK != (err = touch_pad_set_meas_time(TOUCH_PAD_SLEEP_CYCLE_DEFAULT, config->fastclk_meas_cycles))
)
{
ESP_LOGE(TAG, "[%s] err (0x%x)", __func__, err);
return err;
}
return ESP_OK;
}
esp_err_t hulp_configure_touch_pin(gpio_num_t touch_gpio, const hulp_touch_pin_config_t *config)
{
if(!config)
{
return ESP_ERR_INVALID_ARG;
}
int touch_pad_num = hulp_touch_get_pad_num(touch_gpio);
if(touch_pad_num < 0)
{
ESP_LOGE(TAG, "invalid touch pin (%d)", touch_gpio);
return ESP_ERR_INVALID_ARG;
}
esp_err_t err;
if(
ESP_OK != (err = touch_pad_io_init(touch_pad_num)) ||
ESP_OK != (err = touch_pad_set_cnt_mode(touch_pad_num, config->slope, config->tie_opt)) ||
ESP_OK != (err = touch_pad_set_group_mask(0, 0, 1 << touch_pad_num))
)
{
ESP_LOGE(TAG, "[%s] err (0x%x)", __func__, err);
return err;
}
return ESP_OK;
}
#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"
#include "soc/rtc_io_reg.h"
#include "soc/sens_reg.h"
// touch read. also blink the LED (define pin based on your microcontroller)
.bss
// 3.44ms seems to be the time taken for a touch read.
// "time_reg_e" - "time_reg" is about 572, and 1 sec is about 166152 RTC clock cycles, so 572/166152 = 3.44ms.
// touch read start time:
.global time_reg_1
time_reg_1:
.long 0
.global time_reg_2
time_reg_2:
.long 0
.global time_reg_3
time_reg_3:
.long 0
// touch read end time:
.global time_reg_e1
time_reg_e1:
.long 0
.global time_reg_e2
time_reg_e2:
.long 0
.global time_reg_e3
time_reg_e3:
.long 0
.data
.global touch_val
touch_val:
.long 0
.text
.global entry
entry:
.global loop1
loop1:
// turn LED on
// WRITE_RTC_REG(RTC_GPIO_OUT_REG,RTC_GPIO_OUT_DATA_S+12,1,1)
WRITE_RTC_REG(RTC_GPIO_OUT_REG,RTC_GPIO_OUT_DATA_S+6,1,1)
// wait a bit, loop a few times.
MOVE R0, 0x00 //R0 = 0x00
loop_blink:
WAIT 65535
Add R0, R0, 0x01 //R0++
jumpr jump_out, 17, GT // if count >17, jump out
jump loop_blink // otherwise keep looping
jump_out:
// turn LED off
// WRITE_RTC_REG(RTC_GPIO_OUT_REG,RTC_GPIO_OUT_DATA_S+12,1,0)
WRITE_RTC_REG(RTC_GPIO_OUT_REG,RTC_GPIO_OUT_DATA_S+6,1,0)
// wait a bit, loop a few times.
MOVE R0, 0x00 //R0 = 0x00
loop_blink2:
WAIT 65535
Add R0, R0, 0x01 //R0++
jumpr jump_out2, 17, GT // if count >17, jump out
jump loop_blink2 // otherwise keep looping
jump_out2:
// now let's start the touch read code:
.set SIZEOF_UINT32T, 4 // num bytes in a 32-bit word
.set PIN_TOUCH, 0 // GPIO 4
/**
* Touch pad control and status
* See TRM pg 649, Register 30.18: SENS_SAR_TOUCH_CTRL2_REG (0x0084)
*/
#define SENS_SAR_TOUCH_CTRL2_REG (DR_REG_SENS_BASE + 0x0084) // from sens_reg.h
// store system time, just before starting a touch read.
read_time1:
/* Trigger update of register */
WRITE_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_S, 1, 1)
JUMP check_time_valid1 // probably don't need this instruction
check_time_valid1:
/* Check if RTC_CNTL_TIME_VALID bit is 1, otherwise repeat */
READ_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_S, 1)
AND R0, R0, 1
JUMP check_time_valid1, EQ
/* Read timer registers */
READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16)
MOVE R2, time_reg_1
ST R0, R2, 0
REG_RD 0x3FF48010, 31, 16
MOVE R2, time_reg_2
ST R0, R2, 0
REG_RD 0x3FF48014, 15, 0
MOVE R2, time_reg_3
ST R0, R2, 0
/**
*
* ----------------- 1. Begin a new touch read -----------------
*
*/
/**
* Write the register, once with 0, once with 1, to start the sensing.
*
*/
WRITE_RTC_REG(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 1, 0)
WRITE_RTC_REG(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 1, 1)
/**
*
* ----------------- 2. Wait for it to complete -----------------
* Read SENS_TOUCH_MEAS_DONE until it is non-zero (keep looping), meaning that
* the reading is done. (see TRM)
*
* see soc\sens_reg.h. also esp32_technical_reference_manual_en.pdf p. 649
* Set to 1 by FSM, indicating that touch measurement is done. (RO, Read Only)
*
*/
MOVE R1, 0 // to keep count
read_again: // label
/**
* read peripheral register into R0. it'll be bit #10, so 2^10 = 1024 if it is non-zero.
*/
/* Read 1-bit SENS_TOUCH_MEAS_DONE field of SENS_SAR_TOUCH_CTRL2_REG into R0 */
READ_RTC_FIELD(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_MEAS_DONE)
// if R0 < 1, goto read_again. we only care if it is zero or not.
JUMPR read_again, 1, LT // appears the step is automatically calculated by the assembler. For example, jumpr didInit,1,ge, from i2c.s in ArduinoData\packages\esp32\tools\ulptool\src\ulp_examples\ulp_i2c_bitbang
/**
*
* ----------------- 3. Get the value for selected pin -----------------
* Now that we know that the scanning/measuring is over, get the result.
* As we're using only a single pin and don't need generic handling,
* we can skip the swap check for pins 8 & 9.
*/
/* Read 1-bit SENS_TOUCH_MEAS_OUT0 field of SENS_SAR_TOUCH_OUT1_REG into R0 */
READ_RTC_FIELD(SENS_SAR_TOUCH_OUT1_REG, SENS_TOUCH_MEAS_OUT0)
/**
* ----------------- 4. Update the variable with the new value -----------------
* R0 has the the value. set:
* touch_val = R0
*/
move r3, touch_val // R3 = address_of(touch_val) / 4
// ld r0, r3, 0 // LD Rdst, Rsrc, offset. // R0 = MEM[R3+0x00]
// add r0, r0, 1 // ADD Rdst, Rsrc1, imm. r0 = r0 + 1
st r0, r3, 0 // ST Rsrc, Rdst, offset. MEM[R3+0x00] = R0
// store system time after touch read
read_time2:
/* Trigger update of register */
WRITE_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_S, 1, 1)
JUMP check_time_valid2 // probably don't need this instruction
check_time_valid2:
/* Check if RTC_CNTL_TIME_VALID bit is 1, otherwise repeat */
READ_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_S, 1)
AND R0, R0, 1
JUMP check_time_valid2, EQ
/* Read timer registers */
READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16)
MOVE R2, time_reg_e1
ST R0, R2, 0
REG_RD 0x3FF48010, 31, 16
MOVE R2, time_reg_e2
ST R0, R2, 0
REG_RD 0x3FF48014, 15, 0
MOVE R2, time_reg_e3
ST R0, R2, 0
HALT
/*
Put your ULP globals here you want visibility
for your sketch. Add "ulp_" to the beginning
of the variable name and must be size 'uint32_t'
*/
#include "Arduino.h"
extern uint32_t ulp_entry;
extern uint32_t ulp_touch_val;