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

Комментарии (25)


  1. Hlad
    00.00.0000 00:00
    +9

    Есть пара неточностей в статье: внутри кристалла - не кварцевый резонатор (никто не пихает внутрь микросхемы кусок слюды), а RC-цепочка, именно поэтому внутренний источник так чувствителен к температуре. Точность у этой RC-цепочки вполне достаточна для работы UART, да и для ряда многих других приложений (там получается что-то в районе от нескольких сотен до пары тысяч PPM). Часы реального времени, построенные на встроенной RC-цепочке, будут уходить в среднем на пару десятков секунд в сутки, именно поэтому гуляет утверждение, что "низкочастотный внутренний осциллятор менее точен, чем высокочастотный": просто к нему предъявляются более жёсткие требования.


    1. StanislavZm Автор
      00.00.0000 00:00
      +2

      Да, вы правы, спасибо за уточнение.
      Хочу отметить: в своей практике использования CAN шины в автотракторной технике, где температуры в кабине могут гулять зимой от - 30 до + 30 гр. С., заметил довольно сильный уход битрейта при использовании внутреннего осциллятора, что не позволяло принимать данные, пришлось отказаться от идеи его использования.


      1. VelocidadAbsurda
        00.00.0000 00:00
        +3

        Интересно, а если попробовать один раз построить зависимость «температура по встроенному датчику - требуемое для компенсации значение HSITRIM» (так же софтово на плате с внешним резонатором), заложить таблицу в прошивку и периодически подстраивать HSI?

        Если решите попробовать - термодатчику однозначно необходимо усреднение по нескольким выборкам, моментальная температура кристалла ощутимо гуляет в зависимости от нагрузки.


        1. StanislavZm Автор
          00.00.0000 00:00
          +2

          А это была идея для одной из следующих статей)


        1. Hlad
          00.00.0000 00:00
          +1

          Всё равно точности кварца не получить даже близко. Плюс - у разных контроллеров очень большой разброс характеристик.


        1. alex-open-plc
          00.00.0000 00:00

          Так уже есть калибраторы... На сайте ST. Задействован внутренний температурный датчик. Только решение всё равно так себе.


      1. Goron_Dekar
        00.00.0000 00:00
        +1

        А что натолкнуло на мысль об использовании внутреннего генератора?

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

        Но в автопроме нет ни потребности экономить каждую долю цента, ни проблем с местом, а потребление всегда можно уменьшить уходом в сон.

        Всегда не понимал людей, которые не втыкают резистор на питание от USB-C, кварцовый резонатор или по конденсатору на каждую ногу питания микроконтроллера. Для кого пишут Reference Manual? Для каждого отхода от рекомендаций производителя всегда нужно находить вескую причину.


        1. StanislavZm Автор
          00.00.0000 00:00
          +1

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


          1. Goron_Dekar
            00.00.0000 00:00
            +2

            Звучит паршиво, вам придётся разгребать чужие ошибки.

            Для колибровки разбега осциллятора от температуры (индивидуальной для каждого кристалла, так как тримминг RC тоже индивидуальный) не забудьте учесть и дрейф опорного напряжения АЦП внутреннего термометра и нестабильность напряжения на линии VCC (линейный стабилизатор тоже имеет свой температурный коэффициент, как и потребление), там же врядли аналоговый тракт отделён от цифрового.


            1. VelocidadAbsurda
              00.00.0000 00:00
              +1

              Для индивидуальной калибровки, по идее, учитывать все дрейфы по отдельности как раз не нужно, нам же не нужны сами цифры, достаточно соответствий вида «температурный показометр показал -7 -> выставляем HSITRIM в 19».

              А заводская индивидуальная калибровка, кстати, неявно суммируется с HSITRIM, который задаёт сдвиг относительно неё. Возможно, для конкретной задачи и требуемой ей точности (как CAN у автора), хватит и общей для всех устройств таблицы, полученной усреднением с нескольких кристаллов.


    1. fk0
      00.00.0000 00:00

      На пару десятков секунд в сутки запросто уходит обычный часовой кварц (не термокомпенсированный, без подстройки), а не RC-цепочка.

      В сутках 86400 секунд, и какие-нибудь 5 процентов -- это ЧАС времени, а не 20 секунд.

      Точность этой RC-цепочки часто НЕдостаточна для работы с UART во всём температурном диапазоне и хорошо работает лишь на столе, при стандартной температуре. На резонансную частоту этой цепочки влияет не только температура, но и технологическая невозможность получения стабильных характеристик на производстве. И лишь у некоторых контроллеров там то ли какая-то самокалибровка, то ли калибровка лазером на производстве. Не у самых дешёвых.

      Вообще возможность подключения часового кварца к МК часто означает возможность отказа от второго высокочастотного резонатора. Можно запуститься с внутренним генератором и периодически подстраивать его работу ориентируясь на часовой кварц. Не для любых применений, но для тех гдe 5% достаточно и где МК позволяет подстраивать частоту HSI -- сгодится.


  1. jogick
    00.00.0000 00:00

    Идея прикольная. Как по мне, так проще завести генератор и осциллом посмотреть его колебания. На сколько часто уходит частота кварца? Вот заткнуться может (сам видел), ну так этот момент обрабатывается и переходим на внутреннее тактирование


    1. Hlad
      00.00.0000 00:00
      +3

      А если плата идёт в устройства, которые делаются партиями в тысячи штук, на контрактном производстве? Проще поставить вот такой программный костыль.


      1. COKPOWEHEU
        00.00.0000 00:00
        -1

        Проще поставить кварц на известную частоту. А если кто-то полезет менять компоненты на плате от балды, основываясь исключительно на внешнем сходстве и не читая маркировку — ССЗБ.


        1. StanislavZm Автор
          00.00.0000 00:00
          +3

          Проще поставить изначально заложенный в проект кварц. Но данный костыль на то и костыль, что используется в тех случаях, когда кварцев заложенных в проект в наличии нет (например 8 МГц, но большое количество на 16/4 МГц), что позволяет не писать разные версии прошивки под разные кварцы.


          1. COKPOWEHEU
            00.00.0000 00:00

            Даже так проще поправить константу в коде, подправить множители/делители и пересобрать прошивку, чем городить искусственный мозг, который будет пытаться откалибровать внешний кварц по внутреннему RC-генератору с поиском ближайшего распространенного (ага, удачи!), пересчетом всех коэффициентов и молитвами всем технобогам чтобы эта комбинация множителей и делителей вообще существовала. Если впаяют 12.145 МГц например.


        1. Hlad
          00.00.0000 00:00
          +3

          Год назад, например, была ситуация "ну хоть что-то есть, что можно впаять на эту позицию???"


        1. rsashka
          00.00.0000 00:00

          Можно запроектировать сразу несколько кварцев с разной частотой на одну позицию и не ломать голову с несколькими вариантами прошивок в зависимости от наличия конкретных комплектующих у поставщиков.


        1. fk0
          00.00.0000 00:00

          Вообще замена компонентов на производстве на другой тип (даже с одинаковыми на первый взгляд характеристиками), без разрешения разработчика или заказчика -- ССЗБ. Потому, что после такой замены генератор может запускаться опять же на не своей штатной гармонике просто потому, что важны все составляющие, и ёмкость микроконтроллера, и конденсаторов, и она должна соответствовать выбранному типу резонатора. Просто заменить один 8МГц на другой 8МГц другой фирмы -- может вроде бы и прокатить, но потом на выходе окажется 10% брака, например.


    1. fk0
      00.00.0000 00:00
      -1

      На производстве тыкаться осциллографом в каждую плату -- неудобно во-первых, во-вторых долго и дорого, в третьих в сколько-нибудь сложной программе для МК работа внешнего генератора может носить периодический характер (потому, что МК будет "засыпать" вместе с генератором).

      И наконец -- осциллограф это не прибор для определения частоты, частоту на нём можно оценить лишь приблизительно. Хотя это действительно не слишком принципиально. И щуп для осциллографа имеет порядочную ёмкостную составляющую, чтоб сбить работу генератора (особенно если забыть про делитель).

      Насколько может уйти. В небольшом диапазоне может подстраиваться конденсаторами. Либо может стать сходу в три раза больше/меньше. В случаях повреждения резонатора (достаточно падения об пол) может стать какой угодно. Случаи бывают разные.

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


  1. fk0
    00.00.0000 00:00

    	for (uint32_t i = 0; i < 10000; i++){
    		if(RCC->CR & RCC_CR_HSERDY)
                ...

    Фрагмент кода не очень надёжный в том смысле, что его время выполнения несколько не предсказуемо (неизвестно сколько тактов CPU нужно на итерацию цикла, неизвестно как современные CPU распараллелят инструкции и где будут задержки, всё зависит от опций оптимизации компилятора). Лучше иметь итерацию до достижения таймером заданной величины. А таймер запрограмировать на нужное число тактов в зависимости от текущего выбранного источника тактирования. Генератору с кварцевым резонатором чтоб запуститься нужно от миллисекунд до нескольких десятков миллисекунд (для низкочастотных "часовых" резонаторов на порядок больше). Соответственно таймаут может быть на 100мс, например.

    Так как заранее неизвестна частота тактирования низкочастотного резонатора...

    Она-то как раз в даташите указывается. С погрешностью порядка 5%, но всё таки известна.

    А внешний кварцевый резонатор в особо запущенных случаях запускается на какой-нибудь другой гармонике и частота действительно неизвестна. Это надо сказать типовая неисправность. Она может быть вызвана повреждением резонатора, ошибками монтажа, монтажом неправильных номиналов конденсаторов в цепи резонатора, монтажом не того типа резонатора... И стоило бы хотя бы грубо эту частоту оценить, чтоб принять решение, генератор с внешним резонатором вообще работоспособен или нет.

    Обработчики прерывания для всего этого дела -- не нужны. Достаточно в цикле опрашивать специальные регистры. В конце концов, это делается только на старте и процессор в это время ничем другим не занимается.

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

    Сомнительная техника ввиду сказанного выше: так станет невозможно обнаруживать и сигнализировать о явных неисправностях (например, числом вспышек светодиодика). И не обнаруженная неисправность превращается в куда большую проблему, которую уже должен исследовать инженер (иначе бы ограничилось ремонтом на производстве), и сразу непонятно, программная она или вызвана неправильной работой схемы (потому, что с неправильным кварцем -- "программа глючит", что-то одно работает, что-то другое нет и т.п.)

    Поэтому я бы предложил частоту резонатора знать заведомо, или вносить через какой-то механизм конфигурации (путём установки перемычек, путём программирования с компьютера).

    В целом представленный программный код изобилует magic numbers в коде. Почему 10000, а не 100500? А если завтра контроллер будет с другой буковкой -- там 10000 ещё нормально, или уже нет? Или почему interruptsCounter == 100? Значит эта сотня где-то ещё закодирована. В code style некоторых компаний чуть ли не вообще запрещают "волшебные числа" в коде. Как минимум -- нужно объявить (через enum или #define в C, через const int в C++) константу и ей пользоваться, самой константе же дать осмысленные имя и комментарий. За исключением нулей, единиц и ряда очевидных констант. Рекомендую следовать такому же стилю.


    1. StanislavZm Автор
      00.00.0000 00:00

      Спасибо за ваш комментарий.

      Она-то как раз в даташите указывается. С погрешностью порядка 5%, но всё таки известна.

      В статье я приложил скриншот из даташита, он как раз и показывает возможную разбежку частоты низкочастотного резонатора от 17 кГц до 47 кГц.


  1. GarryC
    00.00.0000 00:00
    +1

    Мне представляется сомнительной исходная идея - точность встроенного генератора недостаточна для работы интерфейса, поэтому измерим с его помощью внешний источник тактового сигнала и получим точность выше - как то странно.

    Другое дело, если мы заранее предполагаем некоторый стандартный набор кварцев, существенно (на десятки процентов) отличающихся по частоте и выбираем 1 из них, как, собственно и сделано.


  1. Paha20
    00.00.0000 00:00

    top


  1. ViacheslavMezentsev
    00.00.0000 00:00
    +2

    В своё время тоже искал информацию по теме.

    Положу это тут: 407 vs 427 DFU, auto detect HSE clock speed