Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions Documentation/platforms/avr/avrdx/docs/up_udelay.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
=============================================
AVR DA/DB family ``up_udelay`` implementation
=============================================

NuttX provides functions for busy sleep, these are documented
:doc:`here </reference/os/sleep>`. These functions
use ``BOARD_LOOPSPERMSEC`` configuration value to determine how many loops need
to be done to cause requested delay. Creator of a the board code is supposed
to calibrate this value to make the delay as precise as possible.

This does not match well with AVR DA/DB microcontrollers because the CPU clock
frequency is configurable and unless the user chooses one and sticks with it,
any value in ``BOARD_LOOPSPERMSEC`` is going to be incorrect.

Instead of using the default ``up_udelay`` function, custom one is provided.
When called, it determines current clock settings and infers required loop count
from that.

Configuration
=============

This implementation is enabled automatically and can be turned off using
:menuselection:`System Type --> Use AVR DA/DB implementation of up_udelay`
configuration option. If enabled, there are two additional options:

:menuselection:`External clock is not supported in up_udelay` and
:menuselection:`32.768kHz oscillator is not supported in up_udelay`.

The latter simply excludes the code that checks if any 32.768kHz clock source
is in use.

The former does the same thing for external clock but also has an additional
effect: when not selected, it does not set ``ARCH_HAVE_DYNAMIC_UDELAY``
configuration option. This in turn means that ``BOARD_LOOPSPERMSEC`` will
need to be configured
in :menuselection:`System Type --> Delay loops per millisecond`

The reason for this is that with the external clock, there is no way of knowing
what the current CPU clock frequency is and it is therefore impossible
to calculate the loop count. It needs to be provided by board code's author
to match the external clock the board is using. If the main clock prescaler is
active, the loop value is recalculated to take that into consideration.

Note that ``BOARD_LOOPSPERMSEC`` also needs to be specified if this ``up_udelay``
implementation is disabled altogether.

The other functions - ``up_mdelay`` and ``up_ndelay`` - are unchanged. These
simply multiply or divide their time parameter by 1000 and pass the result
to ``up_udelay``.

Precision
=========

The loop count calculation takes time and that time depends on current settings
and requested wait time. For example, calculation for wait time below
180 microseconds when using high frequency oscillator can be done using 16bit
arithmetic and without expensive division - unless the compiler is set
to optimize for code size.

On the other hand - whenever the main clock prescaler is in effect,
division is unavoidable.

The function attempts to account for this by guessing how much time all
the processing took and reduce the loop count accordingly. Nevertheless,
the wait may be considerably longer than requested for shorter delays
and lower clock speeds.

(For example - 32.768kHz clock with prescaler of 2 needs to do two
divisions, taking 42 milliseconds total.)
3 changes: 3 additions & 0 deletions Documentation/platforms/avr/avrdx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ in :menuselection:`RTOS Features --> Clocks and Timers --> Support tick-less OS`
changed to value of at least 300. Higher value is recommended though,
300us is not going to be precise at all.

Architecture code for this CPU family provides custom ``up_udelay`` function.
More information can be found in :doc:`docs/up_udelay` document

Peripheral Support
==================

Expand Down
3 changes: 2 additions & 1 deletion Documentation/reference/os/sleep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ Busy Sleep Interfaces
---------------------
Spin in a loop for the requested duration and never yield the CPU. The delay accuracy depends on
``CONFIG_BOARD_LOOPSPERMSEC``.
``CONFIG_BOARD_LOOPSPERMSEC`` (unless the architecture/board code replaces these
function(s) with an implementation that does not use the value.)
.. c:function:: void up_mdelay(unsigned int milliseconds)
Expand Down
33 changes: 33 additions & 0 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,39 @@ config ARCH_LDST_16BIT_NOT_ATOMIC
(The same applies for non-interrupt context write and interrupt
context read.)

config ARCH_HAVE_UDELAY
bool
default n
---help---
This configuration option is set for architectures that define
their own version of up_udelay. That function is defined as weak
but due to linker behaviour with regards to static libraries,
it was found that the overriding definition is not included
in the resulting binary, the default (weak) one is still used.

(Found with GCC 14.2 on AVR and Risc-V but likely present
elsewhere too. Manifests when up_udelay override is the only
function in the source file.)

If set, the default up_udelay function is excluded from build.

config ARCH_HAVE_DYNAMIC_UDELAY
bool
default n
depends on ARCH_HAVE_UDELAY
---help---
This configuration option is set if architecture-specific up_udelay
function does not use CONFIG_BOARD_LOOPSPERMSEC. This should be used
for architectures/boards where the user has the option to choose
clock frequency of the CPU.

CONFIG_BOARD_LOOPSPERMSEC must be set to default value of -1
if this is set.

(Keep in mind that up_udelay function may also be used in situations
after a failure - the implementation should always re-read current
hardware settings as anything stored in memory may be corrupted.)

config ARCH_HAVE_TESTSET
bool
default n
Expand Down
61 changes: 60 additions & 1 deletion arch/avr/src/avrdx/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -583,4 +583,63 @@ config AVRDX_HFO_CLOCK_FREQ
default 20000000 if AVRDX_HFO_CLOCK_20MHZ
default 24000000 if AVRDX_HFO_CLOCK_24MHZ

