Atmel 328 Timer0

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

I am trying to learn C++ and am working with Timers. I believe I have set up timer0 to issue an overflow interrupt when the count reaches the top (255) on my 328 running at 16MHz. I am trying to capture the timer count in the interrupt and send it to my screen with the uart. I know the uart takes its time so I believe I disabled interrupts during this transmission.

 

I am sure there are clock cycles that delay the whole process but if it overflows at 255 I was expecting to capture a count between 0 and 10 perhaps?

 

This is not happening and I am wondering where my misunderstanding lies.

 

Here is my current code and a sample output. Can someone point me in the right direction to accomplish the capture of the count at overflow. Should I use timer1 instead?

 

Thanks

 

#define F_CPU 16000000UL
#define BAUDRATE 9600
#define  BD ((F_CPU / (BAUDRATE * 16UL)) - 1)
#include <avr/io.h>
#include <stdio.h>        // Needed for sprintf
#include <avr/interrupt.h>

volatile double yy;

ISR (TIMER0_OVF_vect)
  {
    cli();                              // suspend interrupts
    yy=TCNT0;                    //Get timer counter
    uarttransmit(yy,1,0);        //Send to screen
    sei();                              // restore interrupts
  }

 

int main(void)
{
    uart_init();                             //Call the USART initialization code
    TCCR0B |=(1<<CS00);            //No prescale start timer
    TIMSK0 |=(1<< TOIE0);        //enable interrupt
    sei();                                    // Set interrupts
    yy=0;
    while(1)
        {
                               
        }
}

 

SAMPLE OUTPUT

 

41.000000
245.000000
207.000000
151.000000
77.000000
91.000000
69.000000
92.000000
79.000000
111.000000
14.000000
248.000000
237.000000
208.000000
161.000000
96.000000
119.000000
94.000000
99.000000
149.000000
138.000000
109.000000
75.000000
71.000000
31.000000
241.000000
167.000000
156.000000
127.000000
93.000000
89.000000
130.000000
29.000000
46.000000
39.000000
50.000000
254.000000
216.000000
160.000000
86.000000
100.000000
241.000000 

David Abineri

Last Edited: Sun. Sep 2, 2018 - 10:39 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You ARE getting interrupts but not the value you expect. 

 

For this first step, you should slow down the timer. WAAAAY down. With no prescaler, that means the timer is running at the CPU frequency. If THAT is 8MHz, you get an overflow every 0.125us*256 = 32us. That makes NO sense at all. Sending just 1 message at 9600 baud will take around 10ms, depending on the number of characters at 9600 baud. 

 

So, try a prescaler of 1024 which will give you and overflow every 262,114 CPU clocks. At 8MHz, that will be just over 32 ms and it should behave much more like you expect.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

Thanks, Jim, I also realized that I should set the counter back to zero right after resuming interrupts.

 

Now I am getting 39 in the timer counter consistently except ever 100 or so readings I get a count of 2.  Why would this be do you think?

 

Thanks, Dave

David Abineri

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

I would not even try to guess with such an absurd timer clock rate. For me, such an "experiment" makes no sense. What do you hope to gain out of it?

 

One reason for differences is that not all of the messages have the same number of characters.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

Last Edited: Sun. Sep 2, 2018 - 07:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jim, Good question, ultimately I need to be able to measure a 1000us to 2000us pulse width accurate to perhaps 100us for radio controlled motors.  Can this be done on a 328 do you think?

 

Many thanks,

 

Dave

David Abineri

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

If you are talking about RC servo motors, then, yes. There is lots of code out there to run those things from an M328. It takes some tricks to get the desired resolution at that pulse-width but it is quite workable.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

You will have no problem reading an RC servo pulse - look up Input Capture for the best method.

 

But you are doing a couple of problematic things in your ISR.  First, you never need to cli() or sei() (OK, except for very rare situations).  Second, you are doing an integer to float conversion, and then sending out a string of serial data, all inside a ISR, and that takes a LONG time, relatively speaking.  So what you are seeing is something like

 

INT - Enter ISR - start FP conversion - Another INT - Another INT - finish FP conversion - start sending UART - Another INT - Another INT - Another INT - finish sending UART - Exit ISR - Immediately re-enter ISR due to the dozens of interrupts that have happened since you entered the ISR the last time - Read whatever the timer value is when you re-enter the ISR.

 

Can you see the problem?  You are taking way, way too long in the ISR, and new interrupts are piling up while you are in the ISR, and when you exit the ISR you immediately re-enter it, again and again.

 

The way you want to use an ISR is short and fast something like this:

 

volatile uint8_t yy;  // NOT float or double!
volatile uint8_t flag = 0;

ISR (TIMER0_OVF_vect)
  {
    if (!flag)
    {
        yy=TCNT0;                   //Get timer counter
        flag = 1;                   // tell main loop you have new data
    }
  }

In your main loop, watch for flag go get set to 1.  When set, process your data in yy, then at the end, set flag to 0.

 

The testing of flag inside the ISR is optional, and only matters if you will be getting other interrupts before you can process the data.  This is generally a bad situation, since you will be missing data and only processing 1-of-N data samples.  For your experiment that's OK, but for real work that is almost certainly not acceptable.  If you can guarantee that you can process the data before the next interrupt, you can remove the !flag test in the ISR.  Or you can remove it anyway, and just work with whatever sample is in yy when your main loop starts processing.  For your experiment there will be no difference.

 

Note also that sharing data between an ISR and main code can lead to data corruption issues in the general case.  Your code needs to make sure the main loop is not in the middle or reading or writing data when an ISR begins that also reads or writes that data.  Read up on atomic or protected accesses.  Nothing will make you more crazy than this kind of random once-a-day bug.

