Go Down

Topic: Not able to read 16bit Counter value (Read 462 times) previous topic - next topic

joeaverage

Hi,

Quote
Please, for the love of pete just try this damned code and tell me what happens. 
My apologoies for being so obtuse...I will try that and report.
In the meantime however the suggestion:

TCCR5A=0; has proven to be correct.

This code works and produces genuine 16bit outputs.

Quote
const byte interruptPin=2;
volatile unsigned int inputPeriod;
void setup() {
 pinMode(interruptPin,INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(interruptPin), inputTimer, FALLING);
 TCCR5B = (1<<CS50) | (1<<CS51)| (0<<CS52); //set the pre-scalar as 64
 TCNT5=0x0000;
 TCCR5A = 0;
 Serial.begin(9600);
 interrupts();
 }
void loop() {
  Serial.println(inputPeriod,HEX);
    }
void inputTimer(){
inputPeriod=TCNT5;
TCNT5=0x0000;
}
At least now I can measure the input period of the input signal. I can now code the
manipulation required and set up another timer to generate an output.
That will require that I use output compares so the tips about reading and writing
the two bytes in the correct order and also disabling interrupts while the read/write
is in progress using cli() and sei() will be invaluable to me.

Many thanks to all who contributed to my understanding.

Craig


gfvalvo

That will require that I use output compares so the tips about reading and writing
the two bytes in the correct order and also disabling interrupts while the read/write
is in progress using cli() and sei() will be invaluable to me.
If you read / write the register with 16-bit variables, the compiler will take care of low / hi order bytes stuff for you. Don't continue to over-think this.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

joeaverage

Hi,

Quote
f you read / write the register with 16-bit variables, the compiler will take care of low / hi order bytes stuff for you. Don't continue to over-think this.
That is what I had anticipated and hoped for, but I came unstuck. Clearly I had left some part
of the timer setup unattended or undefined. Once that oversight had been corrected everything
worked fine without having to 'over think it'.

The way I am envisioning the code to work out that the critical read/writes to and from
the timer registers will all ocurr within one or more ISRs covering those timers.

I believe all, or prehaps most, interrupts are disabled when executing an ISR. If my understanding is
correct then specifically disabling interrupts to protect 16 bit read/writes may not be required.

Craig

Delta_G

Hi,

That is what I had anticipated and hoped for, but I came unstuck. Clearly I had left some part
of the timer setup unattended or undefined. Once that oversight had been corrected everything
worked fine without having to 'over think it'.

The way I am envisioning the code to work out that the critical read/writes to and from
the timer registers will all ocurr within one or more ISRs covering those timers.

I believe all, or prehaps most, interrupts are disabled when executing an ISR. If my understanding is
correct then specifically disabling interrupts to protect 16 bit read/writes may not be required.

Craig
You are correct.  Inside an ISR you won't need to disable anything and you can just access the register.  The interrupts are already off there. 

BUT the other variables that you use in the ISR to take the readings WILL need critical sections when they're read in the loop code if they're larger than 8 bits. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

joeaverage

Hi,

Quote
BUT the other variables that you use in the ISR to take the readings WILL need critical sections when they're read in the loop code if they're larger than 8 bits. 
Thanks, very plainly and simply put, makes perfect sense.

Craig

cattledog

Quote
I believe all, or prehaps most, interrupts are disabled when executing an ISR. If my understanding is correct then specifically disabling interrupts to protect 16 bit read/writes may not be required.
The incrementing of TCNTx by the external clock source trigger is not interrupt driven.

Typically, the procedure used to read TCNTx is to stop the counter, make the reading, and restart the timer counting.  This is similar to the disabled interrupts for protected readings.

I don't think that a missed input is buffered/queued like an interrupt, but typically the external clock source is used for a gated time period, and small time gaps in the readings are usually not important for the application.

joeaverage

Hi,

Quote
The incrementing of TCNTx by the external clock source trigger is not interrupt driven.
But I'm not using an external clock source, I'm using the internal 16MHz clock prescaled to
250kHz or 4us per tick.

The external signal causes an interrupt which one records the value of TCNTn at the instant
of the interrupt and resets TCNTn so that it may time the next pulse.

Craig

Delta_G

The incrementing of TCNTx by the external clock source trigger is not interrupt driven.

Typically, the procedure used to read TCNTx is to stop the counter, make the reading, and restart the timer counting.  This is similar to the disabled interrupts for protected readings.

I don't think that a missed input is buffered/queued like an interrupt, but typically the external clock source is used for a gated time period, and small time gaps in the readings are usually not important for the application.
I thought that was the whole point of the double buffered read, so you could read the whole 16 bits without having to stop the timer. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

cattledog

Quote
I thought that was the whole point of the double buffered read, so you could read the whole 16 bits without having to stop the timer.
Yes, on thinking this through, and looking at the data sheet you are correct.

Quote
But I'm not using an external clock source, I'm using the internal 16MHz clock prescaled to 250kHz or 4us per tick.
Sorry, I've been misreading the CS set up, and my postings have been off base.  :(

