Программирование STM32. Часть 4: Настройка RCC

В предыдущей части мы рассмотрели систему тактирования микроконтроллеров STM32. В этой части мы изучим регистры RCC и займемся ее настройкой. Все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1

 

Содержание:

Регистры CR и CFGR

Открываем Reference manual на микроконтроллер STM32F103x8 и переходим к разделу 7: Low-, medium-, high- and XL-density reset and clock control (RCC). RCC имеет довольно много регистров, целых 10 штук. Однако, для настройки источника тактирования и делителей шин нам понадобится только 2 из них.

Clock control register (RCC_CR)

Рис. 1. Биты регистра CR

Описание основных битов регистра:

PLLRDY — флаг готовности PLL. Устанавливается аппаратно и сигнализирует о том, что PLL заблокирован.

PLLON — Включить PLL. Устанавливается и сбрасывается программно. При переходе в режим Stop или Standby сбрасывается аппаратно. Этот бит не может быть сброшен, если PLL используется как источник системного тактирования.

CSSON — включить систему CSS

HSEBYP — Если вместо кварцевого резонатора HSE мы хотим использовать внешний прямоугольный тактовый сигнал, то этот бит нужно установить в 1

HSERDY — Флаг готовности генератора HSE. Аппаратно устанавливается в 1 после успешного запуска и стабилизации частоты HSE-генератора

HSEON — Запустить HSE генератор. Устанавливается и сбрасывается программно. При переходе в режим Stop или Standby сбрасывается аппаратно и HSE генератор останавливается.  Этот бит не может быть сброшен, если HSE используется как источник системного тактирования.

HSIRDY — то же самое, что и HSERDY, только для встроенного RC-генератора HSI

HSION — то же самое, что и HSEON, только для встроенного RC-генератора HSI

 

Clock configuration register (RCC_CFGR)

Рис. 2. Биты регистра CFGR

Описание основных битов регистра:

MCO — подача тактового сигнала на MCO-пин микроконтроллера.

  • 0xx: Функция отключена
  • 100: Выбран System clock (SYSCLK)
  • 101: Выбран сигнал с HSI
  • 110: Выбран сигнал с HSE
  • 111: Выбран сигнал с PLL, который поделен на 2

PLLMUL — коэффициент умножения PLL. Эти биты могут быть записаны программно только при отключенном PLL

  • 0000: Входную частота PLL умножить на 2
  • 0001: —//— на 3
  • 0010: —//— на 4
  • 0011: —//— на 5
  • 0100: —//— на 6
  • 0101: —//— на 7
  • 0110: —//— на 8
  • 0111: —//— на 9
  • 1000: —//— на 10
  • 1001: —//— на 11
  • 1010: —//— на 12
  • 1011: —//— на 13
  • 1100: —//— на 14
  • 1101: —//— на 15
  • 1110: —//— на 16
  • 1111: —//— на 16

Два последних значения соответствуют одинаковому коэффициенту умножения.

PLLXTPRE — Делитель частоты с HSE генератора перед подачей на PLL. Этот бит не может быть изменен, если PLL запущен. При установке в 1 частота HSE будет поделена на 2, если 0, то делитель отключен.

PLLSRC — Источник входной частоты PLL. Не может быть изменен, если PLL запущен.

  • 0: частота HSI генератора поделенная на 2
  • 1: частота HSE генератора. Делитель может быть выбран PLLXTPRE битом.

PPRE2 — Делитель шины APB2 prescaler

  • 0xx: HCLK без деления
  • 100: HCLK / 2
  • 101: HCLK / 4
  • 110: HCLK / 8
  • 111: HCLK / 16

PPRE1 — Делитель шины APB1 prescaler. Частота шины APB1 не должна превышать 36 МГц.

  • 0xx: HCLK без деления
  • 100: HCLK / 2
  • 101: HCLK / 4
  • 110: HCLK / 8
  • 111: HCLK / 16

HPRE — AHB prescaler

  • 0xxx: SYSCLK без деления
  • 1000: SYSCLK / 2
  • 1001: SYSCLK / 4
  • 1010: SYSCLK / 8
  • 1011: SYSCLK / 16
  • 1100: SYSCLK / 64
  • 1101: SYSCLK / 128
  • 1110: SYSCLK / 256
  • 1111: SYSCLK / 512

SWS — Состояние переключателя тактирования системы. Устанавливается аппаратно и указывает на текущий источник тактирования.

  • 00: HSI генератор используется как источник тактирования системы
  • 01: HSE генератор используется как источник тактирования системы
  • 10: PLL используется как источник тактирования системы

SW — Переключатель источника тактирования системы. Изменяется программно для выбора источника SYSCLK. Устанавливается аппаратно для принудительного переключения на HSI генератор переходе в режим Stop или Standby или в случае срыва генерации HSE, который используется в качестве источника SYSCLK (только если активна система CSS)

  • 00: HSI выбран в качестве источника системного тактирования
  • 01: HSE выбран в качестве источника системного тактирования
  • 10: PLL выбран в качестве источника системного тактирования

Коэффициенты

В предыдущей части мы рассмотрели вариант тактирования системы тактирования от HSE генератора через PLL, для удобства скопирую это сюда:

  • Кварц HSE на 8 МГц
  • PLLXTPRE: без деления
  • PLLSRC: HSE генератор
  • PLLMUL = 9
  • SW = PLLCLK
  • AHB Prescaler = 1
  • APB1 Prescaler = 2
  • APB2 Prescaler = 1

И картинку тоже:

Рис. 3. Схема прохождения тактового сигнала при использовании PLL совместно с HSE

Регистры в CMSIS

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

Каждый экземпляр периферии является структурой, в которой находятся все регистры, относящиеся к данному устройству. Почти во всех случаях имя структуры совпадает с именем периферийного модуля. Для микроконтроллера STM32F103C8 все структуры периферийных модулей объявлены в файле stm32f103xb.h:

Рассмотрим, как выполняется обращение к регистрам из программы на Си. Например, нам надо в регистре RCC_CR установить бит HSEON. Это можно сделать одним из следующих способов:

или так:

Думаю, те, кто раньше программировал для микроконтроллеров AVR увидят в этих записях что-то знакомое. Рассмотрим первый случай:

Сначала идет имя периферийного модуля, в нашем случае «RCC». Затем символ «->», после чего имя регистра «CR».  RCC_CR_HSEON_Msk представляет собой вот такой #define:

где 16 — номер бита HSEON в регистре CR (см. рис. 1). RCC_CR_HSEON_Msk есть ни что иное, как битовая маска, имя которой состоит из названия периферийного модуля, имени регистра и бита, а так же постфикса _Msk. В CMSIS есть еще один #define, который является синонимом RCC_CR_HSEON_Msk:

По факту все то же самое, только без _Msk.

Второй случай выглядит аналогичным образом:

где

То есть, RCC_CR_HSEON_Pos является позицией бита в регистре, о чем говорит постфикс _Pos.

А как быть с параметрами, которые имеют несколько битов? К примеру в регистре CFGR мы хотим установить значение множителя PLL равное девяти, имеющее код 0111 (см. рис. 2 биты PLLMUL). Тут вот такое решение:

Первой строчкой мы устанавливаем биты 0, 1 и 2 PLLMUL в единицы, второй строчкой сбрасываем бит 3 в ноль, в итоге получаем 0111. Однако, если мы уверены, что все биты PLLMUL изначально установлены в нули, то вторую строчку мы можем пропустить:

А есть и еще один вариант: значение 0111 в десятичном виде является числом 7. Тогда можно сделать вот так:

Но и тут надо помнить, что такая запись справедлива только в случае, если изначальное значение всех битов PLLMUL равны нулям.

Настройка

Для правильной настройки системы тактирования в микроконтроллерах STM32 необходимо соблюдать определенную последовательность запуска блоков. Если мы хотим переключить систему с внутреннего RC-генератора на HSE через PLL, то необходимо провести следующие операции вот в таком порядке:

  1. Запустить генератор HSE
  2. Настроить PLL
  3. Запустить PLL
  4. Настроить количество циклов ожидания FLASH
  5. Настроить делители шин
  6. Переключиться на работу от PLL

Так, вроде все понятно, хотя… А что это за 4-й пункт? Что за циклы ожидания FLASH? Вот здесь кроется один неочевидный момент. Дело в том, что FLASH-память микроконтроллера, в которой хранится управляющая программа, может работать на максимальной частоте 24 МГц. Обмен данными с FLASH осуществляется через шину AHB (см. рис. 3). А если частота шины AHB выше 24 МГц, но необходимо ввести циклы ожидания обращений к этой памяти, примем, чем выше частота, тем больше этих циклов надо:

  • ноль циклов ожидания, если 0 < SYSCLK ≤ 24 MHz
  • один цикл ожидания, если 24 MHz < SYSCLK ≤ 48 MHz
  • два цикла ожидания, если 48 MHz < SYSCLK ≤ 72 MHz