Last Edited: Sun. Sep 2, 2018 - 08:07 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I understand  of what you describe here kk6gm but just to be clear, I was not concerned in this experiment about missing interrupts, I just want to see how consistent the count was when starting from zero.

 

I stopped interrupt while the uart was transmitting, then started interrupts setting the counter to zero  and knowing that interrupts would be missed but not caring in this experiment. The integer to float here also is not relevant because it does not matter how many interrupts I might miss before the count starts at zero again.

 

Many thanks for your comment.

 

Dave

 

 

David Abineri

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

dabineri wrote:
I stopped interrupt while the uart was transmitting

 

No, the timer continues to signal interrupts, they are just being ignored by the CPU, which is different. So they pile up, as explained in #7.

 

I think something like this should give results closer to what you were expecting:

ISR (TIMER0_OVF_vect)
  {
    yy=TCNT0;                    //Get timer counter
    TIMSK0 &=~(1<< TOIE0);        //disable timer interrupt
    uarttransmit(yy,1,0);        //Send to screen
    TIMSK0 |=(1<< TOIE0);        //enable timer interrupt
  }

 

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

Thanks, El Tangas.

 

kk6gm

 

I looked into using Input Capture but it seems that the 328 has only one Input Capture pin but with RC Servos, in my case, I need to be able to measure the PWM pulse width of each of the four channels. 

 

It seems as if Pin Change Interrupts might be more appropriate for this?

 

What do you all think or can Input capture be used somehow for 4 channels?

 

Thanks,  Dave

David Abineri

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

Yes, you can use pin change interrupts for multiple channels.  You just need to make sure your interrupts are disabled for much less than your desired accuracy (100 us).  Interrupts are disabled both when you execute cli(), and when you are in any ISR (the reason to keep your ISR code short and fast).

 

Just to make certain you understand why you were getting the numbers you were seeing, here is another attempt to explain graphically.  Top line is the timer value, generating interrupt requests at 0 (which are ignored inside the ISR)

 

0.#......................................255 0........................................255 0.................................#......255

INT request                                  INT request                                 INT request 

Enter ISR, read TCNT0 (#)................................................................................Exit ISR - Immediately re-enter ISR and read TCNT0 (#)

 

After the first time the ISR code runs, the entry into the ISR bears no relation to the actual time the most recent interrupt occurred, but is hopelessly behind the interrupts.

 

If you're old enough, think of the time Lucy and Ethel were working in the candy factory. laugh

 

Last Edited: Sun. Sep 2, 2018 - 11:56 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dabineri wrote:
  I am trying to learn C++

Your code seems to be just plain 'C' ?

 

The general recommendation is that learning the 'C' programming language is best done on a PC - without all the complications & inconveniences of a microcontroller.

 

If it really is C++ that you want, see: avr-c++ micro how-to in the  Compilers and General Programming forum ...

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

dabineri wrote:

I just want to see how consistent the count was when starting from zero.

As long as your interrupts are enabled when the timer overflows (including not being in any ISR at that time), your count should be within a couple of CPU cycles (I would expect 1 or 2 cycles).

 

BTW, if you want to use an 8-bit timer and 16 MHz clock to span 2000 us (32,000 CPU clocks), a prescaler of 256 will give a maximum duration of 4 ms, and a resolution of 16 us, so you should be good to go.

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

One more FYI is that 9600 is a very slow baud rate.

The 328 at 16MHz can transmit 2,000,000 baud!

 

--Mike

 

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

kk6gm wrote:
ISR (TIMER0_OVF_vect) { if (!flag) { yy=TCNT0; //Get timer counter

Noone mentioned the elephant in the room.  The sequence in the code fragment >>might<< have some marginal utility in the unusual case that the objective is to measure interrupt latency.  [but in that case it is counter-productive to add code before the capture...]

 

But on the surface, TCNT0 is going to have the value that the datasheet says causes the overflow--assuming the clock rate and prescaler allows such a capture of a useful value.  [remember before CTC we would need to do TCNTn += reload_value; to try to minimize latency?]

 

[edit] A forum search for "measure servo pulse" uncovers dozens of prior discussions.

 

OT:  "Prior" discussions may be rare; same with "Abbot" and even "Brother" for the common monks.

 

A common monk:

Image result for rhesus

Another:

Image result for brother cadfael

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Mon. Sep 3, 2018 - 01:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

kk6gm wrote:
a couple of problematic things in your ISR. First, you never need to cli() or sei()
Just to emphasise that point. A cli() in an ISR is generally pointless as the interrupting system clears the I bit in SREG on the way to the ISR() anyway. So doing a cli() is pointless but, on the whole, benign. What is very dangerous is the use of sei() within an ISR(). If the sei() is performed and then the next interrupt event occurs the ISR can be interrupted by itself. That raises questions of reentrancy but more to the point, if it keeps happening, the ISR prologue may swamp the stack and crash the CPU. So it's not really just a question of you "don't need sei() in an ISR" it's "unless you really know what you are doing and understand the exact sequencing of temporal events you must not sei() within an ISR".

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

Your interrupts are happening very fast: 65536 per second (2^24 Hz / 2^8 ??) .   This is way faster than the Uart command that is inside the interrupt can complete its operation. 

 

Your reading of timer0 will depend on :  overflow: stacking address: go to vector address, stack any saved registers like SREG and any registers used by the interrupt, and then read the Timer0 counter value.  With assembler this value will be maybe 0x0b, but with C, who knows?  Check the assembler listing produced by Studio.

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

Thanks for all the very helpful responses!  I am much better off than I was.   Dave

David Abineri