STM32F411 RC Calibration Using a DS3231 TCXO 1HZ Signal

frequency calibration

STM32 RC oscillator frequency varies from one chip to another due to manufacturing process variations. ST claim each device is factory calibrated for 1% accuracy at 25°C. Thus, after reset this factory calibration value is loaded in the HSICAL[7:0] bits of the RCC clock control register (RCC->CR). Yet, here is what my STM32F411 Nucleo board’s RC clocked out at (a 1Hz timebase generated a period of 0.988 seconds, which is the loss of roughly 17 minutes over a 24 hour period):

Saleae Logic Screen Capture

Of course, further degradation in the supposed 1% accuracy results from voltage and temperature variations. Therefore, ST has graciously provided a method to trim the internal RC through a 4-bit calibration value. This can be accomplished programmatically by using the HSITRIM[4:0] bits of the RCC clock control register (RCC->CR).

The following program is designed to find the best HSITRIM calibration value given a 1Hz reference signal. I used the 1Hz signal generated by a Maxim DS3231 TXCO RTC (in the form of a ChronoDot) for my testing here.

Calibration Procedure
The calibration procedure consists of first measuring the HSI frequency, computing the error by reference to the DS3231 1HZ signal, and finally setting the HSITRIM bits in the RCC_CR register.

We do not measure the HSI frequency directly, but estimate it by counting the clock pulses using a timer. To perform this action, obviously a very accurate reference frequency must be available such as the signal provided by an external DS3231 RTC (see the ST App Note referenced at the end of this post for methods using either the 50/60Hz signal inherent in the mains power, or at lower accuracy, the internal 32kHz RTC crystal).

The following shows how the reference signal period is measured via a timer:
capture description

On each rising edge, two interrupts occur, a capture compare and an update interrupt. The latter is used to count the counter overflows over a reference period. In must be noted, since both interrupts occur at the same time at the beginning of a new period, an extra overflow occurs. This is the reason we subtract 1 from the overflow counter. The number of counted clock pulses is given as follows:
• N is the number of timer overflows during one period of the reference frequency
• Capture1 is the value read from the timer CCR1 register.

Since the timer is clocked by the internal RC, it is easy to compute the real frequency generated by the HSI and compare it to the reference frequency. The error (in Hz) is computed as the absolute value of the difference between the RC frequency and 8,000,000Hz (8MHz):

Error (Hz) = | RC_Frequency – 8000000 |

While iterating through all possible values for the HSITRIM bits of the RCC_CR register, the algorithm calculates the error, and determines the best calibration value. Timer #1 is used for measuring the period. After the calibration is complete, timer #2 is setup with a 1Hz timebase and the GPIOA.5 pin (Nucleo green LED) can be used to check the results. The I2C functions used by this program to command the DS3231 chip to output a 1Hz square wave are outlined in this prior post.

RCC CR Register and HSITRIM Bits
The RCC->CR register with HSITRIM bits is shown below:
cap 2

The description of the HSITRIM bits:
cap 3
Accuracy of Frequency Measurements
The accuracy of frequency measurement is dependent upon on the accuracy and stability of the reference frequency. Since the measurement also depends on the finite resolution of the timer, a reference frequency not exceeding 3000Hz is recommended by ST. The following table gives an idea of the efficiency of calibration versus reference frequency accuracy:
cap 4

Utilizing this calibration program enabled me to trim the STM32F411 Nucleo frequency to 8,004,314 with an error of 4,314 cycles, improving the RC accuracy to within 0.053925% (a 46 second error over a 24-hour period, a 22x improvement over my non-calibrated Nucleo board).

Calibration Program

//input capture & i2c test
//1Hz SQW from Chronodot
#include "stdint.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_it.h"
#include "stm32f4xx_tim.h"
#include "stm32f4xx_i2c.h"
#include "string.h"
#include "stdio.h"
#include "stdlib.h"

//ChronoDot
//SQW  -----> Pa9 (tied thru 10kR to VCC) 
//GND  -----> GND 
//VCC  -----> 5V
//I2C1 GPIO Configuration    
//PB6 ------> I2C1_SCL
//PB7 ------> I2C1_SDA 
//Virtual Comm Port
//PA2 ------> TX 
//PA3 ------> RX

#define DS3231_ADDRESS          0xd0 //I2C 7-bit slave address shifted for 1 bit to the left
#define DS3231_SECONDS          0x00
#define DS3231_CONTROL_REG      0x0e

//initialize vcomm UART pins, baudrate 
void ConfigureUSART2(void) {
  RCC->AHB1ENR |= (1ul<<0);                         //enable GPIOA clock               
  RCC->APB1ENR |= (1ul<<17);                        //enable USART#2 clock             
  //configure PA3 to USART2_RX, PA2 to USART2_TX 
  GPIOA->AFR[0] &= ~((15ul<<4*3) | (15ul<<4*2));
  GPIOA->AFR[0] |= ((7ul<<4*3) | (7ul<<4*2));
  GPIOA->MODER &= ~((3ul<<2*3) | (3ul<<2*2));
  GPIOA->MODER |= ((2ul<<2*3) | (2ul<<2*2));
  //115200 baud @8MHz APB1 peripheral clock (PCLK1)
  USART2->BRR = (8000000ul/115200ul);
  USART2->CR3 = 0x0000;                             //no flow control                 
  USART2->CR2 = 0x0000;                             //1 stop bit                      
  //enable RX, enable TX, 1 start bit, 8 data bits, enable USART                    
  USART2->CR1 = ((1ul<<2) | (1ul<<3) | (0ul<<12) | (1ul<<13));
}

//Write character to Serial Port
int USART2PutChar (int ch) {
  while (!(USART2->SR & 0x0080));
  USART2->DR = (ch & 0xFF);
  return (ch);
}

//Read character from Serial Port
int USART2GetChar(void) {
  if (USART2->SR & 0x0020)
    return (USART2->DR);
  return (-1);
}

void USART2OutString(char *s) {
  while (*s)
    USART2PutChar(*s++);
}  

//printf redirect
//#include "stdio.h"
//implement own __FILE struct 
struct __FILE {
  int dummy;
};
//struct FILE is implemented in stdio.h
FILE __stdout;
// 
int fputc(int ch, FILE *f) {
  //while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
  //send byte to USART2 
  USART2PutChar(ch);
  //if all ok, must return char written 
  return ch;
  //if char not correct, can return EOF (-1) to stop 
  //return -1;
}

//Configures the different system clocks 
void ConfigureSystem(void) { 
  //enable HSI
  RCC->CR |= ((uint32_t)RCC_CR_HSION);                     
  while ((RCC->CR & RCC_CR_HSIRDY) == 0)
    ; //Wait for HSI Ready RCC->CFGR = RCC_CFGR_SW_HSI;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)
    ; //wait for HSI used as system clock
  FLASH->ACR  = FLASH_ACR_PRFTEN;      //enable Prefetch Buffer
  FLASH->ACR |= FLASH_ACR_ICEN;        //instruction cache enable
  FLASH->ACR |= FLASH_ACR_DCEN;        //data cache enable
  FLASH->ACR |= FLASH_ACR_LATENCY_0WS; //flash 0 wait state
  //HCLK = SYSCLK
  RCC->CFGR |= RCC_CFGR_HPRE_DIV4;                         
  //APB1 = HCLK/2
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV1;                        
  //APB2 = HCLK/1
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;                        
  //disable PLL
  RCC->CR &= ~RCC_CR_PLLON;                                
  //PLL configuration: VCO=HSI/M*N, Sysclk=VCO/P
  //PLL_M=16, PLL_N=192, PLL_P=6, PLL_SRC=HSI, PLL_Q=4 for 32MHz SYSCLK/8MHz HCLK/8MHz APB1 & APB2 (PCLK1&2)
  RCC->PLLCFGR = (16ul | (192ul<<6) | (2ul<<16) | (RCC_PLLCFGR_PLLSRC_HSI) | (4ul<<24));
  //enable PLL
  RCC->CR |= RCC_CR_PLLON;                                 
  //Wait till PLL is ready
  while((RCC->CR & RCC_CR_PLLRDY) == 0) 
    __NOP();
  //select PLL as system clock source
  RCC->CFGR &= ~RCC_CFGR_SW;                               
  RCC->CFGR |=  RCC_CFGR_SW_PLL;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
    ; //wait till PLL is system clock src
}

