Can we fix the RTC?

Based on this post by @JohnLincoln I decided to have a look at what's going on with the RTC on the R4.

The problem seems to be that the RTC is using LOCO for the clock source and this is not very accurate or precise. When running on SOSC there is an adjustment that can be made using the RADJ register, but this is not available when LOCO is the clock source.

My first thought was to simply change the clock source. The post by @JohnLincoln showed that millis was pretty accurate, so MOSC must be a better clock source. Unfortunately I found that the only available clocks for the RTC are SOSC and LOCO.

So the next question is, can we make LOCO better? There is a trimming register for LOCO called LOCOUTCR (section 8.2.20) that takes a signed 8 bit value but I can't find a lot of information on how to calculate it.

I found a couple of different application notes from Renesas on different products about trimming clocks and it gave me an idea.

I think we can use the CAC module to determine the frequency deviation using MOSC as the standard and measuring the frequency of LOCO. See section 9 of the Users Manual and section 5.2.10.2 and 5.3.20 of the FSP documents.

The basic idea is to use the CAC to measure the count on LOCO. Whenever it finished a check period, an interrupt can fire. In that ISR, if LOCO is slow then we add one to the trimming register and if LOCO is fast we subtract one. At least we start there, maybe make the adjustments more proportional after we see some numbers. After the adjustment, we start another comparison.

The only real issue I see in the way right now is that there is no provision in IRQManager.h for the CAC interrupts. I don't see how that could be hard to add.

I don't know if we have the CAC code in the core that we have either. That will be the next thing to figure out. If it's not there then this project may get a bit more complicated as we may need to recompile some core resources.

Right off the bat I see that we don't get any FSP support for CAC in our core.

Directly to the registers it must be. This will take a little bit of study on how to get the interrupt vector into the vector table without breaking anything else.

Onward...

It seems as if the Uno R4 wants to outdo the IDE beta.x in terms of unreliability and teething problems.

4 Likes

First let's study the CAC setup. I'm working in section 9 of the User's Manual.

Actually, we need to start in 10.2.4. We have to turn all this stuff on in the MSTPCRC before we can use any of the registers. It will be bit MSTPC0.

We need to define two clocks. A reference clock and a measurement clock. At each tick of the reference clock we it will reset a counter that will count how many ticks the measurement clock makes in that period. So the measurement clock needs to be the faster clock, at least after any prescaling.

Here's a picture of what happens:

From what I can tell, HOCO is our main clock at 48MHz. LOCO is the RTC clock at 32.768 kHz. So for each tick of LOCO there should be (48e6 / 32768) 1464.84 ticks of HOCO. The counters are all 16 bit so we don't need a prescaler. Although, we will be forced to use one because of CACR2.

CACR1 sets our measurement target clock.

Let's set FMCS[2:0] to all zeros to use MOSC. If we keep to rising edges and no divisors then the whole register is 0. If we want to use both clock edges then we set the high bit to 1.

CACR2 sets up the reference clock:

We want RPS to be 1 for an internal clock.
RSCS[2:0] will be 1 0 0 for the LOCO clock.

Looks like we have to take a divider here. Let's use the 1/32 so that's 0's.
and 0's for bits 6 and 7 for no digital filter.

That means we should set bits 4 and 5 in CACR1 to use the 32 prescaler there so our count is still right.

CAICR sets up our interrupts.

Let's set MENDIE and take an interrupt on each measurement.
We'll need to write 1 to MENDFCL to clear the flag.

We will read the CACNTBR register to see the count.

CAULVR and CALLVR hold our upper and lower limits for a mismatch interrupt if we want to enable that.

At the end we set CFME in CACR0 and everything should start.

1 Like

#include "IRQManager.h"

volatile uint16_t result;
volatile bool fired = false;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("Starting CAC Experiment!");

  setupCAC();

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

void loop() {

  heart();

  if (fired) {
    __disable_irq();
    uint16_t copy = result;
    fired = false;
    __enable_irq();
    Serial.print("Result : ");
    Serial.println(copy);
  }
}

void cacMendieHandler() {

  result = R_CAC->CACNTBR;
  // clear interrupt
  R_CAC->CAICR |= (R_CAC_CAICR_MENDFCL_Msk);

  fired = true;
}

void setupCAC() {

  //R_MSTP->MSTPCRC &= ~(R_MSTP_MSTPCRC_MSTPC0_Msk);
  /*
I'm caculating 48MHz on MOSC / 32.767kHz on LOCO should
give a ratio of around 1:1465.
*/
  // set lower limit
  R_CAC->CALLVR = 700;
  //set upper limit
  R_CAC->CAULVR = 2100;

  // Set source clock
  R_CAC->CACR1 = 0x34;

  // set reference clock
  R_CAC->CACR2 = 0x09;

  // set interrupt


  IRQManager::getInstance().addMaskableInterrupt(0x48, cacMendieHandler);

  R_CAC->CAICR = (R_CAC_CAICR_MENDIE_Msk | R_CAC_CAICR_MENDFCL_Msk);

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


void heart() {
  static uint32_t pm = millis();
  uint32_t cm = millis();
  if (cm - pm >= 1000) {
    pm = cm;
    digitalWrite(13, !digitalRead(13));
  }
}

Along with the mod here:

Compiles and runs. But doesn't output anything outside of setup. If I uncomment the MSTPCRC line then the heartbeat stops.

I did a little testing and I can comment out the line to add the ISR and the CAC runs and the code works. I just can't attach the interrupt.

So I started learning a bit more about the interrupts here.

Based on this post I had a look at @susan-parker 's code and I noticed that she was attaching pin interrupts and then changing the interrupt source. I didn't understand at first.

It seems as though all the interrupts in IRQManager are added through FSP configurations. If you look, what actually gets set as the function pointer for the isr are all FSP functions. Then they are looking in the configuration structs to get the handler to call.

For instance the external pin interrupt is added in addPeripheral:

#if EXT_INTERRUPTS_HOWMANY > 0
    /* **********************************************************************
                             PIN EXTERNAL INTERRUPT
       ********************************************************************** */
    else if(p == IRQ_EXTERNAL_PIN && cfg != NULL) {
        external_irq_cfg_t *p_cfg = (external_irq_cfg_t *)cfg;
        if (p_cfg->irq == FSP_INVALID_VECTOR) {
            p_cfg->ipl = EXTERNAL_PIN_PRIORITY;
            p_cfg->irq = (IRQn_Type)last_interrupt_index;
            *(irq_ptr + last_interrupt_index) = (uint32_t)r_icu_isr;
            set_ext_link_event(last_interrupt_index, p_cfg->channel);
            last_interrupt_index++;
        }
    }
#endif

The address that's put in the vector table is to r_icu_isr, not the interrupt handler from Interrupts.h . I can't find the implementation of that. It seems to be in the libfsp maybe.

In the case of the external pin interrupts this ends up calling from Interrupts.cpp:

/* -------------------------------------------------------------------------- */
static void IrqCallback(external_irq_callback_args_t * p_args) {
/* -------------------------------------------------------------------------- */    
    void * param = (void *)p_args->p_context;
    uint32_t ch = p_args->channel;

    CIrq *irq_context = nullptr;

    if(ch < MAX_IRQ_CHANNEL) {
        irq_context = IrqChannel.get(ch,false);
   
        if(irq_context != nullptr) {
            R_BSP_IrqClearPending(irq_context->cfg.irq);
            
            if(irq_context->fnc_param != nullptr) {
                irq_context->fnc_param(param);
            }
            else if(irq_context->fnc_void != nullptr) {
                irq_context->fnc_void();
            }
        }
    }
    
}

And then it determines which pin fired the interrupt and which handler to call. So it seems that at the base level, all the pin change ISRs are sharing one base handler and it's called by another handler that's actually the thing being attached.

I'm sure that there's some extra code in there that we need to be implementing in order for our interrupt to work. In the case of Susan's code, that's being handled by the external pin change isr. She's basically putting her handler into the external pin interrupt pool and then fooling the processor into calling that pin change isr for her interrupt by swapping the event code.

It's pretty smart.

1 Like

I fixed my ISR. I needed to add one line to the handler.

The thing about these interrupts are that there's two places to think about. There's the registers for the CAC itself and the interrupt flags there, but also the registers and interrupt flags in the event link system.

I also needed to clear the IR bit in the IELSRn register for my interrupt. I added a function to list the different even t links and determined that mine was in channel 5.

This code spams out 1444 to 1449 or so repeatedly on my monitor.

#include "IRQManager.h"

volatile uint16_t result;
volatile bool fired = false;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("Starting CAC Experiment!");

  printRegs();
  listEventLinks();

  setupCAC();

  printRegs();
  listEventLinks();

  Serial.println("End Setup!");


}

