boarchuz / HULP

ESP32 ULP Coprocessor Helper
MIT License
180 stars 18 forks source link

touch, ULP FSM instructions, without macros #15

Closed mahesh2000 closed 2 years ago

mahesh2000 commented 3 years ago

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

touch.h:

#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

touch.c:

#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;
}

ulp.s:

#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;