Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение5 мин
Количество просмотров6.2K

Могут возникнуть ситуации, когда по той или иной причине нет возможности установить ранее заложенный в проект вид кварцевого резонатора определённой частоты, или же ситуации, когда происходит отказ кварцевого резонатора. Программист встраиваемых систем может предусмотреть развитие событий таким образом. На примере контроллера 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.

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

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

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

После выставления бита RCC_CR_HSEON включения внешнего кварца в регистре RCC_CR, необходимо подождать некоторое количество времени для его стабилизации. После того, как контроллер получит несколько тактов от кварца, выставляется бит RCC_CR_HSERDY, функция возвращает true и оставляет кварц включённым, в противном случае отключает HSE кварц и возвращает false.

Код данного характера, но для разных источников тактирования, будет выполняться некоторое количество раз, преобразую функцию к следующему виду:

bool isOscilatorInWorkingOrder(__IO uint32_t* oscReg, uint32_t oscOnBitMsk, uint32_t oscRdyBitMsk){
	*oscReg |= oscOnBitMsk;
	for (uint32_t i = 0; i < 10000; i++){
		if(*oscReg & oscRdyBitMsk){
			return true;
		}
	}
	*oscReg &= ~oscOnBitMsk;
	return false;
}

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

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

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.

Теги:
Хабы:
+30
Комментарии25

Публикации

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн