Count 2µs pulses with ULP

jul35_
Posts: 1
Joined: Fri Dec 27, 2019 6:39 pm

Count 2µs pulses with ULP

Postby jul35_ » Fri Dec 27, 2019 8:09 pm

Hello,
until now I have used attachInterrupt() inside the Arduino Code to count ~2µs HIGH LOW HIGH pulses. Because of power consumption limitations I would like to use the ULP to keep track of the pulses and send the main CPU into deep sleep.
With the help of this ulptool (https://github.com/duff2013/ulptool) it is possible to run ULP code from Arduino but it seems that I am hitting a hardware limitation with these short pulses.
I am detecting some pulses with the following code (based on the official pulse counter example) but it isn't really reliable. Slower pulses are detected reliable. Is there any way to speed up the code (I am completely new to assembler) or is it possible to overclock the ULP?
Am I missing something else?

Thank you!

Code: Select all

#include <stdio.h>
#include "esp_sleep.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
#include "soc/rtc_periph.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"

#include "esp32/ulp.h"// Must have this!!!

// include ulp header you will create
#include "ulp_main.h"// Must have this!!!

// Custom binary loader
#include "ulptool.h"// Must have this!!!

// 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");

static void init_ulp_program(void);
static void update_pulse_count(void);

void setup(void)
{
    init_ulp_program();
}

void loop()
{
  delay(60000);
  update_pulse_count();
}

static void init_ulp_program(void)
{
    esp_err_t err = ulp_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
    ESP_ERROR_CHECK(err);

    /* GPIO used for pulse counting. */
    gpio_num_t gpio_num = GPIO_NUM_36;
    assert(rtc_gpio_desc[gpio_num].reg && "GPIO used for pulse counting must be an RTC IO");

    ulp_next_edge = 0;
    ulp_io_number = rtc_gpio_desc[gpio_num].rtc_num; /* map from GPIO# to RTC_IO# */

    /* Initialize selected GPIO as RTC IO, enable input, disable pullup and pulldown */
    rtc_gpio_init(gpio_num);
    rtc_gpio_set_direction(gpio_num, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pulldown_dis(gpio_num);
    rtc_gpio_pullup_en(gpio_num);
    rtc_gpio_hold_en(gpio_num);

    err = ulp_run(&ulp_entry - RTC_SLOW_MEM);
    ESP_ERROR_CHECK(err);
}

static void update_pulse_count(void)
{
    /* ULP program counts signal edges, convert that to the number of pulses */
    uint32_t pulse_count_from_ulp = (ulp_edge_count & UINT16_MAX) / 2;
    /* In case of an odd number of edges, keep one until next time */
    printf("Edge count from ULP: %5d\n", (ulp_edge_count & UINT16_MAX));
    ulp_edge_count = ulp_edge_count % 2;
    printf("Pulse count from ULP: %5d\n", pulse_count_from_ulp);
}
ulp_main.h:

Code: Select all

#include "Arduino.h"

extern uint32_t ulp_entry;
extern uint32_t ulp_next_edge;
extern uint32_t ulp_edge_count;
extern uint32_t ulp_io_number;
pulse_cnt.s:

Code: Select all

#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"

  /* Define variables, which go into .bss section (zero-initialized data) */
  .bss

  /* Next input signal edge expected: 0 (negative) or 1 (positive) */
  .global next_edge
next_edge:
  .long 0

  /* Total number of signal edges acquired */
  .global edge_count
edge_count:
  .long 0

  /* RTC IO number used to sample the input signal. Set by main program. */
  .global io_number
io_number:
  .long 0

  /* Code goes into .text section */
  .text
  .global entry
entry:
  /* Load io_number */
  move r3, io_number
  ld r3, r3, 0

  /* Lower 16 IOs and higher need to be handled separately,
   * because r0-r3 registers are 16 bit wide.
   * Check which IO this is.
   */
  move r0, r3
  jumpr read_io_high, 16, ge

  /* Read the value of lower 16 RTC IOs into R0 */
  READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S, 16)
  rsh r0, r0, r3
  jump read_done

  /* Read the value of RTC IOs 16-17, into R0 */
read_io_high:
  READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + 16, 2)
  sub r3, r3, 16
  rsh r0, r0, r3

read_done:
  and r0, r0, 1
  /* State of input changed? */
  move r3, next_edge
  ld r3, r3, 0
  add r3, r0, r3
  and r3, r3, 1
  jump edge_detected, eq
  /* Not changed */
  jump entry

  .global edge_detected
edge_detected:
  /* Flip next_edge */
  move r3, next_edge
  ld r2, r3, 0
  add r2, r2, 1
  and r2, r2, 1
  st r2, r3, 0
  /* Increment edge_count */
  move r3, edge_count
  ld r2, r3, 0
  add r2, r2, 1
  st r2, r3, 0
  jump entry

boarchuz
Posts: 566
Joined: Tue Aug 21, 2018 5:28 am

Re: Count 2µs pulses with ULP

Postby boarchuz » Tue Dec 31, 2019 11:04 am

So I understand correctly, the signal is normally high (idle) and is then low for >=2 uS? How frequent is this low pulse?

It will be tight, but might be possible. ULP instructions take 4-12 cycles total, at 8 MHz, which works out to 0.5-1.5uS, so you don't have any time to spare. You'll have to strip it down to the bare minimum rather than what you've got there.

I have a library - https://github.com/boarchuz/HULP - that should make it a little bit easier to program the ULP with Arduino (though it's not really necessary here).

Here's an untested example, you can translate to ulp asm if you prefer:

Code: Select all

#include "hulp.h"

#define PULSE_PIN GPIO_NUM_36
#define PULSE_THRESHOLD 100 //How many pulses before waking

void startulp()
{
    enum {
        LBL_BEGIN,
        LBL_POLL,
        LBL_WAKE,
    };

    const ulp_insn_t program[]{
        //Reset R1 to PULSE_THRESHOLD-1
        M_LABEL(LBL_BEGIN),
            I_MOVI(R1, (PULSE_THRESHOLD) - 1 ),
        
        //While high, continue polling the pin
        M_LABEL(LBL_POLL),
            I_GPIO_READ(PULSE_PIN),
            M_BGE(LBL_POLL, 1), 

        //When it goes low:
            I_ADDI(R1, R1, 65535), //Subtract 1 (I've reversed logic to shave off 0.5uS; +65535 == -1)
            M_BXF(LBL_POLL),      //Loop back to LBL_POLL until 0 remaining
                                                   // !! You may need to wait until it goes high again !!

        //Interrupt SoC
            I_WAKE(),

        //Start over
            M_BX(LBL_BEGIN),
    };

    hulp_configure_pin(PULSE_PIN, RTC_GPIO_MODE_INPUT_ONLY);
    hulp_start(program, sizeof(program), 1);
}

void setup() {
    startulp();
    //...
}

Who is online

Users browsing this forum: No registered users and 54 guests