The suggested workaround only reduced the ISR to being called twice per interrupt instead of the 7 times I was seeing:
Code: Select all
uint32_t rtc_intr = READ_PERI_REG(RTC_CNTL_INT_ST_REG);
// disable and clear interrupt
CLEAR_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
WRITE_PERI_REG(RTC_CNTL_INT_CLR_REG, rtc_intr);
And doesn't matter how long I wait before re-enabling the interrupt, I've tried as high as 200ms:
Code: Select all
vTaskDelay(pdMS_TO_TICKS(200));
SET_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
I still kept getting two ISR calls per interrupt. However I've found that if I delay writing to RTC_CNTL_INT_CLR_REG then it prevents the unwanted extra interrupt:
Code: Select all
// disable and clear interrupt
CLEAR_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
ets_delay_us(50);
WRITE_PERI_REG(RTC_CNTL_INT_CLR_REG, rtc_intr);
And that the delay before re-enabling via RTC_CNTL_INT_ENA_REG register in app_main task is unnecessary as it doesn't matter.
However it means we spend more time in the ISR than desired and 50usec is the lowest I could get the delay before it became unreliable to prevent the second ISR call.
Here's the complete program I've been testing with, which is based off
ulp_example_main:
Code: Select all
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "esp_system.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_event_loop.h"
#include "esp_deep_sleep.h"
#include "esp32/ulp.h"
#include "soc/rtc_cntl_reg.h"
#include "ulp_main.h"
#define Trigger_RTCIO 10 // RTC GPIO number (see 4.11 RTC_MUX Pin List)
#define Trigger_GPIO GPIO_NUM_4 // Motion sensor
enum
{
// Shared event group bit flags
TaskFlag_Trigger_Active = (1 << 1), // Trigger went to active state
// NOTE: FreeRTOS reserves the 8 most significant bits.
TaskFlag__WaitFor_App = TaskFlag_Trigger_Active
};
static EventGroupHandle_t sm_TaskEvent = NULL; // shared event group
static volatile uint32_t rtcISRCounter = 0;
static const char *TAG = "trigTest";
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 ulp_ReEnable(void);
static void ulp_InitInterrupt(void);
static void ulp_InitProgram(void);
void app_main()
{
sm_TaskEvent = xEventGroupCreate();
ulp_InitInterrupt();
ulp_InitProgram();
ESP_LOGI(TAG, "main loop");
for(;;)
{
EventBits_t bits = xEventGroupWaitBits(sm_TaskEvent, TaskFlag__WaitFor_App, pdTRUE, pdFALSE, pdMS_TO_TICKS(800));
if(bits & TaskFlag_Trigger_Active)
{
ESP_LOGI(TAG, "ISR %u, Wake %u", rtcISRCounter, ulp_wakeup_counter & 0xFFFF);
ulp_ReEnable();
}
}
}
/**
* ULP interrupt. Occurs only when ULP executes 'wake' instruction
* and the SoC processors are already running.
*/
static void rtc_isr(void *arg)
{
uint32_t rtc_intr = READ_PERI_REG(RTC_CNTL_INT_ST_REG);
// disable and clear interrupt
CLEAR_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
ets_delay_us(50);
WRITE_PERI_REG(RTC_CNTL_INT_CLR_REG, rtc_intr);
if(!(rtc_intr & RTC_CNTL_SAR_INT_ST))
return;
++rtcISRCounter;
const EventBits_t bits = TaskFlag_Trigger_Active;
BaseType_t hptaskWoken, result;
result = xEventGroupSetBitsFromISR(sm_TaskEvent, bits, &hptaskWoken);
if((result == pdPASS) && (hptaskWoken == pdTRUE))
portYIELD_FROM_ISR();
(void)arg;
}
static inline void ulp_ReEnable(void)
{
// wait a couple RTC_SLOW_CLK(~150KHz) cycles before re-enabling interrupt
// vTaskDelay(1);
SET_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
}
static inline void ulp_InitInterrupt(void)
{
// install RTC interrupt handler
esp_intr_alloc(ETS_RTC_CORE_INTR_SOURCE, 0, rtc_isr, NULL, NULL);
// enable ULP to trigger RTC interrupt when SoC cores are already running
SET_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
}
/**
* Initialize ULP and run program.
*/
static void ulp_InitProgram(void)
{
ESP_ERROR_CHECK(ulp_load_binary(0, ulp_main_bin_start,
(ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t)));
/* Initialize some variables used by ULP program.
* Each 'ulp_xyz' variable corresponds to 'xyz' variable in the ULP program.
* These variables are declared in an auto generated header file,
* 'ulp_main.h', name of this file is defined in component.mk as ULP_APP_NAME.
* These variables are located in RTC_SLOW_MEM and can be accessed both by the
* ULP and the main CPUs.
*
* Note that the ULP reads only the lower 16 bits of these variables.
*/
ulp_debounce_counter = 3;
ulp_debounce_max_count = 3;
ulp_next_edge = 1;
ulp_io_number = Trigger_RTCIO;
ulp_edge_count_to_wake_up = 2;
ulp_wakeup_counter = 0;
// initialize RTC IO, input, disable pullup and pulldown
gpio_num_t gpio_num = Trigger_GPIO;
rtc_gpio_set_direction(gpio_num, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_dis(gpio_num);
rtc_gpio_pullup_dis(gpio_num);
rtc_gpio_hold_en(gpio_num);
/* Set ULP wake up period to T = 20ms (3095 cycles of RTC_SLOW_CLK clock).
* Minimum pulse width has to be T * (ulp_debounce_counter + 1) = 80ms.
*/
// REG_SET_FIELD(SENS_ULP_CP_SLEEP_CYC0_REG, SENS_SLEEP_CYCLES_S0, 3095);
ulp_set_wakeup_period(0, 20000);
// start the program
ESP_ERROR_CHECK(ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t)));
}
pulse_cnt.S code as there were modifications:
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
/* Counter started when signal value changes.
Edge is "debounced" when the counter reaches zero. */
.global debounce_counter
debounce_counter:
.long 0
/* Value to which debounce_counter gets reset.
Set by the main program. */
.global debounce_max_count
debounce_max_count:
.long 0
/* Total number of signal edges acquired */
.global edge_count
edge_count:
.long 0
/* Number of edges to acquire before waking up the SoC.
Set by the main program. */
.global edge_count_to_wake_up
edge_count_to_wake_up:
.long 0
/* RTC IO number used to sample the input signal.
Set by main program. */
.global io_number
io_number:
.long 0
/* Number of times we've waken up the system-on-chip processor(s). */
.global wakeup_counter
wakeup_counter:
.long 0
/* Code goes into .text section */
.text
.global entry
entry:
/* Read the value of lower 16 RTC IOs into R0 */
READ_RTC_FIELD(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT)
/* Load io_number, extract the state of input */
move r3, io_number
ld r3, r3, 0
rsh r0, r0, r3
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 changed, eq
/* Not changed */
/* Reset debounce_counter to debounce_max_count */
move r3, debounce_max_count
move r2, debounce_counter
ld r3, r3, 0
st r3, r2, 0
/* End program */
halt
.global changed
changed:
/* Input state changed */
/* Has debounce_counter reached zero? */
move r3, debounce_counter
ld r2, r3, 0
add r2, r2, 0 /* dummy ADD to use "jump if ALU result is zero" */
jump edge_detected, eq
/* Not yet. Decrement debounce_counter */
sub r2, r2, 1
st r2, r3, 0
/* End program */
halt
.global edge_detected
edge_detected:
/* Reset debounce_counter to debounce_max_count */
move r3, debounce_max_count
move r2, debounce_counter
ld r3, r3, 0
st r3, r2, 0
/* 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
/* Compare edge_count to edge_count_to_wake_up */
move r3, edge_count_to_wake_up
ld r3, r3, 0
sub r3, r3, r2
jump wake_up, eq
/* Not yet. End program */
halt
.global wake_up
wake_up:
/* increment wake up counter */
move r3, wakeup_counter
ld r2, r3, 0
add r2, r2, 1
st r2, r3, 0
/* reset edge_count */
move r3, edge_count
move r2, 0
st r2, r3, 0
/* Wake up the SoC, end program */
wake
halt
Also
esp32ulp_mapgen.py really should be making variables "
extern volatile uint32_t".