RA4M1 Interrupts

I want to use the CAC unit on this thread and my first issue is that there's not any FSP support for that function in our core. There's also not a good way to add the interrupt handler I need without modifying core files. So I started a new thread to doc this part and I'll link it back. This issue is going to repeat itself and I want to have a good set of notes for next time.

Interrupts on the RA4M1

The interrupt controller works differently on the RA4M1 chip on the UNO-R4 than it does on the old AVR micro on the UNO-R3. On the old chip the interrupt vector table was set in memory and there was an entry for each possible interrupt source. If you wanted to service an interrupt then you would set the pointer in the appropriate slot in the vector table to your function and then enable the interrupt in whatever register turned it on.

With the new chip, there are non-maskable interrupts that work the same way as the old chips. These cover things like watchdog timer interrupts and voltage monitors. But there are also a number of ICU Event Link slots that can be used as interrupts where you can select the source. This allows us to have a lot more possible interrupt sources than there are slots in the vector table. We just use the ones we want.

On our RA4M1 we get 32 of these slots controlled by the IELSR[0-31] registers. Each register has a 8 bit selection for the event link. This chooses the interrupt source for that interrupt. For example if you set it to 0x5D then this interrupt will service the overflow interrupt for GPT0.

A list of interrupt sources can be found in Table 13.4

Some of these interrupts can do some neat extra things. For instance, some of them can also trigger the DTC or DMA to automatically transfer data without the CPU. This might be handy for a display driver where you have a timer interrupt that is refreshing a display buffer or something. Using the DMA or DTC that could be done without code.

Interrupt Code

The problem that we have here is that everything is controlled through the IRQManager class in IRQManager.h. It contains code that is specific to all sorts of interrupts, but it does not cover them all. For instance, I want to use the CAC interrupt and it is not available here.

I could add code elsewhere to add the even link I want, but I am afraid to mess with this table without letting this IRQManager know. It has an index called last_interrupt_index so it knows where to put the next IRQ. If I start writing to the table and then some other code adds a new IRQ, then it would overwrite the one I just added.

If we look at all the different entries in addPeripheral or any of the addTimer... interrupt functions they all seem to follow a standard pattern. I think the thing to do will be to start here and add a function that allows us to add a general ISR as long as we have the mask.

This is the function I'm adding

//  allows to put any maskable interrupt into the table and keep last_interrupt_index up to date. 
bool IRQManager::addMaskableInterrupt(uint8_t eventMask, Irq_f fnc /*= nullptr*/) {
    /* getting the address of the current location of the irq vector table */
    volatile uint32_t *irq_ptr = (volatile uint32_t *)SCB->VTOR;
    /* set the displacement to the "programmable" part of the table */
    irq_ptr += FIXED_IRQ_NUM;
    bool rv = true;
    
    __disable_irq();
    
    *(irq_ptr + last_interrupt_index) = (uint32_t)fnc;
    R_ICU->IELSR[last_interrupt_index] = eventMask;
    R_BSP_IrqDisable((IRQn_Type)last_interrupt_index);
    R_BSP_IrqStatusClear((IRQn_Type)last_interrupt_index);
    NVIC_SetPriority((IRQn_Type)last_interrupt_index, 12);
    R_BSP_IrqEnable ((IRQn_Type)last_interrupt_index);
    
    last_interrupt_index++;
    
    __enable_irq();
    
    return rv;

}

It compiles. I'll be back to this soon to see if it works.

1 Like

You might also be interested in @susan-parker 's similar investigations...

https://forum.arduino.cc/t/uno-r4-schematic/1160263/21

https://github.com/TriodeGirl/Arduino__UNO_R4_CTC_Clock_Frequency_Accuracy_Measurement_Circuit

https://forum.arduino.cc/t/uno-r4-attachinterrupt/1148214/25

2 Likes

I made a bit of headway on the RTC thread linked earlier. It seems that the code posted above works. But I learned a bit about the interrupts here. Link to RTC thread where this function was used...

The event link structure creates a little more to think about than just attaching a standard interrupt. Firstly, the code needs to be able to reset the IR flag for the event link and that requires knowing which event index you are using. I used this function to list the event links currently set:

void listEventLinks(){
  for (uint8_t i=0; i<32; i++){
    volatile uint32_t val = R_ICU->IELSR[i];
    Serial.print("IELSR");
    Serial.print(i);
    Serial.print(" : ");
    Serial.println(val, HEX);
  }
}

You can use table 13.4 in the User's Manual to match up the event numbers. In my case the output was:

