Arduino Nano Every - setting up timer interrupt ISR

Hi all,

First off, a big thank you to all the contributors to these forums, they are an invaluable learning and reference resource. I've been reading the forums for quite a while but this is my first message here so apologies if I've chosen the wrong section or otherwise flouted some rule I wasn't aware of.

Now to my issue.

I have a pH/temperature logger based on an Arduino Nano. I would like to add some extra functionality and several more pH buffer temperature dependence tables, but as it is, the program code takes 99% of the available memory. This is mostly because it includes a complete menu-driven user interface (with ANSI colour/cursor control etc) via PuTTY. At some point I will rewrite this to talk to a PC-based client which will dispense with the user interface and free a lot of the memory, but I'm not there yet (learning C++ as I go along). In any case, it works well as it is, we've had one in the lab where I work for close to a year now and I've recently had to build a second one because of high/near constant demand.

Then I've heard of the new Arduino Nano Every. Pin-compatible with the original Nano but with 50% more program memory. Sounded like exactly what I needed, so I got two of these. But since it runs on a different processor, the register architecture and names have been changed around. Which means that the bit where my code sets up a timer interrupt does not compile.

I've got the ATMega 4808/4809 datasheet and will at some point crunch my way through it and hopefully eventually understand how to set up a timer ISR on a Nano Every from first principles, but right now I'm being lazy and would just like the damn thing to work.

I found the link below, but it's no help as it doesn't compile for me.

https://forum.arduino.cc/index.php?topic=630695.0

So my question is, could somebody rewrite the code below (register names etc) so it works on an Arduino Nano Every, or point me to somewhere that explains how to do it succinctly (i.e. not a link to the ATmega 4808/09 datasheet, I can do that myself)?

Here is the bit that sets up the timer:

void menu_system::setup_timer1_ISR(volatile unsigned int ms) {
    cli();                                 // disable all interrupts
    TCCR1A = 0;                         // set TCCR1A register to 0
    TCCR1B = 0;                         // same for TCCR1B
    TCNT1 = 0;                          // initialize counter value to 0
    TCCR1B |= (1 << WGM12);                     // turn on CTC mode
    TCCR1B |= (1 << CS12) | (1 << CS10);                // Set CS10 and CS12 bits for
                                    // 1024 prescaler
    if (ms == 0) {                          // ISR will be disabled if ms is zero
        TCCR1B |= (0 << CS12) | (0 << CS11) | (0 << CS10);
                                    // Disable counter
        TIMSK1 |= (0 << OCIE1A);                // disable timer compare int
    } else {                            // calculate OCR1A value for
                                    // requested interval
        float freq = 1.0/(float(ms)/1000.0);            // Interrupt frequency
        float f = freq * 1024;                  // Multiply by prescaler
        long int i = 16000000ul / (long int)f;          // Divide clock speed by that
        i -= 1;                         // Subtract 1
        OCR1A = int(i);                     // Set OCR1A register
        TIMSK1 |= (1 << OCIE1A);                // Enable timer compare ints
    }
    sei();                                      // re-enable all interrupts
}

And here is the ISR:

ISR(TIMER1_COMPA_vect) {
    if (menu_system::ISR_entered) return;
    menu_system::ISR_entered = true;
    menu_system::draw_status();
    menu_system::ISR_entered = false;
}

Thanks, Bart

You say you have memory problems. Did you move as much of your data, e.g. “several more pH buffer temperature dependence tables” to flash memory, from RAM?

What did you find in the ATmega 4808/09 datasheet?

Hi aarg,

The problem is with a lack of program memory, not dynamic memory. But yes, all strings and other large constant data structures are in progmem, while the existing buffer tables are stored in EEPROM (the EEPROM is pre-loaded with the buffer tables by another sketch first before uploading the actual firmware).

Bart

Regarding the 4808 datasheet, I’ve skimmed through it but haven’t properly read it, never mind trying to assimilate it. That’s the point of my question. I will put the time in at some point but at the moment, I’d just like a quick fix. Someone must have run into this problem when trying to make Nano timer interrupts work on a Nano Every and figured it out. For now, that’s all I’m looking for.

Bart

plymdiver: ...but right now I'm being lazy and would just like the damn thing to work.

Then head on over to Gigs and Collaborations and pay somebody to do it for you. This forum is for helping people write their own code.

gfvalvo:
Then head on over to Gigs and Collaborations and pay somebody to do it for you. This forum is for helping people write their own code.

Some learning is by analysing and adapting other peoples’ code, so my question is valid and topical in this forum. Fyi, the entire firmware for the pH/temperature logger is my own code, help is only required with the one tiny (but crucial) timer part.

You really have to compare the timer code that you posted with the information in the ATmega 4808/09 datasheet, and make an attempt to modify it. Otherwise, you're not analyzing or adapting anything, so there isn't anything to "help" with. Specifically, you haven't posted any of your own code. That is the difference between getting help, and having someone write it for you.

Regarding putting constants in PROGMEM, the atmega4809 uses a single address space for both the flash memory and dynamic ram, so there is no PROGMEM as such, the same instructions can access either type memory. My experience is that the compiler will store constants in flash memory automatically.

There is a specific forum on here for the Nano Every, you might check there, as well as the forum for the Uno WiFi rev 2, which also uses the atmega4809.

aarg: datasheet, and make an attempt to modify it. Otherwise, you're not analyzing or adapting anything, so there isn't anything to "help" with. Specifically, you haven't posted any of your own code. That is the difference between getting help, and having someone write it for you.

Incorrect. The analysing and adapting would have been done by myself once I'd received a functional piece of code to look at. That is also help.

The idea behind my original post was that, if there is someone who has come across this issue and solved it, would they mind posting their register setup code here so I can use it for the quick fix that I needed, then look at it in depth to understand it, when I have the time to do so. It's all of 5 lines of code, so it's not like I'm asking someone to write an entire application and it would have saved me a lot of time. If nobody had that code already worked out and handy, then that's fair enough and I'm not asking anyone to spend time on my behalf. I worded my request badly in the original post, and that is the only thing that I will apologise for.

As it is, I ended up spending a couple of hours with the datasheet and figured it out by myself. I'll post the code here so that if anyone else needs help with this, they can analyse and adapt it.

I'll do this in 5 minutes since, as a "newbie" on these forums, it won't let me post more than one message every 5 minutes.

After some time poring over the 4809 datasheet (and what quality time it was!) and playing with the Nano Every and a 'scope, I figured out how to use the Arduino Nano Every's TCA0 timer/counter for periodic interrupts. Below is some example code. It's not particularly elegant as it doesn't use the named bit mask, group, and value constants from the datasheet, but it works. I may rewrite this using the named constants in the future.

I'll do the same for the TCBx counters when I have the time to do so.

// Arduino Nano Every Timer/Counter A example
// =========================================================
// There is one type A timer/counter (TCA0) and it has three
// compare channels (CMP0, CMP1, CMP2).
//
// The clock prescaler (bits 3:1 of CTRLA register) is set to
// 64 by default and changing it breaks delay() and
// millis() (and maybe other things, not checked):
// increasing/decreasing it increases/decreases the length
// of "a second" proportionally. It also changes the PWM
// frequency of pins 5, 9, and 10, although they will still
// do PWM (increasing/decreasing the prescaler value
// decreases/increases the frequency proportionally).
//
// The period value (PER register) is set to 255 by default.
// Changing it affects the duty cycle of PWM pins (i.e. the
// range 0-255 in analogWrite no longer corresponds to a
// 0-100% duty cycle).
//
// PWM pin 5 uses compare channel 2 (CMP2), pin 9 channel
// 0 (CMP0), and pin 10 channel 1 (CMP1).
//
// If you don't mind losing PWM capability (or at least
// changing the frequency and messing up the duty cycle)
// on pins 5, 9, or 10, you can set up these channels for
// your own use.
//
// If you don't mind messing up delay() and millis(), you
// can change the prescaler value to suit your needs. In
// principle, since the prescaler affects millis() and
// delay() in an inversely proportional manner, this could
// be compensated for in your sketch, but this is getting
// messy.
//
// If the prescaler is not changed, the lowest interrupt
// frequency obtainable (by setting PER and CMPn to 65535)
// is about 3.8 Hz.
//
// The interrupt frequency (Hz) given a prescaler value
// and PER value is:
// f = 16000000 / (PER + 1) / prescaler
//
// The PER value for a required frequency f is:
// PER = (16000000 / prescaler / f) - 1
//
// The prescaler can take a setting of 1, 2, 4, 8,
// 16, 64, 256, or 1024. See the ATmega 4809 datasheet
// for the corresponding bit patterns in CTRLA.
//
// http://ww1.microchip.com/downloads/en/DeviceDoc/megaAVR-0-series-Family-Data-Sheet-40002015C.pdf
//
// The program below demonstrates TCA0 interrupts on
// compare channel 1, with and without changing the
// prescaler (change the value of CHANGE_PRESCALER
// from 0 to 1).
//
// PER is the TOP value for the counter. When it
// reaches this value it is reset back to 0. The
// CMP1 value is set to the same value, so that an
// interrupt is generated at the same time that the
// timer is reset back to 0. If you use more than
// one compare channels at the same time, all CMPn
// registers must have a value smaller or equal to PER or
// else they will not generate an interrupt. The
// smallest obtainable frequency is governed by the
// value of PER.
//
// With CHANGE_PRESCALER left at 0, the prescale
// factor is left at its default value of 64, and the
// value of PER is set to the maximum (65535). The
// interrupts fire approx 3.8 times per second while
// the main program prints text to the terminal
// exactly once per second using millis().
//
// When CHANGE_PRESCALER is changed to 1, the prescaler
// factor is changed to 256. The value of PER is calculated
// so that the interrupts will fire exactly once per
// second. The main program still uses millis() to
// print text to the terminal once every 1000 milliseconds
// but now a "second" takes four seconds. This could be
// compensated for by waiting for only 250 milliseconds.
//
// Change CMP1 to CMP0 or CMP2 as required. Change indicated
// bit values too.



#define CHANGE_PRESCALER    0
volatile bool i = false;

void setup() {
    Serial.begin(115200);
    unsigned int per_value;
    cli();
#   if CHANGE_PRESCALER == 0                        // Use default 64 prescale factor
        per_value = 0xFFFF;                         // Use maximum possible PER/CMP value (65535)
        TCA0.SINGLE.PER = per_value;                // Set period register
        TCA0.SINGLE.CMP1 = per_value;               // Set compare channel match value
        TCA0.SINGLE.INTCTRL |= bit(5);              // Enable channel 1 compare match interrupt.
                                                    // Use bit(4) for CMP0, bit(5) CMP1, bit(6) CMP2
#   elif CHANGE_PRESCALER == 1                      // Change prescale factor
        TCA0.SINGLE.CTRLA = B00001101;              // Prescaler set to 256. Use the following for
                                                    // other prescalers:
                                                    // B00000001    1
                                                    // B00000011    2
                                                    // B00000101    4
                                                    // B00000111    8
                                                    // B00001001    16
                                                    // B00001011    64
                                                    // B00001101    256
                                                    // B00001111    1024
        per_value = 0xF423;                         // Value required for 1 Hz (62499)
        TCA0.SINGLE.PER = per_value;                // Set period register
        TCA0.SINGLE.CMP1 = per_value;               // Set compare channel match value
        TCA0.SINGLE.INTCTRL |= bit(5);              // Enable channel 1 compare match interrupt.
                                                    // Use bit(4) for CMP0, bit(5) CMP1, bit(6) CMP2
#   endif
    sei();
}

void loop() {
    unsigned long t = millis();
    int alive_count = 0;
    while(true) {
        if (i) {
            i = false;
            Serial.println("Interrupt!");
        }
        if (millis() - t >= 1000) {
            Serial.print("Still alive ");
            Serial.println(alive_count++);
            t = millis();
        }
    }
}

ISR(TCA0_CMP1_vect) {
    cli();
    i = true;
    TCA0.SINGLE.INTFLAGS |= bit(5);                 // Clears the interrupt flag. Rather confusingly,
                                                    // this is done by setting a bit in the register
                                                    // to 1.
                                                    // Use bit(4) for CMP0, bit(5) CMP1, bit(6) CMP2
    sei();
}

david_2018: Regarding putting constants in PROGMEM, the atmega4809 uses a single address space for both the flash memory and dynamic ram, so there is no PROGMEM as such, the same instructions can access either type memory. My experience is that the compiler will store constants in flash memory automatically.

There is a specific forum on here for the Nano Every, you might check there, as well as the forum for the Uno WiFi rev 2, which also uses the atmega4809.

Thanks for your reply. Didn't know about the Every sub-forum, I'll keep it mind for future reference. As for instructions being able to access the entire memory space, would that mean a sketch could modify its own code? I thought there were protections against that in the fuses and/or the NVM controller (I've skimmed those bits of the datasheet)...