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

В предыдущей части мы рассмотрели систему тактирования микроконтроллеров STM32. В этой части мы изучим регистры RCC и займемся ее настройкой. Все статьи цикла можно посмотреть тут: https://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:

#define TIM2                ((TIM_TypeDef *)TIM2_BASE)
#define TIM3                ((TIM_TypeDef *)TIM3_BASE)
#define TIM4                ((TIM_TypeDef *)TIM4_BASE)
#define RTC                 ((RTC_TypeDef *)RTC_BASE)
#define WWDG                ((WWDG_TypeDef *)WWDG_BASE)
#define IWDG                ((IWDG_TypeDef *)IWDG_BASE)
#define SPI2                ((SPI_TypeDef *)SPI2_BASE)
#define USART2              ((USART_TypeDef *)USART2_BASE)
#define USART3              ((USART_TypeDef *)USART3_BASE)
#define I2C1                ((I2C_TypeDef *)I2C1_BASE)
#define I2C2                ((I2C_TypeDef *)I2C2_BASE)
#define USB                 ((USB_TypeDef *)USB_BASE)
#define CAN1                ((CAN_TypeDef *)CAN1_BASE)
#define BKP                 ((BKP_TypeDef *)BKP_BASE)
#define PWR                 ((PWR_TypeDef *)PWR_BASE)
#define AFIO                ((AFIO_TypeDef *)AFIO_BASE)
#define EXTI                ((EXTI_TypeDef *)EXTI_BASE)
#define GPIOA               ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *)GPIOE_BASE)
#define ADC1                ((ADC_TypeDef *)ADC1_BASE)
#define ADC2                ((ADC_TypeDef *)ADC2_BASE)
#define ADC12_COMMON        ((ADC_Common_TypeDef *)ADC1_BASE)
#define TIM1                ((TIM_TypeDef *)TIM1_BASE)
#define SPI1                ((SPI_TypeDef *)SPI1_BASE)
#define USART1              ((USART_TypeDef *)USART1_BASE)
#define SDIO                ((SDIO_TypeDef *)SDIO_BASE)
#define DMA1                ((DMA_TypeDef *)DMA1_BASE)
#define DMA1_Channel1       ((DMA_Channel_TypeDef *)DMA1_Channel1_BASE)
#define DMA1_Channel2       ((DMA_Channel_TypeDef *)DMA1_Channel2_BASE)
#define DMA1_Channel3       ((DMA_Channel_TypeDef *)DMA1_Channel3_BASE)
#define DMA1_Channel4       ((DMA_Channel_TypeDef *)DMA1_Channel4_BASE)
#define DMA1_Channel5       ((DMA_Channel_TypeDef *)DMA1_Channel5_BASE)
#define DMA1_Channel6       ((DMA_Channel_TypeDef *)DMA1_Channel6_BASE)
#define DMA1_Channel7       ((DMA_Channel_TypeDef *)DMA1_Channel7_BASE)
#define RCC                 ((RCC_TypeDef *)RCC_BASE)
#define CRC                 ((CRC_TypeDef *)CRC_BASE)
#define FLASH               ((FLASH_TypeDef *)FLASH_R_BASE)
#define OB                  ((OB_TypeDef *)OB_BASE)
#define DBGMCU              ((DBGMCU_TypeDef *)DBGMCU_BASE)

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

RCC->CR |= RCC_CR_HSEON_Msk;

или так:

RCC->CR |= (1 << RCC_CR_HSEON_Pos);

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

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

#define RCC_CR_HSEON_Msk    (1<<16)

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

#define RCC_CR_HSEON    RCC_CR_HSEON_Msk

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

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

где

#define RCC_CR_HSEON_Pos    16

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

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

RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;
RCC->CFGR &= ~(RCC_CFGR_PLLMULL_3);

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

RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;

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

RCC->CFGR |= (7 << RCC_CFGR_PLLMULL_Pos);

Но и тут надо помнить, что такая запись справедлива только в случае, если изначальное значение всех битов 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.

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

int ClockInit(void)
{
}

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

RCC->CR |= (1<<RCC_CR_HSEON_Pos); //Запускаем генератор HSE

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

  __IO int StartUpCounter;

  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //Если успешно запустилось, то 
    //выходим из цикла
    if(RCC->CR & (1<<RCC_CR_HSERDY_Pos))
      break;
    
    //Если не запустилось, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Останавливаем HSE
      return 1;
    }
  }

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

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

  //Настраиваем PLL
  RCC->CFGR |= (0x07<<RCC_CFGR_PLLMULL_Pos) //PLL множитель равен 9
            | (0x01<<RCC_CFGR_PLLSRC_Pos); //Тактирование PLL от HSE

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

RCC->CR |= (1<<RCC_CR_PLLON_Pos); //Запускаем PLL

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

  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //Если успешно запустилось, то 
    //выходим из цикла
    if(RCC->CR & (1<<RCC_CR_PLLRDY_Pos))
      break;
    
    //Если по каким-то причинам не запустился PLL, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Останавливаем HSE
      RCC->CR &= ~(1<<RCC_CR_PLLON_Pos); //Останавливаем PLL
      return 2;
    }
  }

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

  //Устанавливаем 2 цикла ожидания для Flash
  //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= (0x02<<FLASH_ACR_LATENCY_Pos); 
  
  //Делители
  RCC->CFGR |= (0x00<<RCC_CFGR_PPRE2_Pos) //Делитель шины APB2 отключен (оставляем 0 по умолчанию)
            | (0x04<<RCC_CFGR_PPRE1_Pos) //Делитель нишы APB1 равен 2
            | (0x00<<RCC_CFGR_HPRE_Pos); //Делитель AHB отключен (оставляем 0 по умолчанию)

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

RCC->CFGR |= (0x02<<RCC_CFGR_SW_Pos); //Переключаемся на работу от PLL

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

  //Ждем, пока переключимся
  while((RCC->CFGR & RCC_CFGR_SWS_Msk) != (0x02<<RCC_CFGR_SWS_Pos))
  {
  }

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

  //После того, как переключились на
  //внешний источник такирования
  //отключаем внутренний RC-генератор
  //для экономии энергии
  RCC->CR &= ~(1<<RCC_CR_HSION_Pos);

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

//Настраиваем тактирование системы от внешнего кварца
//через PLL на саксимально возможных частотах.
//Внешний кварц должен быть на 8МГц
//Возвращает:
//  0 - завершено успешно
//  1 - не запустился кварцевый генератор
//  2 - не запустился PLL
int ClockInit(void)
{
  __IO int StartUpCounter;
  
  ////////////////////////////////////////////////////////////
  //Запускаем кварцевый генератор
  ////////////////////////////////////////////////////////////
  
  RCC->CR |= (1<<RCC_CR_HSEON_Pos); //Запускаем генератор HSE
  
  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //Если успешно запустилось, то 
    //выходим из цикла
    if(RCC->CR & (1<<RCC_CR_HSERDY_Pos))
      break;
    
    //Если не запустилось, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Останавливаем HSE
      return 1;
    }
  }
  
  ////////////////////////////////////////////////////////////
  //Настраиваем и запускаем PLL
  ////////////////////////////////////////////////////////////
  
  //Настраиваем PLL
  RCC->CFGR |= (0x07<<RCC_CFGR_PLLMULL_Pos) //PLL множитель равен 9
            | (0x01<<RCC_CFGR_PLLSRC_Pos); //Тактирование PLL от HSE
  
  
  RCC->CR |= (1<<RCC_CR_PLLON_Pos); //Запускаем PLL
  
  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++)
  {
    //Если успешно запустилось, то 
    //выходим из цикла
    if(RCC->CR & (1<<RCC_CR_PLLRDY_Pos))
      break;
    
    //Если по каким-то причинам не запустился PLL, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~(1<<RCC_CR_HSEON_Pos); //Останавливаем HSE
      RCC->CR &= ~(1<<RCC_CR_PLLON_Pos); //Останавливаем PLL
      return 2;
    }
  }
  
  ////////////////////////////////////////////////////////////
  //Настраиваем FLASH и делители
  ////////////////////////////////////////////////////////////
  
  //Устанавливаем 2 цикла ожидания для Flash
  //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= (0x02<<FLASH_ACR_LATENCY_Pos); 
  
  //Делители
  RCC->CFGR |= (0x00<<RCC_CFGR_PPRE2_Pos) //Делитель шины APB2 отключен
            | (0x04<<RCC_CFGR_PPRE1_Pos) //Делитель нишы APB1 равен 2
            | (0x00<<RCC_CFGR_HPRE_Pos); //Делитель AHB отключен
  
  
  RCC->CFGR |= (0x02<<RCC_CFGR_SW_Pos); //Переключаемся на работу от PLL
  
  //Ждем, пока переключимся
  while((RCC->CFGR & RCC_CFGR_SWS_Msk) != (0x02<<RCC_CFGR_SWS_Pos))
  {
  }
  
  //После того, как переключились на
  //внешний источник такирования
  //отключаем внутренний RC-генератор
  //для экономии энергии
  RCC->CR &= ~(1<<RCC_CR_HSION_Pos);
  
  //Настройка и переклбючение сисемы
  //на внешний кварцевый генератор
  //и PLL запершилось успехом.
  //Выходим
  return 0;
}

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

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

43 комментария: Программирование 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;

        • ivan rusev пишет:

          void RCC_init(void)
          {
          //FLASH->ACR&=~FLASH_ACR_LATENCY ;
          FLASH->ACR|=FLASH_ACR_LATENCY_2;
          RCC->CR|=RCC_CR_HSEON ;
          while(!(RCC->CR& RCC_CR_HSERDY) )
          {

          }
          RCC->CFGR&=~RCC_CFGR_SW;
          RCC->CFGR&=~RCC_CFGR_PLLSRC;
          RCC->CFGR&=~RCC_CFGR_PLLXTPRE;
          RCC->CFGR&=~RCC_CFGR_PLLMULL;
          RCC->CFGR|=RCC_CFGR_PLLMULL9 ;
          RCC->CFGR|=RCC_CFGR_HPRE_DIV1;
          RCC->CFGR|=RCC_CFGR_PPRE1_DIV2;
          RCC->CFGR|=RCC_CFGR_PPRE2_DIV1;
          RCC->CFGR|=RCC_CFGR_ADCPRE_DIV6 ;
          RCC->CFGR|=RCC_CFGR_PLLSRC;
          RCC->CR|= RCC_CFGR_PLLXTPRE_HSE;
          RCC->CR|=RCC_CR_PLLON ;
          while(!(RCC->CR &RCC_CR_PLLRDY))
          {

          }

          RCC->CFGR|=RCC_CFGR_SW_PLL;
          while(!( RCC->CFGR&RCC_CFGR_SWS ))
          {

          }
          Вот мой вариант.Правда я только учусь.Флеш настраивать можно перед запуском шины я где то читал

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

        Возможно проблемы в стандартных файлах (из тех уроков) 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)
    А уже потом настраивать тактирование самого МК. Или включать порт необязательно будет работать и так?

  15. Геннадий пишет:

    Отличный сайт по CMSIS. Спасибо вам за ваши труды. Все по существу никакой воды. В пятницу внес «абонплату» на «развитие сайта и оплату вэбхостинга». Если ни потеряю интерес к теме STM-32 и вашему сайту в частности обязуюсь вносить абон плату на регулярной основе.
    Я бы кнопку «поблагодарить» повесил в конце каждой темы , это на мой взгляд увеличит число благодарных.

  16. Геннадий пишет:

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

  17. Атарасий пишет:

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

  18. Иван пишет:

    Доброго времени.
    Подскажите откуда взят порядок настройки тактирования. Я начинающий и не особо в документации разбираюсь, на сайте ст, черт ногу сломит. RM вроде весь пролистал, не увидел, есть примеры, но там нет четкой последовательности. А у вас так прям по полочкам, по шагам. Покамесь я на основании стандартной функции из какой-то библиотеки запилил свою, работает. Но хочется по феншую. Спасибо.

    • DiMoon пишет:

      Порядок инициализации я когда-то подглядел в коде CMSIS от ST. Но это было в какой-то старой версии, сейчас они вроде как выпилили из CMSIS это.
      В RM где-то указаны максимальные частоты всех шин и узлов, и в процессе настройки нельзя допускать выхода этих частот за допустимые пределы. Стоит обратить внимание, что при настройки PLL, для каждой ее петли есть рекомендации к максимальной и вроде как минимальной частотам, которые могут «циркулировать» в этих петлях. Ну вот и весь феншуй 🙂

    • Виктор М пишет:

      Откройте CubeMX — там есть более понятная схема тактирования МК со всеми множителями и пр. Схема очень удобная для понимания процесса настройки RCC.

  19. Иван пишет:

    Еще маленький вопросик: «а в конце инициализации не надо вызвать SystemCoreClockUpdate();»? Ну что бы там все частоты задефайненые по умолчанию обновились, или это лишнее?

    • DiMoon пишет:

      Я даже толком и не знаю, где используются эти значения частот, возможно, в стантартной библиотеке SPL, но я ею не пользуюсь 🙂
      Да, можно вызвать эту функцию

      • Атарасий пишет:

        Функцию SystemCoreClockUpdate() необходимо вызывать, она обновит значение переменной SystemCoreClock, которую потом можно и нужно использовать во всех настройках, связанных с частотой, например — при настройке скорости USART, или любой другой.

  20. Андрей пишет:

    PLLMUL или PLLMULL? Как правильно?

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

    Статьи просто отличные, пришел к stm32 от AVR, материал дается проще, чем если бы с нуля начинал. Автор просто классно подает материал, именно смотрит в корень, в регистры, потому что именно так программируют профессионалы. На различные готовые библиотеки надо переходить позже. С системой тактирования STM32 долго не мог разобраться, теперь хоть какое-то представление есть, спасибо

Добавить комментарий для Николай Отменить ответ