//configure nucleo led (pa5) pin as output, push-pull, no pull-up/down 
void ConfigureNucleoGPIO(void) {
  RCC->AHB1ENR |= (1ul<<0);                      //enable GPIOA peripheral clock
  GPIOA->MODER &= ~((3ul<<2*5));                //clear both mode bits
  GPIOA->MODER |= ((GPIO_Mode_OUT<<2*5));       //set as general purpose output
  GPIOA->OTYPER &= ~((1ul<<5));                 //clear (push/pull)
  GPIOA->OSPEEDR &= ~((3ul<<2*5));              //clear both speed bits
  GPIOA->OSPEEDR |= ((GPIO_Medium_Speed<<2*5)); //set medium speed
  GPIOA->PUPDR &= ~((3ul<<2*5));                //clear both pull up/down status (none)
}

#define I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED        ((uint32_t)0x00070082)  /* BUSY, MSL, ADDR, TXE and TRA flags */
#define I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED           ((uint32_t)0x00030002)  /* BUSY, MSL and ADDR flags */
#define I2C_EVENT_MASTER_BYTE_RECEIVED                    ((uint32_t)0x00030040)  /* BUSY, MSL and RXNE flags */
#define I2C_TRANSMITTER_MODE   0
#define I2C_RECEIVER_MODE      1
#define I2C_ACK_ENABLE         1
#define I2C_ACK_DISABLE        0
#define FLAG_MASK ((uint32_t)0x00FFFFFF) //I2C FLAG mask

uint32_t I2CTimeout;

ErrorStatus I2CChkEvent(uint32_t I2C_EVENT) {
  uint32_t lastevent = 0;
  ErrorStatus status = ERROR;

  //get the last event value from I2C status register 
  lastevent = (I2C1->SR1 | (I2C1->SR2<<16))&(uint32_t)0x00FFFFFF;
  //check whether the last event contains the I2C_EVENT 
  if ((lastevent&I2C_EVENT) == I2C_EVENT)
    status = SUCCESS; //last event is equal to I2C_EVENT 
  else
    status = ERROR;   //last event is different from I2C_EVENT 
  //return status 
  return status;
}

int16_t I2CStart(uint8_t address, uint8_t direction, uint8_t ack) {
  //generate I2C start pulse 
  I2C1->CR1 |= I2C_CR1_START;
  //wait till I2C is busy 
  I2CTimeout = 20000;
  while (!(I2C1->SR1&I2C_SR1_SB)) {
    if (--I2CTimeout == 0x00) 
      return 1;
  }
  //enable ack if we select it 
  if (ack) 
    I2C1->CR1 |= I2C_CR1_ACK;
  //send write/read bit 
  if (direction == I2C_TRANSMITTER_MODE) {
    //send address with zero last bit 
    I2C1->DR = address & ~I2C_OAR1_ADD0;
    //wait till finished 
    I2CTimeout = 20000;
    while (!(I2C1->SR1&I2C_SR1_ADDR)) {
      if (--I2CTimeout == 0x00) 
        return 1;
    }
  }
  if (direction == I2C_RECEIVER_MODE) {
    //send address with 1 last bit 
    I2C1->DR = address | I2C_OAR1_ADD0;
    //wait till finished 
    I2CTimeout = 20000;
    while (!I2CChkEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {
      if (--I2CTimeout == 0x00) 
        return 1;
    }
  }
  //read status register to clear ADDR flag 
  I2C1->SR2;
  //return 0, everything ok 
  return 0;
}

uint8_t I2CStop(void) {
  //wait till transmitter not empty 
  I2CTimeout = 20000;
  while (((!(I2C1->SR1&I2C_SR1_TXE)) || (!(I2C1->SR1&I2C_SR1_BTF)))) {
    if (--I2CTimeout == 0x00) 
      return 1;
  }
  //generate stop 
  I2C1->CR1 |= I2C_CR1_STOP;
  //return 0, everything ok 
  return 0;
}

void I2CWriteData(uint8_t data) {
  //wait till I2C is not busy anymore 
  I2CTimeout = 20000;
  while (!(I2C1->SR1 & I2C_SR1_TXE) && I2CTimeout) 
    I2CTimeout--;
  //send I2C data 
  I2C1->DR = data;
}

void I2CWrite(uint8_t address, uint8_t reg, uint8_t data) {
  I2CStart(address, I2C_TRANSMITTER_MODE, I2C_ACK_DISABLE);
  I2CWriteData(reg);
  I2CWriteData(data);
  I2CStop();
}

static __I uint8_t PrescTable[16] = { 0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9 };

uint32_t GetPclk1Freq(void) {
  uint32_t tmp = 0, presc = 0, pllvco = 0, pllp = 2, pllsource = 0, pllm = 2;
  uint32_t sysclk, hclk;

  //PLL_VCO = (HSE_VALUE or HSI_VALUE /PLLM)*PLLN //SYSCLK = PLL_VCO/PLLP  
  pllsource = (RCC->PLLCFGR&RCC_PLLCFGR_PLLSRC)>>22;
  pllm = RCC->PLLCFGR&RCC_PLLCFGR_PLLM; 
  if (pllsource != 0)
    //HSE used as PLL clock source 
    pllvco = (16000000/pllm)*((RCC->PLLCFGR & RCC_PLLCFGR_PLLN)>>6);
  else
    //HSI used as PLL clock source 
    pllvco = (16000000/pllm)*((RCC->PLLCFGR&RCC_PLLCFGR_PLLN)>>6); 
  pllp = (((RCC->PLLCFGR&RCC_PLLCFGR_PLLP)>>16) + 1 )*2; 
  sysclk = pllvco/pllp;
  //get HCLK prescaler 
  tmp = RCC->CFGR&RCC_CFGR_HPRE;
  tmp = tmp>>4;
  presc = PrescTable[tmp];
  //HCLK clock frequency 
  hclk = sysclk>>presc;
  //get PCLK1 prescaler 
  tmp = RCC->CFGR&RCC_CFGR_PPRE1;
  tmp = tmp>>10;
  presc = PrescTable[tmp];
  //PCLK1 clock frequency 
  return (hclk>>presc);
}

//I2C_duty_cycle_in_fast_mode I2C duty cycle in fast mode  
#define I2C_DUTYCYCLE_2         ((uint32_t)0x00000000)
#define I2C_DUTYCYCLE_16_9      I2C_CCR_DUTY

uint32_t I2CSpeed(uint32_t pclk, uint32_t speed, uint32_t duty_cycle) {
  uint32_t i2c_speed, fast;

  if (duty_cycle == I2C_DUTYCYCLE_2) 
    fast = (pclk/(speed*3));
  else
    fast = ((pclk/(speed*25)) | I2C_DUTYCYCLE_16_9);
  if (speed <= 100000) {
    if (((pclk/(speed<<1))&I2C_CCR_CCR) < 4)
      i2c_speed = 4;
    else
      i2c_speed = (pclk/(speed<<1));
  } else if ((fast&I2C_CCR_CCR) == 0) 
    i2c_speed = 1;
  else
    i2c_speed = (fast | I2C_CCR_FS);
  return i2c_speed;
}

//setup i2c gpiob pins 6&7
void I2CGPIOInit(void) {
  uint32_t position;

  //enable gpiob peripheral clock
  RCC->AHB1ENR |= (1UL<<1);
  for (position=6; position<=7; position++) {
    //configure Alternate function mapped with the current IO 
    GPIOB->AFR[position>>3] &= ~((uint32_t)0xF<<((uint32_t)(position&(uint32_t)0x07)*4));
    GPIOB->AFR[position>>3] |= ((uint32_t)(GPIO_AF_I2C1)<<(((uint32_t)position&(uint32_t)0x07)*4));
    //configure IO Direction mode (Input, Output, Alternate or Analog) 
    GPIOB->MODER &= ~(((uint32_t)0x00000003)<<(position*2));
    GPIOB->MODER |= (GPIO_Mode_AF<<(position*2));
    //configure the IO Speed 
    GPIOB->OSPEEDR &= ~(((uint32_t)0x00000003)<<(position*2));
    GPIOB->OSPEEDR |= (GPIO_High_Speed<<(position*2));
    //configure the IO Output Type 
    GPIOB->OTYPER &= ~(((uint32_t)0x00000001)<<position) ;
    GPIOB->OTYPER |= (GPIO_OType_OD<<position);
    //activate the Pull-up or Pull down resistor for the current IO 
    GPIOB->PUPDR &= ~(((uint32_t)0x00000003)<<(position*2));
    GPIOB->PUPDR |= (GPIO_PuPd_UP<<(position*2));
  }
}

void ConfigureI2C(void) {
  uint32_t freqrange = 0;
  uint32_t pclk1 = 0;

  //setup i2c gpio pins
  I2CGPIOInit();
  //enable i2c1 clock
  RCC->APB1ENR |= (1UL<<21);
  //disable the selected I2C peripheral 
  I2C1->CR1 &= ~(uint32_t)0x01;
  //get PCLK1 frequency 
  pclk1 = GetPclk1Freq();
  //calculate frequency range 
  freqrange = (pclk1/1000000);
  //configure I2C1 frequency range 
  I2C1->CR2 = freqrange;
  //configure I2C1 rise time:  if(I2CClockSpeed <= 100000) 
  if (100000 <= 100000) 
    I2C1->TRISE = freqrange + 1; 
  else
    I2C1->TRISE = ((freqrange*300)/1000) + 1;
  //configure I2C1 speed 
  I2C1->CCR = I2CSpeed(pclk1, 100000, I2C_DUTYCYCLE_2);
  //configure I2C1 Generalcall and NoStretch mode 
  I2C1->CR1 = (uint32_t)0x00;
  //configure I2C1 Own Address1 and addressing mode 
  I2C1->OAR1 = (uint32_t)0x00004000;
  //configure I2C1 Dual mode and Own Address2 
  I2C1->OAR2 = (uint32_t)0x00;
  //enable the I2C peripheral 
  I2C1->CR1 |= (uint32_t)0x01;
}

//calibration values
volatile uint16_t Capture = 1;     //activate capture & update irq when==1
volatile uint8_t CapCount = 0;     //number of samples counter
volatile uint16_t OvrflwCount = 0; //tim1 counter overflow counter
volatile uint32_t Freq = 0;        //capture frequency
volatile uint32_t CumulFreq = 0;   //cumulative capture frequency
uint32_t FreqErr = 1000000;        //set initially very large
uint16_t CalVal;                   //hsitrim value
uint32_t CalFreq;                  //resultant frequency after calibration
#define SAMPLES 3                  //number of samples/captures

void TIM1_UP_TIM10_IRQHandler(void) { 
  if((TIM1->SR&TIM_IT_Update) && (Capture != 0)) {
    TIM1->SR = (uint16_t)~0x01;
    //number of overflows
    OvrflwCount++; 
  }
}

void TIM1_CC_IRQHandler(void) { 
  uint16_t CapVal = 0;
  
  if ((TIM1->SR&TIM_IT_CC2) && (Capture != 0)) {  
    TIM1->SR = (uint16_t)~0x04;
    CapVal = TIM1->CCR2;
    //note: (overflow - 1) eliminates simultaneous occurance of update & ic irq  
    Freq = CapVal + ( ((OvrflwCount - 1)*0xffff) );
    if (CapCount > 0) {
      //cumulative capture frequencies
      CumulFreq = CumulFreq + Freq;
      //average frequency of all captures
      Freq = CumulFreq/CapCount;
      if (CapCount == SAMPLES) 
        //terminate capturing
        Capture = 0;
    }
  }
  //reset for new capture
  OvrflwCount = 0;
  //increment sample count
  CapCount++;
}

//Configure TIM1
static void ConfigureTIM1(void) {
  uint8_t tmppriority = 0x00;
  uint8_t tmppre = 0x00;
  uint8_t tmpsub = 0x0F;

  //enable timer1 peripheral clock
  RCC->APB2ENR |= (1ul<<0);
  //enable gpioa peripheral clock
  RCC->AHB1ENR |= (1ul<<0);                      
  //TIM1 channel 2 pin (PA9) configuration 
  //configure alternate function 
  GPIOA->AFR[9>>3] &= ~((uint32_t)0xF<<((uint32_t)(9&(uint32_t)0x07)*4));
  GPIOA->AFR[9>>3] |= ((uint32_t)(GPIO_AF_TIM1)<<(((uint32_t)9&(uint32_t)0x07)*4));
  //configure IO Direction mode (Input, Output, Alternate or Analog) 
  GPIOA->MODER &= ~(((uint32_t)0x00000003)<<(9*2));
  GPIOA->MODER |= (GPIO_Mode_AF<<(9*2));
  //configure the IO Speed 
  GPIOA->OSPEEDR &= ~(((uint32_t)0x00000003)<<(9*2));
  GPIOA->OSPEEDR |= (GPIO_High_Speed<<(9*2));
  //configure the IO Output Type 
  GPIOA->OTYPER &= ~(((uint32_t)0x00000001)<<9) ;
  GPIOA->OTYPER |= (GPIO_OType_OD<<9);
  //activate the Pull-up or Pull down resistor for the current IO 
  GPIOA->PUPDR &= ~(((uint32_t)0x00000003)<<(9*2));
  GPIOA->PUPDR |= (GPIO_PuPd_UP<<(9*2));

  //enable the TIM1_CC_IRQn interrupt & set priority
  tmppriority = (0x700 - ((SCB->AIRCR)&(uint32_t)0x700))>>0x08;
  tmppre = (0x4 - tmppriority);
  tmpsub = tmpsub>>tmppriority;
  tmppriority = 0<<tmppre;
  tmppriority |= (uint8_t)(1&tmpsub);
  tmppriority = tmppriority<<0x04;
  NVIC->IP[TIM1_CC_IRQn] = tmppriority;
  NVIC->ISER[TIM1_CC_IRQn>>0x05] = (uint32_t)0x01<<(TIM1_CC_IRQn&(uint8_t)0x1F);
  //enable TIM1__UP_TIM10_IRQn interrupt & set priority
  tmppriority = (0x700 - ((SCB->AIRCR)&(uint32_t)0x700))>>0x08;
  tmppriority = 0<<tmppre;
  tmppriority |= (uint8_t)(2&tmpsub);
  tmppriority = tmppriority<<0x04;
  NVIC->IP[TIM1_UP_TIM10_IRQn] = tmppriority;
  NVIC->ISER[TIM1_UP_TIM10_IRQn>>0x05] = (uint32_t)0x01<<(TIM1_UP_TIM10_IRQn&(uint8_t)0x1F);

  //TIM1 configured input capture mode 
  //with external signal is connected to TIM1 CH2 pin (pa9)  
  //rising edge is used 
  TIM1->CCER = (uint16_t)0;      //disable cc2 so we can set CCMR1 
  TIM1->CCMR1 = (uint16_t)0x100; //CC2S:01 (CC2 channel input, IC2 mapped to TI2)
  TIM1->CCMR2 = (uint16_t)0;
  TIM1->CCER = (uint16_t)0x10;   //CC2E set
  TIM1->SMCR = (uint16_t)0x64;   //TS:110 (filtered input timer 2) & SMS:100 (reset mode)
  TIM1->EGR = (uint16_t)0;
  TIM1->PSC = (uint16_t)0;
  TIM1->SR = (uint16_t)0;        //reset status reg
  TIM1->CR2 = (uint16_t)0;
  //TIM1 CR & DIER are not set here
}

//calibrate internal RC and return frequency 
void Calibrate(void) {
  uint8_t Trim;

  //iterate throught all possible hsitrim values
  for (Trim=0; Trim<32; Trim++) {
    uint32_t Error = 0;

    //toggle led
    GPIOA->ODR ^= GPIO_Pin_5;
    //set new hsitrim    
    RCC->CR &= ~RCC_CR_HSITRIM;
    RCC->CR |= (uint32_t)Trim<<3;
    //reset counters
    Freq = 0;
    CumulFreq = 0;
    CapCount = 0;
    //initate new round of captures
    Capture = 1;
    //wait until all periods of current frequency get measured 
    while(Capture == 1);
    //compute frequency error corresponding to current hsitrim 
    if (Freq >= 8000000) 
      Error = Freq - 8000000;
    else
      Error = 8000000 - Freq;
    //retain best calibration value nearest to 8000000Hz 
    if (FreqErr > Error) {
      FreqErr = Error;
      CalVal = Trim;
      CalFreq = Freq;
    }
  }
  //set best histrim 
  RCC->CR &= ~RCC_CR_HSITRIM;
  RCC->CR |= (uint32_t)CalVal<<3;
}

//timer #2 global interrpt handler
void TIM2_IRQHandler(void) {
  if ((TIM2->SR&TIM_IT_Update) && (TIM2->DIER&TIM_IT_Update)) {
    //clear it pending bit
    TIM2->SR = (uint16_t)~TIM_IT_Update;
    //toggle led
    GPIOA->ODR ^= GPIO_Pin_5;
  }
}

//setup timer #2 interrupt
void ConfigureTIM2(void) {
  uint8_t tmppriority = 0x00;
  uint8_t tmpsub = 0x0F;
  
  //enable timer2 peripheral clock
  RCC->APB1ENR |= (1ul<<0);
  //timer #2 nvic priority
  tmppriority = (0x700 - ((SCB->AIRCR)&(uint32_t)0x700))>>0x08;
  tmpsub = tmpsub>>tmppriority;
  tmppriority = 0;
  tmppriority |= (uint8_t)(1&tmpsub);
  tmppriority = tmppriority<<0x04;
  NVIC->IP[TIM2_IRQn] = tmppriority;
  //enable IRQ channel
  NVIC->ISER[TIM2_IRQn>>0x05] = (uint32_t)0x01<<(TIM2_IRQn&(uint8_t)0x1F);
  TIM2->CR1 = (uint32_t)0;      //all off
  //for 1 second @ 8MHz = 1 * 8,000,000, or 8,000 * 1,000
  //prescale = (8,000 - 1), period = (1,000 - 1)
  TIM2->PSC = 7999;             //set prescaler 
  TIM2->ARR = 999;              //set autoreload  
  TIM2->EGR = (uint16_t)0x01;   //(IM_PSCReloadMode_Immediate) generate update event to reload prescaler immediately 
  TIM2->CR1 = (uint32_t)0x01;   //(TIM_CR1_CEN) enable the counter 
  TIM2->DIER |= (uint16_t)0x01; //(TIM_IT_Update) enable interrupt
}

int main(void) {
  char s[64];
  
  //configure HSI as system clock
  ConfigureSystem();                              
  SystemCoreClockUpdate();
  //configure nucleo led
  ConfigureNucleoGPIO();
  //configure tim1
  ConfigureTIM1();
  //configure i2c
  ConfigureI2C();
  //init vcomm port
  ConfigureUSART2();
  //set DS3231 SQW to 1Hz
  I2CWrite(DS3231_ADDRESS, DS3231_CONTROL_REG, 0x00);

  USART2OutString("Starting Calibration: @1Hz this may take awhile...\n");
  TIM1->CR1 |= (uint16_t)0x05;    //URS & counter enabled
  TIM1->DIER |= (uint16_t)0x45;   //update & CC2 enabled
  Calibrate();
  printf("Freq: %ul Error: %ul HSITRIM: %u Accuracy: %0.4f\r\n", CalFreq, FreqErr, CalVal, 8000000./(double)Freq);
  USART2OutString(s);
  TIM1->CR1 &= (uint16_t)~0x05;    //URS & counter disabled
  TIM1->DIER &= (uint16_t)~0x45;   //update & CC2 disabled

  //flash led @1Hz to signify complete
  ConfigureTIM2();
  while (1);
}

The technique used by this program is an adaptation of that found in the ST App Note AN4067.

Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s