Using the hardware counter in Arduino

First of all, I would like to apologize in advance. I have sort of asked this question a few different ways. But since that first question, I have managed to get a little more up to speed and I think I can finally ask an intelligent, useful question to finally resolve this.

You see, I have an application where I need to detect 9 different tones and then act on those tones. The tones are 5V TTL level already, so no analog hardware is needed.

At first I was trying to trying to measure the frequency of the tones and asked questions because I was having a lot of trouble getting a stable reading. Then once I got a stable method, I was asking about how to put the routine into an interrupt (while dealing with noise rejection). I got all very good answers from others, but in the end what I thought I wanted is not really what I needed.

So, the 9 tones are 200HZ apart. They are all divisible by 200. (1400, 1600, 1800, etc...) So while experimenting with various methods it dawned on me that what I really wanted was to set up a hardware counter with a 5ms gate and count the number of times it overflows. This would give me 7 for 1400Hz, 8 for 1600Hz, etc...

The Freqcount library is doing exactly that. If I set the gate length to 5ms, I get a number like 7, 8,9, etc... and it even allows for the tones not being exact. This is just what I want.

Now, why I am asking yet another question:

The freqcount library has alot of features and overhead because it is intended for counting frequency. I also need to add some features into the concept to completely support what I am trying to do in my application. I already know the exact frequencies I want to detect and I already know that they will be exactly 200HZ apart.

I am also afraid of breaking the library trying to add my own parts to it because I definitely do not understand counters enough to know how to not break it.

I have a feeling that what I want to accomplish can be done in a dozen or so lines of code if I understood the hardware counter a bit more.

What I want to do:

  1. Set up the hardware counter to overflow every 5ms and generate an interrupt each time it overflows.
  2. In the interrupt, I want to keep a count of the number of times that it overflows.
  3. I do not currently need any PWM in my application, but would like to disturb the Arduino core as little as possible.

I have been trying to read different tutorials on the counters to understand how they work, but nothing seems to be clicking for me enough to implement the above.

I am not asking for hand-holding here, but if someone could point me to some great tutorials in the hardware timers (specifically in external counter mode) that have an application close to what I am trying above, I would be really appreciative.

Or, if someone would be gracious enough to show me some code to do the above and be kind enough to help me understand it, you would be my hero.

It seems like what I am looking to do is an abuse of the counters, because I can't seem to find any tutorials or examples that fit just right.

It could even be that I am not using the right terms.

One excellent timer tutorial is by Nick Gammon
Timers and Counters

The first example is a Timer1 counter gated by Timer 2.

In your case, with low frequencies and 5ms gate times you can simplify the code to not deal with Timer1 overflows and set TCNT1 to 0 at the start of each period.

You can probably use a software micros() timer instead of Timer2 for your gate intervals.

FreqCount library is pretty spare, and there is not much extra to a timer/counter sketch you would write.

1 Like

What should make the counter overflow? Overflow at which count of what?

I think that you need 2 timers: one timer for the 5ms interval, and one counter for the tone frequency.

1 Like

I think you are right. I probably just need to get a better understanding of counters and timers to know how to modify it for my needs. Thank you for the tutorial link. I will dig into it and see if I can come out understanding it better.

PMFJI
If the that tone signal is stable enough, why not just measure the duration of one pulse, I think able to get frequency.
But If that idea has already been rejected, sorry to you.
Because I haven't been check past posts... :pensive:

I actually started out doing that after measuring the frequency. The duty cycle is even supposed to be 50%. So grabbing just the first positive pulse and pre-calculating seemed at the time to make perfect sense, but unfortunately the tone frequency is not stable enough and I had complicated filtering code to try to make it fit in ranges. But when dealing with pulse widths, that was way too close together and it overlapped a lot (meaning my code interpreted the 1400Hz tone as 1600Hz or vice versa sometimes).

The tones are coming in over a radio remote as audio and then shaped. That shaping process isn't perfect, so it can sometimes create as much as 40Hz deviation from the intended tone. That is why the 'divide by 200Hz' approach seems to be working so perfectly. Since I am using an integer, anything 1400Hz to 1599Hz will divide into 7. If I round, I can get +-100Hz, but so far I have not needed to do that. The frequency deviation seems to always be above, not below. Using this, I haven't even needed any filter. But I do need to create some rejection for the spurious signals that sometimes look like the tones by taking 3 samples and only passing the result if they are all the same.

Since the tones are sent on a button press from a remote, I get a dozen or so milliseconds of the tone. So I should be able to sample about 3 times and get the same tone. That looks about right on my serial output during test. Any false tones seem to only appear for one measurement.

1 Like

I see.
AFAIK, I thought it would be easy to do that by using the timer in input capture mode and it would be quite possible to sample any number and read a stable value.
But it's not way your approach, I'm leaving.

Thanks explain for project. :blush: +1

1 Like

Sadly, that is exactly where I am getting lost with the counter use. I don't even know how to answer those questions.

It was a great suggestion. It would probably work perfectly if the tones were stable enough.

Even easier, you can use the pulseIn() function built into Arduino for that.

Yes.
But It blocks other all processing code during measurement.
I don't like the use of blocking code.
Also, the input capture by the hardware timer is clearly accurate than that.
And It doesn't block other processing during measurement.

1 Like

This tutorial shows the steps of generating "5 ms time tick" (5TT) using one of the TC (Timer/Counter) Modules of the ATmega328P MCU of the Arduino UNO Board.

1. There are three TC Modules inside the ATmega328P MCU, and these are named as: TC0/TCNT0, TC1/TCNT1, and TC2/TCNT2. Fig-1 has depicted the internal structure of TC1 and this is the module that will be used to genertae "1000 ms time delay" first and then "5 ms time delay".


Figure-1: Internal structure of TC1

2. TC1 is said to be working as a Timer-1 (T1) when it recievs clocking pulse (clkTc1) from internal 16 MHz oscillator after passing through the dividers (System Clock Prescaler and TC1 Clock Prescaler). In Arduino UNO, the System Clock Prescaler has been set at "divide by 1 (/1)" factor. The division factor of TC1 Clock Prescaler is set by the user as required which we will be seen while creating 5 ms Time Tick(5TT).

3. TC1 is said to be working as Counter-1 (C1) when it receives clocking pulse from external source via DPin-5. There is no division factor for this clock signal.

4. TC1 or TCNT1 is a 16-bit register having two parts - lower 8-bit named as TCNT1L and upper 8-bit named TCNT1H.

5. The TC1 can begin counting clkTC1 pulses from an intial value of 0x0000 (0) or from any other suitable presetValue (0xnnnn) and arrives at the maximum value of 0xFFFF (65535) and then goes to the full count 0x10000 (65536) after counting one more clkTC1 pulse (Fig-2). This is the time when "overflow/rollover" occurs and the TOV1 (TC1 Oveflow) flag assumes HIGH (1) state. A user program can continuously poll TOV1 flag to detect the event of overflow or an arrangement could be made to interrupt the MCU by the TOV1 flag. We will use polling strategy in our task of generating 5TT.


Figure-2:

6. Codes to genertae 1000-ms Time Tick (1000TT) using TC1 Module in order to blink L of Fig-1 at 1-sec interval. Step-7 shows how to generate 5TT.

void setup()
{
    Serial.begin(9600);     //PC/IDE is connected with UNO using UART port
    pinMode(13, OUTPUT);    //set direction of DPin-13 to drive L
    TCCR1A = 0x00; //1. write code to operate TCNT1 as up counter in Normal Mode
    TCCR1B = 0x00; //2. write code to keep TCNT1 in STOP condition.
    //3. assume clkTC1 = 16 MHz/1024 = 15625 Hz-----
    //4. Compute presetValue (0xC2F7) for 1-sec timeDelay at clkTC1=15625 Hz
    TCNT1 = 0xC2F7;//5. Write code to store preset value  
    TCCR1B = 0x05; //6. START TCNT1 with division factor of 1024 (Fig-3)
}

void loop()
{
     digitalWrite(13, HIGH);   // L is ON
     timeDelay();                   //insert 1-sec timeDelay to be generated by TCNT1
     digitalWrite(13, LOW);     //L is OFF
     timeDelay();
}

void timeDelay()
{
   //check that TOVe flag has assumed HIGH state; if not wait
   while(bitRead(TIFR1, TOV1) != HIGH)
   {
        ;  //wait
   }
   bitSet(TIFR1, TOV1);      //clear TOV1 flag by putting HIGH at this bit position
   //-------------------------------------------------------------------------------------
   TCNT1 = 0xC2F7;          //re-load preset value for 1-sec timeDelay
}

(1) Upload the above sketch in UNO.
(2) Press the RESET button.
(3) Check that L is blinkinhg at 1-sec (1000 ms) interval.

7. Recompute preset value using Fig-2 so that overflow occurs at 5 ms interval with clkTC1 at 250000 Hz. At 15625 Hz, we get fractional value which will not generate exact 5 ms time tick.

(1) (a) Example for 1000 ms (1-sec) Time Tick at clkTC1 = 15625 Hz
Overflow will occur after counting 15625 pulses.
So, the equation stands as:

Total Count = presetValue + Actual Count in 1-sec (Fig-2)
0x10000 - presetValue = 15625
==> presetValue = 0x1000 - 15625 = 65536 - 15625 = 49911 = 0xC2F7

(b) Example for 5 ms Time Tick at clkTC1 = 15625 Hz
Overflow will occur after counting 15625x(5/1000) pulses (78.125). It is a fractional value; so, change clkTC1 to a higher frequency to avoid fraction. Let us choose: clkTC1 = 16 000 000/64 = 250 000 Hz,

So, the equation stands as:
Total Count = presetValue + Actual Count in 5 ms (Fig-2)
0x10000 - presetValue = 250000*(5/1000)
==> presetValue = 0x1000 - 1250 = 65536 - 1250 = 64286 = 0xFB1E

(2) Load preset value 0xFB1E into TCNT1 (TCNT1 = 0xFB1E;)
(3) Load 0x03 into TCCR1B Register so that TC1 starts with division factor /64 (Fig-3) (TCCR1B = 0x03;)


Fig-3:

8. Final sketch to genertae 5 ms Time Tick using Polling Strategy

void setup()
{
    Serial.begin(9600);     //PC/IDE is connected with UNO using UART port
    pinMode(13, OUTPUT);    //set direction of DPin-13 to drive L
    TCCR1A = 0x00; 
    TCCR1B = 0x00; 
    TCNT1 = 0xFB1E;  //5 ms time delay at clkTC1 = 16 MHz/64 = 250000 Hz  
    TCCR1B = 0x03;   //START TCNT1 with division factor of /64 (Fig-3)
}

void loop()
{
     //do if ou have something to do
     timeDelay();     //spend 5 ms in wastage 
}

void timeDelay()
{
   //check that TOVe flag has assumed HIGH state; if not wait
   while(bitRead(TIFR1, TOV1) != HIGH)
   {
        ;  //wait
   }
   bitSet(TIFR1, TOV1);      //clear TOV1 flag by putting HIGH at this bit position
   //-------------------------------------------------------------------------------------
   TCNT1 = 0xFB1E;          //re-load preset value for 5 ms timeDelay
}

9. Blinking L of Fig-1 at 1-sec interval using TC1 and Interrupt Strategy

volatile bool flag = false;

void setup()
{
  pinMode(13, OUTPUT);    //DPin direction is output
  TCCR1A = 0x0000;        //up counter mode
  TCCR1B = 0x0000;        //TC1 is STOP
  TCNT1 = 0xC2F7;         //preset value for 1 sec timeDelay
  TCCR1B = 0x05;          //Start TC1 with division factor 1024
  //---------------------------------------------------
  bitSet(TIMSK1, TOIE1);    //local part of interrupu logic is enable; Fig-9.1
  bitSet(SREG, 7);           //global part (I) of interrupt logic is enabled; Fig-9.1
  //---------------------------------------------------
  digitalWrite(13, HIGH);  //initially L is ON
}

void loop()
{
  //do something else rather than waiting
}

//ISRTOV1 Rooutine
ISR(TIMER1_OVF_vect)   //ISR Routine for TOV1 interrupt
{
  //bitSet(TIFR1, TOV1); this code is not needed; the TOV1 is auto cleared
  TCNT1 = 0xC2F7;  //re-load preset value
  if (flag == false)    //1-sec has gone; OFF L 
  {
    digitalWrite(13, LOW);   //L is OFF from ON condition
    flag = true;
  }
  else    //toggle L from OFF to ON state
  {
    digitalWrite(13, HIGH);  //L is ON from OFF
    flag = false;
  }
}
1 Like

Thank you so much for this. It will take a bit to digest, but I think I do understand the gist of it.

It would have helped if I had explained what I was not understanding about the counter hardware, but I didn't really know how to explain it. I am a hardware guy, so I sat down and designed how I would implement this in discrete logic.

The "overflow" is for the gate timer. In our case 5ms.

So, for a discrete implementation, I would need some cascading decade counters to make up enough bits to cover my highest frequency I want to measure. I would then need a separate timer to generate a pulse every 5ms.

At every 5ms, I would latch the number in the counters and then reset the counter. The number I latched would be the number I am looking for.

In my case, for 1400Hz:

Every 5ms, the counter should read 7.

Because for every 5ms interval, I should see 7 pulse transitions for 1400Hz.

It was this that was not clicking for me. The counter is just free-running, counting up on rising edge of the incoming pulses. I stop the counter at whatever interval that I want to divide by, read the count, and then clear it to start counting again for the next interval. If the goal was to measure frequency, that interval would be 1 second.

The formula is 5ms/period (in milliseconds) instead of the typical 1s/freq(in hertz). So, basically 5/0.7144 for 1400Hz, the result of which is 7 rounded.

Now that I went through that exercise, a hardware counter example for measuring RPM probably would probably have made things click for me much more quickly because I wouldn't have been stuck on frequency. And the crazy thing is that I have coded for measuring RPM in assembly several times before. I just wasn't making the correlation.

If the above sounds like a convoluted way of understanding it, well that explains why I was having so much trouble. lol. My brain works in weird ways sometimes.

I think I am almost there! Thank you to all those that have replied. Off to do some learning.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.