Прежде чем мы начнём писать библиотеки для работы с классами, нам необходимо разобраться с тактовым генератором и некоторыми функциями для портов ввода/вывода, которым не требуются классы. В этой статье будет рассмотрен принцип инициализации на максимальную тактовую частоту на внешнем кварцевом резонаторе. Если необходимо будет работать или с внутренним источником тактирования или каки-либо ещё нюансы, поняв как устроен тактовый генератор, можно будет над ним издеваться как захотим. Причём на любом МК.
Для начала посмотрим тактовые генераторы разных МК. Я не стал приводить примеры монструозных тактовых генераторов, чтобы раньше времени никто не испугался. Начнём с более простого. Тактовый генератор STM32F030C8T:
Тактовый генератор STM32F103C8T:
Тактовый генератор STM32F301R8T:
Тактовый генератор STM32F407VET:
Как видите, между ними много общего. В каждом тактовом генераторе есть кое-что, что их объединяет:
- Внутренний тактовый HSI генератор на RC-цепочке. У разных МК частота может быть разной. После включения и сброса тактовый генератор находится именно в таком состоянии. Вы можете или остаться на нём или переключиться на кварцевый резонатор. Стабильность этого генератора хуже, чем на кварцевом. Правда в большинстве случаев хватает и этого. Примечание: в других МК он может называться по другому - HSI16, HSI48 и т. д. Но сути это не меняет.
- Тактовый генератор с кварцевым резонатором HSE. Где стоит надпись "Input frequency", указано на какие частоты можно поставить кварцевый резонатор.
- Мультиплексор. Переключает источники тактирования - внутренний или внешний генератор. В зависимости от МК может иметь разное количество входов, а может и отсутствовать, если выводов у корпуса мало.
- Делитель перед модулем PLL. Может входить в состав PLL, может отсутствовать.
- Модуль PLL. Крутая штука, позволяющая тактировать ядро МК практически любыми частотами.
- Ещё один мультиплексор. По стрелочкам можно определиться, что, куда включает.
- Делители шин периферии. Не все шины и не вся периферия может работать на максимальных частотах. Для этого введены дополнительные делители. С ними нужно быть очень осторожными. Прежде чем запускать периферийное устройство, нужно из даташита или Reference manual (RM для краткости) узнать от какой шины тактируется таймер. Самый простой способ - в RM посмотреть в разделе регистров RCC, куда подключено данное устройство и в CubeMX посмотреть частоту тактирования шины.
Теперь нам в CubeMX необходимо выставить все частоты на максимум, который возможен на внешнем кварцевом резонаторе. Я применяю в основном на 8Мгц. Но можно и другой, но не любой. Иначе можете получить дробные частоты и никогда не запустите USB (надеюсь с CubeMX все знакомы):
В данном случае я включил USB, чтобы убедиться, что мы можем получить необходимую частоту. В самой функции делитель можно не включать. Его можно запустить отдельно при инициализации USB, но при этом можно хватануть проблем. Я нашёл некоторый алгоритм, который в большинстве случаев упрощает выбор, но не всегда.
Делитель /M ставим такой, чтобы после него тактовая частота была 2МГц. В нашем случае 8 / 4 = 2. Умножитель *N делаем равным 168, т. е. максимальной тактовой частоте ядра МК. Получаем на выходе 2*168=336МГц. Делитель /P оставляем по умолчанию, получаем 168МГц на выходе второго мультиплексора. Но у нас шина APB получается перегруженной, поэтому ставим там соответствующие делители для получения необходимых частот. Теперь мы знаем коэффициенты деления и умножения и можем перейти непосредственно к программированию. Но нам необходимо учесть, что данная библиотека предназначена для инициализации тактового генератора после сброса, когда все регистры RCC находятся в состоянии "по умолчанию". Какой регистр и в каком состоянии после сброса, можно узнать из описания регистров в RM.
Шаг 1.
Запускаем тактирование модуля управления питанием PWR и конфигуратора системы SYSCFG. Это не всегда нужно, и иногда этот шаг можно опустить:
RCC->APB1ENR |= RCC_APB1ENR_PWREN; RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
Шаг 2.
Запускаем HSE и ждём некоторое время в ожидании запуска. Если кварцевый резонатор битый или трассировка платы неудачна, он может и не запуститься. Если запуск так и не произошёл, мы выходим из функции, ничего не предпринимая:
uint32_t StartUpCounter; RCC->CR |= RCC_CR_HSEON; // Запускаем генератор HSE for (StartUpCounter = 0;; StartUpCounter++) // В цикле ждём определённое время ожидая запуска { if(RCC->CR & RCC_CR_HSERDY) break; // Запуск есть, выходим из цикла if(StartUpCounter > 0x1000) { RCC->CR &= ~RCC_CR_HSEON; // Отключаем HSE return; // Запуска нет, выходим из функции } }
Шаг 3.
Конфигурируем множители/делители PLL:
RCC->PLLCFGR = 0; // На всякий пожарный обнуляем регистр конфигурации PLL RCC->PLLCFGR |= (0x000100 << RCC_PLLCFGR_PLLM_Pos); // Программируем делитель M = 4 RCC->PLLCFGR |= (0x0111 << RCC_PLLCFGR_PLLQ_Pos); // Устанавливаем делитель Q = 7 для USB RCC->PLLCFGR |= (168 << RCC_PLLCFGR_PLLN_Pos); // Устанавливаем умножитель N = 168 RCC->PLLCFGR |= (0x00 << RCC_PLLCFGR_PLLP_Pos); // Устанавливаем делитель P = 2 RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC;
Шаг 4.
Включаем PLL и опять ждём, когда переключится:
RCC->CR |= RCC_CR_PLLON; // Включаем PLL for (StartUpCounter = 0;; StartUpCounter++) { if(RCC->CR & RCC_CR_PLLRDY) break; // Запустился, вываливаемся из цикла if(StartUpCounter > 0x1000) // Не запустился, гад { RCC->CR &= ~RCC_CR_HSEON; // Отключаем HSE RCC->CR &= ~RCC_CR_PLLON; // Останавливаем PLL return; } }
Шаг 5.
Программируем таймаут FLASH, так как не может работать на таких частотах. Таймауты можно узнать в главе "3.5.1 Relation between CPU clock frequency and Flash memory read time":
На наших частотах таймаут равен 5:
FLASH->ACR |= (0x05 << FLASH_ACR_LATENCY_Pos); // Таймаут FLASH=5, т.к. частота задающего генератора 168 МГц
Шаг 6.
Выставляем делители для шин и переключаемся на PLL:
//Делители для шин RCC->CFGR |= (0x04 << RCC_CFGR_PPRE2_Pos) //Делитель шины APB2 равен 2 | (0x05 << RCC_CFGR_PPRE1_Pos) //Делитель нишы APB1 равен 4 | (0x00 << RCC_CFGR_HPRE_Pos); //Делитель AHB отключен (оставляем 0 по умолчанию) RCC->CFGR |= (0x02 << RCC_CFGR_SW_Pos); // Переключаемся на работу от PLL while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != (0x02 << RCC_CFGR_SWS_Pos)) // Ждём переключения { }
Если что-то пойдёт не так, то вспомните первую статью, я говорил о строке, которую можно временно закомментировать:
Теперь она должна быть раскомментирована. Так как при любом сбое, программном или аппаратном, МК уходит в эту функцию, выключает прерывание и зависает в бесконечном цикле. Там вы можете написать свой обработчик ошибок, если хотите чтобы было всё круто, и МК мог выходить из состояния ошибки.
Полностью написанная функция инициализации SystemClock_Config()
есть в каталогах Drivers. Имя файла RCC_***_***.
Теперь в функцию main()
необходимо добавить строку SystemClock_Config(Quartz_8)
, чтобы проинициализировать RCC на 168МГц с резонатором на 8МГц. Но при компиляции выдаст ошибки, так как не хватает кое-чего ещё, что я опишу в следующей статье.
Материал как всегда прилагается на Яндекс Диске и в виде архива. Всё здесь изложенное сделано для нескольких ядер МК. Может быть где-то найдутся ошибки, но для STM32F40xx вроде вылизано. Если найдёте - сообщайте, поправлю.
Тема на форуме - перейти.
А будет что-то вроде полного примера? То есть в функции main() инициализируем порты к примеру и моргаем диодом с определенной частотой?
Обязательно
Отлично!