STM32. CMSIS. Determination of the frequency of the external clock oscillator

There may be situations when, for one reason or another, it is not possible to establish the type of quartz resonator previously included in the project, or situations when the quartz resonator fails. The embedded systems programmer can foresee the development of events in this way. Using the STM32F205RBT6 controller as an example, we will develop / write an algorithm for determining the quartz resonator installed on the board:

1) проверка подключения внешнего кварцевого резонатора/генератора;
  Если не подключен:
2) инициализируем контроллер на работу от HSI с PLL;
Конец.
Если подключен:
2) инициализируем работу контроллера на HSI без PLL;
3) инициализируем LSI;
4) настраиваем TIM5 с тактированием от HSI в режиме „Input Capture mode”;
5) определяем отношение частоты HSI/LSI;
6) инициализируем работу контроллера на HSE без PLL;
7) определяем отношение частоты HSE/LSI;
8) определяем отношение частот HSI/LSI к HSE/LSI и делаем вывод какой частоты кварц подключен;
Если частота не распознана:
9) инициализируем HSI кварц под необходимую частоту с PLL.
Если частота распознана:
9) инициализируем HSE кварц под необходимую частоту с PLL.

The selected controller has two internal crystals: LSI (Low Speed ​​Internal) and HSI (High Speed ​​Internal). The frequency setting accuracy of the built-in resonators, especially the low-frequency one, leaves much to be desired. The temperature inside the crystal strongly affects the accuracy, therefore, when using non-synchronous digital data interfaces, for example, such as CAN, a frequency drift situation may occur (up to 8% [на деле больше]), as a result, it will be problematic to receive data. Therefore, their use, in conditions of changing ambient temperature, is not desirable. However, we will use them to analyze the frequency of the connected external resonator.

First of all, it is worth determining whether the quartz resonator is installed / working:

bool isOscilatorInWorkingOrder(){
	RCC->CR |= RCC_CR_HSEON;
	for (uint32_t i = 0; i < 10000; i++){
		if(RCC->CR & RCC_CR_HSERDY){
			return true;
		}
	}
	RCC->CR &= ~ RCC_CR_HSEON;
	return false;
}

Since the clocking frequency of the low-frequency resonator is not known in advance and it is approximately clear at which high-frequency resonator operates, the next step is to determine the ratio of their frequencies. To do this, we use a timer (TIM5). This timer was chosen because it is the only one presented in the controller that can choose the edge of the signal from the LSI generator as the interrupt trigger.

Let’s initialize the timer:

void timerInit(void){
	RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; // Включаем тактирование таймера

	TIM5->CCMR2 = (TIM5->CCMR2 & (~TIM_CCMR2_CC4S)) | TIM_CCMR2_CC4S_0; // Конфигурируем 4 канал как вход, IC4 маппим к TI4
	TIM5->CCMR2 &= ~TIM_CCMR2_IC4F; // фильтр захвата = 0
	TIM5->CCMR2 &= ~TIM_CCMR2_IC4PSC; // предделитель захвата = 0

	TIM5->OR = TIM_OR_TI4_RMP_0; //через данный регистр маппим TI4 к тактирующему генератору LSI

	TIM5->CCER &= ~(TIM_CCER_CC4NP | TIM_CCER_CC4P);   // захватываем по нарастающему фронту
	TIM5->CCER |= TIM_CCER_CC4E; // Включаем захват, 

	TIM5->ARR = 0xFFFFFFFF; //выставляем максимальное разрешение счёта таймера
	TIM5->CR1 |= TIM_CR1_CEN; // Разрешаем счёт тиков таймером

	NVIC_EnableIRQ(TIM5_IRQn); //Разрешаем глобальное прерывание таймера
}

Clocking enabled, tick count approved, global interrupt enabled, but interrupt not yet triggered, no event set. We will do this a little later, now we will write an interrupt handler:

void hseCheckingTim5IRQHandler(void){
	if(hseCheckingFlag){ //данное условие используется, чтобы задействовать данный обработчик только для этих целей

		static uint8_t interruptsCounter = 0; //счётчик количества прерываний
		interruptsCounter++;
		actCCR = TIM5->CCR4; //запоминаем количество тиков на котором произошло прерывание

		if(interruptsCounter == 100){ //после некоторого количества срабатываний прерываний рассчитываем период LSI в тиках основного генератора
			if(periodCheckingStage == hsiPeriodCheckingStage){
				hsiToLsiPeriod = actCCR - prevCCR;
			} else {
				hseToLsiPeriod = actCCR - prevCCR;
			}
			lsiPeriodCounted = true; // устанавливаем флаг рассчитанного периода
			TIM5->DIER &= ~TIM_DIER_CC4IE; // отключаем прерывание по захвату
			interruptsCounter = 0; //
		}

		prevCCR = actCCR; //запоминаем количество тиков на котором произошло уже обработанное прерывание
	}
}

We will place the written handler function in the function from the interrupt vector for timer 5:

void TIM5_IRQHandler(){
	hseCheckingTim5IRQHandler();
}

Let’s determine the ratio of the periods of the LSI generator in ticks HSI to HSE and return the enum value, what frequency the generator is connected to:

uint8_t checkHseFreq(void){
	uint16_t hsePeriodPlusDefAccur = (hseToLsiPeriod + (hseToLsiPeriod / 100 * defaultHsiAccurancy));
	uint16_t hsePeriodMinusDefAccur = (hseToLsiPeriod - (hseToLsiPeriod / 100 * defaultHsiAccurancy));

	if((hsiToLsiPeriod <= hsePeriodPlusDefAccur) && (hsiToLsiPeriod >= hsePeriodMinusDefAccur)){
		return HSE_FREQ_MHZ_16;
	} else if (((hsiToLsiPeriod / 2) <= hsePeriodPlusDefAccur) && ((hsiToLsiPeriod / 2) >= hsePeriodMinusDefAccur)) {
		return HSE_FREQ_MHZ_8;
	} else if (((hsiToLsiPeriod / 4) <= hsePeriodPlusDefAccur) && ((hsiToLsiPeriod / 4) >= hsePeriodMinusDefAccur)) {
		return HSE_FREQ_MHZ_4;
	} else {
		return HSE_FREQ_ISNT_RECOGNIZED;
	}
}

Then the small thing remains, let’s write the main function that we will call:

uint8_t getHseFreq(void){

	hseCheckingFlag = true; // Устанавливаем флаг проверки внешнего генератора

	if(!isOscilatorInWorkingOrder(HSE_OSCILATOR)){ // проверяем его наличие
		return HSE_ISNT_ENABLE;
	}

  rccReset(); //Делаем ресет основных регистров модуля RCC

	isOscilatorInWorkingOrder(HSI_OSCILATOR); // включаем тактирование HSI генератора
	isOscilatorInWorkingOrder(LSI_OSCILATOR);  // включаем тактирование LSI генератора

	timerInit(); // инициализируем таймер
	lsiPeriodCounted = false; //сбрасываем флаг
	periodCheckingStage = hsiPeriodCheckingStage;  //указываем какой вид генератора проверяем в данный момент
	
	TIM5->DIER |= TIM_DIER_CC4IE; //включаем прерывание таймера по захвату

	while(!lsiPeriodCounte){}; //ожидаем подсчёта периода

	if(!isOscilatorInWorkingOrder(HSE_OSCILATOR)){
		return HSE_ISNT_ENABLE;
	}

	RCC->CFGR |= RCC_CFGR_SW_HSE; // включаем тактирование от HSE
	while ((RCC->CFGR & RCC_CFGR_SWS_HSE) != RCC_CFGR_SWS_HSE) {}; //Ждём стабилизации HSE

	lsiPeriodCounted = false;
	periodCheckingStage = hsePeriodCheckingStage; //указываем какой вид генератора проверяем в данный момент

	TIM5->DIER |= TIM_DIER_CC4IE; //включаем прерывание таймера по захвату

	while(!lsiPeriodCounted){}; //ожидаем подсчёта периода

	hseCheckingFlag = false; // сбрасываем флаг проверки внешнего генератора

	RCC->APB1RSTR = RCC_APB1RSTR_TIM5RST; //приводим таймер в состояние ресета

	return checkHseFreq(); // возвращаем enum значение частоты подключенного генаратора
}

The last thing to do is to initialize the clocking of the controller from the generator to the required frequency, which is decided in each case individually.

Thus, the obtained algorithm for checking the connected quartz oscillator will be able to determine its serviceability and frequency of operation, and in case of failure, it will help the controller start up from an internal clock source.

The source code of the program without a lot of comments to it can be viewed on my GitHub.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *