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% [на деле больше]), вследствие чего, принять данные будет проблематично. Поэтому их использование, в условиях изменения температуры внешней среды, не желательно. Однако, мы ими воспользуемся для анализа частоты подключенного внешнего резонатора.  

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

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.

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

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.


ссылка на оригинал статьи https://habr.com/ru/post/721184/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *