arch/avr/src/avrdx/avrdx_delay: add custom up_udelay function

NuttX builtin up_udelay function uses BOARD_LOOPSPERMSEC configuration
value to determine how many loops need to be done to cause requested
delay. This does not match well with AVR DA/DB microcontrollers
because the CPU clock frequency is configurable.

A board configuration could therefore provide BOARD_LOOPSPERMSEC
valid for one frequency but for other frequencies, the user would
be required to calibrate the value and would still get incorrect
result when changing the clock speed during runtime.

This patch therefore implements dynamic architecture-specific
up_udelay function which determines current clock settings
and infers required loop count from that.

New function was tested by simple application that used up_udelay
to put delays between printf calls.

Signed-off-by: Kerogit <kr.git@kerogit.eu>
This commit is contained in:
Kerogit 2026-03-05 17:43:39 +01:00 committed by Matteo Golin
parent 10dea61e23
commit 0b5bf6c2c5
10 changed files with 711 additions and 36 deletions

View file

@ -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.)

View file

@ -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
==================

View file

@ -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)

View file

@ -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

View file

@ -59,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

View file

@ -90,6 +90,22 @@ 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
*

View file

@ -0,0 +1,350 @@
/****************************************************************************
* arch/avr/src/avrdx/avrdx_delay.c
* Custom implementation of up_udelay
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <sys/types.h>
#include "avrdx.h"
#include "avrdx_delay_loop.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Division takes some 350 clock cycles and the delay loop needs 6 cycles
* per loop. Each division therefore takes as much time as this number
* of loops.
*/
#define DIVISION_LOOP_INCREMENT 55
/* Multiplication is not that difficult */
#define MULTIPLICATION_LOOP_INCREMENT 5
/* Detect if divisions are optimized for size - that means the program
* will actually call the division function even when dividing by constant
* (which can be done without the actual division.)
*/
#if (defined(CONFIG_ARCH_TOOLCHAIN_GCC) && defined(__OPTIMIZE_SIZE__))
# define DIVISION_OPTIMIZED_FOR_SIZE
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_udelay
*
* Description:
* Delay execution for requested number of microseconds.
*
* This is a replacement for core implementation (which is defined
* as weak) specific for AVR DA/DB (AVRxt) cores. The main difference
* is the means used to calculate the number of cycles. The core function
* uses CONFIG_BOARD_LOOPSPERMSEC, a value configured by either the user,
* or the author of board code.
*
* AVRxt chips can run with various clock sources and those can be further
* configured and prescaled during runtime. As such, there is no single
* universal value of CONFIG_BOARD_LOOPSPERMSEC (unless the board uses
* external clock source.)
*
* This implementation therefore attempts to determine current running
* frequency of the MCU and calculate needed loop count from that.
*
* Note - same as core implementation of up_udelay, the function waits
* in a busy-wait loop. As such:
*
* * It is *** NOT multi-tasking friendly ***
* * If interrupted (by interrupt handler or preempted by another task),
* the total wait time is increased by the duration of the outside code.
*
* Input Parameters:
* microseconds - requested wait time
*
* Returned Value: none
*
* Assumptions/Limitations:
* Conversion from microseconds is done in run-time and can take
* a lot of cycles itself.
*
****************************************************************************/
void up_udelay(useconds_t microseconds)
{
/* Determined frequency of the CPU. 16bit variable is used
* to force math with smaller data types.
*/
uint32_t f_cpu;
uint16_t f_cpu_shifted;
/* Variable for determined loop count. The counter accumulates
* number of loops that were already "done" by computations
* in this function.
*/
uint32_t loops;
uint16_t loop_like_counter = 0;
/* This is added to loop_like_counter whenever this function
* does something that divides clock frequency by main prescaler
* if the main prescaler is enabled.
*/
uint8_t main_clock_prescaler = 0;
/* Other helper variables. */
#ifndef DIVISION_OPTIMIZED_FOR_SIZE
uint8_t microseconds_u8;
#endif
uint8_t mclkctrla;
if (microseconds == 0)
{
return;
}
if (CLKCTRL.MCLKCTRLB & CLKCTRL_PEN_bm)
{
/* Main prescaler is enabled. Frequency is divided
* by prescaling ratio, loop count calculation is slowed
* by that.
*/
main_clock_prescaler = DIVISION_LOOP_INCREMENT;
}
/* We need to convert duration in microseconds to loop count.
*
* - one clock cycle takes 1/f seconds (where f is CPU frequency.)
* - one loop of avrdx_delay_loop takes 6 clock cycles. Duration
* of one loop pass is therefore 6/f seconds.
* - we receive microseconds as a parameter, duration of one loop
* pass is 6e6/f microseconds
* - total number of required loops is therefore:
* usec/(6e6/f) == (f * usec) / 6e6
*/
f_cpu = avrdx_current_freq_cpu();
/* Increment the counter if the main clock prescaler is enabled,
* ie. if the frequency obtained from the hardware got divided.
*/
loop_like_counter += main_clock_prescaler;
mclkctrla = CLKCTRL.MCLKCTRLA & CLKCTRL_CLKSEL_GM;
if (mclkctrla == CLKCTRL_CLKSEL_OSCHF_GC)
{
/* All of this works with 32 bit operands and the calculation
* will involve some divisions and those are expensive. We can
* attempt to alleviate that by forcing 16 bit operation instead.
* That is possible but some conditions need to be met:
*
* 1. f_cpu is more than 1MHz (that's minimum for high frequency
* oscillator but it can be prescaled
* 2. microseconds is less than 180
* 3. compiler is not optimizing for size
*
* The first condition allows us to bitshift the frequency by 16
* without losing too much accuracy.
*
* The second condition makes sure that the result fits into 16 bit
* value even with maximum f_cpu (24MHz)
*
* The third condition accounts for the fact that the compiler
* is able to avoid division when dividing by a compile-time
* constant but will not do it if optimizing for size.
*/
#ifndef DIVISION_OPTIMIZED_FOR_SIZE
if ((microseconds < 180) && (f_cpu >= 1000000))
{
/* Condition above allows us to bitshift the frequency 16 bits
* to the right, we will bitshift the denominator as well
* to compensate
*/
f_cpu_shifted = f_cpu >> 16;
microseconds_u8 = microseconds;
loops = microseconds_u8 * f_cpu_shifted / (6000000 >> 16);
}
else
#endif
{
/* Cannot do 16 bit math above for some reason. We still need
* to bitshift the inputs to the multiplication though,
* otherwise any delay (value of microseconds) greater than 178
* overflows even 32 bit math.
*/
if (f_cpu >= 1000000)
{
f_cpu_shifted = f_cpu >> 16;
}
else
{
/* This is safe to do - f_cpu is at least 41666, which
* is 1MHz / 24 from prescaler.
*/
f_cpu_shifted = f_cpu >> 8;
/* Still need to shift something by 8 to the right. Can't do
* microseconds blindly though - the compiler may know
* (from the test above) that the value is less than 180
* and use that knowledge here to determine that bitshifted
* value is 0. Calculated value of loops will be 0 too and
* the function terminates because value of 0 will always
* be less than loop_like_counter which is non-zero.
*
* However - since we know frequency is less than 1MHz,
* we also know that the prescaler is applied. Reading f_cpu
* therefore did a division for 350 clock cycles. Highest
* value of f_cpu can therefore be 833333Hz (20MHz oscillator
* divided by 24 prescaler.) That is 1.2us per cycle.
*
* The division therefore took 420us. If the requested delay
* value is less than 256us, we already achieved that.
*
* So in the end - we CAN do the bitshift blindly.
*/
microseconds = microseconds >> 8;
}
/* This compiles into 2x16bit to 32bit multiplication, followed
* by division. Some 350 cycles for the division,
* 40 for multiplication, this function takes some, let's say
* 420 clock cycles total. This means that this function already
* did 70 loops by itself, without entering the actual loop.
*/
loops = microseconds * f_cpu_shifted / (6000000 >> 16);
loop_like_counter += (DIVISION_LOOP_INCREMENT + \
MULTIPLICATION_LOOP_INCREMENT);
}
}
#ifndef CONFIG_AVRDX_ARCH_UDELAY_NO_OSC32K
else if ((mclkctrla == CLKCTRL_CLKSEL_OSC32K_GC) || \
(mclkctrla == CLKCTRL_CLKSEL_XOSC32K_GC))
{
/* Unlike with the high frequency oscillator, we can't bitshift
* the CPU frequency by 16 bits, that would erase it to zero.
* We can only do bitshift by 8 bits (and we have to because
* otherwise the multiplication between f_cpu and microseconds
* overflows for 2^17 (131072) microseconds or more.
*
* Also cannot assume it's actually 32.768k - main clock prescaler
* may be in effect. We will check for that one though, that could
* save us a lot of time.
*/
if (main_clock_prescaler)
{
/* Main prescaler in use, we are out of luck and need
* to do the computation. Considering that the CPU clock
* is slow and we need to divide (and that obtaining
* f_cpu also did a division), the accuracy of this
* function will most likely be way off.)
*/
f_cpu_shifted = f_cpu >> 8;
loops = f_cpu_shifted * microseconds / (6000000 >> 8);
loop_like_counter += DIVISION_LOOP_INCREMENT;
}
else
{
/* Main prescaler not in use, frequency is a known constant.
*
*/
loops = (32768 >> 8) * microseconds / (6000000 >> 8);
/* Does no division unless optimizing for size */
# ifdef DIVISION_OPTIMIZED_FOR_SIZE
loop_like_counter += DIVISION_LOOP_INCREMENT;
# endif
}
}
#endif
#ifndef CONFIG_AVRDX_ARCH_UDELAY_NO_EXTCLK
else if (mclkctrla == CLKCTRL_CLKSEL_EXTCLK_GC)
{
loops = (USEC_PER_MSEC * microseconds) * CONFIG_BOARD_LOOPSPERMSEC;
avrdx_current_freq_main_prescaler(loops);
loop_like_counter += main_clock_prescaler;
}
#endif
else
{
/* Unsupported clock, wait as long as possible */
loops = UINT32_MAX;
}
if (loops < loop_like_counter)
{
/* This function already took more time that it was supposed to.
* Note - must not pass 0 to avrdx_delay_loop.
*/
return;
}
avrdx_delay_loop(loops - loop_like_counter);
}

View file

@ -0,0 +1,81 @@
/****************************************************************************
* arch/avr/src/avrdx/avrdx_delay_loop.S
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
/****************************************************************************
* External Symbols
****************************************************************************/
.file "avrdx_delay_loop.S"
/****************************************************************************
* Name: avrdx_delay_loop
*
* Description:
* Spins in a loop to cause a delay. On chips with AVRxt cores, it takes:
* (3 + count * 6 + call) cycles where count is the parameter and call
* is the number of cycles needed for instruction that jumps into this
* function (2 cycles for RCALL or 3 cycles for CALL, depends on what
* the compiler/linker does.)
*
* Input Parameters:
* Loop count - uint32_t (registers r22:r25)
*
* Return value: none
*
* Assumptions/Limitations:
* - only called from avrdx_udelay
* - count is not zero
*
****************************************************************************/
.global avrdx_delay_loop
avrdx_delay_loop:
; Because:
; - all of the registers that contain loop count are call-clobbered.
; - avrdx_udelay does not call this function with count set to zero
; we can start counting right away
; Note - r22 cannot be used with SBIW. SBIW takes two clock cycles
; though so we can achieve the same timing with with SUBI/SBC
1:
subi r22, 1 ; subtract immediate, 1 cycle
sbc r23, r1 ; subtract zero register from r26 with carry
; 1 cycle
sbc r24, r1 ; 1 cycle
sbc r25, r1 ; 1 cycle
brne 1b ; 2 cycles if the condition is true (ie. if branching)
; 1 cycle otherwise
; 1 pass through the loop takes 6 cycles total (except for last pass
; which takes 5 cycles)
2:
ret ; 4 cycles

