Okay, finished the base testing, need to tidy it up, but the code works and the results are actually very good!
So, through external means I got the following: Reference frequency was 1.000 003 6 MHz, actual MCU frequency was 48.003 801 2 MHz. I ran the calibration with the exact reference frequency. The calibration determined the internal frequency to be 48.003 805 488 MHz . A difference from the measurement of only ~90 ppb between the real real and the determined real. So the technique works.
Included is the source code. It's a bit of a mess, a work in progress and will cause your eyes to bleed.
Other info:
- The software 'timer' maxes out a little above 1MHz. The code could probably be further optimized to go higher, but it's not necessary for me now.
- With 60 seconds of integration time, there seems to be little enough overhead to not be a concern
- The code is a tad quirky and will max out at different frequencies depending on the optimization settings and probably the phase of the moon.
The code works as follows:
Disable all interrupts aside from the one (store the interrupt state).
Preload the timer counter with a small value to create a delay.
Wait for the CounterState to increment to CounterState_Running.
After that happens, every time the pin changes its value (this will give you a count on both the rising and falling edge), increment the CalibrationCounter. Do this until CounterState is not CounterState_Done.
Everytime the real timer interrupt occurs, latch the last value of CalibrationCounter into CalibrationCounter_latch and increment CounterState .
Once done, do some simple math and print out the result.
Restore other interrupts.
That's it.
ClockCalibration.c:
#include "ClockCalibration.h"
#include <stdio.h>
#include "Miscellany.h"
#include "stm32f0xx_ll_bus.h"
#include "stm32f0xx_ll_tim.h"
#include "stm32f0xx_ll_gpio.h"
//*************************************************************************************************
enum ECounterStates
{
CounterState_NotStarted = 0,
CounterState_Starting,
CounterState_Running,
CounterState_Done
};
static volatile uint32_t CalibrationCounter = 0;
static volatile uint32_t CalibrationCounter_latch = 0;
static volatile uint8_t CounterState = CounterState_NotStarted;
void __ClockCalibration_TimerCallbackFunction(void)
{
LL_TIM_ClearFlag_UPDATE(__ClockCalibration_Timer);
CounterState++;
CalibrationCounter_latch = CalibrationCounter;
}
//*************************************************************************************************
void ClockCalibration_Init(void)
{
LL_TIM_InitTypeDef TIM_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct;
LL_GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
GPIO_InitStruct.Alternate = LL_GPIO_AF_0;
GPIO_InitStruct.Pin = GPIO_AUXIO_Pin;
LL_GPIO_Init(GPIO_AUXIO, &GPIO_InitStruct);
__ClockCalibration_Timer_EnableClock();
NVIC_SetPriority(__ClockCalibration_TimerIRQN, 0);
NVIC_EnableIRQ(__ClockCalibration_TimerIRQN);
TIM_InitStruct.Prescaler = __ClockCalibration_Timer_Prescaler;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_DOWN;
TIM_InitStruct.Autoreload = __ClockCalibration_Timer_Reload;
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
LL_TIM_Init(__ClockCalibration_Timer, &TIM_InitStruct);
LL_TIM_DisableARRPreload(__ClockCalibration_Timer);
LL_TIM_SetClockSource(__ClockCalibration_Timer, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_SetTriggerInput(__ClockCalibration_Timer, LL_TIM_TS_ITR0);
LL_TIM_SetSlaveMode(__ClockCalibration_Timer, LL_TIM_SLAVEMODE_DISABLED);
LL_TIM_DisableIT_TRIG(__ClockCalibration_Timer);
LL_TIM_DisableDMAReq_TRIG(__ClockCalibration_Timer);
LL_TIM_SetTriggerOutput(__ClockCalibration_Timer, LL_TIM_TRGO_RESET);
LL_TIM_DisableMasterSlaveMode(__ClockCalibration_Timer);
}
//*************************************************************************************************
uint8_t ClockCalibration_Execute(double ReferenceFrequency)
{
printf("Starting calibration.\r\n");
LL_TIM_SetCounter(__ClockCalibration_Timer, __ClockCalibration_Timer_InitialPause);
LL_TIM_ClearFlag_UPDATE(__ClockCalibration_Timer);
LL_TIM_EnableCounter(__ClockCalibration_Timer);
LL_TIM_EnableIT_UPDATE(__ClockCalibration_Timer);
uint32_t PrevInterruptEnable = NVIC->ISER[0];
NVIC->ICER[0] = 0xFFFFFFFF;
NVIC_EnableIRQ(__ClockCalibration_TimerIRQN);
CounterState = CounterState_Starting;
CalibrationCounter = 0;
uint32_t PinStat = GPIO_AUXIO->IDR & GPIO_AUXIO_Pin;
uint32_t PinStat_prev = PinStat;
while(CounterState != CounterState_Running);
do
{
PinStat = GPIO_AUXIO->IDR & GPIO_AUXIO_Pin;
if(PinStat != PinStat_prev) CalibrationCounter++;
PinStat_prev = PinStat;
}while(CounterState != CounterState_Done);
LL_TIM_DisableCounter(__ClockCalibration_Timer);
LL_TIM_DisableIT_UPDATE(__ClockCalibration_Timer);
NVIC->ISER[0] = PrevInterruptEnable;
printf("Got pulses: %ld .\r\n", CalibrationCounter_latch);
double Frequency_Measured = CalibrationCounter_latch;
Frequency_Measured /= 2.0;
Frequency_Measured /= __ClockCalibration_Duration;
double FreqRatio = ReferenceFrequency / Frequency_Measured;
double FrequencyLocal_Nominal = __ClockCore_FreqNom;
double FrequencyLocal_Real = __ClockCore_FreqNom * FreqRatio;
printf("Results: %f\r\n", Frequency_Measured);
printf("Results: %f\r\n", FreqRatio);
printf("Results: %f\r\n", FrequencyLocal_Nominal);
printf("Results: %f\r\n", FrequencyLocal_Real);
printf("Done.\r\n");
CounterState = CounterState_NotStarted;
return TRUE;
}
//*************************************************************************************************
ClockCalibration.h:
#ifndef INC_CLOCKCALIBRATION_H_
#define INC_CLOCKCALIBRATION_H_
//*************************************************************************************************
#include <stdint.h>
//*************************************************************************************************
extern uint8_t ClockCalibration_Execute(double ReferenceFrequency);
extern void ClockCalibration_Init(void);
//*************************************************************************************************
#define GPIO_AUXIO GPIOC
#define GPIO_AUXIO_Pin LL_GPIO_PIN_13
//*************************************************************************************************
#define __ClockCalibration_TimerIRQN TIM3_IRQn
#define __ClockCalibration_Timer_EnableClock() {LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);}
#define __ClockCalibration_TimerCallbackFunction TIM3_IRQHandler
#define __ClockCalibration_Timer TIM3
#define __ClockCalibration_Timer_Prescaler 47999 //Timer will be fed 1kHz
#define __ClockCalibration_Timer_Reload 59999 //59999 //Timer will count one minute
#define __ClockCalibration_Timer_InitialPause 100 //Actual counting will commence 100ms after command
#define __ClockCalibration_Duration 60.0
#define __ClockCore_FreqNom 48E6
//*************************************************************************************************
#endif