void loop() {

  heart();

  if (fired) {
    __disable_irq();
    uint16_t copy = result;
    fired = false;
    __enable_irq();
    Serial.print("Result : ");
    Serial.println(copy);
  }
}

void cacMendieHandler() {

  result = R_CAC->CACNTBR;
  // clear interrupt
  R_CAC->CAICR |= (R_CAC_CAICR_MENDFCL_Msk);
  R_ICU->IELSR[5] &= ~(R_ICU_IELSR_IR_Msk);

  fired = true;
}

void setupCAC() {

  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.
*/
  // set lower limit
  R_CAC->CALLVR = 700;
  //set upper limit
  R_CAC->CAULVR = 2100;

  // Set source clock
  R_CAC->CACR1 = 0x34;

  // set reference clock
  R_CAC->CACR2 = 0x09;

  // set interrupt


  IRQManager::getInstance().addMaskableInterrupt(0x48, cacMendieHandler);

  R_CAC->CAICR = (R_CAC_CAICR_MENDIE_Msk | R_CAC_CAICR_MENDFCL_Msk);

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


void heart() {
  static uint32_t pm = millis();
  uint32_t cm = millis();
  if (cm - pm >= 1000) {
    pm = cm;
    digitalWrite(13, !digitalRead(13));
  }
}

void printRegs(){
  volatile uint32_t mstpcrc = R_MSTP->MSTPCRC;
  Serial.print("MSTPCRC : ");
  Serial.println(mstpcrc , HEX);

  volatile uint32_t cacr0 = R_CAC->CACR0;
  Serial.print("CACR0 : ");
  Serial.println(cacr0 , HEX);

  volatile uint32_t cacr1 = R_CAC->CACR1;
  Serial.print("CACR1 : ");
  Serial.println(cacr1 , HEX);

  volatile uint32_t cacr2 = R_CAC->CACR2;
  Serial.print("CACR2 : ");
  Serial.println(cacr2 , HEX);

  volatile uint32_t caicr = R_CAC->CAICR;
  Serial.print("CAICR : ");
  Serial.println(caicr , HEX);

  volatile uint32_t callvr = R_CAC->CALLVR;
  Serial.print("CALLVR : ");
  Serial.println(callvr , HEX);

  volatile uint32_t caulvr = R_CAC->CAULVR;
  Serial.print("CAULVR : ");
  Serial.println(caulvr , HEX);

  volatile uint32_t cacntbr = R_CAC->CACNTBR;
  Serial.print("CACNTBR : ");
  Serial.println(cacntbr , HEX); 

}


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);
  }
}

Along with this addition in IRQManager class:

// Added Delta_G
//  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;

}

Produces:

Starting CAC Experiment!
MSTPCRC : FFFFFFFF
CACR0 : 0
CACR1 : 0
CACR2 : 0
CAICR : 0
CALLVR : 0
CAULVR : 0
CACNTBR : 0
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
MSTPCRC : FFFFFFFE
CACR0 : 1
CACR1 : 34
CACR2 : 9
CAICR : 2
CALLVR : 2BC
CAULVR : 834
CACNTBR : 5A7
IELSR0 : 1E
IELSR1 : A9
IELSR2 : AA
IELSR3 : A8
IELSR4 : AB
IELSR5 : 48
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
End Setup!
Result : 1448
Result : 1447
Result : 1447
Result : 1447
Result : 1448
Result : 1447
Result : 1447
Result : 1447
Result : 1447
Result : 1448
Result : 1447
Result : 1448
Result : 1447
Result : 1447
Result : 1447
Result : 1447
Result : 1448
Result : 1447
Result : 1448
Result : 1447
Result : 1447
Result : 1447
Result : 1448
Result : 1447
Result : 1447
Result : 1447
Result : 1447

Laoded this on my Minima after I finally figured out how to change the udev rules so it would take code.

It works like a charm on Minima too. I think I may do the rest of the work on that now.

This test was done using the Minima, but I expect the Wifi to be identical.

I switched to using the CAC_FERRI interrupt with the upper and lower limits set really tight to see if I could use the trim value in LOCOUTCR to adjust the LOCO clock. The target result of the CAC measurement is 1464 or 1465.

See the code

#include "IRQManager.h"

volatile uint16_t result;
volatile bool fired = false;
volatile int8_t oldLOCOtrim;
volatile int8_t curLOCOtrim;

uint16_t lowerLimit = 1462;
uint16_t upperLimit = 1468;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("Starting CAC Experiment!");

  printRegs();
  listEventLinks();

  setupCAC();

  printRegs();
  listEventLinks();

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

void loop() {

  heart();

  if (fired) {
    __disable_irq();
    uint16_t copy = result;
    fired = false;
    __enable_irq();
    Serial.print("Result : ");
    Serial.print(copy);
    Serial.print("  old Trim : ");
    Serial.print(oldLOCOtrim);
    Serial.print("  new Trim : ");
    Serial.println(curLOCOtrim);
  }
}

void cacFERRIHandler() {

  result = R_CAC->CACNTBR;
  // clear interrupt
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  R_ICU->IELSR[5] &= ~(R_ICU_IELSR_IR_Msk);

  if (result < lowerLimit) {
    adjustLocoTrim(-1);
  } else if (result > upperLimit) {
    adjustLocoTrim(1);
  }

  fired = true;
}

void adjustLocoTrim(int amount) {
  // Unlock PRC0
  // Write A5 to high 8 bits and PRCO_Msk in low bit
  uint16_t curPRCR = R_SYSTEM->PRCR;
  R_SYSTEM->PRCR = curPRCR | (0xA500) | (0x0001);

  oldLOCOtrim = R_SYSTEM->LOCOUTCR;
  curLOCOtrim = curLOCOtrim + amount;
  R_SYSTEM->LOCOUTCR = curLOCOtrim;

  // Lock protection register
  R_SYSTEM->PRCR = curPRCR | (0xA500);
}

void setupCAC() {

  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.
*/
  // set lower limit
  R_CAC->CALLVR = lowerLimit;
  //set upper limit
  R_CAC->CAULVR = upperLimit;

  // Set source clock
  R_CAC->CACR1 = 0x34;

  // set reference clock
  R_CAC->CACR2 = 0x09;

  // set interrupt


  IRQManager::getInstance().addMaskableInterrupt(0x47, cacFERRIHandler);

  R_CAC->CAICR = (R_CAC_CAICR_FERRIE_Msk | R_CAC_CAICR_FERRFCL_Msk);

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


void heart() {
  static uint32_t pm = millis();
  uint32_t cm = millis();
  if (cm - pm >= 1000) {
    pm = cm;
    digitalWrite(13, !digitalRead(13));
  }
}

void printRegs() {
  volatile uint32_t mstpcrc = R_MSTP->MSTPCRC;
  Serial.print("MSTPCRC : ");
  Serial.println(mstpcrc, HEX);

  volatile uint32_t cacr0 = R_CAC->CACR0;
  Serial.print("CACR0 : ");
  Serial.println(cacr0, HEX);

  volatile uint32_t cacr1 = R_CAC->CACR1;
  Serial.print("CACR1 : ");
  Serial.println(cacr1, HEX);

  volatile uint32_t cacr2 = R_CAC->CACR2;
  Serial.print("CACR2 : ");
  Serial.println(cacr2, HEX);

  volatile uint32_t caicr = R_CAC->CAICR;
  Serial.print("CAICR : ");
  Serial.println(caicr, HEX);

  volatile uint32_t callvr = R_CAC->CALLVR;
  Serial.print("CALLVR : ");
  Serial.println(callvr, HEX);

  volatile uint32_t caulvr = R_CAC->CAULVR;
  Serial.print("CAULVR : ");
  Serial.println(caulvr, HEX);

  volatile uint32_t cacntbr = R_CAC->CACNTBR;
  Serial.print("CACNTBR : ");
  Serial.println(cacntbr, HEX);
}


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);
  }
}

