(Very) slow _delay_ms() w/ XC8 and ATmega4809

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

Issue: _delay_ms() macro runs about 5 times slower than it should.

 

MCU/Board: ATmega4809 Curiosity Nano

IDE: MPLAB X v.5.35
Compiler: XC8 v2.20 (AVR GCC 5.4.0)

Optimizations: -O1 and -O2 tested

Code:

#pragma config FREQSEL = FREQSEL_20MHZ
#define F_CPU 20000000UL

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


int main(void)
{
    // Set LED pin PF5 as output
    PORTF.DIRSET = PIN5_bm;

    while (1)
    {
        // Maximum delay: 4294967.295 ms/F_CPU ~= 214 ms
        _delay_ms(200);
        PORTF.OUTTGL = PIN5_bm;
    }
}

 

In addition to very low delays, the compiler also messages: #warning "This file has been moved to <util/delay.h>." But never mind that...

 

Here's the "funny part"... Document "MPLAB® XC8 C Compiler User’s Guide for AVR® MCU" (DS50002750C), page 133, section 7.5.1 states: "The macro F_CPU should be defined as a
constant that specifies the CPU clock frequency (in Hertz)." (It also mentions that optimizations need to be enabled for "accurate delay times". -O1 and -O2 have been tried).

 

...but if I replace F_CPU with _XTAL_FREQ (which does not even appear in the compiler guide), the delay appears to work correctly!

 

 

What's going on? A documentation error or something that I should learn / understand?

 

 

 

This topic has a solution.
Last Edited: Sun. Jun 28, 2020 - 09:39 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As shown in the datasheet, 1/6 prescaler is enabled at startup.
F_CPU has no effect on this.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0<<CLKCTRL_PEN_bp);

This will disable the prescaler and give you the 20E6 Hz clock.

-Sam

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

F_CPU is used by delay.h to compute delay periods and has no effect on your actual cpu speed. Since delay.h creates code at compile time and has no idea what your clock is set to, you have to inform it via the F_CPU define. If you do not define F_CPU, then delay.h will still produce code but will assume 1MHz and warn you that F_CPU is not defined. Its up to you to make sure F_CPU matches the actual clock speed.

 

A 4809 (or any avr0/1) has two options for the cpu clock out of reset, set by the OSCCFG.FREQSEL fuse bits to either OSC20M or OSC16M. Out of reset, the clock CLKCTRL.MCLKCTRLB prescaler is ALWAYS enabled and set to DIV6, which means you get 3.33MHz or 2.66MHz depending on the FREQSEL fuse bits. If you want  something other than DIV6, then you have to set the PDIV and PEN as needed (a CCP protected register).

 

So when you inform delay.h your cpu is running at 20MHz, but is actually 6 times slower, your delay will be 6 times slower. If you do not define F_CPU, then your delay will be ~3 times faster (delay thinks you are at 1MHz, but your cpu is at 3.33MHz).

 

If you use delay.h, then you will have to set F_CPU to a value that matches what the cpu clock will be. Simply changing F_CPU to 3333333ul, will get you accurate delays for now (assuming fuse is OSC20M).

 

One alternative to using delay.h is to use the rtc peripheral which can have a fixed clock of 32kz (internal, or external if you have a 32k crystal). Since it has its own clock and does not depend on what your cpu clock happens to be, you can switch cpu speeds as needed/wanted (and at runtime) and it will get you delays that are always correct. Using this peripheral will also allow non-blocking waits, and you will also be able to keep a 'system time' of some type. In many cases, a simple blocking delay using delay.h is just fine, though.

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

curtvm wrote:

One alternative to using delay.h is to use the rtc peripheral which can have a fixed clock of 32kz (internal, or external if you have a 32k crystal). Since it has its own clock and does not depend on what your cpu clock happens to be, you can switch cpu speeds as needed/wanted (and at runtime) and it will get you delays that are always correct. Using this peripheral will also allow non-blocking waits, and you will also be able to keep a 'system time' of some type. In many cases, a simple blocking delay using delay.h is just fine, though.

 

Now that I have embarrassed myself by overlooking prescaler, I will be taking a good look at the RTC (on my TODO list anyway) :-) 

 

Cheers!

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

Use the rtc for delay(), and you might never notice that the prescaler is wrong!

 

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

tunari wrote:
Here's the "funny part"... Document "MPLAB® XC8 C Compiler User’s Guide for AVR® MCU" (DS50002750C), page 133, section 7.5.1 states: "The macro F_CPU should be defined as a constant that specifies the CPU clock frequency (in Hertz)." (It also mentions that optimizations need to be enabled for "accurate delay times". -O1 and -O2 have been tried). {my emphasis}

 

Is this the root cause for probably the most asked question on avrfreaks?

 

Should that be changed to:

"The macro F_CPU should be defined as a constant that exposes the actual CPU clock frequency (in Hertz)."

 

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

westfw wrote:

Use the rtc for delay(), and you might never notice that the prescaler is wrong!

 

 

Yes, but in this particular case, it wasn't so much "wrong" as I was "stupid" ... :-)

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

N.Winterbottom wrote:

Should that be changed to:

"The macro F_CPU should be defined as a constant that exposes the actual CPU clock frequency (in Hertz)."

 

 

One other thing that might "confuse" is the docs/chips/atmega4809.html ... While it has FREQSEL and options for it (FREQSEL_16MHZ and FREQSEL_20MHZ, of course), it doesn't have MCLKCTRLx settings. It's not a far fetched idea that because prescaler is not among those settings, beginner might forget it exists.

 

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

Don't beat yourself up too much about this, it's a common mistake for people just starting with this series to overlook the prescaler default of 1/6.

Or to notice the prescaler, but not that it is a protected register, that usually causes even more confusion "I'm writing to the prescaler but nothing happens!" I made this mistake several times with a number of protected registers (there are several).