Hello, in Xtensa LX6, there are two cores present. They are almost identical, so I will pick just one of them - Program CPU. This CPU processes an instruction stream. It is the main program running in the CPU. This stream can be stopped and CPU can be redirected to another stream (handler), by something that LX6 calls Exception. Exception can come from a variety of sources, hardware and software, and they are grouped into categories.
To make things complicated, Xtensa is a modifiable architecture. It is comprised of several options, and the way how exceptions (and thus interrupts) work, greatly varies by what architectural options are enabled and disabled when the chip is produced. I will describe how a core in ESP32-WROOM32 works.
All the options and how they are programmable are written here:
https://0x04.net/~mwk/doc/xtensa.pdfExceptions that can cause the CPU to stop executing the main code are listed below:
Instruction Exception category:
------------------------
Illegal instruction
System call
Instruction fetch error
Load or store error
Unaligned data exception
Privileged instruction
Memory access prohibited
Memory privilege violation
Address translation failure
PIF bus error
Window exception
Alloca exception
Coprocessor disabled
External Interrupts category:
------------------------
Level-1 interrupt
Level-1 SW interrupt
Medium-Level interrupt
Medium-Level SW interrupt
High-Level interrupt
High-level SW interrupt
Non-maskable interrupt
Peripheral interrupt
RESET Exception category:
------------------------
RESETMachine-Check exception category:
------------------------
ECC/parity errorDebug exception category:
------------------------
ICOUNT exception
BREAK exception
Instruction breakpoint
Data breakpoint
Debug interrupt
What you would probably care about, is
External Interrupts category, but not quite. On the chip, there may be multiple timers present, and most of them may have the capability to deliver a signal to the CPU to generate an exception, and run your routine code.
Xtensa has internal timers and comparators as well. They can be modified using aprropriate registers - CCOUNT and CCOMPARE, using rsr, wsr, or xsr instructions. Upon match, they trigger Peripheral interrupt.
Then, there are external timers (external relative to the core). These timers are documented in technical specification
https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdfThese timers can be attached to one of 26 available interrupt signal lines that are wired to the CPU. CPU has support for 32 of these interrupt signals, but 6 of them are not connected, an you can connect your peripheral to one of these lines, for them to float an be safe.
How to actually do it?
First, find some nice signal from the list here:
https://github.com/espressif/xtensa-overlays/blob/master/xtensa_esp32/newlib/newlib/libc/sys/xtensa/include/xtensa/config/core-isa.h#L477-L502and then write that number to the lower portion (5 bits) of one of these peripheral interrupt sources:
PRO_TG_T0_LEVEL_INT_MAP - An alarm event on timer 0 generates this interrupt.
PRO_TG_T1_LEVEL_INT_MAP - An alarm event on timer 1 generates this interrupt
PRO_TG_WDT_LEVEL_INT_MAP - Int. Generated when a watchdog timer interrupt stage times out.
That way, when your peripheral device generates an interrupt signal, this signal gets delivered to the CPU and it can not be processed by the exception controller. Why it cannot be processed by exception controller?
Each of these signals instead of NMI (non-maskable interrupt signal) are masked with a bit value in INTENABLE register. So, you need to enable particular bit in this register corresponding to the CPU signal number that you picked (not the number in the c code definitions. From 0 to 5 they are the same, and then they differ (see that core-isa.h file for reference)).
Enabling bit in this register can be done in at least two ways:
1st one - write to the register directly:
2nd one - Use function in the ROM region:
Code: Select allstatic void (*ets_isr_unmask)(uint32_t mask) = (void*) 0x40006808;
ets_isr_unmask(1u << XCHAL_EXTINT6_NUM); //Enables CPU signal 8.
Mind the fact, that those 26 available signals have each their priority and trigger mode (edge trigger, or level trigger). Which interrupt signal number gets wchich priority and trigger mode is determined when chip is produced, and the mapping can be shown here (for ESP32, as the whole article concerns this chip board):
https://github.com/espressif/xtensa-overlays/blob/master/xtensa_esp32/newlib/newlib/libc/sys/xtensa/include/xtensa/config/core-isa.h#L373-L404Even those unconnected signals have interrupt and trigger level specified. Each must have.
There is another masking step, but it may not be important for you, so I will just mention it:
As each of these external signals has its priority, when INTLEVEL bits portion of the PS register is 2 for example, all priorities 2 and below will not interrupt the CPU and will be masked. Privileged code (your firmware surely is) can change this register at any time.
Now, you want to unmask, or enable your peripheral device to actually trigger the signal line. To do this, you just write 1 to the appropriate field in TIMGn_Tx_INT_ENA_REG register (see tech. reference for more details).
Now, you just configure your timer (should do it before setting the interrupts) and in the Xtensa register INTERRUPT, under particular position determined by the used signal number, you can read the status of your interrupt in the CPU perspective. To read exectly what occured in the peripheral - to read peripheral inerrupt status bits, you need to read registers such as:
Code: Select allTIMGn_Tx_INT_RAW_REG
TIMGn_Tx_INT_ST_REG
But there is one very important thing missing. How to call an interrupt handler? Well, to put it simply, you can use the ROM function:
Code: Select all/**
* @param irq_number The CPU interrupt number
* @param function Interrupt handler
**/
static void (*_xtos_set_interrupt_handler)(int irq_number, void* function) = (void*) 0x4000bf78;
_xtos_set_interrupt_handler(XCHAL_EXTINT6_NUM, &my_handler); //Register an interrupt in the appropriate table
OR use this, if you want to pass one 32 bit information to the handler:
Code: Select all/**
* @param irq_number The CPU interrupt number
* @param function Interrupt handler
* @param argument The argument passed to the handler
**/
static void (*_xtos_set_interrupt_handler_arg)(int irq_number, void* function, int argument) = (void*) 0x4000bf34;
That way, whenever your timer reaches the specified thing,
it writes its interrupt status bits and triggers interrupt signal,
since peripheral's Interrupt Enable bit for that interrupt type is enabled, the signal gets routed into a matrix
The matrix connects that signal to a concrete program CPU signal
Signal, when triggered, is compared to an INTENABLE bit mask
Then it passes to the interrupt conroller and triggers 'Level-1 interrupt' or 'High-Level interrupt' based on the priority
Interrupt controller then finds a common offset for that kind of trigger and makes the CPU to jump to address: VECBASE + <handler_offset>, which resides in the ROM region (unless VECBASE points somewhere else)
The CPU switches instruction stream to that address.
Since that handler handles many interrupt sources, it contains a table of concrete interrupt routines.
It does some tweaks (like stack configuration) and some checks and then jumps to your specified handler address.
This article may contain some mistakes, as I don't have much time as I'm writing it, but the general concept is right. You may need to find concrete references and header files, register names, ROM function offsets and so on, for the implementation of the chip that you use.