STM32. CMSIS. Определение частоты внешнего тактирующего осциллятора

Могут возникать ситуации, когда по той или иной причине нет возможности установить ранее заложенный в проект вид кварцевого резонатора, или же ситуации, когда происходит отказ кварцевого резонатора. Программист встраиваемых систем может предусмотреть развитие событий таким образом. На примере контроллера STM32F205RBT6 разработаем/напишем алгоритм определения установленного на плату кварцевого резонатора:

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.

Выбранный контроллер имеет два внутренних кварцевых резонатора: LSI (Low Speed Internal) и HSI (High Speed Internal). Точность выставления частоты у встроенных резонаторов, особенно у низкочастотного, оставляет желать лучшего. На точность сильно влияет температура внутри кристалла, поэтому при использовании несинхронных цифровых интерфейсов передачи данных, например, таких как CAN, может возникнуть ситуация ухода частоты (вплоть до 8% [на деле больше]), вследствие чего, принять данные будет проблематично. Поэтому их использование, в условиях изменения температуры внешней среды, не желательно. Однако, мы ими воспользуемся для анализа частоты подключенного внешнего резонатора.  

70f123a069b4e7c697a30271d49f8367.png

Первым делом, стоит определить установлен/исправен ли кварцевый резонатор:

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;
}

Так как заранее неизвестна частота тактирования низкочастотного резонатора и приблизительно понятно на какой работает высокочастотный, то следующим этапом будет определение отношения их частот. Для этого воспользуемся таймером (TIM5). Данный таймер выбран по той причине, что он единственный из представленных в контроллере, который может выбрать в качестве триггера срабатывания прерывания фронт сигнала от генератора LSI.

35a543b8bad635a6e95793e1946fb545.png

Проведём инициализацию таймера:

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); //Разрешаем глобальное прерывание таймера
}

Тактирование включили, счёт тиков одобрили, глобальное прерывание разрешили, однако прерывание пока ещё не произойдёт, не установили событие. Это сделаем чуть позже, сейчас напишем обработчик прерывания:

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; //запоминаем количество тиков на котором произошло уже обработанное прерывание
	}
}

Написанную функцию обработчика разместим в функции из вектора прерываний для таймера 5:

void TIM5_IRQHandler(){
	hseCheckingTim5IRQHandler();
}

Определим отношение периодов генератора LSI в тиках HSI к HSE и вернём значение enum, какой частоты генератор подключен:

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;
	}
}

Далее остаётся дело за малым, напишем основную функцию, которую будем вызывать:

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 значение частоты подключенного генаратора
}

Последнее, что необходимо сделать — проинициализировать тактирование контроллера от генератора на необходимую частоту, что решается в каждом случае индивидуально.

Таким образом, полученный алгоритм проверки подключенного кварцевого осциллятора сможет определить его исправность и частоту работы, а в случае отказа поможет завестись контроллеру от внутреннего источника тактирования.

Исходный код программы без большого количества комментариев к нему можно посмотреть на моём GitHub.

© Habrahabr.ru