endif
config AVRDX_ARCH_UDELAY
bool "Use AVR DA/DB implementation of up_udelay"
default y
select ARCH_HAVE_UDELAY
---help---
This option enables architecture-specific implementation
of up_udelay.

The implementation is still a busy wait that runs a loop
as many times as needed for requested delay period. It is
"dynamic" meaning that it calculates number of loops needed
from current CPU frequency.

If disabled, BOARD_LOOPSPERMSEC needs to be configured.

config AVRDX_ARCH_UDELAY_NO_EXTCLK
bool "External clock is not supported in up_udelay"
default y
depends on AVRDX_ARCH_UDELAY
select ARCH_HAVE_DYNAMIC_UDELAY
---help---
Selecting this option indicates that the board does not use
external clock source (EXTCLK setting in CLKCTRL.MCLKCTRLA.)

The implementation of up_udelay is able to determine CPU
clock frequency of the other sources but not this one.
If external clock is in use, the implementation needs to fall
back to the value of BOARD_LOOPSPERMSEC which must
be specified.

If external clock is not in use (and this option is set),
BOARD_LOOPSPERMSEC must be set to its default value of -1.

Note that this option does not apply to the XOSC32K setting.
We are able to determine CPU clock frequency rather easily
when driven by external 32.768 kHz crystal oscillator.

Also note that if this option is enabled and the board uses
external clock (EXTCLK), up_udelay will fail and wait for
maximum amount of time.

Say Y if your board does not use external clock.

config AVRDX_ARCH_UDELAY_NO_OSC32K
bool "32.768kHz oscillator is not supported in up_udelay"
default y
depends on AVRDX_ARCH_UDELAY
---help---
Selecting this option indicates that the board does not use
either of OSC32K and XOSC32K settings in CLKCTRL.MCLKCTRLA.
In other words - neither source of 32.768kHz clock is used.

If this is set, the implementation of up_udelay will skip
support for these frequencies to save flash space. Note that
if one of these clock sources is used anyway, up_udelay
will fail and wait for maximum amount of time.

Say Y if your board does not use 32.768kHz clock.

endif # if ARCH_CHIP_AVRDX
8 changes: 7 additions & 1 deletion arch/avr/src/avrdx/Make.defs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ CMN_CSRCS = avr_allocateheap.c avr_copystate.c avr_createstack.c
CMN_CSRCS += avr_doirq.c avr_exit.c avr_idle.c avr_initialize.c
CMN_CSRCS += avr_initialstate.c avr_irq.c avr_lowputs.c
CMN_CSRCS += avr_nputs.c avr_releasestack.c avr_registerdump.c
CMN_CSRCS += avr_schedulesigaction.c avr_sigdeliver.c avr_getintstack.c
CMN_CSRCS += avr_getintstack.c
CMN_CSRCS += avr_stackframe.c avr_switchcontext.c avr_usestack.c

# Configuration-dependent common files

ifeq ($(CONFIG_ENABLE_ALL_SIGNALS),y)
CMN_CSRCS += avr_schedulesigaction.c avr_sigdeliver.c
endif

ifeq ($(CONFIG_AVR_SPI),y)
CMN_CSRCS += avr_spi.c
endif
Expand All @@ -55,6 +59,8 @@ CHIP_CSRCS = avrdx_lowconsole.c avrdx_lowinit.c avrdx_init.c
CHIP_CSRCS += avrdx_serial.c avrdx_serial_early.c
CHIP_CSRCS += avrdx_peripherals.c
CHIP_CSRCS += avrdx_twi.c
CHIP_ASRCS += avrdx_delay_loop.S
CHIP_CSRCS += avrdx_delay.c

# Configuration-dependent files

Expand Down
40 changes: 37 additions & 3 deletions arch/avr/src/avrdx/avrdx.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,53 @@ extern "C"

void up_clkinitialize(void);

/****************************************************************************
* Name: avrdx_current_freq_main_prescaler
*
* Description:
* Reduces given frequency by main clock prescaler. (Note - this is also
* used for non-frequency values. Implementation of up_udelay uses this
* function to reduce number of needed loops when external clock is used.)
*
* Input Parameters:
* frequency - input frequency
*
* Return value: output frequency in Hz
*/

uint32_t avrdx_current_freq_main_prescaler(uint32_t frequency);

/****************************************************************************
* Name: avrdx_current_freq_per
*
* Description:
* Calculate and return current f_per
* Calculate and return current f_per (peripheral clock frequency)
*
* Returned Value: frequency in Hz.
*
* Assumptions:
* Main clock source is internal oscillator
* Assumptions/Limitations:
* Main clock must not be driven by external clock.
*
****************************************************************************/

uint32_t avrdx_current_freq_per(void);

/****************************************************************************
* Name: avrdx_current_freq_cpu
*
* Description:
* Calculate and return current f_cpu (CPU frequency). Returns value
* of avrdx_current_freq_per because both clocks are identical.
*
* Returned Value: frequency in Hz
*
* Assumptions/Limitations:
* Main clock must not be driven by external clock.
*
****************************************************************************/

uint32_t avrdx_current_freq_cpu(void);

/****************************************************************************
* Name: up_consoleinit
*
Expand Down
Loading
Loading