Clear ULP Coprocessor interrupt?

Alynnn
Posts: 3
Joined: Fri Jun 30, 2017 5:57 am

Clear ULP Coprocessor interrupt?

Postby Alynnn » Sat Jul 08, 2017 3:13 am

I'm working on a project that needs to monitor a GPIO to see if it goes high then back to low and deep-sleep when no activity. So I have started experimenting with the ULP coprocessor using the pulse counter examples/system/ulp/ example and am getting the RTC interrupt when ulp executes`wake` instruction as documented. However there's no documentation nor code available that properly clears the ulp generated RTC interrupt.

Here's the code I'm using:

Code: Select all

static void IRAM_ATTR rtc_isr(void *arg)
{
	uint32_t rtc_intr = READ_PERI_REG(RTC_CNTL_INT_ST_REG);

	// clear interrupt
	WRITE_PERI_REG(RTC_CNTL_INT_CLR_REG, rtc_intr);

	// abort when not ULP interrupt
	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 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);
}
Basically my program calls ulp_InitInterrupt() and then calls init_ulp_program() function (copied from ulp_example_main.c) to get the ulp coprocessor going. I use make flash monitor in msys2 to build, upload, and monitor the program.

Once its running I cause the monitored GPIO to go high then low via a PIR motion sensor trigger pin attached to it with its pulse signal (held high before going low) is roughly 1600ms. The moment the GPIO level goes low rtc_isr() is called and the event is successfully passed to the app_main task via event group set bits procedure. However it occurs multiple times and rtcISRCounter gets a value of 7 when app_main task gets to ESP_LOGI() the event.

So it seems to be that the ulp coprocessor RTC interrupt isn't being cleared correctly as I have no other code to go on. Doesn't help that esp32_technical_reference_manual_en.pdf improperly documents registers as it says register at address 0x3ff48048 is RTCIO_RTC_GPIO_PIN8_REG however soc.h + rtc_cntl_reg.h defines RTC_CNTL_INT_CLR_REG at that address:

Code: Select all

// soc.h:
#define DR_REG_RTCCNTL_BASE                     0x3ff48000

// rtc_cntl_reg.h:
#define RTC_CNTL_INT_CLR_REG          (DR_REG_RTCCNTL_BASE + 0x48)
So I can't lookup the register documentation to figure out whats going on.
Anyway, how do I properly clear the ULP coprocessor interrupt?
Last edited by Alynnn on Sat Jul 08, 2017 1:17 pm, edited 1 time in total.

ESP_igrr
Posts: 2071
Joined: Tue Dec 01, 2015 8:37 am

Re: Clear ULP Coprocessor interrupt?

Postby ESP_igrr » Sat Jul 08, 2017 5:09 am

I was able to reproduce this with an ISR triggered twice (instead of just once). Will investigate next week.
In the meantime, the problem may be worked around by masking ULP interrupt in the ISR: CLEAR_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
Another way to work around this is to add at least a 1 RTC_FAST_CLK delay in the ISR before clearing the interrupt flag (ets_delay_us(1); should be sufficient).

I remember there was a similar issue in the past when we cleared an interrupt flag in the ISR, but the actual write took more time to take effect because RTC runs in a slower clock domain. Whether this is the case here, need to check.

ESP_igrr
Posts: 2071
Joined: Tue Dec 01, 2015 8:37 am

Re: Clear ULP Coprocessor interrupt?

Postby ESP_igrr » Mon Jul 10, 2017 4:08 am

We have investigated this, and this is a bug in hardware design. ULP wakeup signal comes from RTC_SLOW_CLK clock domain while the clear signal is in RTC_FAST_CLK clock domain. Therefore ULP interrupt signal remains high for a relatively long time. If you clear the interrupt status, it will immediately be set high again.

The recommended workaround for this is to disable ULP interrupt mask temporarily:

Code: Select all

   uint32_t int_st = READ_PERI_REG(RTC_CNTL_INT_ST_REG);
   CLEAR_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA);
   WRITE_PERI_REG(RTC_CNTL_INT_CLR_REG, int_st);
And then re-enable it from the task context at least 1 RTC_SLOW_CLK cycle later.

This issue affects the following RTC interrupt bits:
  • MAIN_TIMER_INT
  • TOUCH_INT
  • ULP_CP_INT
  • SLP_REJECT_INT
  • SLP_WAKEUP_INT
This issue does not affect the following interrupt bits:
  • BROWN_OUT_INT
  • TIME_VALID_INT
  • WDT_INT
  • SDIO_IDLE_INT
We will be updating the ECO document with this information. Thanks for reporting the issue and sorry for the inconvenience caused.

Alynnn
Posts: 3
Joined: Fri Jun 30, 2017 5:57 am

Re: Clear ULP Coprocessor interrupt?

Postby Alynnn » Tue Jul 11, 2017 7:30 pm

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".

User avatar
ESP_krzychb
Posts: 400
Joined: Sat Oct 01, 2016 9:05 am
Contact:

Re: Clear ULP Coprocessor interrupt?

Postby ESP_krzychb » Fri Jul 21, 2017 8:10 am

Doesn't help that esp32_technical_reference_manual_en.pdf improperly documents registers as it says register at address 0x3ff48048 is RTCIO_RTC_GPIO_PIN8_REG
This has been corrected on July 19, 2017 with V2.1 of ESP32 Technical Reference Manual

@Alynnn, thank you for pointing out this issue!

User avatar
WardMas
Posts: 75
Joined: Fri Jun 19, 2020 9:09 am

Re: Clear ULP Coprocessor interrupt?

Postby WardMas » Fri Dec 31, 2021 1:50 pm

Hi,
I have just came across this problem. Just like you guys mentioned clearing interrupt enable bit (RTC_CNTL_ULP_CP_INT_ENA_M) in RTC_CNTL_INT_ENA_REG register, made my interrupt subroutine be executed twice after every WAKE command execution. Here is a shady solution I applied to come over this problem. A flag is toggled every time the interrupt subroutine is executed and the action is only taken when the flag is enabled. I hope that this can help somebody.

Code: Select all

#define ENABLE				1
#define DISABLE				0
void IRAM_ATTR ulp_isr_handler(void *args)
{
	static uint8_t WorkAroundFlag = DISABLE;
	REG_CLR_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA_M);
	WorkAroundFlag ^= ENABLE;								//Toggle flag to take action one time every 2 interrupts.
	if(WorkAroundFlag)
	{
		ULP_IntFlag = ENABLE;								//here is were I notify the main CPU to take action
		REG_SET_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA_M);
	}
	else
	{
		REG_SET_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_ULP_CP_INT_ENA_M);
	}
}
You can always visit my YouTube channel for embedded systems related tutorials
https://youtube.com/user/wardzx1

Who is online

Users browsing this forum: No registered users and 75 guests