STM32F411 настройка тактирования

плата по ссылке:

на вывод PB0 подаю частоту 1000 гц от внешнего генератора. Таймер 2 настроен на счет импульсов системной шины. Если тактирование брать напрямую от внешнего кварца то частота ровно 25000000 плюс минус 1 гц. То есть он работает от внешнего кварца правильно. Вот код для этого.

// Версия 1
#include <LiquidCrystal.h>            //библиотека для работы с экраном
LiquidCrystal lcd (PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); //(RS,E,D0,D1,D2,D3,D4,D5,D6,D7)
volatile bool flag_Led = 0;
volatile uint16_t irq_counter = 0;
uint32_t tim2_value;
//-------------------------------
void setup()
{
  lcd.begin(20, 4);
  //---Настройка Таймера 2
  // Настройка системной шины
  RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сбрасываем биты PPRE1
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV1; // Устанавливаем делитель APB1 = 1
  // Включаем тактирование таймера TIM2
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем тактирование таймера TIM2
  // Настройка таймера TIM2 для внутренней частоты
  TIM2->CR1 &= ~TIM_CR1_CEN; // Останавливаем таймер перед настройкой
  TIM2->PSC = 0; // Предделитель (PSC) = 0 (нет деления)
  TIM2->ARR = 0xFFFFFFFF; // Максимальное значение счётчика (32 бита)
  TIM2->CNT = 0; // Сбрасываем текущее значение счётчика
  TIM2->EGR |= TIM_EGR_UG; // Принудительное обновление регистров таймера
  TIM2->CR1 |= TIM_CR1_CEN; // Запускаем таймер
  //---Настройка прерывания на PB0
  pinMode(PB0, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PB0), watchdogHandler, RISING);
  //---Настройка системной частоты.
  // Включаем HSE
  RCC->CR |= RCC_CR_HSEON;
  while (!(RCC->CR & RCC_CR_HSERDY)); // Дожидаемся HSE
  // Проверка HSE:
  uint32_t hse_status = (RCC->CR & RCC_CR_HSERDY) ? 1 : 0;
  // Переключаем системный такт на HSE
  RCC->CFGR = (1 << RCC_CFGR_SW_Pos); // SW=1 (HSE)
  // Ждем переключения (с таймаутом)
  uint32_t timeout = 100000; // 100000 итераций
  while ((RCC->CFGR & 0x00000007) != 0x1)
  {
    timeout--;
    if (timeout == 0) break; // Выход, если таймаут
  }
}
void loop()
{
  if (flag_Led == 1)
  {
    // Вывод значения таймера TIM2
    lcd.setCursor(0, 0);
    lcd.print("SYS=");lcd.print(tim2_value);
    flag_Led = 0;
  }
}
void watchdogHandler()
{
  irq_counter += 1;
  if (irq_counter >= 1000)
  { 
    flag_Led = 1; 
    irq_counter = 0;
    tim2_value = TIM2->CNT;// Считываем значение таймера TIM2
    TIM2->CNT = 0;// Сбрасываем таймер TIM2
  }
}

но если я пытаюсь включить работу от внешнего кварца с использованием PLL то у меня тактирование переключается на внутренний генератор и частота равна 94.8 мгц и меняется на несколько кгц. Вот код где я пытаюсь настроить PLL

// Версия 2
#include <LiquidCrystal.h>            //библиотека для работы с экраном
LiquidCrystal lcd (PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); //(RS,E,D0,D1,D2,D3,D4,D5,D6,D7)
volatile bool flag_Led = 0;
volatile uint16_t irq_counter = 0;
uint32_t tim2_value;
uint32_t pllcfgr_value; // Для хранения значения PLLCFGR
bool pll_active = false; // Флаг активности PLL

void setup()
{
  lcd.begin(20, 4);
  //---Настройка Таймера 2
  // Настройка системной шины
  RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сбрасываем биты PPRE1
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV1; // Устанавливаем делитель APB1 = 1
  // Включаем тактирование таймера TIM2
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем тактирование таймера TIM2
  // Настройка таймера TIM2 для внутренней частоты
  TIM2->CR1 &= ~TIM_CR1_CEN; // Останавливаем таймер перед настройкой
  TIM2->PSC = 0; // Предделитель (PSC) = 0 (нет деления)
  TIM2->ARR = 0xFFFFFFFF; // Максимальное значение счётчика (32 бита)
  TIM2->CNT = 0; // Сбрасываем текущее значение счётчика
  TIM2->EGR |= TIM_EGR_UG; // Принудительное обновление регистров таймера
  TIM2->CR1 |= TIM_CR1_CEN; // Запускаем таймер
  //---Настройка прерывания на PB0
  pinMode(PB0, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PB0), watchdogHandler, RISING);
  //---Настройка системной частоты:
  // 1. Включаем HSE
  RCC->CR |= RCC_CR_HSEON;
  while (!(RCC->CR & RCC_CR_HSERDY)); // Дожидаемся HSE
  // Проверка HSE:
  uint32_t hse_status = (RCC->CR & RCC_CR_HSERDY) ? 1 : 0;
  if (!hse_status)
  {
    // Если HSE не активен, выводим ошибку
    lcd.setCursor(0, 0);
    lcd.print("HSE ERROR");
    while(1); // Зацикливаемся
  }
  // Отладочный вывод
  lcd.setCursor(0, 0);
  lcd.print("HSE ON");
  delay(1000);

  // 2. Переключаем системный такт на HSE (базовая настройка версии 1)
  RCC->CFGR = (1 << RCC_CFGR_SW_Pos); // SW=1 (HSE)
  while ((RCC->CFGR & RCC_CFGR_SWS) != (1 << RCC_CFGR_SWS_Pos)); // Ждем переключения
  // Отладочный вывод
  lcd.setCursor(0, 1);
  lcd.print("SWITCH TO HSE");
  delay(1000);

  // 3. Настройка PLL (добавлено без нарушения базовой работы):
  // PLLM=2 (HSE/2 = 12.5 МГц), PLLN=12 → 12.5×12 = 150 МГц
  RCC->PLLCFGR = 
    (2 << 24) | // PLLM=2 (биты 27:24)
    (12 << 8) | // PLLN=12 (биты 21:8)
    (0 << 5) | // PLLP=0b00 → /2 → HCLK=75 МГц
    (0 << 8); // PLLR=0b000 → /2 → APB1=37.5 МГц

  RCC->CR |= RCC_CR_PLLON; // Включаем PLL
  while (!(RCC->CR & RCC_CR_PLLRDY)); // Ждем готовности PLL
  // Отладочный вывод
  lcd.setCursor(0, 2);
  lcd.print("PLL ON");
  delay(1000);

  // 4. Переключаем системный такт на PLL (SW=2)
  RCC->CFGR = (RCC->CFGR & ~(3 << RCC_CFGR_SW_Pos)) | (2 << RCC_CFGR_SW_Pos); // Переключаем системный такт на PLL
  while ((RCC->CFGR & RCC_CFGR_SWS) != (2 << RCC_CFGR_SWS_Pos)); // Ждем переключения
  // Отладочный вывод
  lcd.setCursor(0, 3);
  lcd.print("SWITCH TO PLL");
  delay(1000);

  pllcfgr_value = RCC->PLLCFGR; // Сохраняем значение регистра для вывода
  pll_active = true; // Устанавливаем флаг активности PLL

  // Проверка значений регистров
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("HSE ON");
  lcd.setCursor(0, 1);
  lcd.print("SWITCH TO HSE");
  lcd.setCursor(0, 2);
  lcd.print("PLL ON");
  lcd.setCursor(0, 3);
  lcd.print("SWITCH TO PLL");
  delay(5000); // Ждем 5 секунд для просмотра отладочной информации
  lcd.clear();
}

void loop()
{
  if (flag_Led == 1)
  {
    // Выводим PLLCFGR и системную частоту:
    lcd.setCursor(0, 0);
    lcd.print("SYS="); lcd.print(tim2_value);
    lcd.setCursor(0, 1);
    lcd.print("PLL=");
    lcd.print(pllcfgr_value, HEX); // Выводим значение PLLCFGR в HEX
    flag_Led = 0;
  }
}

void watchdogHandler()
{
  irq_counter += 1;
  if (irq_counter >= 1000)
  { 
    flag_Led = 1; 
    irq_counter = 0;
    tim2_value = TIM2->CNT; // Считываем значение таймера TIM2
    TIM2->CNT = 0; // Сбрасываем таймер TIM2
  }
}

При этом на экран выводится
HSE ON
SWITCH TO HSE
PLL ON
SWITCH TO PLL

SYS=94867568
PLL=201540E

Где у меня ошибка в настройке PLL?

int STM32F411CEU6startHSErcc(void) {
	FLASH->ACR |=FLASH_ACR_LATENCY_3WS; // VOS[1:0] = 0x11, the maximum value of fHCLK = 100 MHz. 3 WS (4 CPU cycles)
	RCC->CR |= RCC_CR_HSEON; // HSE ON
	int StartUpCounter; // wait counter
	for(StartUpCounter=0; ; StartUpCounter++) { // wait ON
	    if (RCC->CR & RCC_CR_HSERDY) break; // HSE is ON
	    if (StartUpCounter > 0x1000) { // timeout
	    	RCC->CR &= ~RCC_CR_HSEON; // HSE OFF
	    	return 1; // exit bad HSE
	    }
	}
	RCC->CR &= ~RCC_CR_PLLON; // PLL OFF
	RCC->CFGR &= ~RCC_CFGR_HPRE_DIV1; // AHB -> 0xxx: system clock not divided -> 100MHz
	RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 -> 100: AHB clock divided by 2 -> 50MHz
	RCC->CFGR &= ~RCC_CFGR_PPRE2_DIV1; // APB2 -> 0xx: AHB clock not divided -> 100MHz
	RCC->CFGR |= RCC_CFGR_SW_PLL; // 10: PLL selected as system clock
	RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLM; // PLLM=0
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLM_2; // PLLM=12
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLM_3; // PLLM=12
	RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLN; // PLLN=0
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLN_5; // PLLN=96
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLN_6; // PLLN=96
	RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLP; // PLLP=0 -> div 2 // 00: PLLP = 2
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // PLL source set HSE
	RCC->CR |= RCC_CR_PLLON; // PLL ON // result = 25MHz / 12 * 96 / 2 = 100MHz
	for(StartUpCounter=0; ; StartUpCounter++) { // wait ON
	    if (RCC->CR & RCC_CR_PLLRDY) break; // PLL is ON
	    if (StartUpCounter > 0x1000) { // timeout
	    	RCC->CR &= ~RCC_CR_PLLON; // PLL OFF
	    	RCC->CR &= ~RCC_CR_HSEON; // HSE OFF
	    	return 2; // exit bad PLL
	    }
	}
	f4setPC13led(1);
	RCC->AHB1ENR |= RCC_APB1ENR_PWREN;
	return 0; // good
}

Внес изменения в код. Но все равно не работает. Значение CFGR=7 SYS=25000000 PLL=C406000. По крайней мере хоть PLL стал меняться.

// Версия 3
#include <LiquidCrystal.h>            //библиотека для работы с экраном
LiquidCrystal lcd (PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); //(RS,E,D0,D1,D2,D3,D4,D5,D6,D7)
volatile bool flag_Led = 0;
volatile uint16_t irq_counter = 0;
uint32_t tim2_value;
uint32_t pllcfgr_value; // Для хранения значения PLLCFGR
bool pll_active = false; // Флаг активности PLL

void setup()
{
  lcd.begin(20, 4);
  //---Настройка Таймера 2
  // Настройка системной шины
  RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сбрасываем биты PPRE1
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 50 МГц
  RCC->CFGR &= ~RCC_CFGR_PPRE2; // Сбрасываем биты PPRE2
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2 = 100 МГц
  // Включаем тактирование таймера TIM2
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем TIM2
  // Настройка таймера TIM2 для внутренней частоты
  TIM2->CR1 &= ~TIM_CR1_CEN; // Останавливаем таймер перед настройкой
  TIM2->PSC = 0; // Предделитель (PSC) = 0 (нет деления)
  TIM2->ARR = 0xFFFFFFFF; // Максимальное значение счётчика (32 бита)
  TIM2->CNT = 0; // Сбрасываем текущее значение счётчика
  TIM2->EGR |= TIM_EGR_UG; // Принудительное обновление регистров таймера
  TIM2->CR1 |= TIM_CR1_CEN; // Запускаем таймер
  //---Настройка прерывания на PB0
  pinMode(PB0, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PB0), watchdogHandler, RISING);
  //---Настройка системной частоты:
  // 1. Включаем HSE
  FLASH->ACR |= FLASH_ACR_LATENCY_3WS; // Задержка для работы с частотой 100 МГц
  RCC->CR |= RCC_CR_HSEON;
  uint32_t StartUpCounter = 0;
  while (!(RCC->CR & RCC_CR_HSERDY))
  {
    StartUpCounter++;
    if (StartUpCounter > 0x1000) {
      // Обработка таймаута и возврат ошибки
      lcd.setCursor(0, 0);
      lcd.print("HSE ERROR");
      while (1); // Зацикливаемся
    }
  }
  // Отладочный вывод
  lcd.setCursor(0, 0);
  lcd.print("HSE ON");
  delay(1000);

  // 2. Переключаем системный такт на HSE
  RCC->CFGR = (1 << RCC_CFGR_SW_Pos); // SW=1 (HSE)
  // Ждем переключения (с таймаутом)
  uint32_t timeout = 100000; // 100000 итераций
  while ((RCC->CFGR & RCC_CFGR_SWS) != (1 << RCC_CFGR_SWS_Pos))
  {
    timeout--;
    if (timeout == 0) break; // Выход, если таймаут
  }
  // Отладочный вывод
  lcd.setCursor(0, 1);
  lcd.print("SWITCH TO HSE");
  delay(1000);

  // 3. Настройка PLL
  // Выключаем PLL перед настройкой
  RCC->CR &= ~RCC_CR_PLLON;
  // Сброс всех битов PLLCFGR
  RCC->PLLCFGR = 0;
  // Настройка PLL
  RCC->PLLCFGR |= (12 << 24); // PLLM = 12 (HSE/12)
  RCC->PLLCFGR |= (96 << 8); // PLLN = 96 (12.5 МГц * 96 = 1200 МГц)
  RCC->PLLCFGR |= (0 << 5); // PLLP = 2 (1200 / 2 = 600 МГц)
  RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // Источник HSE
  // Включаем PLL
  RCC->CR |= RCC_CR_PLLON;
  // Ждем готовности PLL (с таймаутом)
  StartUpCounter = 0;
  while (!(RCC->CR & RCC_CR_PLLRDY))
  {
    StartUpCounter++;
    if (StartUpCounter > 0x1000) {
      // Обработка таймаута и возврат ошибки
      RCC->CR &= ~RCC_CR_PLLON; // Выключаем PLL
      RCC->CR &= ~RCC_CR_HSEON; // Выключаем HSE
      lcd.setCursor(0, 2);
      lcd.print("PLL ERROR");
      while (1); // Зацикливаемся
    }
  }
  // Отладочный вывод
  lcd.setCursor(0, 2);
  lcd.print("PLL ON");
  delay(1000);

  // 4. Переключаем системный такт на PLL (SW=2)
  RCC->CFGR |= RCC_CFGR_SW_PLL; // 10: PLL selected as system clock
  // Ждем завершения переключения (с таймаутом)
  timeout = 100000; // 100000 итераций
  while ((RCC->CFGR & RCC_CFGR_SWS) != (2 << RCC_CFGR_SWS_Pos))
  {
    timeout--;
    if (timeout == 0) break; // Выход, если таймаут
  }
  // Отладочный вывод
  lcd.setCursor(0, 3);
  lcd.print("SWITCH TO PLL");
  delay(1000);

  // Проверка значений регистров
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("HSE ON");
  lcd.setCursor(0, 1);
  lcd.print("SWITCH TO HSE");
  lcd.setCursor(0, 2);
  lcd.print("PLL ON");
  lcd.setCursor(0, 3);
  lcd.print("SWITCH TO PLL");
  delay(5000); // Ждем 5 секунд для просмотра отладочной информации
  lcd.clear();

  // Дополнительная проверка значения RCC->CFGR
  lcd.setCursor(0, 3);
  lcd.print("CFGR=");
  lcd.print(RCC->CFGR, HEX); // Выводим значение RCC->CFGR в HEX
  delay(5000); // Ждем 5 секунд для просмотра отладочной информации
  lcd.clear();

  // Сохраняем значение регистра для вывода
  pllcfgr_value = RCC->PLLCFGR;
  pll_active = true; // Устанавливаем флаг активности PLL
}