IELSR0 : 1E
IELSR1 : A9
IELSR2 : AA
IELSR3 : A8
IELSR4 : AB
IELSR5 : 0
IELSR6 : 0
IELSR7 : 0
IELSR8 : 0
IELSR9 : 0
IELSR10 : 0
IELSR11 : 0
IELSR12 : 0
IELSR13 : 0
IELSR14 : 0
IELSR15 : 0
IELSR16 : 0
IELSR17 : 0
IELSR18 : 0
IELSR19 : 0
IELSR20 : 0
IELSR21 : 0
IELSR22 : 0
IELSR23 : 0
IELSR24 : 0
IELSR25 : 0
IELSR26 : 0
IELSR27 : 0
IELSR28 : 0
IELSR29 : 0
IELSR30 : 0
IELSR31 : 0

which shows that only the first five slots are being used.

I checked out what interrupts these are. 1E is AGT0 interrupt. I believe that's the old Timer0 interrupt driving micros and millis.

The other four are all related to the serial port.

The other thing to think about is that this now gives you two places to go and reset an interrupt flag in your handler. Not only do you need to deal with whatever peripheral triggered the interrupt and clear a flag there, but you also need to clear the IR flag in the event link register for the link you are using.

After looking at @susan-parker 's solution I was inspired to do something similar. On the previously linked RTC thread, I used this code to allow me to hijack the rtc carry interrupt in a similar way to what she had done with the pin change interrupt. Thus I can attach event link interrupts without needing to change any core code. The only issue is that it has to include the RTC code to work, even if you're using it for something else.

I figure I'll probably want to do that again so I pulled the code out into a couple of library files.
Link to github
EventLinkInterrupt.cpp (2.6 KB)
EventLinkInterrupt.h (1.8 KB)
.

The major difference is that I can set the rtc configuration back like it was and no other code has to be any the wiser. I don't have to give up any other ISR. The only caveat is that you can't do this if you already have the carry isr attached. Although now that I think of it that's just a little bit of code to fix.

To call it you need the 8 bit event code and a void function that takes no arguments for a handler. Pass the two to
int eventLinkIndex = attachEventLinkInterrupt(eventCode, func);
and it will return an int with the index in the event controller for the interrupt you just attached.

When you create your ISR, call resetEventLink(eventLinkIndex) with that same index and that will handle the interrupt flags in the event link register. You still need to clear whatever interrupt flags you have for the event that you linked.

An example is in the RTC - CAC test code that I've been playing with. Setting up the CAC handler is:

eventLinkIndex = attachEventLinkInterrupt(0x47, cacFERRIHandler);

And then my ISR has a line to clear the FERRI interrupt and the call to resetEventLink with my eventLinkIndex.

void cacFERRIHandler() {
  // record CAC counter register
  uint16_t result = R_CAC->CACNTBR;
  // clear interrupt flags
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  resetEventLink(eventLinkIndex);

I haven't tested this with multiple channels yet, but it should theoretically work to set up more than one interrupt on consecutive channels in the ELC. That's going to be more testing later.

1 Like

Did you figure out a way to map more than 32 simultaneous peripheral interrupts to the NVIC at the same time? This is a really strange thing that Renesas has done, given that the CM4 architecture allows for 496 external interrupts. I was hoping that there was at least a way to say "map all of the USART interrupts to this NVIC vector", but I didn't see anything in my relatively brief reading of the datasheet.

(I also find the IRQManager to be unnecessarily complex. I would have thought that a constant mapping between the "core" interrupts and the NVIC would have been sufficient, with a simpler scheme to add additional interrupts from user requests.)

(questionable code on top of questionable hardware. And both without explanations as to "WHY??" Sigh. I applaud your willingness to delve into it!)

No, what I mean was that I could re-use the code to attach more interrupts. I still only have the 32 channels in the ELC.

I was more pointing out the difference between this hack and hacking an external pin interrupt. That code can only attach one interrupt and then you have to find another interrupt to replace. This keeps placing the same interrupt in the table and then replacing it each time. So it should be re-usable to fill the whole table if you want to. I mean to test it soon.

What really needs to happen is for a function like the one I wrote above to show up in IRQManager so we don't have to hack anything to add an interrupt.

I have two things to add. Firstly, I've been calling it the ELC. But it's the ILC. Both take event link codes, but the ELC just sends links to other modules and the ILC fires interrupts to the NVIC.

Secondly, I never came back to post the results of the test to see if I could attach more than one interrupt with this code. Here it is. I set the CAC so all three of its possible interrupts would fire and attached all three to the ILC using those event link codes.

Nothing here required any modification of core files, just the library files in post #4 on this thread.


#include "EventLinkInterrupt.h"

int mendiEventLinkIndex;
volatile bool mendiFired = false;
int ferriEventLinkIndex;
volatile bool ferriFired = false;
int ovfiEventLinkIndex;
volatile bool ovfiFired = false;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("Starting ILC Experiment!");
  
  // list the event links before and after 
  // so we can see our interrupts are attached
  listEventLinks();

  setupCAC();

  listEventLinks();

  Serial.println("End Setup!");
}

void loop() {
  if (mendiFired) {
    Serial.println("MENDI Fired.");
    mendiFired = false;
  }
  if (ferriFired) {
    Serial.println("FERRI Fired.");
    ferriFired = false;
  }
  if (ovfiFired) {
    Serial.println("OVFI Fired.");
    ovfiFired = false;
  }
}

void cacMendiHandler() {
  // clear interrupt flag in peripheral hardware
  R_CAC->CAICR |= (R_CAC_CAICR_MENDFCL_Msk);
  // clear event link interrupt flag
  resetEventLink(mendiEventLinkIndex);
  mendiFired = true;
}

void cacFerriHandler() {
  // clear interrupt flag in peripheral hardware
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  // clear event link interrupt flag
  resetEventLink(ferriEventLinkIndex);
  ferriFired = true;
}

void cacOvfiHandler() {
  // clear interrupt flag in peripheral hardware
  R_CAC->CAICR |= (R_CAC_CAICR_OVFFCL_Msk);
  // clear event link interrupt flag
  resetEventLink(ovfiEventLinkIndex);
  ovfiFired = true;
}


void setupCAC() {

  // Enable the CAC in the Module Stop Control Register
  R_MSTP->MSTPCRC &= ~((uint32_t)R_MSTP_MSTPCRC_MSTPC0_Msk);
  /*
I'm caculating 48MHz on MOSC / 32.767kHz on LOCO should
give a ratio of around 1:1465 when they both have the same pre-scaler
with ref clock on LOCO set to 1/8192 and the measure clock on HOCO set to 1/32
we should have 1:375000 and that will give us plenty of overflows.  
it will also take the LOCO clock about 250ms to generate MENDI and FERRI. 
*/
  //  Set limits loose so we get a MENDI interrupt.
  R_CAC->CALLVR = 1464;
  R_CAC->CAULVR = 1466;

  // Set source clock
  //  (EDGES[1:0]=00 Rising Edge) (TCSS[1:0]=11 1/32 divider) (FMCS[2:0]=010 HOCO) (CACREFE = 0 Disable CACREF pin) 
  R_CAC->CACR1 = 0x34;

  // set reference clock                          = 00 for 1/32 divider
  // (DFS[1:0] = 00 No Digital Filter) (RCDS[1:0] = 11 1/8192 divider) (RSCS[2:0] = 100 LOCO) (RPS = 1 Internal Clock Reference)
  R_CAC->CACR2 = 0x39;

  //  Enable interrupts in the peripheral hardware and clear flags there.
  R_CAC->CAICR |= (R_CAC_CAICR_MENDIE_Msk | R_CAC_CAICR_MENDFCL_Msk);
  R_CAC->CAICR |= (R_CAC_CAICR_FERRIE_Msk | R_CAC_CAICR_FERRFCL_Msk);
  R_CAC->CAICR |= (R_CAC_CAICR_OVFIE_Msk | R_CAC_CAICR_OVFFCL_Msk);

  // set interrupts
  //  See table 13.4 in the User's Manual for Event Link Codes (Event Number)
  // For the CAC interrupts it is 
  //0x47 for CAC_FERRI 
  //0x48 for CAC_MENDI
  //0x49 CAC_OVFI
  mendiEventLinkIndex = attachEventLinkInterrupt(0x48, cacMendiHandler);
  ferriEventLinkIndex = attachEventLinkInterrupt(0x47, cacFerriHandler);
  ovfiEventLinkIndex = attachEventLinkInterrupt(0x49, cacOvfiHandler);

  
  // Set CFME in CACR0 to turn on the CAC unit
  R_CAC->CACR0 = (R_CAC_CACR0_CFME_Msk);
}


void listEventLinks(){
  for (uint8_t i=0; i<32; i++){
    volatile uint32_t val = R_ICU->IELSR[i];
    Serial.print("IELSR");
    Serial.print(i);
    Serial.print(" : ");
    Serial.println(val, HEX);
  }
}

Output:

