Timer in CTC mode interrupts at irregular intervals

Go To Last Post
4 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Hello forumites,

 

Setup: ATmega324PB with internal 8MHz clock, using avr-gcc compiler on Atmel Studio 7.

 

Problem: When trying to use Timer 1 in CTC mode, I observe that the ISR is not being serviced at the regular interval I intend after the first request. This is either in the dynamic (adjustable) period mode I would like and fixed trigger periods that I tested to see what was wrong. So I present three example types of the ways I have tried to test this: ADJUSTABLE_MODE, TEST_FIXED_5SEC_COUNTER, TEN_MS_COUNTER.

 

 

I am new to programming AVRs. I have a custom Interface Board that is meant to accept digital inputs from the NI USB-6001 (whose pins will be in Open Collector mode). For now, the most basic thing I would like to do is see a pin toggle (on PD2) and use it to turn ON an output line on my Interface Board (PA1) and associated LED (on PD3). This is a stripped back, basic version of what I am actually trying to implement: an on-time 0-5000ms based on a lookup table (LUT) (using other digital inputs from the the NI 6001 and reading pin change interrupt lines on my ATmega324PB as index for the LUT with the ADJUSTABLE_MODE originally, and TEN_MS_COUNTER as a differnet attempt as the equation of (F_CPU/MS_TO_SECONDS)/PRESCALAR)*FrameLengthInMs lost some ticks in the arithmetic. However, the TEN_MS_COUNTER does appear to have a logic issue behind it, so please consider the other two modes with a greater weighting.

 


Initialisation on the Interface Board:

  1. PA1 and PD3 are set up as digital outputs with a low initial level.  
  2. All the Digital Input pins (PORT_DIR_IN) that will interface with the NI USB 6001 over a connector have an external 1K pull-up resistor to 5V, and not using the internal pull-ups (PORT_PULL_OFF). This is how ATMEL START set it the direction and pull mode (seemed a bit repetitious IMO, but whatever):

    static inline void PORTD_set_pin_dir(const uint8_t pin, const enum port_dir direction)

    {

        switch (direction) {

        case PORT_DIR_IN:

            DDRD &= ~(1 << pin);

            break;

        case PORT_DIR_OUT:

            DDRD |= 1 << pin;

            break;

        case PORT_DIR_OFF:

            DDRD &= ~(1 << pin);

            PORTD |= 1 << pin;

            break;

        default:

            break;

        }

    }

     

    static inline void PORTD_set_pin_pull_mode(const uint8_t pin, const enum port_pull_mode pull_mode)

    {

        if (pull_mode == PORT_PULL_UP) {

            DDRD &= ~(1 << pin);

            PORTD |= 1 << pin;

        } else if (pull_mode == PORT_PULL_OFF) {

            PORTD &= ~(1 << pin);

        }

    }

  3. In addition, unused pins are Digital Input with Internal Pull Ups enabled. 

Pseudocode for this:

 

main ()

{

Setup board. (Pin change interrupt on pins used for LUT index, Go Signal, Out Lines.)

Initilaise External Interrupt.

Initialise 16-bit Timer in CTC mode, without turning it on.

 

for(;;)

{

Run Trigger Application.

}

}

 

/**Trigger_Application.c**/

#define F_CPU 8000000

#define PRESCALAR 1024

#define MS_TO_SECONDS 1000

static const uint16_t ClockPulseFrequency = (F_CPU/MS_TO_SECONDS)/PRESCALAR;

static volatile bool g_okToReadRangeBits = false;

static volatile bool g_GoSignalReceived = false;

static volatile bool g_OpCompRegLoaded = false;

static volatile uint16_t g_MS_Value = 0;

 

ISR(PCINT1)

{

Set flag to store PINx value.

}

 

ISR(INT0)

{

Set flag to turn on Output Line.

}

 

ISR(TIMER1_Compare_Vect)

{

#if ADJUSTABLE_MODE

   Turn off LED and pull down PA1.

   Clear AlreadyLoaded flag.

#endif

 

#if TEST_FIXED_5SEC_COUNTER

   Stop the Timer - CS1[2:0] bits to 0.

   Turn off LED and pull down PA1.

   Clear AlreadyLoaded flag.

#endif

 

#ifdef TEN_MS_COUNTER

    g_MS_Value+=10; /* Tick happens every 10ms, so update running time value by 10. */    

#endif

}

 

Trigger_Application()

{

#if ADJUSTABLE_MODE

Read PINx value in LUTIndexValue.

 

if (FlagToStorePINxValue)

{

Retrieve period to maintain the O/P pin PA1 high - OnTimeForPin(LUTIndexValue).

}

 

If (Go Signal received && (OnTimeForPinA1>0))

{

         if (!AlreadyLoaded)

{

Calculate load value and store in a 16 bit variable. --(ClockPulseFrequency*FrameLengthInMs)/MS_TO_SECONDS);.

 

Load the timer compare value.

 

Start timer at F_CPU/1024 (From prescaler) - CS1[2:0] set here.

 

Set AlreadyLoaded flag to true.

}

}

#endif

 

#ifdef TEN_MS_COUNTER

    /* If Go Signal received and a non-zero frame length, start Timer 1.*/

    if (temp_GoSignalReceived && (FrameLengthInMs > 0))

    {

        cli();

        if (!g_OpCompRegLoaded)

        {

            Start timer at F_CPU/64 (From prescaler).

            g_OpCompRegLoaded = true;

            g_GoSignalReceived = false;

            

            Set high PA1 and turn on the LED.

        }

        sei();

    }

    

    cli();

    temp_ms_Value = g_MS_Value;

    sei();

    /* While count is true...*/

    if (temp_ms_Value)

    {

        /* Check the count of 10ms ticks has reached the framelength. */

        if (deadline_reached(FrameLengthInMs))

        {   

            If yes, reset the count.

            

            

            Stop the Timer.

            

            Pull down PA1 and turn off the associated LED.

        }

    }

    #endif

 

#if TEST_FIXED_5SEC_COUNTER       /*Used to see if I can get a regular 5 sec interrupt. */

If (Go Signal received)

{

         Start timer at F_CPU/1024 (From prescaler) - CS1[2:0] set here.

         Set Go Signal received flag to false.

         Set high PA1.

         Set high LED.

}

 

 

}

 

END PSEUDOCODE


 

ACTUAL CODE******

 

I am using the 16-bit Timer 1 in CTC mode (trying to). The Timer1 initiallisation, which occurs before the main() while(1) loop is:

 

#define F_CPU 8000000

#ifdef TEN_MS_COUNTER

#define T1A_PRESCALAR 64

#define T1A_TARGET_FREQUENCY 100

#endif

#ifdef TEST_FIXED_5SEC_COUNTER       

#define T1A_PRESCALAR 1024

#define T1A_TARGET_TIME_SEC 5

#endif

 

int8_t TIMER_1_init()

{

    /* Enable TC1 */

    PRR0 &= ~(1 << PRTIM1);

    

    TCCR1B |= (1 << WGM12); /* TC16 Mode 4 CTC */ /* Don't turn on the clock source yet. */

       

    #ifdef TEN_MS_COUNTER

    OCR1A = F_CPU/(T1A_PRESCALAR*T1A_TARGET_FREQUENCY) - 1; /* Output compare A: 1249 for an interrupt every 100Hz. */

    #endif

    #ifdef ADJUSTABLE_MODE

    // OCR1A = 0x00; /* Output compare B: 0x0 */

    #endif

    #ifdef TEST_FIXED_5SEC_COUNTER 

    OCR1A = ((F_CPU*T1A_TARGET_TIME_SEC)/T1A_PRESCALAR) - 1; /* Output compare A: 39062.5 for an interrupt every 5s. */

    #endif

 

    TIMSK1 = 1 << OCIE1A; /* Output Compare A Match Interrupt Enable: enabled */

 

   return 0;

}

 

The 'Go Signal' received from the NI 6001 arrives at PD2 to use INT0. This is set to interrupt at rising edge.

 

void EXTERNAL_IRQ_0_init(void)

{

    EICRA = (1<<ISC01) | (1<<ISC00); //ISC0[1:0] set to 11 so that the rising edge of INT0 generates an interrupt request.

    EIMSK = (1<<INT0); // Enable interrupt request on INT0.

}

 

 

For the Trigger_Application(), running in the while(1) loop:

 

 

/*** Trigger_Application.c ****/

 

#define PRESCALAR 1024

#define MS_TO_SECONDS 1000

static const uint8_t ClockPulseFrequency = 8; /* ~((F_CPU/MS_TO_SECONDS)/PRESCALAR). */ 

static volatile bool g_okToReadRangeBits = false;

static volatile bool g_GoSignalReceived = false;

static volatile bool g_OpCompRegLoaded = false;

static volatile uint16_t g_MS_Value = 0;

 

ISR(INT0_vect)

{

    g_GoSignalReceived = true;

}

 

ISR(TIMER1_COMPA_vect)

{

    #ifdef TEST_FIXED_5SEC_COUNTER    

    PA1_ENABLE_OP_set_level(false);

    PD3_ENABLE_LED_set_level(false);

    

    /* Stop the Timer */

    TCCR1B |=  (0 << CS12) | (0 << CS11) | (0 << CS10);

    g_OpCompRegLoaded = false;

    #endif

}

void Trigger_Application(void)

{

   

uint8_t CurrentRangeBits = 0;

    static uint_fast16_t FrameLengthInMs = 0;

    uint16_t CounterLoadValue = 0;

    bool temp_okToReadRangeBits = false;

    bool temp_GoSignalReceived = false;

    uint16_t temp_ms_Value = 0;    

        

#ifndef TEST_FIXED_COUNTER      /* AVR forum: this is common to the ADJUSTABLE and the TEN_MS_COUNTER modes.

    // Check if range bits must be updated.    

    cli();

    temp_okToReadRangeBits = g_okToReadRangeBits;

    sei();

    if (temp_okToReadRangeBits)

    {

        cli();

        /* Reset the shared variable to read RangeBits.*/

        g_okToReadRangeBits = false; 

        /* Store the frame length range as seen by the change on the RangeBits Port Input pins. */

        CurrentRangeBits = PINB;

        sei();

        

        /* Retrieve the detector frame length from the look up table.*/

        FrameLengthInMs = OnTimeForDetector(CurrentRangeBits);

    }

    #endif

   

    cli();

    /* Store current value of Go Signal and reset it. */

    temp_GoSignalReceived = g_GoSignalReceived;

    sei();

 

#ifdef ADJUSTABLE_MODE

    if (temp_GoSignalReceived && (FrameLengthInMs > 0))

    {        

        //cli();

        if (!g_OpCompRegLoaded)

        //sei();

        {

            /* Convert frame length to a counter value for the Timer Counter and

             * load the timer compare value register.

             * */

            OCR1A = (ClockPulseFrequency*FrameLengthInMs) - 1;

        

            /* Start timer at F_CPU/1024 (From prescaler). */

            TCCR1B |=  (1 << CS12) | (0 << CS11) | (1 << CS10);

            cli();

            g_OpCompRegLoaded = true;

            g_GoSignalReceived = false;

            sei();

        }

        

        PA1_OP_set_level(true);

        LED_set_level(true);

    }

#endif

    

#ifdef TEN_MS_COUNTER

    /* If Go Signal received and a non-zero frame length, start Timer 1.*/

    if (temp_xrayGoSignalReceived && (FrameLengthInMs > 0))

    {

        cli();

        if (!g_OpCompRegLoaded)

        {

            /* Start timer at F_CPU/64 (From prescaler). */

            TCCR1B |=  (0 << CS12) | (1 << CS11) | (1 << CS10);

            g_OpCompRegLoaded = true;

            g_GoSignalReceived = false;

            

            /* Enable source connected to PA1 and turn on the related LED. */

            PA1_ENABLE_OP_set_level(true);

            LED_set_level(true);

        }

        sei();

    }

    

    cli();

    temp_ms_Value = g_MS_Value;

    sei();

    /* While count is true...*/

    if (temp_ms_Value)

    {

        /* Check the count of 10ms ticks has reached the framelength. */

        if (deadline_reached(FrameLengthInMs))

        {   

            /* If yes, reset the count. */

            cli();

            g_MS_Value = 0;

            g_OpCompRegLoaded = false;

            sei();

            /* Stop the Timer */

            TCCR1B |=  (0 << CS12) | (0 << CS11) | (0 << CS10);

            /* Disable the source conected to PA1 and turn off the associated LED. */

            PA1_OP_set_level(false);

            LED_set_level(false);

        }

    }

#endif

 

#ifdef TEST_FIXED_COUNTER

    if (temp_xrayGoSignalReceived)

    {   

        /* Start timer at F_CPU/1024 (From prescaler). */

        TCCR1B |=  (1 << CS12) | (0 << CS11) | (1 << CS10);

        PA1_OP_set_level(true);

        LED_set_level(true);

        cli();

        g_GoSignalReceived = false;

        sei();

    }

#endif

}

 


In none of the cases do I get a regular or expected tick after the first request, as seen on a Picoscope. RED trace is the INT0 Go Signal, BLUE trace is the PA1 line monitored.

 

 

1. Adjustable case: The first instance is of near enough the right length. However, every subsequent updated framelenght request (or even unadjusted) depicts a lower period than expected. I can see the OCR1A register being loaded with the correct value.  

 

 

2. TEST_FIXED5SEC_COUNTER: I see the varying period for PA1 being high as shown in the trace below. The second Go Signal during the On Time is ignored, as is correct. But why the irregular intervals?

 

 

3. TEN_MS_COUNTER case: There appears to be a logic issue in this part of the code at the moment, but it's left in for a complete view of all I've tried.

 

I feel like it has something to do with the TCNT remainder. But as far as I understood, this should be reset in CTC mode. And I am only enabling the Timer clock source when ready to do so, and disabling them when I see it appropriate. 

 

Any suggestions would be much appreciated. I have spent far too long on what I had thought would be a trivial thing.

This topic has a solution.

AS

Last Edited: Tue. May 19, 2020 - 11:18 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I didn't have time to look at your code closely (got a phone meeting coming up), but this line of code is a huge problem:

 

 TCCR1B |=  (0 << CS12) | (0 << CS11) | (0 << CS10);

 

OR'ing any bit or value with 0 does not change the bit or value.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well...that's embarassing. It's even a personal hybrid copy-paste gotcha. Thank you for pointing that out, because that was literally the thing that was going wrong! I had also missed out disabling Timer 1 in my intended mode, hence why that example mode gave variable periods. 

 

I must now wear the cone of shame.

AS

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AdityaS wrote:
I must now wear the cone of shame.

It's a big club. smiley