Quote
The external signal causes an interrupt which one records the value of TCNTn at the instant
of the interrupt and resets TCNTn so that it may time the next pulse.
You may want to look at the Input Capture interrupt instead of an external interrupt to catch the TCNT values. There is less latency. See this Nick Gammon posting
https://www.gammon.com.au/forum/?id=11504&reply=12#reply12

Delta_G

You may want to look at the Input Capture interrupt instead of an external interrupt to catch the TCNT values. There is less latency. See this Nick Gammon posting
https://www.gammon.com.au/forum/?id=11504&reply=12#reply12
Has the added benefit of stopping the timer for you to read.  This will get you more accurate results as with the current method the timer is still running for the few cycles it takes to call the interrupt. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

jremington

#40
Aug 22, 2019, 03:40 am Last Edit: Aug 22, 2019, 03:43 am by jremington
Glad to see the foregoing problems were user error.

I highly recommend Nick Gammon's Input Capture tutorial.  The following is based on his code, which together with a GPS precision 1PPS output as a trigger, is able to measure the frequency of the 16 MHz system clock to 1 Hz.

Code: [Select]
// Frequency timer using input capture unit
// Author: Nick Gammon
// Date: 31 August 2013
// added averaging JR 2015
// Input: GPS 1PPS capture signal on Pin D8

volatile boolean first;
volatile boolean triggered;
volatile unsigned int overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
{
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;

  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;

  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
  {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;
  }

  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
}  // end of TIMER1_CAPT_vect

void prepareForInterrupts ()
{
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet

  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
}  // end of prepareForInterrupts


void setup ()
{
  Serial.begin(115200);
  Serial.println("Frequency Counter");

  pinMode(8, INPUT_PULLUP);
  pinMode(7, OUTPUT);
  digitalWrite(7, LOW);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);


  // set up for interrupts
  prepareForInterrupts ();
} // end of setup

void loop ()
{
  static unsigned long average = 0;
  static int n = 0;
  // wait till we have a reading
  if (!triggered)
    return;

  PINB |= (1 << 5); //blink LED

  // period is elapsedTime, clock ticks per second

  unsigned long elapsedTime = finishTime - startTime;
 
  Serial.println (elapsedTime);
  average += elapsedTime;
  n++;
  if (n == 10) {
    Serial.print("Clock period, average of ten: ");
    Serial.println(average / 10);
    n = 0;
    average = 0;
  }

  // so we can read it
  delay (500);

  prepareForInterrupts ();
}   // end of loop




joeaverage

Hi,
I have written the code and tested it out.

My original intention was to have a pulse conversion device of reasonable accuracy over the
range 3Hz to 300Hz for the purpose of an automotive speedometer correction device.

The lower limit turned out to be 4Hz, still very useable. At 300Hz I have an error of 0.3%
and is still under 1% at 1500Hz. All in all very satisfactory.

I have used float conversions, float divides and then float to integer re-conversions in the
main loop. If I were to expend some time and effort I could probably increase the overall
accuracy by reducing the CPU load due to these calculations. Having said that the
actual performance as is more than adequate.

To cattledog: it was necessary to write to a 16 bit output compare register and I did
use cli() and sei() to protect the write. Thanks for the tip.

Many thanks to those who helped.

Craig


gfvalvo

Has the added benefit of stopping the timer for you to read.
AFAIK, it doesn't "stop the timer". It captures the timer's value "soon after" the selected input edge occurs and stores it in a register for you to read. What "soon after" means is described in the datasheet. The timer continues to run. Thus, you can use successive input captures to measure the time between successive edges (within the accuracy of "soon after").
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

Delta_G

AFAIK, it doesn't "stop the timer". It captures the timer's value "soon after" the selected input edge occurs and stores it in a register for you to read. What "soon after" means is described in the datasheet. The timer continues to run. Thus, you can use successive input captures to measure the time between successive edges (within the accuracy of "soon after").
Yeah, I meant in the figurative sense.  It gives you a freeze frame of the exact moment if you will. 

It's also important that you don't reset the counter register to 0 in the ISR when you do input capture.  That causes you to lose a few cycles between the trigger and the reset.  Just get a copy of the ICRx register and change the edge on the trigger if you need to.  When the next trigger fires the next interrupt you can just subtract and the 16 bit unsigned rollover works just like it does with Blink Without Delay so you keep getting an accurate count. 
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

GolamMostafa

#44
Aug 24, 2019, 08:13 am Last Edit: Aug 24, 2019, 08:47 am by GolamMostafa
@OP

TCCR5A=0; has proven to be correct.

This code works and produces genuine 16bit outputs.
Why has 'the process of TCCR5A = 0' solved the problem when TCCR5A Register contains 0x0000 at power up? Do you think that the value has got changed after the Arduino MEGA has taken over the control? What is the changed value and how does it contradict with the Normal Mode operation of TCNT5 that results in getting always 0x00 at the upper part of TCNT5?

Go Up