void loop()
{
  if (flag_Led == 1)
  {
    // Выводим PLLCFGR и системную частоту:
    lcd.setCursor(0, 0);
    lcd.print("SYS="); lcd.print(tim2_value);
    lcd.setCursor(0, 1);
    lcd.print("PLL=");
    lcd.print(pllcfgr_value, HEX); // Выводим значение PLLCFGR в HEX
    flag_Led = 0;
  }
}

void watchdogHandler()
{
  irq_counter += 1;
  if (irq_counter >= 1000)
  { 
    flag_Led = 1; 
    irq_counter = 0;
    tim2_value = TIM2->CNT; // Считываем значение таймера TIM2
    TIM2->CNT = 0; // Сбрасываем таймер TIM2
  }
}

О каких ты изменениях говоришь?
Если ты подаешь внешний сигнал, то естественно оно будет отличаться от «эталонного», что у мк используется.

а куб вот по другому думает

еще раз прочитал, ниче не понял
говорите про 25 МГц настраиваете на 100???

А я о не так? Кварц на 25 мгц, тактирование systick 100 мгц.

я вот это прочитал

Мне к сожалению сложно судить о правильности.
Т к arduino ide не использую для stm32.

Вероятно ТС не совсем понимает что он делает, поэтому и с терминологией проблемы.

2 лайка

подождем…)

Посмотрел по дагонали код, а че вы таймер настраиваете до тактирования мк?
И выводить системные сообщения во время настроек…такое себе решение.

Вы сами понимаете как код работает?

Эта строка что делает?

RCC->PLLCFGR |= (0 << 5); /
1 лайк

На плате стоит кварц на 25 мгц. Я хочу затактировать системную шину от внешнего кварца и получить частоту работы системной шины 100 мгц. На PB0 подаю частоту 1000 гц для формирования точного интервала времени измерения = 1 секунда. Таймер 2 настраиваю на счет тактов внутренней частоты. Если за время 1 секунда таймер 2 насчитает 100000000 значит частота ровно 100 мгц. Код версии 1 включает тактирование внешнего кварца и сигнал HSE подает на блок SCM и SYSCLK = 25 мгц. В данном случае не используя PLL. Поскольку частота SYSCLK меняется на +/- 1 гц то значит все работает правильно. Когда я подключаю модуль PLL у меня тактирование берется от внутреннего генератора. поскольку частота не точно 8 мгц то SYSCLK= 94.8 мгц и частота меняется +/- несколько десятков кгц. Так вопрос как правильно настроить PLL на работу от внешнего кварца на 25 мгц.

Ниче не понял.
Настройка sysclk 100 mhz выше приведена.
Значит вы в своём коде накосячили.

я вот тоже))
особенно это

2 лайка

А кто мешает взять частоту на PLL c кварца? Внутренний генератор обычно сильно зависит от температуры и питания. Получить на нём точную стабильную частоту невозможно.

я даже в куб загнал, не понимаю какие проблемы у него :thinking:

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 12;
  RCC_OscInitStruct.PLL.PLLN = 96;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }
}

Думаю у ТС проблема в пониманиизадачи, поэтому и странные вопросы.
У 411 все просто с тактированием, там сложно ошибиться.

1 лайк

Так ТС и спрашивает, как это сделать