nik182
06.Апрель.2025 19:41:35
21
Дипсик:
Для настройки PLL в STM32F411CEU6 с использованием внешнего кварца 25 МГц и получения тактовой частоты 100 МГц необходимо выполнить следующие шаги:
1. Настройка тактирования через PLL
Микроконтроллер STM32F411 использует PLL для умножения частоты.
Формула расчёта частоты после PLL:
fвых=fвх×PLLNPLLM×PLLPfвых=PLLM×PLLPfвх×PLLN
Где:
PLLM — предделитель для входа PLL (делит частоту кварца перед подачей на PLL).
PLLN — множитель PLL.
PLLP — постделитель для выхода PLL (должен быть 2, 4, 6 или 8 ).
Для 100 МГц при кварце 25 МГц можно выбрать:
PLLM = 25 (частота на вход PLL: 25 МГц25=1 МГц2525МГц=1МГц).
PLLN = 200 (умножение: 1 МГц×200=200 МГц1МГц×200=200МГц).
PLLP = 2 (итоговая частота: 200 МГц2=100 МГц2200МГц=100МГц).
Проверка ограничений STM32F411:
Вход PLL (после PLLM): 1–2 МГц (у нас 1 МГц — OK).
VCO (PLLN × вход PLL): 100–432 МГц (у нас 200 МГц — OK).
Выход PLL (после PLLP): ≤ 100 МГц (у нас 100 МГц — OK).
Спойлер
#include "stm32f4xx_hal.h"
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// Настройка источника тактирования (HSE + PLL)
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // Включаем внешний кварц 25 МГц
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 25; // Делитель для входа PLL
RCC_OscInitStruct.PLL.PLLN = 200; // Множитель PLL
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // Постделитель PLLP = 2
RCC_OscInitStruct.PLL.PLLQ = 4; // Для USB/SDIO (необязательно)
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// Настройка делителей для шин
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // Тактирование от PLL
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // HCLK = 100 МГц
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1 = 50 МГц
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 = 100 МГц
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3); // Задержка FLASH = 3 (для 100 МГц)
}
Итоговая схема PLL
25 МГц (HSE) → PLLM(25) → 1 МГц → PLLN(200) → 200 МГц → PLLP(2) → 100 МГц (SYSCLK)
После настройки микроконтроллер будет работать на 100 МГц с стабильным тактированием от внешнего кварца.
1 лайк
господи, там как хошь можно извращаться
25 МГц (HSE) → PLLM(13) → 1,923076923076923 МГц → PLLN(104) → 200 МГц → PLLP(2) → 100 МГц (SYSCLK)
Ага. Вот я и извращаюсь. Наверное что то делаю не так с настройками. Схема работает на внутреннем генераторе. Системная частота 94.8 мгц.
// Версия 4
#include <LiquidCrystal.h> // библиотека для работы с экраном
LiquidCrystal lcd(PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); // (RS,E,D0-D7)
volatile bool flag_Led = 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 (до настройки PLL)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем тактирование таймера 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);
//--- Настройка системной частоты на 100 МГц через PLL
RCC->CR |= RCC_CR_HSEON; // Включаем HSE
while (!(RCC->CR & RCC_CR_HSERDY)); // Ждем готовности HSE
RCC->PLLCFGR = (25 << RCC_PLLCFGR_PLLM_Pos) | // PLLM=25 (25 МГц / 25 = 1 МГц)
(200 << RCC_PLLCFGR_PLLN_Pos) | // PLLN=200 (1 МГц × 200 = 200 МГц)
RCC_PLLP_DIV2; // PLLP=2 (200 МГц / 2 = 100 МГц)
RCC->CR |= RCC_CR_PLLON; // Включаем PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); // Ждем готовности PLL
// Переключение системного такта на PLL
RCC->CFGR = (2 << RCC_CFGR_SW_Pos); // SW=2 (PLL)
uint32_t timeout = 100000; // Таймаут для проверки переключения
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
{
timeout--;
if (timeout == 0) break; // Выход при таймауте
}
//Настройка делителей тактов
RCC->CFGR &= ~RCC_CFGR_HPRE; // Сброс HPRE (AHB делитель)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK = 100 МГц (AHB = SYSCLK / 1)
RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сброс PPRE1 (APB1 делитель)
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 50 МГц (AHB / 2)
}
//---------------------
void loop()
{
if (flag_Led)
{
// Вывод значения таймера TIM2 на LCD
lcd.setCursor(0, 0);lcd.print("SYS=");lcd.print(tim2_value);
flag_Led = 0;
}
}
//--------------------
void watchdogHandler()
{
flag_Led = 1;
tim2_value = TIM2->CNT; // Сохраняем текущее значение таймера
TIM2->CNT = 0; // Сброс счетчика
}
в какой строке, по твоему, ты переключаешь тактирование PLL на HSE?
вот @andycat так делает
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // PLL source set HSE
Ну я думаю это вставить в 32 строку. Но это тоже не помогло.
// Версия 5
#include <LiquidCrystal.h> // библиотека для работы с экраном
LiquidCrystal lcd(PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); // (RS,E,D0-D7)
volatile bool flag_Led = 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 (до настройки PLL)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем тактирование таймера 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);
//--- Настройка системной частоты на 100 МГц через PLL
RCC->CR |= RCC_CR_HSEON; // Включаем HSE
while (!(RCC->CR & RCC_CR_HSERDY)); // Ждем готовности HSE
// Настройка PLL
RCC->PLLCFGR = 0; // Сброс PLLCFGR
RCC->PLLCFGR = (25 << RCC_PLLCFGR_PLLM_Pos) | // PLLM=25 (25 МГц / 25 = 1 МГц)
(200 << RCC_PLLCFGR_PLLN_Pos) | // PLLN=200 (1 МГц × 200 = 200 МГц)
RCC_PLLP_DIV2; // PLLP=2 (200 МГц / 2 = 100 МГц)
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // Источник HSE
RCC->CR |= RCC_CR_PLLON; // Включаем PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); // Ждем готовности PLL
// Переключение системного такта на PLL
RCC->CFGR = (2 << RCC_CFGR_SW_Pos); // SW=2 (PLL)
uint32_t timeout = 100000; // Таймаут для проверки переключения
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
{
timeout--;
if (timeout == 0) break; // Выход при таймауте
}
// Настройка делителей тактов
RCC->CFGR &= ~RCC_CFGR_HPRE; // Сброс HPRE (AHB делитель)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK = 100 МГц (AHB = SYSCLK / 1)
RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сброс PPRE1 (APB1 делитель)
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 50 МГц (AHB / 2)
}
//---------------------
void loop()
{
if (flag_Led)
{
// Вывод значения таймера TIM2 на LCD
lcd.setCursor(0, 0);lcd.print("SYS=");lcd.print(tim2_value);
flag_Led = 0;
}
}
//--------------------
void watchdogHandler()
{
flag_Led = 1;
tim2_value = TIM2->CNT; // Сохраняем текущее значение таймера
TIM2->CNT = 0; // Сброс счетчика
}
вы не находите противоречий?
переключать источник на выключенный PLL, а только потом его включать?
STM32 не дура, сразу валится в Error_Handler() и включает внутренний HSI, шоб совсем не сдохнуть))
Может и были противоречия в этих строках но похоже она слишком умная. Все равно работать не хочет.
// Версия 6
#include <LiquidCrystal.h> // библиотека для работы с экраном
LiquidCrystal lcd(PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); // (RS,E,D0-D7)
volatile bool flag_Led = 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 (до настройки PLL)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем тактирование таймера 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);
//--- Настройка системной частоты на 100 МГц через PLL
RCC->CR |= RCC_CR_HSEON; // Включаем HSE
while (!(RCC->CR & RCC_CR_HSERDY)); // Ждем готовности HSE
// Настройка PLL
RCC->PLLCFGR = 0; // Сброс PLLCFGR
RCC->PLLCFGR = (25 << RCC_PLLCFGR_PLLM_Pos) | // PLLM=25 (25 МГц / 25 = 1 МГц)
(200 << RCC_PLLCFGR_PLLN_Pos) | // PLLN=200 (1 МГц × 200 = 200 МГц)
RCC_PLLP_DIV2; // PLLP=2 (200 МГц / 2 = 100 МГц)
RCC->CR |= RCC_CR_PLLON; // Включаем PLL
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // Источник HSE
while (!(RCC->CR & RCC_CR_PLLRDY)); // Ждем готовности PLL
// Переключение системного такта на PLL
RCC->CFGR = (2 << RCC_CFGR_SW_Pos); // SW=2 (PLL)
uint32_t timeout = 100000; // Таймаут для проверки переключения
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
{
timeout--;
if (timeout == 0) break; // Выход при таймауте
}
// Настройка делителей тактов
RCC->CFGR &= ~RCC_CFGR_HPRE; // Сброс HPRE (AHB делитель)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK = 100 МГц (AHB = SYSCLK / 1)
RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сброс PPRE1 (APB1 делитель)
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 50 МГц (AHB / 2)
}
//---------------------
void loop()
{
if (flag_Led)
{
// Вывод значения таймера TIM2 на LCD
lcd.setCursor(0, 0);lcd.print("SYS=");lcd.print(tim2_value);
flag_Led = 0;
}
}
//--------------------
void watchdogHandler()
{
flag_Led = 1;
tim2_value = TIM2->CNT; // Сохраняем текущее значение таймера
TIM2->CNT = 0; // Сброс счетчика
}
te238s
07.Апрель.2025 13:51:04
28
Как-то через опу сначала настраивать экранчик, таймеры, а потом уже тактирование.
Надо:
Настроить тактирование. ОБЯЗАТЕЛЬНО дождаться сигнала готовности( PLL штука по меркам МК ОЧЕНЬ ДОЛГО устаканивается)
Включаем тактирование нужного модуля
Настраиваем модуль.
Иной порядок приводит к непредсказуемым багам. Коттрые ни один дебаг не в состоянии отловить- частота-то неизвестна в конкретный момент!
мне после этого надоело что то объяснять.
1 лайк
Вроде делаю порядок все по правильному коду. Порядок настройки такой:
Включение HSE и проверка его готовности.
Настройка параметров PLL (PLLM, PLLN, PLLP) до включения PLL .
Включение PLL и проверка его готовности.
Переключение системного такта на PLL .
Настройка делителей частот шин (AHB, APB1).
// Версия 7
#include <LiquidCrystal.h> // библиотека для работы с экраном
LiquidCrystal lcd(PB8, PB7, PB6, PB5, PB4, PB3, PA15, PA12, PB13, PB12); // (RS,E,D0-D7)
volatile bool flag_Led = 0;
uint32_t tim2_value;
//--------------------
void setup()
{
//--- Настройка системной частоты на 100 МГц через PLL
RCC->CR |= RCC_CR_HSEON; // Включаем HSE
while (!(RCC->CR & RCC_CR_HSERDY)); // Ждем готовности HSE
RCC->PLLCFGR = 0; // Сброс PLLCFGR
RCC->PLLCFGR = // Задаем параметры PLL ДО ВКЛЮЧЕНИЯ:
(25 << RCC_PLLCFGR_PLLM_Pos) | // PLLM=25 (25 МГц / 25 = 1 МГц)
(200 << RCC_PLLCFGR_PLLN_Pos) | // PLLN=200 (1 МГц × 200 = 200 МГц)
RCC_PLLP_DIV2; // PLLP=2 (200 МГц / 2 = 100 МГц)
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; // Указываем источник HSE
RCC->CR |= RCC_CR_PLLON; // Включаем PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); // Ждем готовности
// Переключение системного такта на PLL
RCC->CFGR = (2 << RCC_CFGR_SW_Pos); // SW=2 (PLL)
uint32_t timeout = 100000; // Таймаут для проверки переключения
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
{
timeout--;
if (timeout == 0) break; // Выход при таймауте
}
// Настройка делителей тактов
RCC->CFGR &= ~RCC_CFGR_HPRE; // Сброс HPRE (AHB делитель)
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // HCLK = 100 МГц (AHB = SYSCLK / 1)
RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сброс PPRE1 (APB1 делитель)
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 50 МГц (AHB / 2)
//Настройка экрана.
lcd.begin(20, 4);
//--- Настройка Таймера 2
// Настройка системной шины
RCC->CFGR &= ~RCC_CFGR_PPRE1; // Сбрасываем биты PPRE1
RCC->CFGR |= RCC_CFGR_PPRE1_DIV1; // Устанавливаем делитель APB1 = 1 (до настройки PLL)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Включаем тактирование таймера 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);
}
//---------------------
void loop()
{
if (flag_Led)
{
// Вывод значения таймера TIM2 на LCD
lcd.setCursor(0, 0);lcd.print("SYS=");lcd.print(tim2_value);
flag_Led = 0;
}
}
//--------------------
void watchdogHandler()
{
flag_Led = 1;
tim2_value = TIM2->CNT; // Сохраняем текущее значение таймера
TIM2->CNT = 0; // Сброс счетчика
}
Спасибо всем за помощь. Действительно настраивается легко. Теперь все работает правильно. Оказалось виноват внешний кварцевый генератор. Кварц возбуждался на 1 гармонике на 25 мгц. А должен был на 4 гармонике и получилось бы 100 мгц.Поскольку кварц маленький и паять его не охота то вместо него поставил другой кварц с выходом TTL на 3.3 вольта и подключил его к выводу PA0 и все заработало. Я почему то думал что если у кварца 4 вывода то это генератор с внешним питанием. Оказалось нет. Посмотрел на схему платы и очень удивился этому. Теперь частота стабильна 100 мгц +/- 1 Гц. Правильный код в предыдущем сообщении.
te238s
08.Апрель.2025 20:38:11
32
Сбил с толку, однако)
А PA0 это input frequence? Подглядел, это вход таймера вроде.
Но вход внеш.кварца 4-26МГц. Как он может выдавать 100МГц?
тут, по моему продолжение потока сознания, нихрена не относящиеся не начальному вопросу, не к последствиям решения
чего одно это стоит
а из позитива и всем за науку, это
че делал, как подключал, одни загадки и обрывки кода.
и то из сетей похоже…
ладно срослось и ладно, “вопрос решен” сам за себя говорит…
2 лайка
Ну поскольку человеку надоело что то объяснять так зачем спрашивать что да как получилось, если на простейшие и легчайшие вопросы отвечать лень. А вот про потоки сознания поговорить это очень челу важно. Вам бы в предсказатели податься. Там можно вообще ничего не делать а только говорить.
так я там и работаю.
и встречный совет, научитесь четко выражать свои мысли.
Комментарии по тактированию от внешнего генератора. Проверил на платах STM32F411 и STM32G431. в обоих вариантах все работает и переключается как на внешний генератор так и на внутренний. И дело оказывается было не в коде. Даже в исправном коде на PLL не переходит.
Неправильный вариант. Я настройки тактирования прописывал в Setup напрямую.
Правильный вариант (рабочий). Настройки генератора надо было прописать сюда void SystemClock_Config(void) {} а уже из setup вызывать SystemClock_Config(); В чем разница я не знаю но это у меня заработало только в таком варианте причем сразу.
nik182
18.Апрель.2025 07:11:49
37
Перед вызовом setup вызываются скрытые init() и прочие которые собственно настраивают камень под нужды программы. Работает main.cpp как и положено, но неофитам это не видно. Поэтому выход из loop, в частности, просто пребрасывает в начало loop.
Спойлер main.cpp
#include <Arduino.h>
// Declared weak in Arduino.h to allow user redefinitions.
int atexit(void (* /*func*/ )()) { return 0; }
// Weak empty variant initialization function.
// May be redefined by variant files.
void initVariant() __attribute__((weak));
void initVariant() { }
void setupUSB() __attribute__((weak));
void setupUSB() { }
int main(void)
{
init();
initVariant();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}