SystemClock

Пытаемся разобраться зачем нужен SystemClock в контроллерах STM32.

В Cube MX постоянно , даже в самом минимальном проекте, будет присутствовать создание и инициализация SystemClock. И SysTick всегда присутствует в списке выбора таймеров TIM1,TIM2.., но ведь это не типичный таймер.

Прерывание этого таймер жестко прописано в таблице прерываний и называется SysTick_IRQn.

Есть предположение , что этот SystemClock обслуживает системный загрузчик . И поэтому должен присутствовать по идеалогии STM32 всегда.

Если убрать из проекта Cube MX все и даже внешний кварц, то остается еще встроенный некий источник HSI (High Speed Internal) на 16MHz. Это внутренний высокочастотный RC генератор. Поэтому значение 16MHz нам не изменить никак.

Вот картинка Clock configuration . Внешний кварц HSE отключен. Тогда тактирование идет только от внутреннего генератора HSI.

фотка 1

Куб всегда добавляет нам в проект инициализацию таймера с приоритетом 0 , то есть с наивысшим приоритетом. Вот он наш минимальный проект:


int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();

  while (1)
  {
  }
}

SystemClock_Config

Что делает SystemClock_Config? Она просто прописывает то ,что отображено на картинке Clock configuration. Все очевидно.


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

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;  // 1

  RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 2
 //либо будет RCC_OscInitStruct.HSIState = RCC_HSI_ON;

  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 3
 // либо будет RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;

  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 4
  // PLLSource  только в том случает будет если на System Clock Mux заводится через PLLCLK линию

  RCC_OscInitStruct.PLL.PLLM = 10;
  RCC_OscInitStruct.PLL.PLLN = 200;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 5;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses 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_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

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

RCC_OscInitStruct.OscillatorType устанавливает откуда берется источник тактирования от HSI (внутренний) или HSE (внешний) применительно к входу в System Clock Mux.

Вторая строчка обратите внимание будет либо
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
либо
RCC_OscInitStruct.HSIState = RCC_HSI_ON;

HAL_Init

Что делает HAL_Init в части относительно SystemClock ? Она в подфункции HAL_InitTick настраивает частоту срабатывания таймера SystemClock. Как тут написано настраивается на 1мс. И срабатывает это прерывание от внутреннего HSI RC таймера, потому что SystemCoreClock в коде равен 16000000 при любом раскладе.

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  /*Configure the SysTick to have interrupt in 1ms time basis*/
  HAL_SYSTICK_Config(SystemCoreClock/1000U);

  /*Configure the SysTick IRQ priority */
  HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
 // TickPriority можно выставить в любое значение 0..15

  /* Return function status */
  return HAL_OK;
}

HSI oscillator clock – внутренний высокочастотный RC генератор. RC - это Resistor / Condensator контур.

Что тут важно - это что SystemCoreClock равен 16000000. То есть частота системного таймера отсчитывается от только внутреннего высокочастотного генератора (HSI). И этим он по-видимому отличается от других таймеров.

Обработчик прерывания системного таймера - это просто счет тиков. И можем использовать этот счетчик для своих целей.

__weak uint32_t HAL_GetTick(void)
{
  return uwTick;
}
__weak void HAL_IncTick(void)
{
  uwTick++;
}

Всем известная функция HAL_Delay() пользуется именно HAL_GetTick() для формирования задержки n миллисеунд.

FreeRTOS

Интересная особенность FreeRTOS , что она показывает у прерывания System Tick Timer Preemption Priority значение 15 . Хотя по коду
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
TickPriority = TICK_INT_PRIORITY имеет значение всегда 0.
То есть не надо путать Preemption Priority с реально устанавливаемым приоритетом прерыванию.

Если в Кубе для Time base source выбрать в качестве источника НЕ SysTick, а любой свободный из таймеров TIM..., то приоритет этого прерывания будет не 15 , а только 0 и никак иначе.

Источником (может быть любой таймер), который используется донором для системы, помечается префиксом Time base:.