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.
esp32_ulp_touch.ino:
#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");
}
stuff.h:
// 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
#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
ulp_main.h:
/*
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;
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
.esp32_ulp_touch.ino:
stuff.h:
touch.h:
touch.c:
ulp.s:
ulp_main.h: