ATmega2560 timer 5, interrupt and main code interactions.

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

UPDATE - I believe that I have found the problem.

 

The counter hits the top and both resets to 0 and sets the OCF1A interrupt bit in the TIFR5 register. If application code now accesses the timer value it will be very low and because the interrupt has not yet been serviced adding the needed 32,000 counts for the previous 32ms, the current total will be lower than the previous total.

 

This is a race condition between counter 5, rolling over, interrupt generation – servicing and application code.  There is a window where the rollover occurs the interrupt has not yet run and application code is getting a new time lapse lower value.

 

Anybody have experience with this configuration and know of an elegant solution? 

 

---------------------------------------------------------------------------------------------------

 

I have spent the last several days trying to get this to work. I have cut, chopped and hacked everything out that I can.

 

I'm hoping someone here can see the issue.

 

I am using timer 5 in a very simple configuration.  It is configured into CTC mode and uses compare register A to trigger an interrupt every 32ms (64,000 counts).  Using a divide by 8 pre-scaler and 16Mhz clock i get a 2Mhz timer clock which is 0.5us.  The timer counts 64,000 0.5us ticks and then generates an interrupt that adds 23,000 to a running total.  This configuration seems to work and will allow more than an hour to pass before a rollover would need to be handled.  I say that it works because in all of my testing anytime a break point is hit and i take time to look at the running total, its value is ALWAYS an exact integer multiple of 32,000.

 

When something needs to be timed the Timer:Init function is called to get a starting point.  This also seems to work.  When a break point is hit the usStart value is set correctly and does not seem to change unexpectedly while the code is running.

 

So where is the issue???  It's with the elapsed function.  Calling it repeatedly will randomly return values that are less than the previous value.  This should not be possible in the short term (before a rollover).  Taking any current total time and subtracting the starting time should always give a larger delta time.  I'm getting approximately a 100 to 150 errors a minute.

 

Here is some output from the main loop:

The loopCnt is the total number of times the error has occurred so far.

cnt1 is the number of times the entire process worked before the error.

newTest is the last value returned from the elapsed routine.

oldTest is the previous value returned from the elapsed routine.

delta is the difference the new and old values.

========================================= 
loopCnt         11 
cnt1         78180 

newTest 0x6CDE57 
oldTest 0x6D5B4B 
delta   0x7CF4 
========================================= 
loopCnt         12 
cnt1         31641 

newTest 0x72BA57 
oldTest 0x73374B 
delta   0x7CF4 
========================================= 
loopCnt         13 
cnt1          1528 

newTest 0x733757 
oldTest 0x73B44B 
delta   0x7CF4 
========================================= 
loopCnt         14 
cnt1         15223 

newTest 0x762557 
oldTest 0x76A24B 
delta   0x7CF4

 

What is interesting but so far of no help to me is that the delta is always very, very close to 32,000.  0x7CF4 = 31988.  This value changes up or down a few counts based on how i have changed the code while testing.

 

Thanks in advance for any help.

 

The timer code.

#include <avr/io.h>
#include <avr/interrupt.h>

#include "timer.h"

//*****************************************************************************

volatile uint32_t us;
volatile uint32_t usStart;

volatile uint32_t elapsed;
volatile uint16_t usCnt;

//*****************************************************************************
// Handle timer/counter 5 Compare Match A interrupt every 32ms,
// which is 64,000 0.5us counter ticks.

ISR(TIMER5_COMPA_vect)
{
  us += 32000UL; // 32,000 not 64,000 because 0.5us clocks are being counted.
}

//*****************************************************************************
//*****************************************************************************
//                              PUBLIC METHODS
//*****************************************************************************
//*****************************************************************************

// Create a Timer object.
Timer timer = Timer();

//*****************************************************************************
// Object constructor.
Timer::Timer()
{
}

//*****************************************************************************
// Setup timer 5 as follows:
// 16 MHz / 8 (using prescaler) yields 2Mhz (aka 0.5us).
// 0.5us * 64000 (using OCR5A) yields 32,000us or 32ms.
void Timer::setup(void)
{
  cli();

  us = 0U;

  // On 16 bit register pairs write the  high byte first.
  TCCR5A = 0x00U; // All stop, no compare outputs, no input capture,
  TCCR5B = 0x00U; // and no clock source.
  TCCR5C = 0x00U;

  TCNT5H = 0x00U; // Preset counter to 0.
  TCNT5L = 0x00U;

  OCR5AH = 0xF9U; // Output Compare A: 0xF9FF = 63,999.
  OCR5AL = 0xFFU;
  OCR5BH = 0x00U; // Output Compare B, not used.
  OCR5BL = 0x00U;
  OCR5CH = 0x00U; // Output Compare C, not used.
  OCR5CL = 0x00U;

  ICR5H  = 0x00U; // Input Capture, not used.
  ICR5L  = 0x00U;

  // Interrupt Enable Masks.
  TIMSK5 = (1U << OCIE5A);  // Only enable Timer 5 Compare A Match Interrupt.

  // Set Waveform Generation Mode Bits = CTC, TOP = OCRnA and prescaler = 8.
  TCCR5B |= ((1U << WGM52) | (1U << CS51));

  sei();
}

//*****************************************************************************

void Timer::Init()
{
  cli();

  // Divide the counter value by 2 because 0.5us clocks are being counted.
  usCnt = (TCNT5 >> 1);

  // Setup initial value.
  usStart = us + usCnt;

  sei();
}

uint32_t Timer::Elapsed()
{
  cli();

  // Divide the counter value by 2 because 0.5us clocks are being counted.
  usCnt = (TCNT5 >> 1);

  elapsed = (us + usCnt) - usStart;

  sei();

  return elapsed;
}

 

The main code.


#include <avr/io.h>

#include "Timer.h"
#include "Usart0.h"
#include "printf.h"

// ***************************************************************************

int main(void)
{
  timer.setup();
  Usart0Setup();

  // Test the timers
  uint32_t loopCnt = 0;

  timer.Init();

  do
  {
    uint32_t cnt1 = 0;
    uint32_t newTest = 0;
    uint32_t oldTest = 0;

    do
    {
      cnt1++;
      oldTest = newTest;
      newTest = timer.Elapsed();
    }
    while (newTest >= oldTest);

    loopCnt++;

    printf("========================================= \r\n");
    printf("loopCnt %10lu \r\n", loopCnt);
    printf("cnt1    %10lu \r\n\r\n", cnt1);

    printf("newTest 0x%8lX \r\n", newTest);
    printf("oldTest 0x%8lX \r\n", oldTest);
    printf("delta   0x%8lX \r\n", oldTest - newTest);
  }
  while (1);
}

 

This topic has a solution.
Last Edited: Fri. Nov 22, 2019 - 10:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ijststd wrote:
its value is ALWAYS an exact integer multiple of 32,000.
So my first question would be why bother with 32,000 increments if it will always be a multiple of 32,000 ? You might as well count up in ones and call the variable "thirty_two_thousand_us" instead of "us". That way if you current rollover is about 1 hour your new rollover will be 32,000 hours which is about 3.6 years.

 

As for your problem, if you don't want to break while running "elapsed" on the "watched kettle never boils" principle then log it's key variables to an array then stop everything after a few minutes then examine the array to see what was going on with the variables over a period of time.

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

Thanks for your response.

The counters real time value (which is from 0 to 32,000us) is added to a running total which is incremented by 32,000 whenever application code needs to know the elapsed time.  This yields us times with only a single interrupt every 32ms.

Please see my update to the original post.

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

updating previous posts - especially when there have already been replies - makes things very confusing as it destroys the sequence of discussion.

 

Much better to make a new post - in the proper sequence - with the update.

 

You can always quote your own post(s), if it helps.

 

Anyhow, if the problem is resolved - see Tip #5.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Will do.

 

Identifying the problem is the first step to solving it.  I am now trying different ways to detect the time between hardware counter rollover and interrupt completion so that corrective action can be taken in the fewest possible cycles.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The solution seems to be to simply detect when hardware counter rolls over and also that the interrupt has completed via a simple bool flag.

It is working and i will be testing it over the next couple of days to be sure.