13:51:17.452 -> Starting ILC Experiment!
13:51:17.452 -> IELSR0 : 33
13:51:17.452 -> IELSR1 : 34
13:51:17.452 -> IELSR2 : 31
13:51:17.452 -> IELSR3 : 32
13:51:17.452 -> IELSR4 : 1E
13:51:17.452 -> IELSR5 : 0
13:51:17.452 -> IELSR6 : 0
13:51:17.452 -> IELSR7 : 0
13:51:17.452 -> IELSR8 : 0
13:51:17.452 -> IELSR9 : 0
13:51:17.452 -> IELSR10 : 0
13:51:17.452 -> IELSR11 : 0
13:51:17.452 -> IELSR12 : 0
13:51:17.452 -> IELSR13 : 0
13:51:17.452 -> IELSR14 : 0
13:51:17.452 -> IELSR15 : 0
13:51:17.452 -> IELSR16 : 0
13:51:17.452 -> IELSR17 : 0
13:51:17.452 -> IELSR18 : 0
13:51:17.452 -> IELSR19 : 0
13:51:17.452 -> IELSR20 : 0
13:51:17.452 -> IELSR21 : 0
13:51:17.452 -> IELSR22 : 0
13:51:17.452 -> IELSR23 : 0
13:51:17.452 -> IELSR24 : 0
13:51:17.452 -> IELSR25 : 0
13:51:17.452 -> IELSR26 : 0
13:51:17.452 -> IELSR27 : 0
13:51:17.452 -> IELSR28 : 0
13:51:17.452 -> IELSR29 : 0
13:51:17.452 -> IELSR30 : 0
13:51:17.452 -> IELSR31 : 0
13:51:17.452 -> IELSR0 : 33
13:51:17.452 -> IELSR1 : 34
13:51:17.452 -> IELSR2 : 31
13:51:17.452 -> IELSR3 : 32
13:51:17.452 -> IELSR4 : 1E
13:51:17.452 -> IELSR5 : 48
13:51:17.452 -> IELSR6 : 47
13:51:17.452 -> IELSR7 : 49
13:51:17.452 -> IELSR8 : 0
13:51:17.452 -> IELSR9 : 0
13:51:17.452 -> IELSR10 : 0
13:51:17.452 -> IELSR11 : 0
13:51:17.452 -> IELSR12 : 0
13:51:17.452 -> IELSR13 : 0
13:51:17.452 -> IELSR14 : 0
13:51:17.452 -> IELSR15 : 0
13:51:17.452 -> IELSR16 : 0
13:51:17.452 -> IELSR17 : 0
13:51:17.452 -> IELSR18 : 0
13:51:17.452 -> IELSR19 : 0
13:51:17.452 -> IELSR20 : 0
13:51:17.452 -> IELSR21 : 0
13:51:17.452 -> IELSR22 : 0
13:51:17.452 -> IELSR23 : 0
13:51:17.452 -> IELSR24 : 0
13:51:17.452 -> IELSR25 : 0
13:51:17.452 -> IELSR26 : 0
13:51:17.452 -> IELSR27 : 0
13:51:17.452 -> IELSR28 : 0
13:51:17.452 -> IELSR29 : 0
13:51:17.452 -> IELSR30 : 0
13:51:17.452 -> IELSR31 : 0
13:51:17.452 -> End Setup!
13:51:17.612 -> OVFI Fired.
13:51:17.643 -> OVFI Fired.
13:51:17.707 -> OVFI Fired.
13:51:17.740 -> OVFI Fired.
13:51:17.803 -> OVFI Fired.
13:51:17.835 -> MENDI Fired.
13:51:17.835 -> FERRI Fired.
13:51:17.867 -> OVFI Fired.
13:51:17.900 -> OVFI Fired.
13:51:17.963 -> OVFI Fired.
13:51:17.996 -> OVFI Fired.
13:51:18.028 -> OVFI Fired.
13:51:18.060 -> MENDI Fired.
13:51:18.060 -> FERRI Fired.
13:51:18.124 -> OVFI Fired.
13:51:18.156 -> OVFI Fired.
13:51:18.189 -> OVFI Fired.
13:51:18.252 -> OVFI Fired.
13:51:18.284 -> OVFI Fired.
13:51:18.316 -> MENDI Fired.
13:51:18.316 -> FERRI Fired.
13:51:18.349 -> OVFI Fired.
13:51:18.412 -> OVFI Fired.
13:51:18.444 -> OVFI Fired.
13:51:18.477 -> OVFI Fired.
13:51:18.540 -> OVFI Fired.
13:51:18.572 -> MENDI Fired.
13:51:18.572 -> FERRI Fired.

I've added some additional code over at the github repo. There is now a detach method to turn the interrupt off and a reattach method to reuse the same slot in the ILC. reattachEventLinkInterrupt takes an event code, so it is totally possible to attach the interrupt to a different event.