View file

@ -0,0 +1,92 @@
/****************************************************************************
* arch/avr/src/avrdx/avrdx_delay_loop.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __ARCH_AVR_ETC_ETC_H
#define __ARCH_AVR_ETC_ETC_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdint.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Public Types
****************************************************************************/
#ifndef __ASSEMBLY__
/****************************************************************************
* Public Data
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Inline Functions
****************************************************************************/
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: avrdx_delay_loop
*
* Description:
* Spins in a loop to cause a delay. On chips with AVRxt cores, it takes:
* (3 + count * 6 + call) cycles where count is the parameter and call
* is the number of cycles needed for instruction that jumps into this
* function (2 cycles for RCALL or 3 cycles for CALL, depends on what
* the compiler/linker does.)
*
* Input Parameters:
* Loop count - uint32_t (registers r22:r25)
*
* Return value: none
*
* Assumptions/Limitations:
* - only called from avrdx_udelay
* - count is not zero
*
****************************************************************************/
void avrdx_delay_loop(uint32_t count);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __ASSEMBLY__ */
#endif

View file

@ -300,11 +300,46 @@ const IOBJ uint8_t avrdx_usart_portmux_masks[] =
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: avrdx_current_freq_per_oschf
*
* Description:
* Calculate and return current f_per (peripheral clock frequency)
*
* Assumptions:
* Main clock source is internal oscillator
*
****************************************************************************/
static uint32_t avrdx_current_freq_per_oschf(void)
{
uint32_t f_per;
/* Shortcut variables */
uint8_t frqsel;
/* Calculate frequency in MHz, then divide it by main prescaler,
* if set.
*/
frqsel = (CLKCTRL.OSCHFCTRLA & CLKCTRL_FRQSEL_GM) >> CLKCTRL_FRQSEL_GP;
f_per = 1000000UL * avrdx_frqsel_mhz[frqsel];
return avrdx_current_freq_main_prescaler(f_per);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: avrdx_current_freq_main_prescaler
*
* Description:
* Reduces given frequency by main clock prescaler.
* 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
@ -329,39 +364,6 @@ uint32_t avrdx_current_freq_main_prescaler(uint32_t frequency)
return frequency;
}
/****************************************************************************
* Name: avrdx_current_freq_per_oschf
*
* Description:
* Calculate and return current f_per (peripheral clock frequency)
*
* Assumptions:
* Main clock source is internal oscillator
*
****************************************************************************/
uint32_t avrdx_current_freq_per_oschf(void)
{
uint32_t f_per;
/* Shortcut variables */
uint8_t frqsel;
/* Calculate frequency in MHz, then divide it by main prescaler,
* if set.
*/
frqsel = (CLKCTRL.OSCHFCTRLA & CLKCTRL_FRQSEL_GM) >> CLKCTRL_FRQSEL_GP;
f_per = 1000000UL * avrdx_frqsel_mhz[frqsel];
return avrdx_current_freq_main_prescaler(f_per);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: avrdx_current_freq_per
*