and this was the output:

Result : 1510  old Trim : -1  new Trim : 0
Result : 1455  old Trim : 0  new Trim : -1
Result : 1508  old Trim : -1  new Trim : 0
Result : 1455  old Trim : 0  new Trim : -1
Result : 1509  old Trim : -1  new Trim : 0
Result : 1454  old Trim : 0  new Trim : -1
Result : 1509  old Trim : -1  new Trim : 0
Result : 1454  old Trim : 0  new Trim : -1
Result : 1509  old Trim : -1  new Trim : 0
Result : 1455  old Trim : 0  new Trim : -1
Result : 1510  old Trim : -1  new Trim : 0
Result : 1455  old Trim : 0  new Trim : -1
Result : 1510  old Trim : -1  new Trim : 0

So it looks like even the smallest adjustment to the LOCOUTCR is too much for our purposes.

Perhaps it would be possible to slow this down and employ a counter to keep track of how long you spent over and under and try to balance that out, but it seems like an awful lot of resources to waste when the millis clock is already there.

In this code the reference clock is LOCO and there's a 1/32 divider on it. It's a 32.768kHZ clock so the period is 30.518us times 32 which gives 976.652us. We're getting roughly one interrupt per millisecond.

We're counting MOSC which is HOCO at 48MHz with a 1/32 divider and that gives the previously calculated ratio of 1:1464.8

One interrupt per millisecond isn't too crazy. It could be slower, but that's not too much computation.

Here's an idea that keep up an accumulated error and balances out the slow and fast. I'll try to combine with the RTC code in a little bit:

See Code

#include "IRQManager.h"

volatile uint16_t result;
volatile bool fired = false;
volatile int8_t oldLOCOtrim;
volatile int8_t curLOCOtrim;

uint16_t lowerLimit = 1462;
uint16_t upperLimit = 1468;

volatile int error;

volatile int16_t oldAcc = 0;
volatile int16_t accumulatedError = 0;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("Starting CAC Experiment!");

  printRegs();
  listEventLinks();

  setupCAC();

  printRegs();
  listEventLinks();

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

void loop() {

  heart();

  if (fired) {
    __disable_irq();
    uint16_t copy = result;
    fired = false;
    __enable_irq();
    Serial.print("Result : ");
    Serial.print(copy);
    Serial.print("  accumulated error : ");
    Serial.print(accumulatedError);
    Serial.print("  old Trim : ");
    Serial.print(oldLOCOtrim);
    Serial.print("  new Trim : ");
    Serial.println(curLOCOtrim);
  }
}

void cacFERRIHandler() {
  result = R_CAC->CACNTBR;
  // clear interrupt
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  R_ICU->IELSR[5] &= ~(R_ICU_IELSR_IR_Msk);

  int error = result - 1465;
  accumulatedError = accumulatedError + error;
  // If the accumulated error changes sign then we should adjust the trim
  if((accumulatedError < 0 ) && (oldAcc >= 0)){
    adjustLocoTrim(-1);
  }
  else if ((accumulatedError > 0) && (oldAcc <= 0)){
    adjustLocoTrim(1);
  }
  oldAcc = accumulatedError;
  fired = true;
}

void adjustLocoTrim(int amount) {
  // Unlock PRC0
  // Write A5 to high 8 bits and PRCO_Msk in low bit
  uint16_t curPRCR = R_SYSTEM->PRCR;
  R_SYSTEM->PRCR = curPRCR | (0xA500) | (0x0001);

  oldLOCOtrim = R_SYSTEM->LOCOUTCR;
  curLOCOtrim = curLOCOtrim + amount;
  R_SYSTEM->LOCOUTCR = curLOCOtrim;

  // Lock protection register
  R_SYSTEM->PRCR = curPRCR | (0xA500);
}

void setupCAC() {

  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.
*/
  // set lower limit
  R_CAC->CALLVR = lowerLimit;
  //set upper limit
  R_CAC->CAULVR = upperLimit;

  // Set source clock
  R_CAC->CACR1 = 0x34;

  // set reference clock
  R_CAC->CACR2 = 0x09;

  // set interrupt


  IRQManager::getInstance().addMaskableInterrupt(0x47, cacFERRIHandler);

  R_CAC->CAICR = (R_CAC_CAICR_FERRIE_Msk | R_CAC_CAICR_FERRFCL_Msk);

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


void heart() {
  static uint32_t pm = millis();
  uint32_t cm = millis();
  if (cm - pm >= 1000) {
    pm = cm;
    digitalWrite(13, !digitalRead(13));
  }
}

void printRegs() {
  volatile uint32_t mstpcrc = R_MSTP->MSTPCRC;
  Serial.print("MSTPCRC : ");
  Serial.println(mstpcrc, HEX);

  volatile uint32_t cacr0 = R_CAC->CACR0;
  Serial.print("CACR0 : ");
  Serial.println(cacr0, HEX);

  volatile uint32_t cacr1 = R_CAC->CACR1;
  Serial.print("CACR1 : ");
  Serial.println(cacr1, HEX);

  volatile uint32_t cacr2 = R_CAC->CACR2;
  Serial.print("CACR2 : ");
  Serial.println(cacr2, HEX);

  volatile uint32_t caicr = R_CAC->CAICR;
  Serial.print("CAICR : ");
  Serial.println(caicr, HEX);

  volatile uint32_t callvr = R_CAC->CALLVR;
  Serial.print("CALLVR : ");
  Serial.println(callvr, HEX);

  volatile uint32_t caulvr = R_CAC->CAULVR;
  Serial.print("CAULVR : ");
  Serial.println(caulvr, HEX);

  volatile uint32_t cacntbr = R_CAC->CACNTBR;
  Serial.print("CACNTBR : ");
  Serial.println(cacntbr, HEX);
}


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);
  }
}
Result : 1456  accumulated error : 19  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 10  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 1  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : -9  old Trim : 0  new Trim : -1
Result : 1508  accumulated error : 34  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 24  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 14  old Trim : -1  new Trim : 0
Result : 1454  accumulated error : 3  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : -6  old Trim : 0  new Trim : -1
Result : 1510  accumulated error : 39  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 30  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 21  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 12  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 3  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : -7  old Trim : 0  new Trim : -1
Result : 1510  accumulated error : 38  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 28  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 18  old Trim : -1  new Trim : 0
Result : 1454  accumulated error : 7  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : -3  old Trim : 0  new Trim : -1
Result : 1509  accumulated error : 41  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 32  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 23  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 14  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 5  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : -5  old Trim : 0  new Trim : -1
Result : 1510  accumulated error : 40  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 30  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 21  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 11  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 1  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : -8  old Trim : 0  new Trim : -1
Result : 1509  accumulated error : 36  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 26  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 17  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 7  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : -2  old Trim : 0  new Trim : -1
Result : 1510  accumulated error : 43  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 34  old Trim : -1  new Trim : 0
Result : 1456  accumulated error : 25  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 15  old Trim : -1  new Trim : 0
Result : 1455  accumulated error : 5  old Trim : -1  new Trim : 0
Result : 1454  accumulated error : -6  old Trim : 0  new Trim : -1

Tried to add it to the RTCAutomatic sketch to see if it changed anything. If I comment the line in setup setupCAC(); then the program works and I see output about rtc and millis both. But if I have the CAC turned on then I don't see any output from loop. All I see is the initial printing from setup.

If I move the serial prints back into the ISR like they are in the RTCAutomatic example then I see those, but not the prints about millis from loop.

Why does anything in loop suddenly stop printing?

EDIT: Because I've still hard coded the 5 for the event link index in my CAC interrupt handler. That only works in my test program. There are more interrupts here.

/*
 * RTC_AutomaticExample
 *
 * This example sets the RTC (Real Time Clock) on the Portenta C33 automatically by
 * retrieving the date and time from the computer you upload the sketch from, at the
 * point when you start the upload.
 *
 * Next, it gets the current time from the RTC and prints it to the Serial Monitor.
 * It then sets an RTC alarm to fire every time the seconds value of the time is zero.
 * The alarm, which now goes off once a minute, triggers a callback that prints the
 * current time to the Serial Monitor.
 *
 * Find the full UNO R4 WiFi RTC documentation here:
 * https://docs.arduino.cc/tutorials/uno-r4-wifi/rtc
 */

// Include the RTC library
#include "RTC.h"
#include "IRQManager.h"


DayOfWeek convertDayOfWeek(String s)
{
  if (s == String("Mon"))
  {
    return DayOfWeek::MONDAY;
  }
  if (s == String("Tue"))
  {
    return DayOfWeek::TUESDAY;
  }
  if (s == String("Wed"))
  {
    return DayOfWeek::WEDNESDAY;
  }  
  if (s == String("Thu"))
  {
    return DayOfWeek::THURSDAY;
  }  
  if (s == String("Fri"))
  {
    return DayOfWeek::FRIDAY;
  }  
  if (s == String("Sat"))
  {
    return DayOfWeek::SATURDAY;
  }  
  if (s == String("Sun"))
  {
    return DayOfWeek::SUNDAY;
  }  
}

Month convertMonth(String s)
{
  if (s == String("Jan"))
  {
    return Month::JANUARY;
  }
  if (s == String("Feb"))
  {
    return Month::FEBRUARY;
  }
  if (s == String("Mar"))
  {
    return Month::MARCH;
  }
  if (s == String("Apr"))
  {
    return Month::APRIL;
  }
  if (s == String("May"))
  {
    return Month::MAY;
  }
  if (s == String("Jun"))
  {
    return Month::JUNE;
  }
  if (s == String("Jul"))
  {
    return Month::JULY;
  }
  if (s == String("Aug"))
  {
    return Month::AUGUST;
  }
  if (s == String("Sep"))
  {
    return Month::SEPTEMBER;
  }
  if (s == String("Oct"))
  {
    return Month::OCTOBER;
  }  
  if (s == String("Nov"))
  {
    return Month::NOVEMBER;
  }
  if (s == String("Dec"))
  {
    return Month::DECEMBER;
  }  
}

RTCTime currentRTCTime()
{
  // Get a compilation timestamp of the format: Wed May 10 08:54:31 2023
  // __TIMESTAMP__ is a GNU C extension macro
  // We can't use the standard macros __DATE__ and __TIME__ because they don't provide the day of the week
  String timeStamp = __TIMESTAMP__;
  // Extract the day of the week
  int pos1 = timeStamp.indexOf(" ");
  DayOfWeek dayOfWeek = convertDayOfWeek(timeStamp.substring(0, pos1));
  // Extract the month
  ++pos1;
  int pos2 = timeStamp.indexOf(" ", pos1);
  Month month = convertMonth(timeStamp.substring(pos1, pos2));
  // Extract the day
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int day = timeStamp.substring(pos1, pos2).toInt();
  // Extract the hour
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(":", pos1);
  int hour = timeStamp.substring(pos1, pos2).toInt();
  // Extract the minute
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(":", pos1);
  int minute = timeStamp.substring(pos1, pos2).toInt();
  // Extract the second
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int second = timeStamp.substring(pos1, pos2).toInt();
  // Extract the year
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int year = timeStamp.substring(pos1, pos2).toInt();

  return RTCTime(day, month, year, hour, minute, second, dayOfWeek, SaveLight::SAVING_TIME_INACTIVE);
}

RTCTime currentTime;
volatile boolean rtcAlarmFired;

void alarmCallback() {
  RTC.getTime(currentTime);
  rtcAlarmFired = true;
}

void printAlarmInfo(){
  Serial.print("An RTC alarm was triggered at: ");
  Serial.print(currentTime.getYear());
  Serial.print("-");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("-");
  Serial.print(currentTime.getDayOfMonth());
  Serial.print(" ");
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) ;
  
  // Initialize the RTC
  RTC.begin();

  // Get the current date and time when the sketch is uploaded and set the RTC
  RTCTime timeToSet = currentRTCTime();
  RTC.setTime(timeToSet);

  // Retrieve the date and time from the RTC and print them
  Serial.println("The RTC was just set to: ");
  RTCTime currentTime;
  RTC.getTime(currentTime); 
  Serial.print(currentTime.getYear());
  Serial.print("-");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("-");
  Serial.print(currentTime.getDayOfMonth());
  Serial.print(" ");
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());

  // Create an alarm time with the seconds value set to zero
  RTCTime alarmTime;
  alarmTime.setSecond(0);
  // Tell the RTC to only match on the seconds value
  AlarmMatch alarmMatch;
  alarmMatch.addMatchSecond();
  // Set the alarm callback function
  RTC.setAlarmCallback(alarmCallback, alarmTime, alarmMatch);

  setupCAC();

}

const unsigned long oneMinute = 60000ul;
volatile int16_t errorRep = 0;
volatile boolean cacFired = false;
void loop()
{
  static unsigned long pm = millis();
  unsigned long cm = millis();
  if(cm-pm >= oneMinute){
    pm += oneMinute;
    Serial.print("Millis triggered at: ");
    Serial.println(cm);
  }
  if(rtcAlarmFired){
    printAlarmInfo();
    rtcAlarmFired = false;
  }
  if(cacFired){
    Serial.println(errorRep);
    cacFired = false;
  }
}

void cacFERRIHandler() {
  // record CAC counter register
  uint16_t result = R_CAC->CACNTBR;
  // clear interrupt flags
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  R_ICU->IELSR[5] &= ~(R_ICU_IELSR_IR_Msk);
  
  // Calculate error and and add to running total
  int error = result - 1465;
  int16_t accumulatedError = accumulatedError + error;
  errorRep = accumulatedError;
  // Static variable to keep track of old value to determine sign changes
  static int16_t oldAcc = 0;
  // If the accumulated error changes sign then we should adjust the trim
  if((accumulatedError < 0 ) && (oldAcc >= 0)){
    adjustLocoTrim(-1);
  }
  else if ((accumulatedError > 0) && (oldAcc <= 0)){
    adjustLocoTrim(1);
  }
  oldAcc = accumulatedError;
  cacFired = true;
}

void adjustLocoTrim(int amount) {
  // Unlock PRC0
  // Write A5 to high 8 bits and PRCO_Msk in low bit
  uint16_t curPRCR = R_SYSTEM->PRCR;
  R_SYSTEM->PRCR = curPRCR | (0xA500) | (0x0001);

  int8_t curLOCOtrim = R_SYSTEM->LOCOUTCR;
  R_SYSTEM->LOCOUTCR = curLOCOtrim + amount;

  // Lock protection register with old value.
  R_SYSTEM->PRCR = curPRCR | (0xA500);
}

void setupCAC() {

  R_MSTP->MSTPCRC &= ~((uint32_t)R_MSTP_MSTPCRC_MSTPC0_Msk);
  // Calculating 48MHz HOCO divided by 32.768 LOCO using the same divider gives
  //  48,000,000 / 32,768 == 1464.84375
  // set lower limit
  R_CAC->CALLVR = 1462;
  //set upper limit
  R_CAC->CAULVR = 1468;

  // Set source clock
  //(EDGES=00 rising only, TCSS=11 1/32 divider, FMCS=010 HOCO, CACREFE=0 Disable CACREF pin input)
  R_CAC->CACR1 = 0x34; 

  // set reference clock
  // (DFS=00 no filter, RCDS=00 1/32 divider, RSCS=100LOCO, RPS=1 internal clock source)
  R_CAC->CACR2 = 0x09;

  // set interrupt (this function added to core by me)
  IRQManager::getInstance().addMaskableInterrupt(0x47, cacFERRIHandler);

  // enable interrupt and clear any pending flag
  R_CAC->CAICR = (R_CAC_CAICR_FERRIE_Msk | R_CAC_CAICR_FERRFCL_Msk);

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

Here's some corrected code. This prints correctly.

/*
 * RTC_AutomaticExample
 *
 * This example sets the RTC (Real Time Clock) on the Portenta C33 automatically by
 * retrieving the date and time from the computer you upload the sketch from, at the
 * point when you start the upload.
 *
 * Next, it gets the current time from the RTC and prints it to the Serial Monitor.
 * It then sets an RTC alarm to fire every time the seconds value of the time is zero.
 * The alarm, which now goes off once a minute, triggers a callback that prints the
 * current time to the Serial Monitor.
 *
 * Find the full UNO R4 WiFi RTC documentation here:
 * https://docs.arduino.cc/tutorials/uno-r4-wifi/rtc
 */

// Include the RTC library
#include "RTC.h"
#include "IRQManager.h"


DayOfWeek convertDayOfWeek(String s)
{
  if (s == String("Mon"))
  {
    return DayOfWeek::MONDAY;
  }
  if (s == String("Tue"))
  {
    return DayOfWeek::TUESDAY;
  }
  if (s == String("Wed"))
  {
    return DayOfWeek::WEDNESDAY;
  }  
  if (s == String("Thu"))
  {
    return DayOfWeek::THURSDAY;
  }  
  if (s == String("Fri"))
  {
    return DayOfWeek::FRIDAY;
  }  
  if (s == String("Sat"))
  {
    return DayOfWeek::SATURDAY;
  }  
  if (s == String("Sun"))
  {
    return DayOfWeek::SUNDAY;
  }  
}

Month convertMonth(String s)
{
  if (s == String("Jan"))
  {
    return Month::JANUARY;
  }
  if (s == String("Feb"))
  {
    return Month::FEBRUARY;
  }
  if (s == String("Mar"))
  {
    return Month::MARCH;
  }
  if (s == String("Apr"))
  {
    return Month::APRIL;
  }
  if (s == String("May"))
  {
    return Month::MAY;
  }
  if (s == String("Jun"))
  {
    return Month::JUNE;
  }
  if (s == String("Jul"))
  {
    return Month::JULY;
  }
  if (s == String("Aug"))
  {
    return Month::AUGUST;
  }
  if (s == String("Sep"))
  {
    return Month::SEPTEMBER;
  }
  if (s == String("Oct"))
  {
    return Month::OCTOBER;
  }  
  if (s == String("Nov"))
  {
    return Month::NOVEMBER;
  }
  if (s == String("Dec"))
  {
    return Month::DECEMBER;
  }  
}

RTCTime currentRTCTime()
{
  // Get a compilation timestamp of the format: Wed May 10 08:54:31 2023
  // __TIMESTAMP__ is a GNU C extension macro
  // We can't use the standard macros __DATE__ and __TIME__ because they don't provide the day of the week
  String timeStamp = __TIMESTAMP__;
  // Extract the day of the week
  int pos1 = timeStamp.indexOf(" ");
  DayOfWeek dayOfWeek = convertDayOfWeek(timeStamp.substring(0, pos1));
  // Extract the month
  ++pos1;
  int pos2 = timeStamp.indexOf(" ", pos1);
  Month month = convertMonth(timeStamp.substring(pos1, pos2));
  // Extract the day
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int day = timeStamp.substring(pos1, pos2).toInt();
  // Extract the hour
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(":", pos1);
  int hour = timeStamp.substring(pos1, pos2).toInt();
  // Extract the minute
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(":", pos1);
  int minute = timeStamp.substring(pos1, pos2).toInt();
  // Extract the second
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int second = timeStamp.substring(pos1, pos2).toInt();
  // Extract the year
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int year = timeStamp.substring(pos1, pos2).toInt();

  return RTCTime(day, month, year, hour, minute, second, dayOfWeek, SaveLight::SAVING_TIME_INACTIVE);
}

RTCTime currentTime;
volatile boolean rtcAlarmFired;
uint8_t eventLinkIndex;

void alarmCallback() {
  RTC.getTime(currentTime);
  rtcAlarmFired = true;
}

void printAlarmInfo(){
  Serial.print("An RTC alarm was triggered at: ");
  Serial.print(currentTime.getYear());
  Serial.print("-");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("-");
  Serial.print(currentTime.getDayOfMonth());
  Serial.print(" ");
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) ;
  
  // Initialize the RTC
  RTC.begin();

  // Get the current date and time when the sketch is uploaded and set the RTC
  RTCTime timeToSet = currentRTCTime();
  RTC.setTime(timeToSet);

  // Retrieve the date and time from the RTC and print them
  Serial.println("The RTC was just set to: ");
  RTCTime currentTime;
  RTC.getTime(currentTime); 
  Serial.print(currentTime.getYear());
  Serial.print("-");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("-");
  Serial.print(currentTime.getDayOfMonth());
  Serial.print(" ");
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());

  // Create an alarm time with the seconds value set to zero
  RTCTime alarmTime;
  alarmTime.setSecond(0);
  // Tell the RTC to only match on the seconds value
  AlarmMatch alarmMatch;
  alarmMatch.addMatchSecond();
  // Set the alarm callback function
  RTC.setAlarmCallback(alarmCallback, alarmTime, alarmMatch);

  setupCAC();

}

const unsigned long oneMinute = 60000ul;
// volatile bool cacFired = false;
// volatile int errRep;
void loop()
{
  static unsigned long pm = millis();
  unsigned long cm = millis();
  if(cm-pm >= oneMinute){
    pm += oneMinute;
    Serial.print("Millis triggered at: ");
    Serial.println(cm);
  }
  if(rtcAlarmFired){
    printAlarmInfo();
    rtcAlarmFired = false;
  }
  // if(cacFired){
  //   Serial.println(errRep);
  //   cacFired = false;
  // }
}

void cacFERRIHandler() {
  // record CAC counter register
  uint16_t result = R_CAC->CACNTBR;
  // clear interrupt flags
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  R_ICU->IELSR[eventLinkIndex] &= ~(R_ICU_IELSR_IR_Msk);
  
  // Static variable to keep track of old value to determine sign changes
  static int16_t accumulatedError = 0;  
  // Calculate error and and add to running total
  int16_t newAcc = accumulatedError + (result - 1465);
  // If the accumulated error changes sign then we should adjust the trim
  if((newAcc < 0 ) && (accumulatedError >= 0)){
    adjustLocoTrim(-1);
  }
  else if ((newAcc > 0) && (accumulatedError <= 0)){
    adjustLocoTrim(1);
  }
  accumulatedError = newAcc;
  // errRep = accumulatedError;
  // cacFired = true;
}

void adjustLocoTrim(int amount) {
  // Unlock PRC0
  // Write A5 to high 8 bits and PRCO_Msk in low bit
  uint16_t curPRCR = R_SYSTEM->PRCR;
  R_SYSTEM->PRCR = curPRCR | (0xA500) | (0x0001);

  int8_t curLOCOtrim = R_SYSTEM->LOCOUTCR;
  R_SYSTEM->LOCOUTCR = curLOCOtrim + amount;

  // Lock protection register with old value.
  R_SYSTEM->PRCR = curPRCR | (0xA500);
}

void setupCAC() {

  R_MSTP->MSTPCRC &= ~((uint32_t)R_MSTP_MSTPCRC_MSTPC0_Msk);
  // Calculating 48MHz HOCO divided by 32.768 LOCO using the same divider gives
  //  48,000,000 / 32,768 == 1464.84375
  //  Using the 1/128 divider for LOCO makes it 5859.375
  // set lower limit
  R_CAC->CALLVR = 1462;
  //set upper limit
  R_CAC->CAULVR = 1467;

  // Set source clock
  //(EDGES=00 rising only, TCSS=11 1/32 divider, FMCS=010 HOCO, CACREFE=0 Disable CACREF pin input)
  R_CAC->CACR1 = 0x34; 

  // set reference clock
  // (DFS=00 no filter, RCDS=01 1/32 divider, RSCS=100LOCO, RPS=1 internal clock source)
  R_CAC->CACR2 = 0x09;

  // set interrupt (this function added to core by me)
  IRQManager::getInstance().addMaskableInterrupt(0x47, cacFERRIHandler);
  //  Find which event link it gave us. 
  for(uint8_t i = 0; i < 32; i++){
    volatile uint32_t val = R_ICU->IELSR[i];
    if((val & 0xFF) == 0x47){
      eventLinkIndex = i;
      break;
    }
  }

  // enable interrupt and clear any pending flag
  R_CAC->CAICR = (R_CAC_CAICR_FERRIE_Msk | R_CAC_CAICR_FERRFCL_Msk);

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

There's some stuff where I commented out that I had it printing the accumulated error from the isr. I was able to confirm that it does jump up into the 40s and then come down by around 10 per interrupt until it goes a little negative and then jumps back into the 40s. That's as expected. It's mostly running fast, but every now and then we let it run slow for a little bit to catch up.

However the results for the RTC test are what really matter.

For the case where I have the CAC turned off setupCAC(); is commented out.

21:46:08.596 -> The RTC was just set to: 
21:46:08.596 -> 2023-10-18 21:45:50
21:46:18.535 -> An RTC alarm was triggered at: 2023-10-18 21:46:0
21:47:08.591 -> Millis triggered at: 61558
21:47:18.116 -> An RTC alarm was triggered at: 2023-10-18 21:47:0
21:48:08.592 -> Millis triggered at: 121558
21:48:17.665 -> An RTC alarm was triggered at: 2023-10-18 21:48:0
21:49:08.589 -> Millis triggered at: 181558
21:49:17.245 -> An RTC alarm was triggered at: 2023-10-18 21:49:0
21:50:08.583 -> Millis triggered at: 241558
21:50:16.794 -> An RTC alarm was triggered at: 2023-10-18 21:50:0
21:51:08.580 -> Millis triggered at: 301558
21:51:16.374 -> An RTC alarm was triggered at: 2023-10-18 21:51:0
21:52:08.612 -> Millis triggered at: 361558
21:52:15.955 -> An RTC alarm was triggered at: 2023-10-18 21:52:0
21:53:08.612 -> Millis triggered at: 421558
21:53:15.505 -> An RTC alarm was triggered at: 2023-10-18 21:53:0
21:54:08.608 -> Millis triggered at: 481558
21:54:15.086 -> An RTC alarm was triggered at: 2023-10-18 21:54:0
21:55:08.612 -> Millis triggered at: 541558
21:55:14.642 -> An RTC alarm was triggered at: 2023-10-18 21:55:0
21:56:08.615 -> Millis triggered at: 601558
21:56:14.228 -> An RTC alarm was triggered at: 2023-10-18 21:56:0
21:57:08.622 -> Millis triggered at: 661558
21:57:13.784 -> An RTC alarm was triggered at: 2023-10-18 21:57:0
21:58:08.603 -> Millis triggered at: 721558
21:58:13.350 -> An RTC alarm was triggered at: 2023-10-18 21:58:0

millis is doing pretty good, but the RTC is fast by about 400ms per minute.

When I uncomment setupCAC() and let the CAC run the results:

21:59:42.952 -> The RTC was just set to: 
21:59:42.952 -> 2023-10-18 21:59:24
22:00:18.935 -> An RTC alarm was triggered at: 2023-10-18 22:0:0
22:00:42.951 -> Millis triggered at: 61625
22:01:18.934 -> An RTC alarm was triggered at: 2023-10-18 22:1:0
22:01:42.922 -> Millis triggered at: 121625
22:02:18.969 -> An RTC alarm was triggered at: 2023-10-18 22:2:0
22:02:42.924 -> Millis triggered at: 181625
22:03:18.963 -> An RTC alarm was triggered at: 2023-10-18 22:3:0
22:03:42.920 -> Millis triggered at: 241625
22:04:18.965 -> An RTC alarm was triggered at: 2023-10-18 22:4:0
22:04:42.921 -> Millis triggered at: 301625
22:05:18.969 -> An RTC alarm was triggered at: 2023-10-18 22:5:0
22:05:42.928 -> Millis triggered at: 361625
22:06:18.976 -> An RTC alarm was triggered at: 2023-10-18 22:6:0
22:06:42.930 -> Millis triggered at: 421625
22:07:18.979 -> An RTC alarm was triggered at: 2023-10-18 22:7:0
22:07:42.954 -> Millis triggered at: 481625
22:08:19.085 -> An RTC alarm was triggered at: 2023-10-18 22:8:0
22:08:43.020 -> Millis triggered at: 541625
22:09:19.115 -> An RTC alarm was triggered at: 2023-10-18 22:9:0

Looks like the RTC got a lot more accurate once that CAC correction was turned on. At least compared to millis.

I let it keep running. After almost 2 hours we have:

23:31:19.703 -> An RTC alarm was triggered at: 2023-10-18 23:31:0
23:31:43.017 -> Millis triggered at: 5521625
23:32:19.737 -> An RTC alarm was triggered at: 2023-10-18 23:32:0
23:32:43.033 -> Millis triggered at: 5581625
23:33:19.766 -> An RTC alarm was triggered at: 2023-10-18 23:33:0
23:33:43.082 -> Millis triggered at: 5641625
23:34:19.801 -> An RTC alarm was triggered at: 2023-10-18 23:34:0
23:34:43.091 -> Millis triggered at: 5701625
23:35:19.850 -> An RTC alarm was triggered at: 2023-10-18 23:35:0
23:35:43.106 -> Millis triggered at: 5761625
23:36:19.867 -> An RTC alarm was triggered at: 2023-10-18 23:36:0
23:36:43.120 -> Millis triggered at: 5821625
23:37:19.869 -> An RTC alarm was triggered at: 2023-10-18 23:37:0
23:37:43.152 -> Millis triggered at: 5881625
23:38:19.904 -> An RTC alarm was triggered at: 2023-10-18 23:38:0
23:38:43.135 -> Millis triggered at: 5941625
23:39:19.921 -> An RTC alarm was triggered at: 2023-10-18 23:39:0
23:39:43.142 -> Millis triggered at: 6001625
23:40:19.940 -> An RTC alarm was triggered at: 2023-10-18 23:40:0
23:40:43.162 -> Millis triggered at: 6061625
23:41:19.867 -> An RTC alarm was triggered at: 2023-10-18 23:41:0
23:41:43.039 -> Millis triggered at: 6121625
23:42:19.795 -> An RTC alarm was triggered at: 2023-10-18 23:42:0
23:42:43.000 -> Millis triggered at: 6181625
23:43:19.769 -> An RTC alarm was triggered at: 2023-10-18 23:43:0
23:43:42.977 -> Millis triggered at: 6241625
23:44:19.747 -> An RTC alarm was triggered at: 2023-10-18 23:44:0
23:44:42.930 -> Millis triggered at: 6301625
23:45:19.750 -> An RTC alarm was triggered at: 2023-10-18 23:45:0
23:45:42.981 -> Millis triggered at: 6361625
23:46:19.810 -> An RTC alarm was triggered at: 2023-10-18 23:46:0
23:46:42.999 -> Millis triggered at: 6421625
23:47:19.815 -> An RTC alarm was triggered at: 2023-10-18 23:47:0
23:47:43.002 -> Millis triggered at: 6481625
23:48:19.850 -> An RTC alarm was triggered at: 2023-10-18 23:48:0
23:48:43.001 -> Millis triggered at: 6541625
23:49:19.842 -> An RTC alarm was triggered at: 2023-10-18 23:49:0
23:49:43.044 -> Millis triggered at: 6601625

So over 2 hours the RTC is almost a second slow. That's a whole lot better than 400ms per minute.

I think I know why this is. I am targeting 1465 counts, but the real number is 1464.84375
I know how to correct for that by doing a little math and skipping one count every now and then sort of like what they do with the millis error.

Still, I think it is a HUGE improvement over what it was doing.

Here's a little bit extra that might be key. There's also a pin you can use to drive the CAC reference. It's pin 204 on the chip. On the WiFi that's used for the led matrix. But on the Minima it's the secret LOVE pin. So you can hook a stable clock signal to it to drive the correction for the LOCO clock.

So even though there's not a way to add the SOSC oscillator, there's at least a usable way to connect something to the CAC.

It's still keeping up.

Started here:

00:35:42.642 -> The RTC was just set to: 
00:35:42.642 -> 2023-10-19 0:35:24
00:36:18.645 -> An RTC alarm was triggered at: 2023-10-19 0:36:0
00:36:42.633 -> Millis triggered at: 61698
00:37:18.636 -> An RTC alarm was triggered at: 2023-10-19 0:37:0
00:37:42.613 -> Millis triggered at: 121698
00:38:18.617 -> An RTC alarm was triggered at: 2023-10-19 0:38:0
00:38:42.625 -> Millis triggered at: 181698
00:39:18.650 -> An RTC alarm was triggered at: 2023-10-19 0:39:0
00:39:42.672 -> Millis triggered at: 241698
00:40:18.655 -> An RTC alarm was triggered at: 2023-10-19 0:40:0
00:40:42.673 -> Millis triggered at: 301698
00:41:18.651 -> An RTC alarm was triggered at: 2023-10-19 0:41:0
00:41:42.672 -> Millis triggered at: 361698
00:42:18.677 -> An RTC alarm was triggered at: 2023-10-19 0:42:0
00:42:42.664 -> Millis triggered at: 421698
00:43:18.648 -> An RTC alarm was triggered at: 2023-10-19 0:43:0
00:43:42.665 -> Millis triggered at: 481698
00:44:18.649 -> An RTC alarm was triggered at: 2023-10-19 0:44:0
00:44:42.636 -> Millis triggered at: 541698
00:45:18.933 -> An RTC alarm was triggered at: 2023-10-19 0:45:0
00:45:42.690 -> Millis triggered at: 601698
00:46:18.664 -> An RTC alarm was triggered at: 2023-10-19 0:46:0
00:46:42.648 -> Millis triggered at: 661698

Now I'm here:

09:57:19.560 -> An RTC alarm was triggered at: 2023-10-19 9:57:0
09:57:42.838 -> Millis triggered at: 33721698
09:58:19.554 -> An RTC alarm was triggered at: 2023-10-19 9:58:0
09:58:42.835 -> Millis triggered at: 33781698
09:59:19.556 -> An RTC alarm was triggered at: 2023-10-19 9:59:0
09:59:42.839 -> Millis triggered at: 33841698
10:00:19.558 -> An RTC alarm was triggered at: 2023-10-19 10:0:0
10:00:42.839 -> Millis triggered at: 33901698
10:01:19.552 -> An RTC alarm was triggered at: 2023-10-19 10:1:0
10:01:42.833 -> Millis triggered at: 33961698
10:02:19.580 -> An RTC alarm was triggered at: 2023-10-19 10:2:0
10:02:42.862 -> Millis triggered at: 34021698
10:03:19.577 -> An RTC alarm was triggered at: 2023-10-19 10:3:0
10:03:42.862 -> Millis triggered at: 34081698
10:04:19.581 -> An RTC alarm was triggered at: 2023-10-19 10:4:0
10:04:42.863 -> Millis triggered at: 34141698
10:05:19.581 -> An RTC alarm was triggered at: 2023-10-19 10:5:0
10:05:42.838 -> Millis triggered at: 34201698
10:06:19.588 -> An RTC alarm was triggered at: 2023-10-19 10:6:0
10:06:42.866 -> Millis triggered at: 34261698
10:07:19.581 -> An RTC alarm was triggered at: 2023-10-19 10:7:0
10:07:42.864 -> Millis triggered at: 34321698
10:08:19.578 -> An RTC alarm was triggered at: 2023-10-19 10:8:0
10:08:42.860 -> Millis triggered at: 34381698
10:09:19.584 -> An RTC alarm was triggered at: 2023-10-19 10:9:0
10:09:42.866 -> Millis triggered at: 34441698
10:10:19.580 -> An RTC alarm was triggered at: 2023-10-19 10:10:0
10:10:42.860 -> Millis triggered at: 34501698
10:11:19.579 -> An RTC alarm was triggered at: 2023-10-19 10:11:0
10:11:42.860 -> Millis triggered at: 34561698
10:12:19.583 -> An RTC alarm was triggered at: 2023-10-19 10:12:0
10:12:42.866 -> Millis triggered at: 34621698

Look like millis is slow by about 200ms and RTC is slow by about a second over about 10 hours.

The RTC in the same code on the same board with the setupCAC(); line commented lost 27 seconds in an hour. That's a useless clock.

My next thought is to try to do this without the core change. There's the carry interrupt that's listed in addPeripheral but not used anywhere. The handler is implemented in the core in libfsp.a where I can't change it, so I'll have to work around that. The thing I'm really concerned about is having that lastInterruptIndex variable be right so another event link doesn't get added over the top of mine.

I have a plan. It's a modification of what @susan-parker did. I'll add the rtc carry handler and then I'll search the event links to see which index it got attached to. Then I'll replace that entry in the vector table so it points to my handler instead of the one in the core. And then I'll change the event link to point to the CAC interrupt.

I listed the event links that are currently running in this program, but on a Minima this time.

12:12:22.326 -> Listing Event Links
12:12:22.326 -> IELSR0 : 33
12:12:22.326 -> IELSR1 : 34
12:12:22.326 -> IELSR2 : 31
12:12:22.326 -> IELSR3 : 32
12:12:22.326 -> IELSR4 : 1E
12:12:22.326 -> IELSR5 : 26
12:12:22.326 -> IELSR6 : 47

The first 4 are the USBFS interrupts. 1E in slot 4 is the AGT0_AGTI interrupt driving micros and millis. 26 is the RTC_ALM interrupt and 47 is the CAC_FERRI interrupt that I put in.

Good, there's no code using the carry interrupt so that's what I'll hijack.

Ok, here's a version that works without any modifications to core files. This hijacks the rtc carry isr. I just tested it on Minima and it's working. It's also working on my R4-WiFi board but the performance is a little different. It still stays in pretty good sync with millis, but millis on this board is not very good either. There's a bit more wobble to it, but I think that may be lag because of the different serial link.

To test it, simply add the two library files to your sketch folder and #include "CAC_Correction.h" in your sketch. Then add the line setupCAC() to the setup function after the RTC is setup.

CAC_Correction.cpp (3.8 KB)
CAC_Correction.h (287 Bytes)
R4_RTC_CAC_Test.ino (4.6 KB)

/*
 * R_4RTC_CAC_Test.ino
 *
 *  Modified from Arduino RTC_AutomaticExample.ino
 *
 *
 */

#include "RTC.h"
#include "CAC_Correction.h"


DayOfWeek convertDayOfWeek(String s) {
  if (s == String("Mon")) {
    return DayOfWeek::MONDAY;
  }
  if (s == String("Tue")) {
    return DayOfWeek::TUESDAY;
  }
  if (s == String("Wed")) {
    return DayOfWeek::WEDNESDAY;
  }
  if (s == String("Thu")) {
    return DayOfWeek::THURSDAY;
  }
  if (s == String("Fri")) {
    return DayOfWeek::FRIDAY;
  }
  if (s == String("Sat")) {
    return DayOfWeek::SATURDAY;
  }
  if (s == String("Sun")) {
    return DayOfWeek::SUNDAY;
  }
}

Month convertMonth(String s) {
  if (s == String("Jan")) {
    return Month::JANUARY;
  }
  if (s == String("Feb")) {
    return Month::FEBRUARY;
  }
  if (s == String("Mar")) {
    return Month::MARCH;
  }
  if (s == String("Apr")) {
    return Month::APRIL;
  }
  if (s == String("May")) {
    return Month::MAY;
  }
  if (s == String("Jun")) {
    return Month::JUNE;
  }
  if (s == String("Jul")) {
    return Month::JULY;
  }
  if (s == String("Aug")) {
    return Month::AUGUST;
  }
  if (s == String("Sep")) {
    return Month::SEPTEMBER;
  }
  if (s == String("Oct")) {
    return Month::OCTOBER;
  }
  if (s == String("Nov")) {
    return Month::NOVEMBER;
  }
  if (s == String("Dec")) {
    return Month::DECEMBER;
  }
}

RTCTime currentRTCTime() {
  // Get a compilation timestamp of the format: Wed May 10 08:54:31 2023
  // __TIMESTAMP__ is a GNU C extension macro
  // We can't use the standard macros __DATE__ and __TIME__ because they don't provide the day of the week
  String timeStamp = __TIMESTAMP__;
  // Extract the day of the week
  int pos1 = timeStamp.indexOf(" ");
  DayOfWeek dayOfWeek = convertDayOfWeek(timeStamp.substring(0, pos1));
  // Extract the month
  ++pos1;
  int pos2 = timeStamp.indexOf(" ", pos1);
  Month month = convertMonth(timeStamp.substring(pos1, pos2));
  // Extract the day
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int day = timeStamp.substring(pos1, pos2).toInt();
  // Extract the hour
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(":", pos1);
  int hour = timeStamp.substring(pos1, pos2).toInt();
  // Extract the minute
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(":", pos1);
  int minute = timeStamp.substring(pos1, pos2).toInt();
  // Extract the second
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int second = timeStamp.substring(pos1, pos2).toInt();
  // Extract the year
  pos1 = ++pos2;
  pos2 = timeStamp.indexOf(" ", pos1);
  int year = timeStamp.substring(pos1, pos2).toInt();

  return RTCTime(day, month, year, hour, minute, second, dayOfWeek, SaveLight::SAVING_TIME_INACTIVE);
}

RTCTime currentTime;
volatile boolean rtcAlarmFired;

void alarmCallback() {
  // Save the time and note the event for loop to report.
  RTC.getTime(currentTime);
  rtcAlarmFired = true;
}

void printAlarmInfo() {
  Serial.print("An RTC alarm was triggered at: ");
  Serial.print(currentTime.getYear());
  Serial.print("-");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("-");
  Serial.print(currentTime.getDayOfMonth());
  Serial.print(" ");
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());
}

void setup() {
  Serial.begin(115200);
  while (!Serial)
    ;

  // Initialize the RTC
  RTC.begin();

  // Get the current date and time when the sketch is uploaded and set the RTC
  RTCTime timeToSet = currentRTCTime();
  RTC.setTime(timeToSet);

  // Retrieve the date and time from the RTC and print them
  Serial.println("The RTC was just set to: ");
  RTCTime currentTime;
  RTC.getTime(currentTime);
  Serial.print(currentTime.getYear());
  Serial.print("-");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("-");
  Serial.print(currentTime.getDayOfMonth());
  Serial.print(" ");
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());

  // Create an alarm time with the seconds value set to zero
  RTCTime alarmTime;
  alarmTime.setSecond(0);
  // Tell the RTC to only match on the seconds value
  AlarmMatch alarmMatch;
  alarmMatch.addMatchSecond();
  // Set the alarm callback function
  RTC.setAlarmCallback(alarmCallback, alarmTime, alarmMatch);

  setupCAC();
}

const unsigned long oneMinute = 60000ul;
void loop() {
  static unsigned long pm = millis();
  unsigned long cm = millis();
  if (cm - pm >= oneMinute) {
    pm += oneMinute;
    Serial.print("Millis triggered at: ");
    Serial.println(cm);
  }
  if (rtcAlarmFired) {
    printAlarmInfo();
    rtcAlarmFired = false;
  }
}

