Top.Mail.Ru

Часть 3. STM32 и С++. Инициализация тактового генератора.

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

Для начала посмотрим тактовые генераторы разных МК. Я не стал приводить примеры монструозных тактовых генераторов, чтобы раньше времени никто не испугался. Начнём с более простого. Тактовый генератор STM32F030C8T:

Тактовый генератор STM32F103C8T:

Тактовый генератор STM32F301R8T:

Тактовый генератор STM32F407VET:

Как видите, между ними много общего. В каждом тактовом генераторе есть кое-что, что их объединяет:

  1. Внутренний тактовый HSI генератор на RC-цепочке. У разных МК частота может быть разной. После включения и сброса тактовый генератор находится именно в таком состоянии. Вы можете или остаться на нём или переключиться на кварцевый резонатор. Стабильность этого генератора хуже, чем на кварцевом. Правда в большинстве случаев хватает и этого. Примечание: в других МК он может называться по другому - HSI16, HSI48 и т. д. Но сути это не меняет.
  2. Тактовый генератор с кварцевым резонатором HSE. Где стоит надпись "Input frequency", указано на какие частоты можно поставить кварцевый резонатор.
  3. Мультиплексор. Переключает источники тактирования - внутренний или внешний генератор. В зависимости от МК может иметь разное количество входов, а может и отсутствовать, если выводов у корпуса мало.
  4. Делитель перед модулем PLL. Может входить в состав PLL, может отсутствовать.
  5. Модуль PLL. Крутая штука, позволяющая тактировать ядро МК практически любыми частотами.
  6. Ещё один мультиплексор. По стрелочкам можно определиться, что, куда включает.
  7. Делители шин периферии. Не все шины и не вся периферия может работать на максимальных частотах. Для этого введены дополнительные делители. С ними нужно быть очень осторожными. Прежде чем запускать периферийное устройство, нужно из даташита или 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 вроде вылизано. Если найдёте - сообщайте, поправлю.

Подписаться
Уведомить о
guest

3 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Banw
7 месяцев назад

А будет что-то вроде полного примера? То есть в функции main() инициализируем порты к примеру и моргаем диодом с определенной частотой?

Banw
Ответ на комментарий  Эдуард
7 месяцев назад

Отлично!

3
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x