load prohibited on esp32 during ISR execution - interrupt

I have set up an interrupt that changes a boolean to true and in void loop(), I am constantly checking if the boolean is true like so:
TTGOClass *ttgo;
bool irq = false;
void setup()
{
Serial.begin(115200);
ttgo = TTGOClass::getWatch();
ttgo->begin();
ttgo->openBL();
ttgo->tft->fillScreen(TFT_BLACK);
ttgo->tft->drawString("T-Watch AXP202", 25, 50, 4);
ttgo->tft->setTextFont(4);
ttgo->tft->setTextColor(TFT_WHITE, TFT_BLACK);
pinMode(AXP202_INT, INPUT_PULLUP);
attachInterrupt(AXP202_INT, [] {
irq = true;
}, FALLING);
//!Clear IRQ unprocessed first
ttgo->power->enableIRQ(AXP202_PEK_SHORTPRESS_IRQ | AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_CHARGING_IRQ, true);
ttgo->power->clearIRQ();
}
void loop()
{
if (irq) {
irq = false;
ttgo->power->readIRQ();
if (ttgo->power->isVbusPlugInIRQ()) {
ttgo->tft->fillRect(20, 100, 200, 85, TFT_BLACK);
ttgo->tft->drawString("Power Plug In", 25, 100);
}
if (ttgo->power->isVbusRemoveIRQ()) {
ttgo->tft->fillRect(20, 100, 200, 85, TFT_BLACK);
ttgo->tft->drawString("Power Remove", 25, 100);
}
if (ttgo->power->isPEKShortPressIRQ()) {
ttgo->tft->fillRect(20, 100, 200, 85, TFT_BLACK);
ttgo->tft->drawString("PowerKey Press", 25, 100);
}
ttgo->power->clearIRQ();
}
delay(1000);
}
The t-watch 2020 (an esp32 smartwatch) runs on a battery and this weird method found in its example sketches (the code above is an example not the actual code because it is quite chaotic) wastes quite a lot of precious battery power. So I tried throwing the code from the if inside void loop() right into attachInterrupt(AXP202_INT, [] but... I needed to execute lengthy commands (I think that's the cause of the problem)such as Serial.print() and delay() (they are absolutely necesarry), the esp32 crashes returning the following error:
16:18:32.486 -> Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.
16:18:32.486 -> Core 0 register dump:
16:18:32.486 -> PC : 0x4009947b PS : 0x00060233 A0 : 0x8009894f A1 : 0x3ffbd150
16:18:32.486 -> A2 : 0x3ffba5a8 A3 : 0x3ffbd2dc A4 : 0x00000001 A5 : 0x00000001
16:18:32.486 -> A6 : 0x00060223 A7 : 0x00000000 A8 : 0x00000000 A9 : 0x3ffba5a8
16:18:32.486 -> A10 : 0x3ffba5a8 A11 : 0x00060023 A12 : 0x00060021 A13 : 0x3ffc0910
16:18:32.486 -> A14 : 0x00000003 A15 : 0x00060023 SAR : 0x00000000 EXCCAUSE: 0x0000001c
16:18:32.519 -> EXCVADDR: 0x00000004 LBEG : 0x40093948 LEND : 0x40093964 LCOUNT : 0x00000000
16:18:32.519 ->
16:18:32.519 -> ELF file SHA256: 0000000000000000
16:18:32.519 ->
16:18:32.519 -> Backtrace: 0x4009947b:0x3ffbd150 0x4009894c:0x3ffbd170 0x4009726f:0x3ffbd190 0x400972fd:0x3ffbd1b0 0x40098fbe:0x3ffbd1d0 0x4009909f:0x3ffbd210 0x40096b06:0x3ffbd240
16:18:32.519 ->
16:18:32.519 -> Rebooting...
the code is like this:
TTGOClass *ttgo;
bool axpIrq = false; //axpIrq for button press and power plug in/remove events.
bool lenergy = false;
bool BLaudio = false;
bool keepAwake = false;
//some initializations
ttgo = TTGOClass::getWatch();
ttgo->begin();
ttgo->lvgl_begin();
pinMode(AXP202_INT, INPUT_PULLUP);
attachInterrupt(AXP202_INT, [] {
axpIrq = true;
ttgo->power->readIRQ();
if (ttgo->power->isPEKShortPressIRQ()) {
//ttgo->power->clearIRQ();
Serial.println("button pressed");
low_energy();
}
ttgo->power->clearIRQ();
}, FALLING);
low energy is defined in another file:
void low_energy() {
//portENTER_CRITICAL(&synch);
if (!keepAwake) {
if (ttgo->bl->isOn()) {
//Serial.println("backlight on, turning off");
ttgo->closeBL();
ttgo->stopLvglTick();
ttgo->bma->enableStepCountInterrupt(false);
ttgo->displaySleep();
//lenergy = true;
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_HIGH_LEVEL);
esp_sleep_enable_gpio_wakeup ();
if (!BLaudio) {
setCpuFrequencyMhz(20);
esp_light_sleep_start();
//Serial.println("BLaudio is off, light sleep starts");
} else {
//Serial.println("BLaudio is on, light sleep won't start");
}
//Serial.println("screen off");
} else {
//Serial.println("Waking up");
setCpuFrequencyMhz(160);
ttgo->startLvglTick();
ttgo->displayWakeup();
ttgo->rtc->syncToSystem();
lv_disp_trig_activity(NULL);
ttgo->openBL();
ttgo->bma->enableStepCountInterrupt();
displayTime(true);
}
}
//portEXIT_CRITICAL(&synch);
}
I've tried replacing bool axpIrq = false; with volatile bool axpIrq = false;, uncommenting portENTER_CRITICAL(&synch); and portEXIT_CRITICAL(&synch); and still no results. If I am right when I think that lengthy commands are the problem, how can I execute the commands while the CPU continues in void loop normally(make a callback executed on the second core? I don't know)? If I am not right, what's the actual problem and how can I solve it?

Two problems here.
First, interrupt handlers need to be defined using the IRAM_ATTR attribute in order to ensure that they're already loaded into instruction memory (IRAM). The ESP32 understandably doesn't like having to load code from flash to RAM in order to service an interrupt. You need to make sure it's already there. If you don't, you see exactly the error you're seeing. Instruction RAM is also an extremely limited resource; you don't want to occupy more of it than is absolutely necessary.
You're specifying a lambda expression as the interrupt handler. I'm not sure whether you can use IRAM_ATTR with a lambda. To be sure, break out the interrupt handler into a function that's properly defined using IRAM_ATTR:
void IRAM_ATTR handle_interrupt() {
axpIrq = true;
ttgo->power->readIRQ();
if (ttgo->power->isPEKShortPressIRQ()) {
//ttgo->power->clearIRQ();
Serial.println("button pressed");
low_energy();
}
ttgo->power->clearIRQ();
}
...
attachInterrupt(AXP202_INT, handle_interrupt, FALLING);
...
Second, you're doing WAY too much in your interrupt handler. I quoted your existing code above, but there's no way that's going to work. Unless you really know what you're doing, your handler shouldn't do much more than set a volatile boolean flag variable and return. In this case, you're calling this TTGO library, Serial, possibly Bluetooth it looks like?
You'd need to ensure that every single function you call, and every single function those functions call, etc, are defined using IRAM_ATTR. You're not going to want to do that because you'd need to modify lots of third party code, and it would take up a lot - possibly more than there is - of instruction RAM.
You also don't know that any of these functions you're calling from the interrupt handler are re-entrant. You don't know if they lock out interrupts or not. It's simply not safe to call them unless you do know this - their internal data structures may be in an inconsistent state when they're interrupted and re-entered. The best thing to assume with any high level libraries is that they're not intended to be called from interrupt handlers.
Finally, interrupts are intended to be handled quickly so that the normal flow of code can be resumed.
Your first chunk of code isn't a "weird method" - it's the normal way that interrupts are handled in Arduino code on the ESP32, and it's how you write interrupt driven code that does much more work than is safe to do from inside an interrupt service routine. This is the "right" way to do it in an ESP32 Arduino program, which is why the examples showed this kind of code.
Your real problem seems to be how to do this and not kill your battery. This is a much bigger set of issues; you'll need to learn about ESP32 sleep modes and ESP-IDF/FreeRTOS tasks, how to create one and wake it up from an interrupt handler. This all depends on the rest of the watch code as well and how it's designed to save power. If you try to go further with this the best thing to do is to come back and post questions about specific problems you encounter while trying to make that work.

Related

Raspberry Pi Pico locks up when I try to use interrupts

I'm trying to use encoders to track the movement of three wheels on a robot, but as soon as any of the motors move the robot "locks up", it stops responding to commands, stops printing to the serial monitor, and just keeps spinning its wheels until I turn it off. I cut out everything except just the code to track one encoder and tried turning the wheel by hand to sus out the problem, but it still locked up. And even more strangely, now it will start spinning one of the wheels even though I've removed any code that should have it do that, even by mistake.
I used the Arduino IDE to program the pico since I've got no familiarity with python, but I can't find any information or troubleshooting tips for using interrupts with the pico that don't assume you're using micropython.
Here's the simplified code I'm using to try to find the problem. All it's meant to do is keep track of how many steps the encoder has made and print that to the serial monitor every second. Ive tried removing the serial and having it light up LEDs instead but that didn't help.
int encA = 10;
int encB = 11;
int count = 0;
int timer = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(encA),readEncoder,RISING);
timer = millis();
}
void loop() {
// put your main code here, to run repeatedly:
if (timer - millis() > 5000) {
Serial.println(count);
timer = millis();
}
}
void readEncoder() {
int bVal = digitalRead(encB);
if (bVal == 0) {
count--;
}
else{
count++;
}
}
Does the mapping function digitalPinToInterrupt for the Pi Pico work?
Can you try just using the interrupt number that corresponds to the pi?
attachInterrupt(9,readEncoder,RISING); //Or the number 0-25 which maps to that pin
https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__irq.html
You have the wrong pin to encoder in your example (maybe you incorrectly copy and pasted)?
attachInterrupt(digitalPinToInterrupt(**encA**),readEncoder,RISING);
void readEncoder() {
int bVal = digitalRead(**encB**); ...}
There is similar code on GitHub that you could modify and try instead.
https://github.com/jumejume1/Arduino/blob/master/ROTARY_ENCODER/ROTARY_ENCODER.ino
It might help you find a solution.
Also,
https://www.arduino.cc/reference/en/libraries/rpi_pico_timerinterrupt/
The interrupt number corresponds to the pin (unless you have reassigned it or disabled it) so for pin 11 the code can be:
attachInterrupt(11, buttonPressed, RISING);
This works:
bool buttonPress = false;
unsigned long buttonTime = 0; // To prevent debounce
void setup() {
Serial.begin(9600);
pinMode(11, INPUT_PULLUP);
attachInterrupt(11, buttonPressed, RISING);
// can be CHANGE or LOW or RISING or FALLING or HIGH
}
void loop() {
if(buttonPress) {
Serial.println(F("Pressed"));
buttonPress= false;
} else {
Serial.println(F("Normal"));
}
delay(250);
}
void buttonPressed() {
//Set timer to work for your loop code time
if (millis() - buttonTime > 250) {
//button press ok
buttonPress= true;
}
buttonTime = millis();
}
See: https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__irq.html for disable, enable etc.

Using performance monitor unit to generate an interrupt on Xilinx ZynqZC706

I am currently trying to use the performance monitor to generate an interrupt when an overflow of Data Cache misses occurs. I have enabled the pmu and the IRQ for the performance monitor (PMINTENSET is 1 for the counter). I am able to see that the overflow flag is set when the overflow occurs but the interrupt is never triggered. I think I am missing something when setting up the interrupt. I am using Xilinx SDK 2018.2.
I have attached my code for setting up the interrupt:
XScuGic xInterruptController; /* Interrupt controller instance */
static void setup_interrupt(void)
{
uint32_t status;
XScuGic_Config *pxGICConfig;
pxGICConfig = XScuGic_LookupConfig( XPAR_SCUGIC_0_DEVICE_ID );
if (pxGICConfig==NULL)
{
xil_printf("\nERROR LOOKING UP CONFIGURATION");
for(;;);
}
status = XScuGic_CfgInitialize( &xInterruptController, pxGICConfig, pxGICConfig->CpuBaseAddress );
if (status != XST_SUCCESS)
{
xil_printf("\nERROR INITIALIZING CONFIGURATION");
for(;;);
}
status = XScuGic_SelfTest(&xInterruptController);
if (status != XST_SUCCESS)
{
xil_printf("\nERROR: SELF TEST FAILURE");
for(;;);
}
/*
* Initialize the exception table.
*/
Xil_ExceptionInit();
status = RegisterInterruptExceptions(&xInterruptController);
if (status != XST_SUCCESS) {
xil_printf("\nERROR: SetUP Interrupt System Failed");
for(;;);
}
status = XScuGic_Connect( &xInterruptController, XPS_PMU0_INT_ID, (Xil_ExceptionHandler) pmuIRQ_handler, ( void * ) &xInterruptController);
if (status!= XST_SUCCESS)
{
xil_printf("\nERROR CONNECTING INTERRUPT");
for(;;);
}
XScuGic_SetPriorityTriggerType(&xInterruptController, XPS_PMU0_INT_ID, 8, 0b10); // Priority 8 (second highest) and high level sensitivity
XScuGic_InterruptMaptoCpu(&xInterruptController, 0, XPS_PMU0_INT_ID);
// Enable the interrupt for the xTimer in the interrupt controller.
XScuGic_Enable( &xInterruptController, XPS_PMU0_INT_ID );
}
int RegisterInterruptExceptions(XScuGic *XScuGicInstancePtr)
{
/*
* Connect the interrupt controller interrupt handler to the hardware
* interrupt handling logic in the ARM processor.
*/
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler,XScuGicInstancePtr);
/*
* Enable interrupts in the ARM
*/
Xil_ExceptionEnable();
return XST_SUCCESS;
}
void pmuIRQ_handler( void *CallbackRef )
{
xil_printf("Interrupt occurred\n");
}
I am not sure if I need to use Vivado to map the PMU interrupt to the GIC? I couldn't find any examples on generating interrupts using the performance monitor. I am currently using the default ZC706 HW platform provided by Xilinx SDK and I am not sure if I need to generate a bitstream in Vivado the maps the PMU to the GIC? I thought that this was done by using XScuGic_InterruptMaptoCpu().
I tried with both XPS_PMU0_INT_ID and XPS_PMU1_INT_ID, but neither worked. I tried to follow this post on using shared peripheral interrupts since PMU is this type of interrupt: https://forums.xilinx.com/t5/Processor-System-Design-and-AXI/Using-Private-and-Shared-interrupts-on-Zynq/m-p/773673
Thanks for the help,
Javier
The last parameter is incorrect. It should be 0b01 For high-level sensitivity instead of 0b10, as shown below:
XScuGic_SetPriorityTriggerType(&xInterruptController, XPS_PMU0_INT_ID, 8, 0b01); // Priority 8 (second highest) and high level sensitivity

STM32F411: is clearing an external interrupt flag really necessary?

I've bought an STM32F411 nucleo board and now I'm trying to understand various bits and pieces of the HAL. Starting with external interrupts seemed to be a good idea, because the board has a push button which is connected to PC13. So I've set up a simple toggle-the-frequency blinky. The code below is a bit simplified:
#define LED_PIN GPIO_PIN_5
#define BTN_PIN GPIO_PIN_13
static uint32_t blink_period = 250;
int main(void)
{
HAL_Init();
SystemClock_Config();
__GPIOA_CLK_ENABLE();
GPIO_InitTypeDef pinConfig;
pinConfig.Pin = (LED_PIN);
pinConfig.Pull = GPIO_NOPULL;
pinConfig.Mode = GPIO_MODE_OUTPUT_PP;
pinConfig.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(GPIOA, &pinConfig);
__GPIOC_CLK_ENABLE();
pinConfig.Pin = (BTN_PIN);
pinConfig.Pull = GPIO_NOPULL;
pinConfig.Mode = GPIO_MODE_IT_FALLING;
pinConfig.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOC, &pinConfig);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x0F, 0x00);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
while (1)
{
HAL_GPIO_TogglePin(GPIOA, LED_PIN);
HAL_Delay(blink_period);
}
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(BTN_PIN);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == BTN_PIN)
{
if (blink_period == 500)
{
blink_period = 250;
}
else
{
blink_period = 500;
}
}
}
When I push the button, an interrupt is generated and the blinky frequency changes from 1 to 2 Hz (or vice-versa). This works as intended, but why? I forgot to clear the pending interrupt flag, so the ISR should be called over and over. The datasheet clearly states that
When the selected edge occurs on the external interrupt line, an interrupt request is generated. The pending bit corresponding to the interrupt line is also set. This request is
reset by writing a ‘1’ in the pending register.
Reading a bit further reveals that this is a bit different for events:
When the selected edge occurs on the event line, an event pulse is generated. The pending bit corresponding to the event line is not set.
However, I'm not setting the button pin mode to any of the GPIO_MODE_EVT_... modes so I'm not using the event mechanism (to be honest I don't yet know what that even is - I just think that I'm not using it. Any hints are welcome).
So somewhere I should have to call void HAL_NVIC_ClearPendingIRQ (IRQn_Type IRQn), shouldn't I? It seems that clearing the flag by software is not necessary, because the ISR is not called more than once per falling edge. I've added a breakpoint in HAL_GPIO_EXTI_Callback to verify this.
Edit
As mentioned in the comments, the flag clearing code is in ST's implementation of the GPIO interrupt handler:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
This handler needs to be called by the actual ISR (which is done in my code) and it clears the pending flag corresponding to the GPIO_Pin argument. So I have to write an ISR which sorts out which flags are set, and call HAL_GPIO_EXTI_IRQHandler for each, which in turn calls my HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin), again with the pin as an argument. For each external interrupt, the pin number would get checked some ~3 times (in the ISR, in the handler and in the callback)!
If that is the solution, I want my problem back.
You don't have to call HAL_NVIC_ClearPendingIRQ (IRQn_Type IRQn) because the pending bit in the NVIC will be cleared automatically upon entering HAL_GPIO_EXTI_IRQHandler.
The HAL_GPIO_EXTI_IRQHandler() implementation clears the pending bit in the peripheral, not in the NVIC. If it didn't clear the pending bit by calling __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin), then the handler would be called again and again. The point is that you must distinguish between the interrupt pending bit in the peripheral and the pending bit in the NVIC.

Safely detect, if function is called from an ISR?