/*
*   CAC_Correction.h
*   
*
*/

#ifndef CAC_CORRECTION_H
#define CAC_CORRECTION_H

#include "Arduino.h"
#include "IRQManager.h"
#include "RTC.h"

void cacFERRIHandler();
void adjustLocoTrim(int amount);
void setupCAC();
void startCacInterrupt();



#endif  // defined CAC_CORRECTION_H
/*
*   CAC_Correction.cpp
*   
*
*/


#include "CAC_Correction.h"

uint8_t eventLinkIndex;

void cacFERRIHandler() {
  // record CAC counter register
  uint16_t result = R_CAC->CACNTBR;
  // clear interrupt flags
  R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
  R_ICU->IELSR[eventLinkIndex] &= ~(R_ICU_IELSR_IR_Msk);

  // Static variable to keep track of old value to determine sign changes
  static int16_t accumulatedError = 0;
  // Static variable to count interrupts
  static uint8_t ticks = 0;
  // Calculate error and and add to running total
  int16_t newAcc = accumulatedError + (result - 1465);
  // Every 32 interrupt cycles we need to add 5 ticks to the error to account
  // to account for the fractional difference in the ratio
  if (++ticks >= 32) {
    newAcc += 5;
    ticks -= 32;
  }
  // If the accumulated error changes sign then we should adjust the trim
  if ((newAcc < 0) && (accumulatedError >= 0)) {
    adjustLocoTrim(-1);
  } else if ((newAcc > 0) && (accumulatedError <= 0)) {
    adjustLocoTrim(1);
  }
  accumulatedError = newAcc;
}

void adjustLocoTrim(int amount) {
  // Unlock PRC0
  // Write A5 to high 8 bits and PRCO_Msk in low bit
  uint16_t curPRCR = R_SYSTEM->PRCR;
  R_SYSTEM->PRCR = curPRCR | (0xA500) | (0x0001);

  int8_t curLOCOtrim = R_SYSTEM->LOCOUTCR;
  R_SYSTEM->LOCOUTCR = curLOCOtrim + amount;

  // Lock protection register with old value.
  R_SYSTEM->PRCR = curPRCR | (0xA500);
}

void setupCAC() {

  R_MSTP->MSTPCRC &= ~((uint32_t)R_MSTP_MSTPCRC_MSTPC0_Msk);
  // Calculating 48MHz HOCO divided by 32.768 LOCO using the same divider gives
  //  48,000,000 / 32,768 == 1464.84375
  //  Using the 1/128 divider for LOCO makes it 5859.375
  // set lower limit
  R_CAC->CALLVR = 1462;
  //set upper limit
  R_CAC->CAULVR = 1467;

  // Set source clock
  //(EDGES=00 rising only, TCSS=11 1/32 divider, FMCS=010 HOCO, CACREFE=0 Disable CACREF pin input)
  R_CAC->CACR1 = 0x34;

  // set reference clock
  // (DFS=00 no filter, RCDS=01 1/32 divider, RSCS=100LOCO, RPS=1 internal clock source)
  R_CAC->CACR2 = 0x09;

  // set interrupt (this function added to core by me)
  // IRQManager::getInstance().addMaskableInterrupt(0x47, cacFERRIHandler);
  // //  Find which event link it gave us.
  // for (uint8_t i = 0; i < 32; i++) {
  //   volatile uint32_t val = R_ICU->IELSR[i];
  //   if ((val & 0xFF) == 0x47) {
  //     eventLinkIndex = i;
  //     break;
  //   }
  // }
  startCacInterrupt();

  // enable interrupt and clear any pending flag
  R_CAC->CAICR = (R_CAC_CAICR_FERRIE_Msk | R_CAC_CAICR_FERRFCL_Msk);

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

extern rtc_instance_ctrl_t rtc_ctrl;
extern rtc_cfg_t rtc_cfg;

void startCacInterrupt() {
  RTCIrqCfg_t rtc_irq_cfg;

  rtc_irq_cfg.req = RTC_CARRY;
  rtc_irq_cfg.cfg = &rtc_cfg;
  rtc_irq_cfg.ctrl = &rtc_ctrl;

  if (IRQManager::getInstance().addPeripheral(IRQ_RTC, &rtc_irq_cfg)) {
    // Find the event link we just got
    for (uint8_t i = 0; i < 32; i++) {
      volatile uint32_t val = R_ICU->IELSR[i];
      // RTC_CUP event code is 0x28
      if ((val & 0xFF) == 0x28) {
        eventLinkIndex = i;
        break;
      }
    }
    //  If we found something.
    if (eventLinkIndex > 0) {
      /* 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 += 16;
      __disable_irq();
      // replace the rtc_carry_isr handler with our own
      *(irq_ptr + eventLinkIndex) = (uint32_t)cacFERRIHandler;
      // replace the event link code (0x47 is CAC_FERRI)
      R_ICU->IELSR[eventLinkIndex] = 0x47;
      // clear any pending flags
      R_CAC->CAICR |= (R_CAC_CAICR_FERRFCL_Msk);
      R_ICU->IELSR[eventLinkIndex] &= ~(R_ICU_IELSR_IR_Msk);
      __enable_irq();
    }
  }
}
3 Likes

Turns out pin P400 can also be configured as CACREF. WHile we don't have access to 204 on the WiFi, 400 is there at the little IIC connector thingy. That might be an easier place to put something.

This is great! Huge improvement for my R4 WIFI.

One of the things my R4 WIFI does is control lighting based on time (even as an alarm clock), and the drift meant I had to reupload every night and it would still be off by ~5 minutes in the morning.

I added CAC_Correction yesterday, and this morning the drift was too small to tell, less than a minute. Which is already good enough, though I'll have to record some numbers to be more accurate.

This Arduino UNO R4 WiFi VRTC & OFF Pins | Arduino Documentation might be helpful.