473 lines
15 KiB
C
473 lines
15 KiB
C
/*
|
|
* File: clocks.c
|
|
* Description: STM32W108 internal, clock specific HAL functions
|
|
* This file is provided for completeness and it should not be modified
|
|
* by customers as it comtains code very tightly linked to undocumented
|
|
* device features
|
|
*
|
|
* <!--(C) COPYRIGHT 2010 STMicroelectronics. All rights reserved. -->
|
|
*/
|
|
|
|
#include PLATFORM_HEADER
|
|
#include "error.h"
|
|
|
|
#include "hal/hal.h"
|
|
#include "hal/micro/cortexm3/mpu.h"
|
|
#include "hal/micro/cortexm3/mfg-token.h"
|
|
|
|
|
|
//Provide a simple means for enabling calibration debug output
|
|
#define CALDBG(x)
|
|
//#define CALDBG(x) x
|
|
|
|
//The slowest frequency for the 10kHz RC source is 8kHz (125us). The PERIOD
|
|
//register updates every 16 cycles, so to be safe 17 cycles = 2125us. But,
|
|
//we need twice this maximum time because the period measurement runs
|
|
//asynchronously, and the value of CLKRC_TUNE is changed immediately before
|
|
//the delay.
|
|
#define SLOWRC_PERIOD_SETTLE_TIME 4250
|
|
//The CLK_PERIOD register measures the number of 12MHz clock cycles that
|
|
//occur in 16 cycles of the SlowRC clock. This is meant to smooth out the the
|
|
//noise inherently present in the analog RC source. While these 16 cycles
|
|
//smooths out most noise, there is still some jitter in the bottom bits of
|
|
//CLK_PERIOD. To further smooth out the noise, we take several readings of
|
|
//CLK_PERIOD and average them out. Testing has shown that the bottom 3 and 4
|
|
//bits of CLK_PERIOD contain most of the jitter. Averaging 8 samples will
|
|
//smooth out 3 bits of jitter and provide a realiable and stable reading useful
|
|
//in the calculations, while taking much less time than 16 or 32 samples.
|
|
#define SLOWRC_PERIOD_SAMPLES 8
|
|
//The register CLK1K_CAL is a fractional divider that divides the 10kHz analog
|
|
//source with the goal of generating a 1024Hz, clk1k output.
|
|
// 10000Hz / CLK1K_CAL = 1024Hz.
|
|
//Since the CLK_PERIOD register measures the number of 12MHz cycles in 16
|
|
//cycles of the RC:
|
|
// 16 * 12000000
|
|
// ------------- = ~10kHz
|
|
// CLK_PERIOD
|
|
//and
|
|
// ~10kHz / 1024 = X
|
|
//where X is the fractional number that belongs in CLK1K_CAL. Since the
|
|
//integer portion of CLK1K_CAL is bits 15:11 and the fractional is 10:0,
|
|
//multiplying X by 2048 (bit shift left by 11) generates the proper CLK1K_CAL
|
|
//register value.
|
|
//
|
|
//Putting this all together:
|
|
// 16 * 12000000 * 2048 384000000
|
|
// -------------------- = ------------ = CLK1K_CAL
|
|
// CLK_PERIOD * 1024 CLK_PERIOD
|
|
//
|
|
#define CLK1K_NUMERATOR 384000000
|
|
void halInternalCalibrateSlowRc( void )
|
|
{
|
|
int8u i;
|
|
int32u average=0;
|
|
int16s delta;
|
|
int32u period;
|
|
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "halInternalCalibrateSlowRc:\r\n");
|
|
)
|
|
|
|
////---- STEP 1: coarsely tune SlowRC in analog section to ~10kHz ----////
|
|
//To operate properly across the full temperature and voltage range,
|
|
//the RC source in the analog section needs to be first coarsely tuned
|
|
//to 10kHz. The CLKRC_TUNE register, which is 2's compliment, provides 16
|
|
//steps at ~400Hz per step yielding approximate frequences of 8kHz at 7
|
|
//and 15kHz at -8.
|
|
//Start with our reset values for TUNE and CAL
|
|
CLK_PERIODMODE = 0; //measure SlowRC
|
|
CLKRC_TUNE = CLKRC_TUNE_RESET;
|
|
CLK1K_CAL = CLK1K_CAL_RESET;
|
|
//wait for the PERIOD register to properly update
|
|
halCommonDelayMicroseconds(SLOWRC_PERIOD_SETTLE_TIME);
|
|
//Measure the current CLK_PERIOD to obtain a baseline
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT,
|
|
"period: %u, ", CLK_PERIOD);
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "%u Hz\r\n",
|
|
((int16u)(((int32u)192000000)/((int32u)CLK_PERIOD))));
|
|
)
|
|
//For 10kHz, the ideal CLK_PERIOD is 19200. Calculate the PERIOD delta.
|
|
//It's possible for a chip's 10kHz source RC to be too far out of range
|
|
//for the CLKRC_TUNE to bring it back to 10kHz. Therefore, we have to
|
|
//ensure that our delta correction does not exceed the tune range so
|
|
//tune has to be capped to the end of the vailable range so it does not
|
|
//wrap. Even if we cannot achieve 10kHz, the 1kHz calibration can still
|
|
//properly correct to 1kHz.
|
|
//Each CLKRC_TUNE step yields a CLK_PERIOD delta of *approximately* 800.
|
|
//Calculate how many steps we are off. While dividing by 800 may seem
|
|
//like an ugly calculation, the precision of the result is worth the small
|
|
//bit of code and time needed to do a divide.
|
|
period = CLK_PERIOD;
|
|
//Round to the nearest integer
|
|
delta = (19200+400) - period;
|
|
delta /= 800;
|
|
//CLKRC_TUNE is a 4 bit signed number. cap the delta to 7/-8
|
|
if(delta > 7) {
|
|
delta = 7;
|
|
}
|
|
if(delta < -8) {
|
|
delta = -8;
|
|
}
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "TUNE steps delta: %d\r\n",
|
|
delta);
|
|
)
|
|
CLKRC_TUNE = delta;
|
|
//wait for PERIOD to update before taking another sample
|
|
halCommonDelayMicroseconds(SLOWRC_PERIOD_SETTLE_TIME);
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT,
|
|
"period: %u, ", CLK_PERIOD);
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "%u Hz\r\n",
|
|
((int16u)(((int32u)192000000)/((int32u)CLK_PERIOD))));
|
|
)
|
|
//The analog section should now be producing an output of ~10kHz
|
|
|
|
////---- STEP 2: fine tune the SlowRC to 1024Hz ----////
|
|
//Our goal is to generate a 1024Hz source. The register CLK1K_CAL is a
|
|
//fractional divider that divides the 10kHz analog source and generates
|
|
//the clk1k output. At reset, the default value is 0x5000 which yields a
|
|
//division of 10.000. By averaging several samples of CLK_PERIOD, we
|
|
//can then calculate the proper divisor need for CLK1K_CAL to make 1024Hz.
|
|
for(i=0;i<SLOWRC_PERIOD_SAMPLES;i++) {
|
|
halCommonDelayMicroseconds(SLOWRC_PERIOD_SETTLE_TIME);
|
|
average += CLK_PERIOD;
|
|
}
|
|
//calculate the average, with proper rounding
|
|
average = (average+(SLOWRC_PERIOD_SAMPLES/2))/SLOWRC_PERIOD_SAMPLES;
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "average: %u, %u Hz\r\n",
|
|
((int16u)average), ((int16u)(((int32u)192000000)/((int32u)average))));
|
|
)
|
|
|
|
//using an average period sample, calculate the clk1k divisor
|
|
CLK1K_CAL = (int16u)(CLK1K_NUMERATOR/average);
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT,"CLK1K_CAL=%2X\r\n",CLK1K_CAL);
|
|
)
|
|
//The SlowRC timer is now producing a 1024Hz tick (+/-2Hz).
|
|
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "DONE\r\n");
|
|
)
|
|
}
|
|
|
|
|
|
//The slowest frequency for the FastRC source is 4MHz (250ns). The PERIOD
|
|
//register updates every 256 cycles, so to be safe 257 cycles = 64us. But,
|
|
//we need twice this maximum time because the period measurement runs
|
|
//asynchronously, and the value of OSCHF_TUNE is changed immediately before
|
|
//the delay.
|
|
#define FASTRC_PERIOD_SETTLE_TIME 128
|
|
//The CLK_PERIOD register measures the number of 12MHz cycles in 256
|
|
//cycles of OSCHF:
|
|
// 256 * 12000000
|
|
// ------------- = ~12MHz
|
|
// CLK_PERIOD
|
|
void halInternalCalibrateFastRc(void)
|
|
{
|
|
int32s newTune = -16;
|
|
|
|
CALDBG(
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "halInternalCalibrateFastRc:\r\n");
|
|
)
|
|
|
|
////---- coarsely tune FastRC in analog section to ~12MHz ----////
|
|
//The RC source in the analog section needs to be coarsely tuned
|
|
//to 12MHz. The OSCHF_TUNE register, which is 2's compliment, provides 32
|
|
//steps at ~0.5MHz per step yielding approximate frequences of 4MHz at 15
|
|
//and 20MHz at -16.
|
|
CLK_PERIODMODE = 1; //measure FastRC
|
|
CALDBG(
|
|
//start at the fastest possible frequency
|
|
OSCHF_TUNE = newTune;
|
|
//wait for the PERIOD register to properly update
|
|
halCommonDelayMicroseconds(FASTRC_PERIOD_SETTLE_TIME);
|
|
//Measure the current CLK_PERIOD to obtain a baseline
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT,
|
|
"period: %u, ", CLK_PERIOD);
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "%u kHz\r\n",
|
|
((int16u)((((int32u)3072000000)/((int32u)CLK_PERIOD))/1000)));
|
|
)
|
|
//For 12MHz, the ideal CLK_PERIOD is 256. Tune the frequency down until
|
|
//the period is <= 256, which says the frequency is as close to 12MHz as
|
|
//possible (without going over 12MHz)
|
|
//Start at the fastest possible frequency (-16) and increase to the slowest
|
|
//possible (15). When CLK_PERIOD is <=256 or we run out of tune values,
|
|
//we're done.
|
|
for(;newTune<16;newTune++) {
|
|
//decrease frequency by one step (by increasing tune value)
|
|
OSCHF_TUNE = newTune;
|
|
//wait for the PERIOD register to properly update
|
|
halCommonDelayMicroseconds(FASTRC_PERIOD_SETTLE_TIME);
|
|
//kickout if we're tuned
|
|
if(CLK_PERIOD>=256) {
|
|
break;
|
|
}
|
|
}
|
|
CALDBG(
|
|
//Measure the current CLK_PERIOD to show the final result
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT,
|
|
"period: %u, ", CLK_PERIOD);
|
|
stSerialPrintf(ST_ASSERT_SERIAL_PORT, "%u kHz\r\n",
|
|
((int16u)((((int32u)3072000000)/((int32u)CLK_PERIOD))/1000)));
|
|
)
|
|
|
|
//The analog section should now be producing an output of 11.5MHz - 12.0MHz
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define OSC24M_BIASTRIM_OFFSET (0x2)
|
|
#define OSC24M_BIASTRIM_MIN (0+OSC24M_BIASTRIM_OFFSET)
|
|
#define OSC24M_BIASTRIM_MAX OSC24M_BIASTRIM_OSC24M_BIAS_TRIM_MASK
|
|
#define OSC24M_BIASTRIM_MSB (1 << (OSC24M_BIASTRIM_OSC24M_BIAS_TRIM_BITS-1))
|
|
#define OSC24M_BIASTRIM_UNINIT (0xFFFF)
|
|
tokTypeMfgOsc24mBiasTrim biasTrim=OSC24M_BIASTRIM_UNINIT;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//This function is intended to be called periodically, from the stack and
|
|
//application, to check the XTAL bias trim is within appropriate levels
|
|
//and adjust if not. This function is *not* designed to be used before
|
|
//halInternalSwitchToXtal has been called.
|
|
void halCommonCheckXtalBiasTrim(void)
|
|
{
|
|
//HI is set indicating the trim value is too high. Decrement the trim.
|
|
if((OSC24M_COMP & OSC24M_HI) == OSC24M_HI) {
|
|
biasTrim--;
|
|
}
|
|
|
|
//LO is cleared indicating the trim value is too low. Inrement the trim.
|
|
if((OSC24M_COMP & OSC24M_LO) != OSC24M_LO) {
|
|
biasTrim++;
|
|
//Add an offset to the bias trim as a factor of safety.
|
|
if(biasTrim < (OSC24M_BIASTRIM_MAX - OSC24M_BIASTRIM_OFFSET)) {
|
|
biasTrim += OSC24M_BIASTRIM_OFFSET;
|
|
} else {
|
|
biasTrim = OSC24M_BIASTRIM_MAX;
|
|
}
|
|
}
|
|
|
|
//Don't allow bias trim to dip below the offset regardless of LO.
|
|
if(biasTrim<OSC24M_BIASTRIM_OFFSET) {
|
|
biasTrim = OSC24M_BIASTRIM_OFFSET;
|
|
}
|
|
|
|
OSC24M_BIASTRIM = biasTrim;
|
|
}
|
|
|
|
static boolean setBiasCheckLow(void)
|
|
{
|
|
OSC24M_BIASTRIM = biasTrim;
|
|
halCommonDelayMicroseconds(1500);
|
|
return ((OSC24M_COMP & OSC24M_LO) == OSC24M_LO);
|
|
}
|
|
|
|
void halInternalSearchForBiasTrim(void)
|
|
{
|
|
int8u bit;
|
|
|
|
//Enable the XTAL so we can search for the proper bias trim (NOTE: This
|
|
//will also forcefully ensure we're on the OSCHF so that we don't
|
|
//accidentally trip the NMI while searching.)
|
|
OSC24M_CTRL = OSC24M_CTRL_OSC24M_EN;
|
|
|
|
//Do a binary search of the 4-bit bias trim values to find
|
|
//smallest bias trim value for which LO = 1.
|
|
biasTrim = 0;
|
|
bit = (OSC24M_BIASTRIM_MSB << 1);
|
|
do {
|
|
bit >>= 1;
|
|
biasTrim += bit;
|
|
//Set trim and wait for 1.5ms to allow the oscillator to stabilize.
|
|
if(setBiasCheckLow()) {
|
|
biasTrim -= bit;
|
|
}
|
|
} while(bit);
|
|
|
|
//If the last bias value went too low, increment it.
|
|
if((OSC24M_COMP & OSC24M_LO) != OSC24M_LO) {
|
|
biasTrim++;
|
|
}
|
|
|
|
//Add an offset to the bias trim as a factor of safety.
|
|
if(biasTrim < (OSC24M_BIASTRIM_MAX - OSC24M_BIASTRIM_OFFSET)) {
|
|
biasTrim += OSC24M_BIASTRIM_OFFSET;
|
|
} else {
|
|
biasTrim = OSC24M_BIASTRIM_MAX;
|
|
}
|
|
|
|
//Using the shadow variable, the clock switch logic will take over from here,
|
|
//enabling, verifying, and tweaking as needed.
|
|
}
|
|
|
|
|
|
//This function configures the flash access controller for optimal
|
|
//current consumption when FCLK is operating at 24MHz. By providing
|
|
//this function the calling code does not have to be aware of the
|
|
//details of setting FLASH_ACCESS.
|
|
static void halInternalConfigXtal24MhzFlashAccess(void)
|
|
{
|
|
ATOMIC(
|
|
BYPASS_MPU(
|
|
#if defined(CORTEXM3_STM32W108)
|
|
FLASH_ACCESS = (FLASH_ACCESS_PREFETCH_EN |
|
|
(1<<FLASH_ACCESS_CODE_LATENCY_BIT));
|
|
#endif
|
|
)
|
|
)
|
|
}
|
|
|
|
//NOTE: The global "shadow" variable biasTrim will be set by either:
|
|
// A) TOKEN_MFG_OSC24M_BIAS_TRIM when booting fresh
|
|
// B) searchForBiasTrim() when booting fresh and the token is not valid
|
|
// C) halInternalSwitchToXtal() if halInternalSwitchToXtal() already ran
|
|
void halInternalSwitchToXtal(void)
|
|
{
|
|
boolean loSet;
|
|
boolean hiSet;
|
|
boolean setTrimOneLastTime = FALSE;
|
|
|
|
//If it hasn't yet been initialized,
|
|
//preload our biasTrim shadow variable from the token. If the token is
|
|
//not set, then run a search to find an initial value. The bias trim
|
|
//algorithm/clock switch logic will always use the biasTrim shadow
|
|
//variable as the starting point for finding the bias, and then
|
|
//save that new bias to the shadow variable.
|
|
if(biasTrim == OSC24M_BIASTRIM_UNINIT) {
|
|
halCommonGetMfgToken(&biasTrim, TOKEN_MFG_OSC24M_BIAS_TRIM);
|
|
if(biasTrim == 0xFFFF) {
|
|
halInternalSearchForBiasTrim();
|
|
}
|
|
}
|
|
|
|
//Ensure the XTAL is enabled (with the side effect of ensuring we're
|
|
//still on OSCHF).
|
|
OSC24M_CTRL = OSC24M_CTRL_OSC24M_EN;
|
|
|
|
do {
|
|
//Set trim to our shadow variable and wait for 1.5ms to allow the
|
|
//oscillator to stabilize.
|
|
loSet = setBiasCheckLow();
|
|
hiSet = (OSC24M_COMP & OSC24M_HI) == OSC24M_HI;
|
|
|
|
//The bias is too low, so we need to increment the bias trim.
|
|
if(!loSet) {
|
|
biasTrim++;
|
|
}
|
|
|
|
//The bias is too high, so we need to decrement the bias trim.
|
|
if(hiSet) {
|
|
//but don't trim below our min value
|
|
if(biasTrim>OSC24M_BIASTRIM_MIN) {
|
|
biasTrim--;
|
|
setTrimOneLastTime = TRUE;
|
|
}
|
|
}
|
|
|
|
//Kickout when HI=0 and LO=1 or we've hit the MAX or the MIN
|
|
} while( (hiSet || !loSet) &&
|
|
(biasTrim<OSC24M_BIASTRIM_MAX) &&
|
|
(biasTrim>OSC24M_BIASTRIM_MIN) );
|
|
|
|
//The LO bit being cleared means we've corrected up from the bottom and
|
|
//therefore need to apply the offset. Additionally, if our trim value
|
|
//is below the offset, we still need to apply the offset. And, when
|
|
//applying the offset respect the max possible value of the trim.
|
|
if(!loSet || (biasTrim<OSC24M_BIASTRIM_OFFSET)){
|
|
if(biasTrim < (OSC24M_BIASTRIM_MAX - OSC24M_BIASTRIM_OFFSET)) {
|
|
biasTrim += OSC24M_BIASTRIM_OFFSET;
|
|
} else {
|
|
biasTrim = OSC24M_BIASTRIM_MAX;
|
|
}
|
|
setTrimOneLastTime = TRUE;
|
|
}
|
|
|
|
if(setTrimOneLastTime) {
|
|
setBiasCheckLow();
|
|
}
|
|
|
|
//We've found a valid trim value and we've waited for the oscillator
|
|
//to stabalize, it's now safe to select the XTAL
|
|
OSC24M_CTRL |= OSC24M_CTRL_OSC24M_SEL;
|
|
|
|
//If the XTAL switch failed, the NMI ISR will trigger, creeping the bias
|
|
//trim up higher, and if max bias is reached the ISR will trigger a reset.
|
|
|
|
//Our standard mode of operation is 24MHz (CPU/FCLK is sourced from SYSCLK)
|
|
CPU_CLKSEL = CPU_CLKSEL_FIELD;
|
|
//Configure flash access for optimal current consumption at 24MHz
|
|
halInternalConfigXtal24MhzFlashAccess();
|
|
}
|