317 lines
12 KiB
C
317 lines
12 KiB
C
/**
|
||
******************************************************************************
|
||
* @file sysclk.c
|
||
* @author Разваляев Алексей
|
||
* @brief Драйвер тактирования системы на основе PLIB035.
|
||
* Этот файл содержит:
|
||
* + Инициализацию системного тактирования (PLL, осцилляторы)
|
||
* + Настройку частоты ядра и периферии
|
||
* + Управление системным таймером (SysTick)
|
||
* + Функции для работы со временем (millis, micros)
|
||
* + Систему периодических коллбеков
|
||
* + Настройку тактирования периферии
|
||
*
|
||
******************************************************************************
|
||
* @attention
|
||
*
|
||
* Этот драйвер должен быть инициализирован ПЕРВЫМ в программе, до любой другой периферии.
|
||
* Неправильная настройка тактирования может привести к неработоспособности всего МК.
|
||
*
|
||
* Использование этого драйвера предполагает наличие корректных настроек:
|
||
* - Определены константы SYSCLK_CORE_CLOCK_MHZ и SYSCLK_Oscil_Type в periph_config.h
|
||
* - Определен тип системного тика SYSCLK_TickType в periph_config.h
|
||
*
|
||
******************************************************************************
|
||
* @verbatim
|
||
==============================================================================
|
||
##### Как использовать этот драйвер #####
|
||
==============================================================================
|
||
|
||
1. Настройка в periph_config.h:
|
||
(+) SYSCLK_CORE_CLOCK_MHZ - частота ядра в МГц (например, 100)
|
||
(+) SYSCLK_Oscil_Type - источник тактирования (RCU_Oscil_OSE или RCU_Oscil_OSI)
|
||
(+) SYSCLK_TickType - период системного тика (от SYSCLK_Tick_1us до SYSCLK_Tick_100ms)
|
||
|
||
2. Инициализация (в начале main()):
|
||
(+) sysclk_init() - настраивает PLL, SysTick и счетчики времени
|
||
(+) В SysTick_Handler() вызвать sysclk_irq_handler()
|
||
|
||
3. Работа со временем:
|
||
(+) millis() - текущее время в миллисекундах (переполнение через 49 дней)
|
||
(+) micros() - текущее время в микросекундах (точность зависит от SYSCLK_TickType)
|
||
|
||
4. Периодические задачи:
|
||
(+) SYSCLK_Set_Callback(func, period_ms) - регистрация функции для периодического вызова
|
||
(+) Максимум 16 функций, период должен быть >= периода системного тика
|
||
|
||
5. Настройка тактирования периферии:
|
||
(+) rcu_set_clock_adc(source, freq_mhz, enable) - для АЦП
|
||
(+) Частота АЦП не должна превышать 12.5 МГц согласно datasheet
|
||
|
||
==============================================================================
|
||
##### Особенности работы #####
|
||
==============================================================================
|
||
|
||
- Выбор SYSCLK_TickType влияет на:
|
||
- Точность micros() (при 1ms тике micros() дает значения с шагом 1000)
|
||
- Нагрузку на ЦП (1us = 1М прерываний в секунду, 100ms = 10 прерываний в секунду)
|
||
- Минимальный период коллбеков (не может быть меньше SYSCLK_TickType)
|
||
|
||
- При настройке PLL:
|
||
- Драйвер автоматически подбирает делители для заданной частоты
|
||
- Если частота недостижима - вызовется Error_Handler()
|
||
- Всегда проверяйте supported frequencies в datasheet
|
||
|
||
- Коллбеки выполняются в контексте прерывания SysTick:
|
||
- Не должны выполняться долго
|
||
- Не должны вызывать блокирующие функции
|
||
|
||
@endverbatim
|
||
******************************************************************************
|
||
*/
|
||
|
||
/* Includes ------------------------------------------------------------------*/
|
||
#include "periph_config.h"
|
||
|
||
/* Private variables ---------------------------------------------------------*/
|
||
/** @brief Счетчик миллисекунд */
|
||
__IO uint32_t msTick = 0;
|
||
|
||
/** @brief Счетчик микросекунд */
|
||
__IO uint32_t usTick = 0;
|
||
|
||
/** @brief Инкремент для миллисекунд */
|
||
static __IO uint32_t msTickInc = 0;
|
||
|
||
/** @brief Инкремент для микросекунд */
|
||
static __IO uint32_t usTickInc = 0;
|
||
|
||
/** @brief Обработчик системных коллбеков */
|
||
static SYSCLK_CallbackHandleTypeDef hsyscb = {0};
|
||
//-- Defines -------------------------------------------------------------------
|
||
|
||
|
||
//-- Private function prototypes -----------------------------------------------
|
||
static inline void millis_inc(void);
|
||
static inline void micros_inc(void);
|
||
|
||
//-- Peripheral init functions -------------------------------------------------
|
||
/**
|
||
* @brief Инициализация системного тактирования.
|
||
* @details Настраивает PLL, SysTick и счетчики времени.
|
||
* Должна быть вызвана первой в функции main().
|
||
*/
|
||
void sysclk_init(void)
|
||
{
|
||
OperationStatus status;
|
||
status = RCU_PLL_AutoConfig(SYSCLK_CORE_CLOCK_MHZ*__MHZ, SYSCLK_Oscil_Type);
|
||
if (status == ERROR)
|
||
{
|
||
Error_Handler();
|
||
}
|
||
SystemCoreClockUpdate();
|
||
// RCU_ClkOutConfig(RCU_SysPeriphClk_PLLClk, 1, ENABLE);
|
||
// RCU_ClkOutCmd(ENABLE);
|
||
|
||
SysTick_Config(SYSCLK_CORE_CLOCK_MHZ*__MHZ/SYSCLK_TickType);
|
||
switch(SYSCLK_TickType)
|
||
{
|
||
case SYSCLK_Tick_1us:
|
||
usTickInc = 1;
|
||
msTickInc = 1;
|
||
break;
|
||
case SYSCLK_Tick_10us:
|
||
usTickInc = 10;
|
||
msTickInc = 1;
|
||
break;
|
||
case SYSCLK_Tick_100us:
|
||
usTickInc = 100;
|
||
msTickInc = 1;
|
||
break;
|
||
case SYSCLK_Tick_1ms:
|
||
usTickInc = 1000;
|
||
msTickInc = 1;
|
||
break;
|
||
case SYSCLK_Tick_10ms:
|
||
usTickInc = 10000;
|
||
msTickInc = 10;
|
||
break;
|
||
case SYSCLK_Tick_100ms:
|
||
usTickInc = 100000;
|
||
msTickInc = 100;
|
||
break;
|
||
default:
|
||
/* Должен быть определен в periph_config.h */
|
||
Error_Handler();
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief Общий обработчик прерываний SysClock
|
||
* @details Обрабатывает мс и мкс и вызывает коллбеки
|
||
*/
|
||
void sysclk_irq_handler(void)
|
||
{
|
||
static uint32_t usAccumulator = 0; // Накопитель мкс
|
||
/* Инкремент микросекунд */
|
||
micros_inc();
|
||
|
||
if(msTickInc == 1)
|
||
{
|
||
/* Аккумулятивный метод для миллисекунд (без деления) */
|
||
usAccumulator += usTickInc;
|
||
if (usAccumulator >= 1000) {
|
||
millis_inc();
|
||
usAccumulator -= 1000; // Вычитание быстрее деления
|
||
}
|
||
}
|
||
else
|
||
{
|
||
millis_inc();
|
||
}
|
||
|
||
/* Вызов зарегистрированных коллбеков */
|
||
for(int i = 0; i < hsyscb.CallbackInUse; i++)
|
||
{
|
||
if(hsyscb.Callback[i] != NULL)
|
||
{
|
||
/* Проверка истекшего времени */
|
||
uint32_t elapsed = msTick - hsyscb.CallbackPrevMs[i];
|
||
if(elapsed >= hsyscb.CallbackPeriod[i])
|
||
{
|
||
/* Обновление времени последнего вызова и вызов коллбека */
|
||
hsyscb.CallbackPrevMs[i] = msTick;
|
||
hsyscb.Callback[i]();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Добавление периодического коллбека.
|
||
* @param Callback Указатель на функцию-коллбек
|
||
* @param PeriodInMs Период вызова коллбека в миллисекундах
|
||
* @retval OperationStatus OK при успехе, ERROR при ошибке
|
||
* @note Максимальное количество коллбеков: SYSCLK_NUMB_OF_CUSTOM_CALLBACKS
|
||
* @note Минимальный период: текущий период тика SysTick
|
||
*/
|
||
OperationStatus SYSCLK_Set_Callback(void (*Callback)(void), uint32_t PeriodInMs)
|
||
{
|
||
/* Проверка валидности указателя на функцию */
|
||
if(Callback == NULL)
|
||
return ERROR;
|
||
|
||
/* Проверка минимального периода */
|
||
if(PeriodInMs < msTickInc)
|
||
return ERROR;
|
||
|
||
/* Проверка доступности свободных слотов */
|
||
if(hsyscb.CallbackInUse >= SYSCLK_NUMB_OF_CUSTOM_CALLBACKS)
|
||
return ERROR;
|
||
|
||
/* Регистрация коллбека */
|
||
hsyscb.Callback[hsyscb.CallbackInUse] = Callback;
|
||
hsyscb.CallbackPeriod[hsyscb.CallbackInUse] = PeriodInMs;
|
||
hsyscb.CallbackPrevMs[hsyscb.CallbackInUse] = msTick;
|
||
|
||
hsyscb.CallbackInUse++;
|
||
|
||
return OK;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* @brief Получение текущего времени в миллисекундах.
|
||
* @retval uint32_t Текущее время в миллисекундах
|
||
* @note Переполнение происходит через ~49 дней
|
||
*/
|
||
uint32_t millis(void)
|
||
{
|
||
return msTick;
|
||
}
|
||
|
||
/**
|
||
* @brief Получение текущего времени в микросекундах.
|
||
* @retval uint32_t Текущее время в микросекундах
|
||
* @note Переполнение происходит через ~71 минуту
|
||
*/
|
||
uint32_t micros(void)
|
||
{
|
||
return usTick;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* @brief Настройка тактирования АЦП.
|
||
* @param ClkSrc Источник тактирования (RCU_PeriphClk_TypeDef)
|
||
* @param ClkMHz Желаемая частота АЦП в МГц
|
||
* @param state Состояние (ENABLE/DISABLE)
|
||
* @retval OperationStatus OK при успехе, ERROR при ошибке
|
||
* @note Временное отключает тактирование АЦП во время настройки
|
||
*/
|
||
OperationStatus rcu_set_clock_adc(RCU_PeriphClk_TypeDef ClkSrc, float ClkMHz, FunctionalState state)
|
||
{
|
||
uint32_t adc_raw_clock = 0;
|
||
float adc_clock_div = 0;
|
||
|
||
/* Отключение тактирования АЦП для настройки */
|
||
RCU_ADCClkCmd(DISABLE);
|
||
|
||
/* Определение частоты источника тактирования */
|
||
switch(ClkSrc)
|
||
{
|
||
case RCU_PeriphClk_OSEClk:
|
||
adc_raw_clock = RCU_GetOSEClkFreq();
|
||
break;
|
||
case RCU_PeriphClk_OSIClk:
|
||
adc_raw_clock = RCU_GetOSIClkFreq();
|
||
break;
|
||
case RCU_PeriphClk_PLLClk:
|
||
adc_raw_clock = RCU_GetPLLClkFreq();
|
||
break;
|
||
case RCU_PeriphClk_PLLDivClk:
|
||
adc_raw_clock = RCU_GetPLLDivClkFreq();
|
||
break;
|
||
default:
|
||
return ERROR;
|
||
}
|
||
|
||
/* Расчет делителя частоты */
|
||
adc_clock_div = adc_raw_clock / (ClkMHz * __MHZ);
|
||
if(adc_clock_div < 1)
|
||
return ERROR;
|
||
|
||
/* Настройка источника тактирования и делителя */
|
||
RCU_ADCClkConfig(ClkSrc, (uint32_t)(adc_clock_div - 1), ENABLE);
|
||
|
||
/* Включение тактирования, если запрошено */
|
||
if(state == ENABLE)
|
||
{
|
||
RCU_ADCClkCmd(ENABLE);
|
||
}
|
||
|
||
return OK;
|
||
}
|
||
|
||
|
||
|
||
|
||
/**
|
||
* @brief Инкремент счетчика миллисекунд.
|
||
*/
|
||
static inline void millis_inc(void)
|
||
{
|
||
msTick+=msTickInc;
|
||
}
|
||
/**
|
||
* @brief Инкремент счетчика микросекунд.
|
||
*/
|
||
static inline void micros_inc(void)
|
||
{
|
||
usTick+=usTickInc;
|
||
}
|