0
\$\begingroup\$

I'm using atmega8 running at 1MHz. I wrote a function that counts the milliseconds and it was working flawlessly.

However, when I modified the code to count microseconds the result was weird to my eye, e.g. instead of blinking the LED each second, the LED was turned on for one second and then turned off permanently.

Is there anything I'm not aware of? Could it be related to the fact that timer2 is 8bit (some overflows happening)?

The timer.c (commented snippets are working)

#define F_CPU 16000000UL

#include <avr/interrupt.h>
#include <util/atomic.h>
#include "../include/timers.h"

volatile unsigned long milliseconds;
volatile unsigned long microseconds;

ISR (TIMER2_COMP_vect)
{
    microseconds += 50;

    if (microseconds % 1000 == 0) {
        milliseconds++;
    }
}

void timers_init(void)
{
    TCCR2 |= (1 << WGM21);

    //Set the prescaler to 8
    TCCR2 |= (1 << CS21);

    //OCRn = [ (Clock / Prescaler) * Seconds ] - 1
    OCR2 = 99;

    TIMSK |= (1 << OCIE2);

    sei();
}

unsigned long timers_millis(void)
{
    unsigned long millis;

    ATOMIC_BLOCK(ATOMIC_FORCEON) {
        millis = milliseconds;
    }

    return millis;
}

unsigned long timers_micros(void)
{
    unsigned long micros;

    ATOMIC_BLOCK(ATOMIC_FORCEON) {
        micros = microseconds;
    }

    return micros;
}

And calling the LED blinker

while (1) {
    if (timers_micros() % 1000000 == 0) {
        leds_on(LEDS_REGISTER_PORT, LEDS_PORT_LEFT);
    } else {
        leds_off(LEDS_REGISTER_PORT, LEDS_PORT_LEFT);
    }

    if (timers_millis() % 1000 == 0) {
        leds_on(LEDS_REGISTER_PORT, LEDS_PORT_RIGHT);
    } else {
        leds_off(LEDS_REGISTER_PORT, LEDS_PORT_RIGHT);
    }
}

UPDATE

The previous code has been updated to reflect the changes in soft / hardware.

I've updated the hardware with external 16MHz oscillator, set the fuse bits, modified the macros, e.g. F_CPU accordingly. Now, I just wanted to check if my counter works as I suspect but it failed - the led RIGHT that acquires timers_millis() and is supposed to turn on every second works fine but the other led LEFT flashes randomly. The only pattern with LEFT is that if it turns on the RIGHT is on as well.

I've run out of ideas and solutions. What could be wrong with my soft / hardware and how can I count microseconds precisely?

\$\endgroup\$
  • \$\begingroup\$ how can you handle 1us interrupts on 1MHz uC? It will never work. \$\endgroup\$ – P__J__ Jun 13 '17 at 13:08
  • \$\begingroup\$ Dang, of course you're right! //OCR2 == 1, interupt will happen every 1 / 1000000 = 1us is just not possible. \$\endgroup\$ – JimmyB Jun 13 '17 at 13:09
  • \$\begingroup\$ So what is the finest resolution I can get? \$\endgroup\$ – sitilge Jun 13 '17 at 13:14
  • \$\begingroup\$ Calculate yourself - how many ticks entry to the interrupt takes, how long the interrupt routine is executed and the exit overhead. But it will not be very reliable as you disable interrupts to make opertions atomic. So probably to avoid huge inaccuracies you need to multutiple this time by 3 or 4 probably. And I afraid you will be very close to .2 -.5ms. If you need microseconds to control for example software SPI - it will not work, you will need to find another sollution \$\endgroup\$ – P__J__ Jun 13 '17 at 13:21
  • \$\begingroup\$ @PeterJ Ok, so I followed your and the other comments below. Please, see the updated question. \$\endgroup\$ – sitilge Jun 14 '17 at 9:38
1
\$\begingroup\$

The problem is with this part of the code:

while (1) {
    if (timers_millis() % 1000 == 0) {
        leds_on(LEDS_REGISTER_PORT, LEDS_PORT_RIGHT);
    } else {
        leds_off(LEDS_REGISTER_PORT, LEDS_PORT_RIGHT);
    }
}

timers_millis() % 1000 is only 0 for multiples of 1000. You turn the leds on for just one millisecond before turning them off again. What you would want is to take the millisecond counter modulo your desired period and then check whether the remainder is in the first half or the second half. For a period of 2 seconds, you'd need to check whether the remainder is in the first or second second:

if (timers_millis() % 2000 < 1000) // 0000..0999 ms
    // turn on
else                               // 1000..1999 ms
    // turn off
\$\endgroup\$
  • \$\begingroup\$ Hmm, I guess in this case the persistence of vision occurs. The LED seems to be on all the time. Still, I wonder - the code was running fine without the modifications and the time for LED to blink (probably, one clock cycle) was enough for me to detect it. Theoretically, nothing has changed this time, not in the blinking process. Am I correct? \$\endgroup\$ – sitilge Jun 13 '17 at 12:46
1
\$\begingroup\$

At 1 MHz CPU clock, and TIMER2 running at 1 MHz, you have 256 CPU cycles per timer overflow.

if (microseconds % 1000 == 0) {
    milliseconds++;
}

The % 1000 will most likely require much more than 256 CPU cycles, so you will miss a couple of overflow interrupts. Plus, you will be spending almost all the CPU time in the ISR because the moment the ISR is finished the next overflow is already pending, instantly triggering the ISR again.

How about the following:

  1. make a microsecond counter of uint16_t
  2. in the timer's overflow ISR, do

    microseconds += 256; if ( microseconds >= 1000 ) { microseconds = microseconds - 1000; milliseconds++; }

  3. timer_microseconds() returns milliseconds * 1000 + microseconds.

\$\endgroup\$
0
\$\begingroup\$

Unless you are running your ave at 256Mhz or faster, you aren't getting 1us interrupt out of it.

Instead take a look of various timing implementations, like the arduino and see how sub us resolutions can be done.

I wrote about a set of routines (I can post the link later) but it essentially builds on a 32 bit over flow counter augmented by the timer counter itself.

Depending on the timer used, you get 16 ticks per us.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.