/** ****************************************************************************** * @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; }