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

Первоначально плата STM32F103 работает от внутреннего генератора на частоте 48 мГц.
Сделал настройку по ссылке но ничего не поменялось на двух разных платах. Почему может не работать?

https://dimoon.ru/obuchalka/stm32f1/uroki-stm32f103-chast-4-nastroyka-rcc.html?ysclid=m77p7n5frs371276003

Код.

//#include <stm32f1xx.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd (PA9, PA8, PB15, PB14, PB13, PB12); //(RS,E,D4,D5,D6,D7)
uint32_t Tay_ind =  1000000;
uint32_t time_new_ind,time_old_ind,time_del_ind;
  //-------------------------------------------------------------------------//
int ClockInit(void)
{
  __IO int 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 1;
    }
  }
  ////////////////////////////////////////////////////////////
  // Настраиваем и запускаем PLL
  ////////////////////////////////////////////////////////////
  // Настраиваем PLL
  RCC->CFGR |= RCC_CFGR_PLLMULL2 // PLL множитель равен 9
            | RCC_CFGR_PLLSRC;   // Тактирование PLL от HSE
  RCC->CR |= RCC_CR_PLLON; // Запускаем PLL
  // Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter = 0; ; StartUpCounter++)
  {
    // Если успешно запустилось, то выходим из цикла
    if(RCC->CR & RCC_CR_PLLRDY)
      break;
    // Если по каким-то причинам не запустился PLL, то отключаем все, что включили и возвращаем ошибку
    if(StartUpCounter > 0x1000)
    {
      RCC->CR &= ~RCC_CR_HSEON; // Останавливаем HSE
      RCC->CR &= ~RCC_CR_PLLON; // Останавливаем PLL
      return 2;
    }
  }
  ////////////////////////////////////////////////////////////
  // Настраиваем FLASH и делители
  ////////////////////////////////////////////////////////////
  // Устанавливаем 2 цикла ожидания для Flash
  // так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= FLASH_ACR_LATENCY_2; 
  // Делители
  RCC->CFGR |= RCC_CFGR_PPRE2_DIV1 // Делитель шины APB2 отключен
            | RCC_CFGR_PPRE1_DIV2  // Делитель шины APB1 равен 2
            | RCC_CFGR_HPRE_DIV1; // Делитель AHB отключен
  RCC->CFGR |= RCC_CFGR_SW_PLL; // Переключаемся на работу от PLL
  // Ждем, пока переключимся
  while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
  {
  }
  // После того, как переключились на внешний источник тактирования
  // отключаем внутренний RC-генератор для экономии энергии
  RCC->CR &= ~RCC_CR_HSION;
  // Настройка и переключение системы на внешний кварцевый генератор и PLL завершилось успехом.
  // Выходим
  return 0;
}
//-------------------------------------------------------------------------//
void setup()
{
 //-------------------------------------------------------------------------//
  // Инициализация тактирования
  if(ClockInit() != 0) {
    // Обработка ошибки инициализации тактирования
    while(1);
  }
 //-------------------------------------------------------------------------//
  lcd.begin(20, 4);
  //-------------------------------------------------------------------------//
  // Включаем тактирование TIM2
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
  // Настройка TIM2 для счета миллисекунд
  TIM2->CR1 = 0;  // Остановка таймера
  TIM2->PSC = 47999;  // Предделитель 48000 (48 МГц / 48000 = 1 кГц)
  TIM2->ARR = 0xFFFF;  // Автоперезагрузка на максимум (16 бит)
  TIM2->CCER &= ~TIM_CCER_CC1P;  // Полярность: считаем по фронту 
  TIM2->EGR = 1;  // Обновление регистров
  TIM2->CR1 |= (1 << 0);  // Запуск TIM2
  //-------------------------------------------------------------------------//
}
void loop()
{
  time_new_ind= micros();
  time_del_ind=time_new_ind-time_old_ind;
  if (time_del_ind>=Tay_ind)
  {
    //-----------------------------------------------------------------------//
    uint32_t milliseconds = TIM2->CNT;
    uint32_t sysclk_freq = HAL_RCC_GetSysClockFreq();
    //-----------------------------------------------------------------------//
    lcd.clear();
    lcd.setCursor(0, 0);lcd.print("T= ");lcd.print(millis());
    lcd.setCursor(0, 1);lcd.print("N= ");lcd.print(milliseconds);
    lcd.setCursor(0, 2);lcd.print("F= ");lcd.print(sysclk_freq);
    time_old_ind=time_new_ind;
  }
}

на каких платах?

На какой частоте с таким кодом работают платы ?

В 31 строке надо смотреть что в итоге за множитель получается …RCC_CFGR_PLLMULL9 нужен скорее всего для кварца 8 МГц. Вы как то странно читали описание по ссылке и привели свой вариант кода !!!

Если первая строка = комментарий - откуда берутся все константы ??? Вы уверены что это вообще компилируется и прошивается ???

Плата такая и что самое интересное прошивается и не дает ошибок. На меня это не похоже.
Плата работает на частоте 48 мГц. В 86 строке настраиваю предделитель. Время с таймера и время миллисекунд отображаются синхронно.
https://aliexpress.ru/item/1005004918334754.html?sku_id=12000034303875973&spm=a2g2w.stores.seller_list.3.2ee647dcIl7d7x

Кварц какой на плате ?
И тут в процессе возник еще вопрос - на какой частоте вы хотите запустить микроконтроллер ?

если это действительно плата от Weact, то они только 8мГц.

Ну для начала хотел бы запустить на 72 мгц. Но пока получилось только запустить на 8 мГц от внутреннего генератора. Строки с 6 по 17.

//работает на частоте 8 мгц от внутреннего генератора
#include <LiquidCrystal.h>
LiquidCrystal lcd (PA9, PA8, PB15, PB14, PB13, PB12); //(RS,E,D4,D5,D6,D7)
uint32_t Tay_ind =  1000000;
uint32_t time_new_ind,time_old_ind,time_del_ind;
  //-------------------------------------------------------------------------//
void SystemClock_Config(void) {
    // Включение HSI (внутренний генератор 8 МГц)
    RCC->CR |= RCC_CR_HSION; // Включение HSI
    while (!(RCC->CR & RCC_CR_HSIRDY)); // Ожидание готовности HSI

    // Переключение на HSI как источник тактирования
    RCC->CFGR &= ~RCC_CFGR_SW; // Сброс источника тактирования
    RCC->CFGR |= RCC_CFGR_SW_HSI; // Использование HSI как источника тактирования
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI); // Ожидание переключения на HSI
}
//-------------------------------------------------------------------------//
void setup()
{
 //-------------------------------------------------------------------------//
 SystemClock_Config(); // Настройка тактирования
  //-------------------------------------------------------------------------//
  lcd.begin(20, 4);
  //-------------------------------------------------------------------------//
  // Включаем тактирование TIM2
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
  // Настройка TIM2 для счета миллисекунд
  TIM2->CR1 = 0;  // Остановка таймера
  TIM2->PSC = 7999;  // Предделитель 48000 (8 МГц / 8000 = 1 кГц)
  TIM2->ARR = 0xFFFF;  // Автоперезагрузка на максимум (16 бит)
  TIM2->CCER &= ~TIM_CCER_CC1P;  // Полярность: считаем по фронту 
  TIM2->EGR = 1;  // Обновление регистров
  TIM2->CR1 |= (1 << 0);  // Запуск TIM2
  //-------------------------------------------------------------------------//
}
void loop()
{
  time_new_ind= micros();
  time_del_ind=time_new_ind-time_old_ind;
  if (time_del_ind>=Tay_ind)
  {
    //-----------------------------------------------------------------------//
    uint32_t milliseconds = TIM2->CNT;
    uint32_t sysclk_freq = HAL_RCC_GetSysClockFreq();
    //-----------------------------------------------------------------------//
    lcd.clear();
    lcd.setCursor(0, 0);lcd.print("T= ");lcd.print(millis());
    lcd.setCursor(0, 1);lcd.print("N= ");lcd.print(milliseconds);
    lcd.setCursor(0, 2);lcd.print("F= ");lcd.print(sysclk_freq);
    time_old_ind=time_new_ind;
  }
}

Запустил в работу от внешнего генератора на 8 мгц.

  //-------------------------------------------------------------------------//
void SystemClock_Config(void) {
    // Включение HSE (внешний кварцевый генератор 8 МГц)
    RCC->CR |= RCC_CR_HSEON; // Включение HSE
    while (!(RCC->CR & RCC_CR_HSERDY)); // Ожидание готовности HSE

    // Настройка FLASH (для работы на частоте HSE)
    FLASH->ACR |= FLASH_ACR_PRFTBE; // Включение предварительной выборки
    FLASH->ACR &= ~FLASH_ACR_LATENCY; // Сброс задержки
    FLASH->ACR |= FLASH_ACR_LATENCY_0; // Установка задержки в 0 тактов (для частот до 24 МГц)

    // Переключение на HSE как источник тактирования
    RCC->CFGR &= ~RCC_CFGR_SW; // Сброс источника тактирования
    RCC->CFGR |= RCC_CFGR_SW_HSE; // Использование HSE как источника тактирования
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSE); // Ожидание переключения на HSE
}
//-------------------------------------------------------------------------//

Добавил настройку шин.

void SystemClock_Config(void) {
    // Включение HSE (внешний кварцевый генератор 8 МГц)
    RCC->CR |= RCC_CR_HSEON; // Включение HSE
    while (!(RCC->CR & RCC_CR_HSERDY)); // Ожидание готовности HSE

    // Настройка FLASH (для работы на частоте HSE)
    FLASH->ACR |= FLASH_ACR_PRFTBE; // Включение предварительной выборки
    FLASH->ACR &= ~FLASH_ACR_LATENCY; // Сброс задержки
    FLASH->ACR |= FLASH_ACR_LATENCY_0; // Установка задержки в 0 тактов (для частот до 24 МГц)

    // Настройка делителей шин
    RCC->CFGR &= ~(RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2); // Сброс делителей
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;   // AHB без деления (частота = HSE = 8 МГц)
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV1;  // APB1 без деления (частота = HSE = 8 МГц)
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;  // APB2 без деления (частота = HSE = 8 МГц)

    // Переключение на HSE как источник тактирования
    RCC->CFGR &= ~RCC_CFGR_SW; // Сброс источника тактирования
    RCC->CFGR |= RCC_CFGR_SW_HSE; // Использование HSE как источника тактирования
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSE); // Ожидание переключения на HSE
}

пример функции инициализации RCC для STM32F103C8T6 с использованием внешнего кварца 8 МГц для достижения тактовой частоты 72 МГц:

#include "stm32f10x.h"

void SystemClock_Config(void) {
    // 1. Включение HSE и ожидание готовности
    RCC->CR |= RCC_CR_HSEON;              // Включить HSE
    while(!(RCC->CR & RCC_CR_HSERDY));    // Ждем стабилизации генератора

    // 2. Настройка задержки Flash-памяти
    FLASH->ACR |= FLASH_ACR_LATENCY_2;    // Два цикла ожидания (для 48-72 МГц)
    FLASH->ACR |= FLASH_ACR_PRFTBE;       // Включить предварительную выборку

    // 3. Настройка делителей шин
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;      // AHB  = 72 MHz
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;     // APB1 = 36 MHz
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;     // APB2 = 72 MHz

    // 4. Конфигурация PLL (8MHz * 9 = 72MHz)
    RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;     // Источник PLL - HSE
    RCC->CFGR |= RCC_CFGR_PLLMULL9;       // Множитель PLL x9

    // 5. Включение PLL и ожидание готовности
    RCC->CR |= RCC_CR_PLLON;              // Запуск PLL
    while(!(RCC->CR & RCC_CR_PLLRDY));    // Ждем готовности PLL

    // 6. Переключение на PLL как источник системного такта
    RCC->CFGR |= RCC_CFGR_SW_PLL;         // Выбираем PLL как системный источник
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Ждем переключения
}

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

// Версия 11.2 (STM32F103 + TTL 8 МГц, 7 тестов + отладка)  
#include <LiquidCrystal.h>  
LiquidCrystal lcd(PA9, PA8, PB15, PB14, PB13, PB12); // RS, E, D4-D7  

// Глобальные переменные  
uint32_t time_old_ind = 0;       // Для отслеживания времени (секунды)  
uint32_t stepStartTime = 0;      // Время начала текущего этапа  
uint32_t regDisplayTime = 0;     // Время последнего обновления регистра  
uint8_t setupStep = 0;           // Текущий этап настройки  
uint8_t debugStep = 0;           // Текущий отображаемый регистр  
bool setupCompleted = false;     // Флаг завершения настройки  
bool messageShown = false;       // Флаг отображения сообщения  

// Структура для хранения информации о регистрах  
struct RegisterInfo {  
  const char* name;              // Имя регистра  
  uint32_t expected;             // Ожидаемое значение  
  volatile uint32_t* reg;        // Указатель на регистр  
};  

// Инициализация регистров для отладки  
RegisterInfo regs[] = {  
  {"RCC_CR",   RCC_CR_HSEON | RCC_CR_HSERDY | RCC_CR_PLLON | RCC_CR_PLLRDY, &RCC->CR},  
  {"RCC_CFGR", RCC_CFGR_PLLXTPRE_HSE_DIV2 | RCC_CFGR_PLLMULL2 | RCC_CFGR_PLLSRC | RCC_CFGR_SW_PLL, &RCC->CFGR}  
};  

// Функция расчета частоты процессора  
uint32_t getSysClockFreq() {  
  uint32_t hse = 8000000;        // Частота внешнего генератора (8 МГц)  
  uint32_t pllDiv = 2;           // Делитель HSE (по умолчанию /2)  
  uint32_t pllMul = 2;           // Множитель PLL (по умолчанию x2)  

  // Чтение делителя из регистра RCC_CFGR  
  if (RCC->CFGR & RCC_CFGR_PLLXTPRE_HSE_DIV2) pllDiv = 2;  

  // Чтение множителя из регистра RCC_CFGR  
  switch (RCC->CFGR & RCC_CFGR_PLLMULL) {  
    case RCC_CFGR_PLLMULL2: pllMul = 2; break;  
    // Добавьте другие множители при необходимости  
  }  

  return (hse / pllDiv) * pllMul; // Расчет итоговой частоты  
}  

// Настройка LCD и тактирования  
void setup() {  
  lcd.begin(20, 4);              // Инициализация LCD 20x4  
  RCC->CR |= RCC_CR_HSEBYP;      // Активация bypass режима для TTL  
  pinMode(PC14, INPUT);          // Настройка OSC_IN как входа  
}  

// Основной цикл программы  
void loop() {  
  if (!setupCompleted) {  
    switch (setupStep) {  
      // Шаг 0: Включение HSE  
      case 0:  
        RCC->CR |= RCC_CR_HSEON; // Активация HSE  
        lcd.clear();  
        lcd.print("Test 1: HSE...");  
        setupStep++;  
        stepStartTime = millis();  
        break;  

      // Шаг 1: Ожидание HSE  
      case 1:  
        if (RCC->CR & RCC_CR_HSERDY) {  
          if (!messageShown && millis() - stepStartTime >= 1000) {  
            lcd.clear();  
            lcd.print("Test 2: HSE OK");  
            messageShown = true;  
            stepStartTime = millis();  
          }  
          if (messageShown && millis() - stepStartTime >= 1000) {  
            setupStep++;  
            messageShown = false;  
          }  
        } else if (millis() - stepStartTime > 2000) {  
          lcd.print("HSE FAIL!");  
          while(1);  
        }  
        break;  

      // Шаг 2: Настройка делителя HSE/2  
      case 2:  
        RCC->CFGR |= RCC_CFGR_PLLXTPRE_HSE_DIV2;  
        if (!messageShown) {  
          lcd.clear();  
          lcd.print("Test 3: DIV OK");  
          messageShown = true;  
          stepStartTime = millis();  
        }  
        if (millis() - stepStartTime >= 1000) {  
          setupStep++;  
          messageShown = false;  
        }  
        break;  

      // Шаг 3: Настройка множителя PLL x2  
      case 3:  
        RCC->CFGR |= RCC_CFGR_PLLMULL2;  
        if (!messageShown) {  
          lcd.clear();  
          lcd.print("Test 4: PLL MUL OK");  
          messageShown = true;  
          stepStartTime = millis();  
        }  
        if (millis() - stepStartTime >= 1000) {  
          setupStep++;  
          messageShown = false;  
        }  
        break;  

      // Шаг 4: Выбор источника PLL (HSE)  
      case 4:  
        RCC->CFGR |= RCC_CFGR_PLLSRC;  
        if (!messageShown) {  
          lcd.clear();  
          lcd.print("Test 5: PLL SRC OK");  
          messageShown = true;  
          stepStartTime = millis();  
        }  
        if (millis() - stepStartTime >= 1000) {  
          setupStep++;  
          messageShown = false;  
        }  
        break;  

      // Шаг 5: Включение PLL  
      case 5:  
        RCC->CR |= RCC_CR_PLLON;  
        if (RCC->CR & RCC_CR_PLLRDY) {  
          if (!messageShown) {  
            lcd.clear();  
            lcd.print("Test 6: PLL RDY");  
            messageShown = true;  
            stepStartTime = millis();  
          }  
          if (millis() - stepStartTime >= 1000) {  
            setupStep++;  
            messageShown = false;  
          }  
        }  
        break;  

      // Шаг 6: Переключение на PLL  
      case 6:  
        RCC->CFGR |= RCC_CFGR_SW_PLL;  
        if ((RCC->CFGR & RCC_CFGR_SWS) == RCC_CFGR_SWS_PLL) {  
          if (!messageShown) {  
            lcd.clear();  
            lcd.print("Test 7: SYSCLK OK");  
            messageShown = true;  
            stepStartTime = millis();  
          }  
          if (millis() - stepStartTime >= 1000) {  
            setupCompleted = true;  
          }  
        }  
        break;  
    }  
  } else {  
    // Режим отладки после настройки  
    if (millis() - regDisplayTime >= 6000) {  
      debugStep = (debugStep + 1) % 2;  
      regDisplayTime = millis();  
    }  

    if (millis() - time_old_ind >= 1000) {  
    // Строка 0: Ожидаемое значение регистра (EX: ...)  
    lcd.setCursor(0, 0);  
    lcd.print(regs[debugStep].name);  
    lcd.print(" EX:0x");  // Сокращено с "EXP:0x"  
    lcd.print(regs[debugStep].expected, HEX);  
    lcd.print("    ");  

    // Строка 1: Фактическое значение регистра (AC: ...)  
    lcd.setCursor(0, 1);  
    lcd.print(regs[debugStep].name);  
    lcd.print(" AC:0x");  // Сокращено с "ACT:0x"  
    lcd.print(*regs[debugStep].reg, HEX);  
    lcd.print("    ");  

    // Строка 2: Частота процессора (обновляется каждую секунду)  
      lcd.setCursor(0, 2);  
      lcd.print("Freq:");  
      lcd.print(getSysClockFreq() / 1000000);  
      lcd.print("MHz       ");  

    // Строка 3: Время работы (обновляется каждую секунду)  
      lcd.setCursor(0, 3);  
      lcd.print("T=");  
      lcd.print(millis() / 1000);  
      lcd.print("s  ");  
      time_old_ind = millis();  
    }  
  }  
}