I've also added some examples that show how to use the various functions. The only difference between this and interrupt code that you might be used to is the need to keep track of the index for your interrupt in the ILC.

Commit 863acde628bc9375520f6215171ed3a566062270

1 Like

EventLinkInterrupt.h is now available through the library manager.

Please let me know if you find any issues.

2 Likes

Just stumbled across this, and now I'm a bit confused.

I was under the impression that the whole point of the event link controller is to avoid generating CPU interrupts, and instead connecting actions on peripherals directly with interrupts from other peripherals, without having the CPU run any software code.

See The Event Link Controller – Automatically Manage Low Level System Tasks on RA MCUs | Renesas

As such I assumed that using IELSR to generate user callbacks is sort of missing the point, but... A glance in the RA4M1 user manual shows that actually the interrupt vector table almost entirely consists of IELSR entries, like there aren't even any "classic" interrupts in the table for any peripheral. I would have expected to find at least one entry for each peripheral in there.

That means, as far as I understand, going through the event link stuff is indeed as you say the only way to attach an ISR to a peripheral... Which means that you can actually only have up to 32 peripheral events with an ISR at any given time

Something tells me I'm missing something here. I guess 32 is "enough". But it doesn't feel "comfortably enough" - keeping in mind that many peripherals have multiple events that need to be handled...

That is the purpose of the ELC. There is also the Interrupt Link Controller which uses those same event links to fire interrupts. There are a lot more events that can fire the ILC than the ELC.

There is code here for both.

OK. I don't think Interrupt Link Controller is an official term though.
The only 2 results I get for that on google are both authored by you, one is your github page, and the other is some thread in this forum.
According to the RA4M1 manual, the thing that fires interrupts to the NVIC is the same thing that would also enable the DTC (if applicable and if supported for the respective event).
It's just that not all events can be linked to DTC or DMA (Table 13.4 in the manual).

Where did you find the info that those are 2 separate entities?

Interrupt Controller Unit. Section 13 of the users manual.

I keep getting the name confused with something else. Sorry about that.

Either way, it uses the same signals that the ELC uses.

If you look at table 13.4 it shows what each event code can be used to trigger. Most all of them can fire an interrupt. Most can also trigger DTC or DMA.

The Event Link Controller is section 18. It doesn't fire interrupts. There are many peripherals that can be attached there for many different functions. The one I've used most recently was to use the AGT0 underflow interrupt that drives the millis counter to also trigger the CTSU to start a reading.

The new renesas-uno platform 1.2.0 has a new function for attaching generic interrupts that aren't covered elsewhere in the IRQManager.h

Here's a test sketch that attaches IRQ0 using the new function. This could be done with attachInterrupt instead, but pin interrupts are easy to set up so I used this for illustration. The function will be handy for things like the CAC and CTSU that aren't implemented in the Arduino core and aren't covered in IRQManger.h

#include "IRQManager.h"

volatile bool interruptFired = false;

GenericIrqCfg_t cfg;

void setup() {

  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("\n\n *** " __FILE__ " ***\n\n");

  R_ICU->IRQCR[0] = 0xB0;  // Configure some peripheral for an interrupt

  // Create a generic interrupt configuration and attach
  cfg.irq = FSP_INVALID_VECTOR;
  cfg.ipl = 12;
  cfg.event = ELC_EVENT_ICU_IRQ0;  
  IRQManager::getInstance().addGenericInterrupt(cfg, Myirq0_callback);

  // Enable the interrupt at the peripheral. 
  R_PFS->PORT[1].PIN[5].PmnPFS = (1 << R_PFS_PORT_PIN_PmnPFS_PCR_Pos) | (1 << R_PFS_PORT_PIN_PmnPFS_ISEL_Pos);

}

void loop() {
  Serial.println("Loop Running");
  delay(500);

  if (interruptFired) {
    interruptFired = false;
    Serial.println("Interrupt Fired");
  }
}

void Myirq0_callback() {
  R_ICU->IELSR[cfg.irq] &= ~(R_ICU_IELSR_IR_Msk);
  interruptFired = true;
}

The configuration struct has three members. The irq needs to be set to FSP_INVALID_VECTOR or your interrupt won't be attached. The function from IRQManager will give you back the irq number here once it is attached.

The ipl is the interrupt priority. If you're not sure then use 12.

The event is the ELC event code from section 13.4 in the hardware manual. That part is covered above in this thread. A list of the defines can be found in the variants folder in elc_defines.h

1 Like

Since this makes EventLinkInterrupt redundant, I'm going to consider that library deprecated. It will no longer be maintained going forward.

Any use should switch to this function.