I'm developing software for an ARM Cortex M3 (NXP LPC1769) microncontroller. At the moment I'm searching for a mechansim to detect if my function is called within an ISR. I asume that I have to check a register. Based on this information I would like to call difficult functions.
I already checked the reference manual, if there is a register containing the necessary information.
For example I tried to detect if I'm called from an ISR (I used SysTick-ISR) based on the "Interrupt Active Bit Register" (IABR) register. This register should be != 0 if an ISR is active. But the value was 0x00000000. This implies that no interrupt is active. Besides this test I checked the NVIC and SC register in the reference manual searching for a register containing the necessary flag but I didn't found one.
Does anybody know a suitable register / mechanism for my problem?
You need to test the VECTACTIVE field of the Interrupt Control State Register.
I use the following:
//! Test if in interrupt mode
inline bool isInterrupt()
{
return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0 ;
}
SCM and SCB_ICSR_VECTACTIVE_Msk are defined in the CMSIS (core_cm3.h), which I imagine would be included indirectly by your part specific header (lpc17xx.h or similar I guess). I am using C++, including stdbool.h in C will get you a bool type, or change to an int or typedef of your own.
It is then used thus for example:
void somefunction( char ch )
{
if( isInterrupt() )
{
// Do not block if ISR
send( ch, NO_WAIT ) ;
}
else
{
send( ch, TIMEOUT ) ;
}
}
If a solution is required that assumes no knowledge of the architecture consider the following:
volatile int interrupt_nest_count = 0 ;
#define ENTER_ISR() interrupt_nest_count++
#define EXIT_ISR() interrupt_nest_count--
#define IN_ISR() (interrupt_nest_count != 0)
void isrA()
{
ENTER_ISR() ;
somefunction( 'a' ) ;
EXIT_ISR() ;
}
void isrB()
{
ENTER_ISR() ;
somefunction( 'b' ) ;
EXIT_ISR() ;
}
void somefunction( char ch )
{
if( IN_ISR() )
{
// Do not block if ISR
send( ch, NO_WAIT ) ;
}
else
{
send( ch, TIMEOUT ) ;
}
}
However the question refers to safely detecting the interrupt context, and this relies on the enter/exit macros being added to all ISRs.
After some discussion and more searching I found the right register:
Interrupt Program Status Register: The IPSR contains the exception type number of
the current Interrupt Service Routine (ISR). See the register summary in Table 626 for
its attributes.
If a function isn't called from an isr the value of the register is IPSR == 0
The simplest method is to pass the context as a parameter to the function. It is also platform independent.
typedef enum _context {
normal_context = 0,
isr_context = 1
} context;
Call to the function from ISR:
func(param1, param2, isr_context);
Call to the function from normal code:
func(param1, param2, normal_context);
If the ISR code is not under your control and you are just passing a function pointer, then just use two different wrapper functions. One that passes isr_context and another that passes normal_context as a parameter to the function.
The best way is probably to make two different functions: one that is called from the ISR and another that is called from the rest of the program.
If that isn't an option, then you could determine the caller with pure standard C, no registers needed:
inline void my_func (const char* caller);
static void isr (void)
{
my_func(__func__);
}
inline void my_func (const char* caller)
{
if(strcmp(caller, "isr")==0)
{
// was called from isr
}
else
{
// called from elsewhere
}
}
If you give your ISRs smart names, the above code will be quick enough to run from an isr.

How to wake up a process blocked by pause()?

I need to block and wake a process using SIGUSR2 and SIGUSR1 respectively. Below here's my signal handler sub routine. How do I wake a process blocked by pause?
void sig_handler(int sig) {
static int i = 1;
if(sig == SIGUSR2) {
pause();
}
else if(sig == SIGUSR1) {
/* I don't what to write here */
}
}
Also, I read somewhere pause() is not a good programming practice, is there any other means to suspend a process for some time?
See this page
In general, doing a lot of works in signals is ... tricky. Some things are not async-signal-safe, and therefore it makes robust programming there a bit difficult. In your case, pause() waits for a signal to arrive, but since you are calling it from the signal handler, it is not going to work there (I think).
As to making the process sleep and resume on signals. Look at the page I linked above. The best way is to have the signal handlers simply set flags and have the main thread (i.e. in main() or in an event loop) react to these flags. As recommended by the page, use sigsuspend when SIGUSR2 is received to pause the process until SIGURS1 is received.
It's simple. Use the 'kill' system call-
void sig_handler(int sig) {
static int i = 1;
if(sig == SIGUSR2) {
pause();
}
else if(sig == SIGUSR1) {
kill(<pid of process to wake up>, sig);
// make sure that process with pid has registered for sig
}
}