Надеюсь, с этим все ясно.

Хочу отметить, что порядок настройки может быть и таким:

  1. Настроить количество циклов ожидания FLASH
  2. Настроить делители шин
  3. Запустить генератор HSE
  4. Настроить PLL
  5. Запустить PLL
  6. Переключиться на работу от PLL

Просто надо помнить, что мы сидим на ветке, которую пилим, и важно все отпилить и подставить в нужной последовательности, чтоб не упасть. В случае с STM32, «упасть» не получится, так как в этих микроконтроллерах реализована аппаратная защита от неправильной последовательности действий: мы не сможем остановить генератор, от которого сейчас работаем или переключиться на источник тактового сигнала, если он еще не запущен. Так что в худшем случае у нас просто ни чего не получится, что то же является не очень приятным.

Итак, поехали! Возьмем за основу проект, который мы создали во 2-й части, когда подключали CMSIS: https://github.com/DiMoonElec/stm32f103c8_empty_project.

Создадим функцию, в которую будем добавлять код инициализации:

Первым делом запускаем генератор HSE:

После этого нам надо дождаться установки флага HSERDY, который указывает на успешный запуск генератора. Можно это сделать по-простому с помощью цикла while() {}, но тогда мы рискуем зависнуть в нем навсегда, если что-то случится с кварцевым резонатором. Хотелось бы иметь возможность каким-то образом сигнализировать о невозможности запуска. Вот моя реализация:

Тут реализован тайм-аут на запуск HSE. Если генератор успел запуститься до возникновения условия if(StartUpCounter > 0x1000), то мы выходим из цикла for() с помощью инструкции break.

Так, HSE запустили. Переходим к настройке PLL:

Тут просто настраиваем коэффициент умножения и выбираем источник тактирования PLL. Далее, запускаем PLL:

После этого ждем успешного запуска. Тут ожидание реализовано точно так же, как и для HSE:

После этого настраиваем FLASH и делители:

Ну и торжественный момент переключение на работу от PLL:

Ждем завершения переключения:

Поздравляю! Мы запустились от PLL! После этого можно отключить RC-генератор HSI, так как он нам больше не нужен:

Приведу весь код функции переключения на работу от PLL:

На этом все! В следующей части мы наконец приступим к написанию «Hello, World» для микроконтроллера STM32F103C8. Продолжение тут.

Метки: , , , . Закладка Постоянная ссылка.

29 комментариев: Программирование STM32. Часть 4: Настройка RCC

  1. Михаил пишет:

    Спасибо большое за Ваши статьи. Очень помогли разобраться с тактированием на CMSIS. Мало в интернете статей про CMSIS так что не останавливайтесь!

  2. Александр пишет:

    Хорошие статьи,грамотная подача материала на очень
    доступном языке,таких статей действительно мало.Не останавливайтесь!

  3. Александр пишет:

    Здравствуйте! Помогите, пожалуйста разобраться в коде. Вы написали:

    if(StartUpCounter > 0x1000)

    Почему именно > 0x1000? Это мы произвольное время ожидания выбираем или оно где-то указано?

    • DiMoon пишет:

      Это время можно выбрать произвольно, но больше, чем время запуска соответствующего блока. Но в пределах разумного естественно))

      На самом деле, в какой-то старой версии CMSIS в файле system_stm32f1xx.c была функция инициализации системы тактирования, которая вызывалась до вызова функции main(). Потом это выпилили в новых версиях CMSIS, я без понятия нафига, было удобно. И вот как раз там и было реализовано такое решение с таймаутом. И 0x1000 взял оттуда.

      • DiMoon пишет:

        А вообще, время запуска генератора, PLL, время переключения частот, должны быть указаны где-то в даташите, но меня этот вопрос ни разу не интересовал, поэтому значения назвать не могу

  4. Александр пишет:

    Добрый вечер!) Очень интересный цикл статей) рад, что нашел подробную информацию о работе с STM32. У меня возник следующий вопрос: что означает знак амперсанда «&» (строчки 23, 52, 83) в условиях if? Это проверка условия с применением логической операции «И»? Мое знание языка СИ подсказывает, что операция «И» обозначается как «&&»

    • DiMoon пишет:

      Доброго времени суток! Спасибо за отзыв 🙂
      В языке Си знак & — побитовая операция И, а && — логическая операция И.
      Условие внутри if(…) является истинным только в том случае, если результат внутри скобок не равен числу ноль, если же результат ноль, то условие ложно.
      Итак, разберем конструкцию
      if(RCC->CR & (1<<RCC_CR_HSERDY_Pos))
      {
      …..
      }

      RCC->CR — 32-х битный регистр, его можно рассматривать как обычный uin32_t.
      Результат выполнения (1<<RCC_CR_HSERDY_Pos) можно так же представить как обычную переменную uin32_t.
      Перепишем условие так:
      uint32_t a = RCC->CR;
      uint32_t m = (1<<RCC_CR_HSERDY_Pos);
      if(a & m)
      {
      …..
      }
      между a и m будет произведена логическая операция И, и если результат этой операции будет равен нулю, то условие не выполнится, если же любое ненулевое значение, то выполнится.

      Теперь дам ответ на главный вопрос: "А нафига мы это все делаем?". Давайте разбираться. RCC_CR_HSERDY_Pos — позиция интересующего нас бита в регистре RCC->CR, с помощью (1<<RCC_CR_HSERDY_Pos) мы создаем маску, в которой все биты равны нулю кроме нас интересующего, он будет равен единице.
      Результат выполнения операции (RCC->CR & (1<<RCC_CR_HSERDY_Pos)) будет отличен от нуля только в том случае, если интересующий нас бит в регистре RCC->CR будет установлен в единицу, и условие if() выполнится. При этом состояние других битов в регистре RCC->CR мы будем игнорировать.
      Данная конструкция является стандартной при программировании МК для определения состояния бита в каком-либо регистре.

      Можно было написать и так:
      if((RCC->CR & (1<<RCC_CR_HSERDY_Pos)) != 0)
      {
      …..
      }

      ______________________________________
      Постарался рассказать максимально подробно, надеюсь, все будет понятно 🙂

  5. Alex пишет:

    Спасибо за ваши труды! Благодаря вашей статье разобрался почему у меня некоторые чипы стартуют а некоторые виснут, а все из-за инициализации flash!

    вот тоже самое на SPL использую

    #include «stm32f10x.h»
    #include «stm32f10x_rcc.h»
    #include «stm32f10x_flash.h»

    void ClockInit(void)
    {
    ErrorStatus HSEStartUpStatus;

    RCC_DeInit(); /*Resets the RCC clock configuration to the default reset state*/
    RCC_HSEConfig(RCC_HSE_ON); /* Enable HSE используем внешний кварц */
    HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* Wait till HSE is ready */

    if (HSEStartUpStatus == SUCCESS)
    {
    //Настраиваем и запускаем PLL
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* PLLCLK = 8MHz /1 * 9 = 72 MHz */
    RCC_PLLCmd(ENABLE); /* Enable PLL */
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {} /* ждем старта PLL */
    //Устанавливаем 2 цикла ожидания для Flash так как частота ядра 48 MHz < SYSCLK <= 72 MHz
    LASH_SetLatency(FLASH_Latency_2);
    //Делители
    RCC_HCLKConfig(RCC_SYSCLK_Div1);/* HCLK = SYSCLK (72MHz) Делитель AHB 72MHz max */
    RCC_PCLK1Config(RCC_HCLK_Div2); /* PCLK1 = HCLK/2 (36MHz) Делитель AHB1 36MHz max */
    RCC_PCLK2Config(RCC_HCLK_Div1); /* PCLK2 = HCLK (72MHz) Делитель AHB2 72MHz max */
    RCC_ADCCLKConfig(RCC_PCLK2_Div6); /*ADC CLK = HCLK/6 (12MHz) Делитель ADC 14MHz max */

    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* /Переключаемся на работу от PLL */
    while (RCC_GetSYSCLKSource() != 0x08) {} /* Ждем, пока переключимся */
    }

  6. MasterElectric пишет:

    Случайно наткнулся хороший цикл статей, очень подробное описание. Порядок перехода от HSI к тактированию от PLL не верный.

  7. andrej пишет:

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

  8. andrej пишет:

    понял. по ходу это переменные так обозначаются в iar

    • DiMoon пишет:

      __IO определено в недрах CMSIS вот так:
      #define __IO volatile /*!< Defines \'read / write\' permissions */
      По сути, это модификатор переменной volatile. StartUpCounter — обычная переменная типа int, которая является тайм-аут счетчиком. В старых версиях CMSIS, в которых еще была автоматическая настройка источника тактирования при старте, тайм-аут счетчик был реализован именно так, идею слизал оттуда

  9. Sergei пишет:

    Отличная статья! Все просто отлично изложено! Большое спасибо!

  10. Георг пишет:

    Доброго времени суток. Внимательно прочитал эту статью, ранее пытался учиться по похожим урокам в Ютубе(CMSIS+FreeRTOS). И так как все программы и начальные библиотеки взял от туда, не могу понять куда копать)). Есть проблема, МК — STM32F103C8. Пытаюсь затактировать по тем, или Вашим урокам, но не выходит. Точнее если убрать HSE, явно вижу замедление работы, но если выставляю HSE и PLL камень игнорирует множители PLL и HSE и работает (на глазок) где то от 4МГц (4 думаю из за выставленного делителя HSE но это не точно). Внешний кварц уверен рабочий, так как HSERDY пропускал через while.
    Может сможете подсказать, куда копать?
    З.Ы. частоту замерял по отладочному светодиоду, for на 1000000 итераций и секундомеру))).

    • DiMoon пишет:

      Здравствуйте! А готовности PLL после его запуска дожидаетесь? Попробуйте код инициализации из стати (ClockInit(), последний листинг)

      • Георг пишет:

        Пожалуй приложу часть кода. К сожалению не знаю как на этом сайте вложить под спойлер, так что как есть))

        RCC->CR |= ((uint32_t)RCC_CR_HSEON); //Enable HSE //Vkluchit kvartc
        while (!(RCC->CR & RCC_CR_HSERDY)); //Jdem pereclucheniya na kvartc

        RCC->CFGR &= ~RCC_CFGR_SW; //ochistit vibor taktirovaniya shini
        RCC->CFGR &= ~RCC_CFGR_PLLMULL; //ochistit
        mnojitel chastoti
        RCC->CFGR &= ~RCC_CFGR_PLLSRC; //ochistit
        istochnik taktirovania
        RCC->CFGR &= ~RCC_CFGR_PLLXTPRE; //ochistit delytel HSE

        RCC->CFGR |= RCC_CFGR_PLLMULL9; //mnojitel chastoti 9
        RCC->CFGR |= RCC_CFGR_PLLSRC_HSE; //istochnik taktirovania PLL - kvartc
        RCC->CR |= RCC_CR_PLLON; //vkluchit
        mnojitel
        while((RCC->CR & RCC_CR_PLLRDY) == 0) {}
        FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY; //takt FLASH

        RCC->CFGR |= RCC_CFGR_HPRE_DIV1; //AHB delitel 1
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; //APB1 delitel 2
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; //APB2 delitel 2

        //RCC->CFGR |= RCC_CFGR_PLLXTPRE_HSE_Div2; //HSE delitel 2

        RCC->CFGR |= RCC_CFGR_SW_PLL; //vibor taktirovaniya shini ot mnojitela
        while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_1) {}

        RCC->CR &= ~RCC_CR_HSION;

      • Георг пишет:

        Возможно проблемы в стандартных файлах (из тех уроков) Startup32f10x или System32f10x но не хватает знаний для обнаружение((. Переходить на Ваши исходники будет тяжеловато (всё таки только те уложил в голове, да и keil понравился.

  11. Георг пишет:

    Так и не разобрался со своей проблемой((
    За то выяснил, что делители все работают — HSE, APB
    Множитель не работает.

    • Денис пишет:

      Проверять на глаз гиблое дело. Не факт что каждая операция выполнятся один такт. Осциллограф в помощь но не забываем о быстродействии порта.

  12. Slava пишет:

    Не знал что нужно настраивать FLASH. Меня интересует где искать это для других чипов, есть ли в документации на микроконтроллер описание последовательности установки тактирования?

    • DiMoon пишет:

      Описание последовательности настройки есть в Ref. Manual-е на микроконтроллер, но там надо внимательно читать все. Насчет Flash — это скорее всего тоже будет описано в Ref. Manual-е на данный контроллер, если нету, то искать в Programm Manual-е

  13. Николай пишет:

    Здравствуйте. Хотелось бы уточнить. В основной программе настройка RCC идет до инициализации портов или после?

    • DiMoon пишет:

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

      • Николай пишет:

        Тогда вопрос. Если включение портов идет после настройки тактирования, а внешний кварц в STM32F103C8T подключен через пины PD0 и PD1 порта D, то по идее МК никогда не перейдет на тактирование от HSE. Или я чего то не понимаю?

  14. Николай пишет:

    Меня интересует вопрос. Нужно ли перед настройкой тактирования включать пины порта на который подключается кварц?
    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;(stm32f103c8t6)
    А уже потом настраивать тактирование самого МК. Или включать порт необязательно будет работать и так?

    • DiMoon пишет:

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

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

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