Unexpected Timer/Counter B interrupt frequency on ATtiny204

68 views Asked by At

I'm trying to implement a timing system on an ATtiny204 using Timer/Counter B in Microchip studio but I'm getting a very unexpected interrupt frequency based on my register and fuse settings.

I have my OSCCFG.FREQSEL fuse set to the 20MHz clock, and I'm initializing the clock controller with the following two lines which should give me a 20Mhz CLK_CPU and a 312.5kHz CLK_PER

CLKCTRL.MCLKCTRLA = CLKCTRL_CLKSEL_OSC20M_gc; //Select internal 20Mhz oscillator
CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm; //Set peripheral clock to 1/64th of CPU clock

I'm initializing the Timer/Counter B module with the following lines:

TCB0.CCMP = MATCH_VALUE; //Some value such that we get 1000 interrupts per second
TCB0.CTRLB = TCB_CNTMODE_INT_gc; //Periodic interrupt mode
TCB0.EVCTRL = TCB_CAPTEI_bm; //Enable input capture event (probably not needed)
TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; //Use CLK_PER not CLK_PER/2 and enable timer
TCB0.INTCTRL = TCB_CAPT_bm; //Enable interrupt

From what I read in the datasheet, in periodic interrupt mode, the timer counts until there's a match between its value and TCB0.CCMP then triggers the interrupt, and starts counting again from zero. The interrupt handler just increments a master timestamp uint16_t and flips a pin on and off so I can read the interrupt frequency with a logic analyzer, and clears the interrupt flag.

ISR(TCB0_INT_vect){
    if(++master & 1)
        VPORTA.OUT &= ~_BV(4);
    else
        VPORTA.OUT |= _BV(4);
    TCB0_INTFLAGS = TCB_CAPT_bm; //Clear the flag that triggered this interrupt
}

From my understanding, the proper value for MATCH_VALUE should be 20000000/64/1000 which is about 312, but this value gives me an interrupt frequency of about 10.6kHz.

I've found from experimentation that 3360 (0xD20) gives me almost exactly a 1kHz signal, but I cannot figure out how that makes sense mathematically based on the settings.

I originally thought my microcontroller was damaged and the clock was just going at the wrong speed, but I tried the firmware on a new chip and got the same behavior.

What is wrong in my code or my math? Is it safe to use my value from experimentation or have I done something that will cause other issues?

2

There are 2 answers

0
emacs drives me nuts On BEST ANSWER

Some special function registers (SFRs) on Xmega devices have a configuration change protection (CCP). Before writing to such an SFR, one has to write a special pass-byte to the CCP register, think "password".

Also this is a timed sequence which means that the write to the target SFR has to be carried out within a few CPU cycles after writing the correct byte to CCP. This means

  • C/C++ is unsafe because you have no control over the instructions the compiler generates and over its timings.

In addition, interrupts have to be disabled. As far as I remember, writing the right pass-byte to CCP starts an atomic sequence for some cycles, but better check against the data sheet. In that case, no explicit disabling IRQs is required even when IRQs might be on.

AVR-LibC provides an inline assembly macro to facilitate such writes:

#include <avr/io.h>
// In some function:
    // Select internal 20 MHz oscillator.
    _PROTECTED_WRITE (CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc);
    // Set peripheral clock to 1/64th of CPU clock.
    _PROTECTED_WRITE (CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm);

In addition, there is ccp_write_io() in avr/cpufunc.h which calls a library function (and thus would generate more overhead in general). Also mind AVR-LibC issue #906.

0
Willis Hershey On

I missed a section of the datasheet that said that CLKCTRL.MCLKCTRLA and CLKCTRL.MCLKCTRLB were write-protected, so writing to those registers was being silently ignored by the CPU and the default CLK_CPU -> CLK_PER prescaler value of 6 was being used instead of the expected value of 64, which I based my math on.

CPU_CCP = 0xD8; //Unprotect CLKCTRL.MCLKCTRLA
CLKCTRL.MCLKCTRLA = CLKCTRL_CLKSEL_OSC20M_gc; //Select internal 20Mhz oscillator
CPU_CCP = 0xD8; //Unprotect CLKCTRL.MCLKCTRLB
CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm; //Set peripheral clock to 1/64th of CPU clock

After the insertion of those two lines the frequency became what I was expecting.