При подключении драйвера RST_Clock через "Manage Run-Time Environment" к проекту Keil, в зависимости выбранного микроконтроллера, автоматически подключаются следующие файлы :
МК | Файлы драйвера |
---|---|
1986ВЕ8Т | MDR_RST_Clock_VE8x.c / MDR_RST_Clock_VE8x.h |
1923ВК014 | |
ESila | MDR_RST_Clock_ESila.c / MDR_RST_Clock_ESila.h |
остальные | MDR_RST_Clock.c / MDR_RST_Clock_VEx.h |
Для вызова функций из этих драйверов в коде необходимо дописать #include <MDR_RST_Clock.h>, что подключит необходимые заголовочники:
#if defined (USE_MDR1986VE8) || defined (USE_MDR1923VK014) #include <MDR_RST_Clock_VE8x.h> #elif defined (USE_ESila) #include <MDR_RST_Clock_ESila.h> #else #include <MDR_RST_Clock_VEx.h> #endif
Для задания частоты требуется чтобы были затактированы периферийные блоки RST_Clock и BKP. Кроме этого в микроконтроллерах типа 1986ВЕ8 требуется ввести ключ в регистры KEY для разблокировки доступа к регистрам блоков. Все эти действия выполняет функция MDR_CLK_Enable_RST_BPK(). После настройки тактирования программист может оставить тактирование блоков или отключить. Для большей универсальности это реализовано двумя различными функциями - MDR_CLK_Disable_RST и MDR_CLK_Disable_BKP. Регистр KEY в этих функциях тоже сбрасывается.
void MDR_CLK_Enable_RST_BPK(void); - "Включение" блоков RST_CLock и BKP void MDR_CLK_Disable_RST(void); - "Выключение" блока RST_CLock void MDR_CLK_Disable_BKP(void); - "Выключение" блока BKP
Таким образом, чтобы задать тактирование, например с умножением от HSE, необходимо заполнить структуру cfgPLL_HSE и вызвать две функции (предполагается, что текущее тактирование от HSI):
MDR_CLK_Enable_RST_BPK(); MDR_CPU_SetClock_PLL_HSE(&cfgPLL_HSE, true);
Для упрощения настройки тактовой частоты все манипуляции с периферией спрятаны в функциях, каждая из которых реализует готовую настройку на свой источник тактирования. Вызовы этих функций выглядят так:
Функция |
---|
Тактирование напрямую от генераторов |
result = MDR_CPU_SetClock_LSI(&cfgLSI, LSI_TIMEOUT) |
result = MDR_CPU_SetClock_LSE(&cfgLSE, LSE_TIMEOUT) |
result = MDR_CPU_SetClock_HSI(&cfgHSI, HSI_TIMEOUT, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE(&cfgHSE, HSE_TIMEOUT, fromLowerFreq) |
Дополнительно для 1986ВЕ8, 1923ВК014, ESila |
result = MDR_CPU_SetClock_HSE0(&cfgHSE, HSE0_TIMEOUT, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE1(&cfgHSE, HSE1_TIMEOUT, fromLowerFreq) |
Тактирование через PLL |
result = MDR_CPU_SetClock_PLL_HSI(&cfgPLL_HSI, fromLowerFreq) |
result = MDR_CPU_SetClock_PLL_HSE(&cfgPLL_HSE, fromLowerFreq) |
Дополнительно для 1986ВЕ8, 1923ВК014, ESila |
result = MDR_CPU_SetClock_HSI_PLL0(&cfgPLL_HSI, fromLowerFreq) |
result = MDR_CPU_SetClock_HSI_PLL1(&cfgPLL_HSI, fromLowerFreq) |
result = MDR_CPU_SetClock_HSI_PLL2(&cfgPLL_HSI, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE0_PLL0(&cfgPLL_HSE, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE0_PLL1(&cfgPLL_HSE, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE0_PLL2(&cfgPLL_HSE, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE1_PLL0(&cfgPLL_HSE, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE1_PLL1(&cfgPLL_HSE, fromLowerFreq) |
result = MDR_CPU_SetClock_HSE1_PLL2(&cfgPLL_HSE, fromLowerFreq) |
Полагаю, что название этих функций говорит само за себя и интуитивно понятно от какого источника затактируется ядро. Все настройки необходимые для задания тактирования сведены в структуры, которые уходят первым параметром в функции. Опираясь на эти настройки, каждая функция:
1 Проверяет не является ли новая частота текущей, если является то выход с MDR_SET_CLOCK_ERR__SRC_USING. 2 Включает необходимый генератор. 3 Дожидается его готовности за количество циклов GEN_TIMEOUT. Если не дождалась, выход с MDR_SET_CLOCK_ERR__GEN_NotReady. 4 Включает PLL. 5 Дожидается готовности PLL за количество циклов PLL_TIMEOUT. Если не дождалась, выход с MDR_SET_CLOCK_ERR__PLL_NotReady. 6 Переход на новую частоту. 7 Выход с MDR_SET_CLOCK_OK.
В функциях, где PLL не используется, шагов 4 и 5 нет. По существу, это функции, которые сейчас каждый пишет сам, в каждом своем проекте на основе текущей версии SPL.
Для 1986ВЕ8Т в файле MDR_ConfigVE8.h задается какой из генераторов (HSE0/HSE1) и PLL (PLL0,PLL1,PLL2) использовать по умолчанию. Таким образом функция MDR_CPU_SetClock_PLL_HSE() на самом деле это вызов одной из MDR_CPU_SetClock_HSE0_PLL0 - MDR_CPU_SetClock_HSE1_PLL2. Это позволяет использовать одну функцию в коде для разных МК. Тоже самое относится к функции MDR_CPU_SetClock_HSE. Лучше избегать условной компиляции, пока есть возможность. Для 1923ВЕ014 и Электросилы подход такой-же.
Возникновение проблем с настройкой тактирования отслеживаются по статусу который возвращают функции:
typedef enum { MDR_SET_CLOCK_OK, MDR_SET_CLOCK_ERR__GEN_NotReady, MDR_SET_CLOCK_ERR__SRC_USING, MDR_SET_CLOCK_ERR__PLL_NotReady, MDR_SET_CLOCK_ERR__Undef, } MDR_CPU_SetClockResult;
Последний статус MDR_SET_CLOCK_ERR_Undef зарезервирован на неизвестную ошибку.
Статус MDR_SET_CLOCK_ERR_SRC_USING связан с тем, что нельзя настраивать источник частоты, на котором сейчас работает CPU. Если частота исчезнет, то исполнение команд прервется. Нельзя так-же переключать сразу несколько мультиплексоров в тракте выбора частоты, из-за этого могут случиться гонки при переключении. Необходимо переходить на новую частоту переключением одного мультиплексора, при этом новая частота уже должна быть полностью сформирована. Поэтому если возникает необходимость перестроить текущий источник тактирования, то необходимо:
В 1986ВЕ8Т, 1923ВК014 и Электросила при проверке источника используется текущий вход мультиплексора MAX_CLK. Функции возвращают MDR_SET_CLOCK_ERR_SRC_USING если должен настраиваться тот-же вход MAX_CLK, который сейчас является активным.
В остальных микроконтроллерах проверяется вход на мультиплексоре HCLK_SEL. Функции возвращают MDR_SET_CLOCK_ERR_SRC_USING если текущая частота идет с CPU_C3 и новая частота должна быть оттуда же. В таком случае рекомендуется предварительно переключиться на HSI_dir через мультиплексор HCLK_SEL. Это реализует функция MDR_CPU_SetClock_HSI_Dir().
Основное потребление микросхемы определяется динамическими токами, которые протекают при переключении КМОП пар транзисторов. Чем выше рабочая частота, тем больше потребляет микросхема. Поэтому при смене частоты требуется подстройка блоков LDO, которые формируют цифровое питание ядра. В микроконтроллерах вроде 1986ВЕ9х таких параметров два - SelectRI и Low. Значения этих параметров должны выбираться исходя из тактовой частоты МК, при этом оба поля записываются одним и тем-же значением. Раздельное управление параметрами было реализовано для большей гибкости, но судя по всему ни разу не потребовалось. Поэтому в МК, вроде 1986ВЕ8Т, блоки LDO имеют только один объединенный параметр - LowSRI (в спецификации SRILOW). Эти параметры задаются в блоке BKP и не сбрасываются при Reset, как и все регистры данного блока.
Так-же, в зависимости от частоты ядра необходимо задавать задержку доступа к памяти EEPROM или OTP. Память не может работать с той частотой на которую способно ядро, поэтому при неправильно выбранной задержке чтение данных может происходить некорректно.
Эти параметры составляют структуру MDR_CLK_FreqSupport и должны выставляться согласно частоте. Поля в этой структуре различаются в зависимости от микроконтроллера
//1986ВЕ8Т typedef struct { MDR_CLK_Delay_OTP delayAccessOTP; // Задержка доступа к ОТР MDR_CLK_LDO_LowSRI lowSRI; // Подстройка LDO под частоту } MDR_CLK_FreqSupport; #define MDR_CPU_FREQ_SUPP(del, sri) {.delayAccessOTP = del, \ .lowSRI = sri} //1923ВК014 typedef struct { MDR_CLK_LDO_LowSRI lowSRI; // Подстройка LDO под частоту } MDR_CLK_FreqSupport; #define MDR_CPU_FREQ_SUPP(sri) {.lowSRI = sri} // Остальные typedef struct { MDR_CLK_Delay_EEPROM delayAccessEEPROM; // Задержка доступа к флеш памяти MDR_CLK_LDO_LowSRI lowSRI; // Подстройка LDO под частоту } MDR_CLK_FreqSupport; #define MDR_CPU_FREQ_SUPP(delEE, sri) {.delayAccessEEPROM = delEE, \ .lowSRI = sri}
Инициализировать структуру MDR_CLK_FreqSupport удобно макросом MDR_CPU_FREQ_SUPP.
В 1923ВК014 нет ни флеш-памяти, ни OTP, поэтому в его структуре MDR_CLK_FreqSupport всего один параметр для LDO. Типы параметров заданы через ENUM, это позволяет прыгнуть на определение типа и выбрать необходимые значение. Вот как заданы эти значения например для 1986ВЕ9х.
// Стабилизация потребления в зависимости от частоты #define MDR_CLK_LDO_LowSRI MDR_BKP_LOW_RI // Такты паузы ядра для доступа к данным EEPROM. // EEPROM не работает быстрее чем 25МГц (18МГц). Считывается за раз четыре 32-разрядных слова. #define MDR_CLK_Delay_EEPROM MDR_EEPROM_DELAY typedef enum { MDR_LOWRI_le10MHz = 0, /*!< 10MHz : CPU Clock less then 10MHz : +I ~ 300uA */ MDR_LOWRI_le200KHz = 1, /*!< 200KHz : CPU Clock less then 200KHz : +I ~ 6.6uA */ MDR_LOWRI_le500KHz = 2, /*!< 500KHz : CPU Clock less then 500KHz : +I ~ 20A */ MDR_LOWRI_le1MHz = 3, /*!< 1MHz : CPU Clock less then 1MHz : +I ~ 80uA */ MDR_LOWRI_GensOff = 4, /*!< GensOffz: Generators Off : +I ~ 2uA */ MDR_LOWRI_le40MHz = 5, /*!< 40MHz : CPU Clock less then 40MHz : +I ~ 900uA */ MDR_LOWRI_le80MHz = 6, /*!< 80MHz : CPU Clock less then 80MHz : +I ~ 4.4mA */ MDR_LOWRI_gt80MHz = 7, /*!< above_80MHz : CPU Clock above 10MHz : +I ~ 19mA */ } MDR_BKP_LOW_RI; typedef enum { EEPROM_Delay_le25MHz = 0, /*!< le25MHz : CPU freq is up to 25MHz */ EEPROM_Delay_le50MHz = 1, /*!< le50MHz : CPU freq is up to 50MHz */ EEPROM_Delay_le75MHz = 2, /*!< le75MHz : CPU freq is up to 75MHz */ EEPROM_Delay_le100MHz = 3, /*!< le100MHz : CPU freq is up to 100MHz */ EEPROM_Delay_le125MHz = 4, /*!< le125MHz : CPU freq is up to 125MHz */ EEPROM_Delay_le150MHz = 5, /*!< le150MHz : CPU freq is up to 150MHz */ EEPROM_Delay_le175MHz = 6, /*!< le175MHz : CPU freq is up to 175MHz */ EEPROM_Delay_le200MHz = 7, /*!< le200MHz : CPU freq is up to 200MHz */ } MDR_EEPROM_DELAY;
В именах используются следующие сокращения:
Данная структура MDR_CLK_FreqSupport используется во всех структурах настроек тактирования что уходят в функции. Причем значения MDR_CLK_FreqSupport в железе должны быть выставлены для наихудшего случая перед переходом на частоту. Для этого в функции передается параметр fromLowerFreq и в зависимости от него шаг "6 Переход на новую частоту" реализуется такими вариантами:
Т.е. в момент переключения частоты должны быть активны параметры MDR_CLK_FreqSupport для большей частоты между старой и новой.
// ==== 1986VE8x, 1923VK014, ESila ==== typedef struct { MDR_CLK_LSI_TRIM freqTrim; // Подстройка частоты генератора LSI uint16_t divMaxToCpu_0; // Делитель частоты Max_Clock в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к ОТР и настройка LDO под частоту } MDR_CPU_CfgLSI; // Макрос с параметрами по умолчанию из MDR_ConfigXX.h #define MDR_CPU_CFG_LSI_DEF {.freqTrim = LSI_FREQ_TRIM, \ .divMaxToCpu_0 = 0, \ .freqSupp.delayAccessOTP = LSI_OTP_DELAY, \ .freqSupp.lowSRI = LSI_LOW_SRI} // ==== Остальные микроконтроллеры ==== typedef struct { MDR_CLK_LSI_TRIM freqTrim; // Подстройка частоты генератора LSI MDR_CLK_FreqSupport freqSupp; // Задержка доступа к флеш памяти и настройка LDO под частоту } MDR_CPU_CfgLSI; // Макрос с параметрами по умолчанию из MDR_ConfigXX.h #define MDR_CPU_CFG_LSI_DEF { .freqTrim = LSI_FREQ_TRIM, \ .freqSupp.delayAccessEEPROM = LSI_DELAY_EEPROM, \ .freqSupp.lowSRI = LSI_LOW_SRI}
// Тактирование ядра от внутреннего генератора LSI, ~40КГц MDR_CPU_SetClockResult MDR_CPU_SetClock_LSI(const MDR_CPU_CfgLSI *cfgLSI, uint32_t timeoutCycles); // Вариант с параметрами по умолчанию из MDR_ConfigXX.h __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_LSI_def(void) { MDR_CPU_CfgLSI cfgLSI = MDR_CPU_CFG_LSI_DEF; return MDR_CPU_SetClock_LSI(&cfgLSI, LSI_TIMEOUT); }
Структуры различаются лишь параметром divMaxToCpu_0, который есть для 1986ВЕ8Т-подобных МК. Этот параметр задает делитель MAX_CLK в CPU_CLK.
Как видно из структуры, для переключения тактирования на LSI необходимо указать параметры MDR_CLK_FreqSupport и подстройку частоты внутреннего RC генератора. Генератор имеет большой разброс с типовым значением в 40КГц, но поскольку отклонение в каждом образце непредсказуемо, то подстройку частоты можно выставить вручную. Все параметры данной функции определены по умолчанию в MDR_CongifVExx.h, что позволяет использовать функцию по умолчанию - MDR_CPU_SetClock_LSI_def().
Настройки тактирования сведены в структуры, рассмотрим их типы:
typedef enum { MDR_CLK_Resonator, // ByPass Off MDR_CLK_Generator, // ByPass On } MDR_CLK_Source; // ==== 1986VE8x, 1923VK014, ESila ==== typedef struct { MDR_CLK_Source freqSource; // Выбор что снаружи - генератор или резонатор uint16_t divMaxToCpu_0; // Делитель частоты Max_Clock в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к ОТР и настройка LDO под частоту } MDR_CPU_CfgLSE; // ==== Остальные микроконтроллеры ==== typedef struct { MDR_CLK_Source freqSource; // Выбор что снаружи - генератор или резонатор MDR_CLK_FreqSupport freqSupp; // Задержка доступа к флеш памяти и настройка LDO под частоту } MDR_CPU_CfgLSE;
// Тактирование ядра от внешнего генератора LSE, частоту в Гц необходимо указать в MDR_ConfigXX.h MDR_CPU_SetClockResult MDR_CPU_SetClock_LSE(const MDR_CPU_CfgLSE *cfgLSE, uint32_t timeoutCycles); // Варианты с параметрами по умолчанию из MDR_ConfigXX.h __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_LSE_Res_def(void) { MDR_CPU_CfgLSE cfgLSE = MDR_CPU_CFG_LSE_RES_DEF; return MDR_CPU_SetClock_LSE(&cfgLSE, LSE_TIMEOUT); } __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_LSE_Gen_def(void) { MDR_CPU_CfgLSE cfgLSE = MDR_CPU_CFG_LSE_GEN_DEF; return MDR_CPU_SetClock_LSE(&cfgLSE, LSE_TIMEOUT); }
Для настройки LSE необходимо указать тип внешнего источника сигнала, это либо генератор, либо резонатор. Частоту подключенного источника в герцах необходимо записать в LSE_FREQ_HZ файл MDR_CongifVExx.h чтобы библиотека правильно рассчитывала частоты от этого источника. Для настройки тактирования по умолчанию можно использовать функции MDR_CPU_SetClock_LSE_Gen_def() и MDR_CPU_SetClock_LSE_Res_def().
// ==== 1986VE8x, 1923VK014, ESila ==== typedef struct { MDR_CLK_HSI_TRIM freqTrim; // Подстройка частоты генератора HSI bool selDiv2; // Делитель на 2 выхода с генератора uint16_t divMaxToCpu_0; // Делитель частоты Max_Clock в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к ОТР и настройка LDO под частоту } MDR_CPU_CfgHSI; // ==== остальные МК ==== typedef struct { MDR_CLK_HSI_TRIM freqTrim; // Подстройка частоты генератора HSI bool selDiv2; // Делитель на 2 выхода с генератора MDR_Div256P divC3; // Делитель частоты от делителя C3 в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к флеш памяти и настройка LDO под частоту } MDR_CPU_CfgHSI;
// Тактирование ядра от внутреннего генератора HSI, ~8МГц (6МГц .. 10МГц) MDR_CPU_SetClockResult MDR_CPU_SetClock_HSI(const MDR_CPU_CfgHSI *cfgHSI, uint32_t timeoutCycles, bool fromLowerFreq); // Варианты с параметрами по умолчанию из MDR_ConfigXX.h __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_HSI_def(bool fromLowerFreq) { MDR_CPU_CfgHSI cfgHSI = MDR_CPU_CFG_HSI_DEF; return MDR_CPU_SetClock_HSI(&cfgHSI, HSI_TIMEOUT, fromLowerFreq); } __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_HSIdiv2_def(bool fromLowerFreq) { MDR_CPU_CfgHSI cfgHSI = MDR_CPU_CFG_HSI_DIV2_DEF; return MDR_CPU_SetClock_HSI(&cfgHSI, HSI_TIMEOUT, fromLowerFreq); }
Здесь добавился делитель с CPU_C3 для обычных микроконтроллеров, тип MDR_Div256P задает делитель от 1 до 256, значения являются степенями двойки - 1, 2, 4, 8 .. 256. Значения заданы ENUM-ами, ошибиться сложно.
Кроме этого для задания тактирования от HSI необходимо задать подстройку частоты, выбрать частоту непосредственно с HSI или HSI/2 и параметры MDR_CLK_FreqSupport. С параметрами по умолчанию из MDR_CongifVExx.h можно использовать функции MDR_CPU_SetClock_HSI_def(fromLowerFreq) и MDR_CPU_SetClock_HSIdiv2_def(fromLowerFreq). Здесь в fromLowerFreq необходимо указать с большей частоты происходит переключение на HSI или с меньшей. От этого зависит очередность применения параметров MDR_CLK_FreqSupport в железо.
// ==== 1986VE8x, 1923VK014, ESila ==== typedef struct { MDR_CLK_Source freqSource; // Выбор что снаружи - генератор или резонатор bool selDiv2; // Делитель на 2 выхода с генератора uint16_t divMaxToCpu_0; // Делитель частоты Max_Clock в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к ОТР и настройка LDO под частоту } MDR_CPU_CfgHSE; // ==== Остальные МК ==== typedef struct { MDR_CLK_Source freqSource; // Выбор что снаружи - генератор или резонатор bool selDiv2; // Делитель на 2 выхода с генератора MDR_Div256P divC3; // Делитель частоты от делителя C3 в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к флеш памяти и настройка LDO под частоту } MDR_CPU_CfgHSE;
// ==== 1986VE8x, 1923VK014, ESila ==== MDR_CPU_SetClockResult MDR_CPU_SetClock_HSE0(const MDR_CPU_CfgHSE *cfgHSE, uint32_t timeoutCycles, bool fromLowerFreq) MDR_CPU_SetClockResult MDR_CPU_SetClock_HSE1(const MDR_CPU_CfgHSE *cfgHSE, uint32_t timeoutCycles, bool fromLowerFreq) // ==== остальные ==== // Тактирование ядра от внешнего резонатора или генератора HSE или HSE/2 от мультиплексора CPU_C1 MDR_CPU_SetClockResult MDR_CPU_SetClock_HSE(const MDR_CPU_CfgHSE *cfgHSE, uint32_t timeoutCycles, bool fromLowerFreq); // Варианты с параметрами по умолчанию из MDR_ConfigXX.h __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_HSE_def(MDR_CLK_Source freqSource, bool fromLowerFreq) { MDR_CPU_CfgHSE cfgHSE = MDR_CPU_CFG_HSE_SRC_DEF(freqSource); return MDR_CPU_SetClock_HSE(&cfgHSE, HSE_TIMEOUT, fromLowerFreq); } __STATIC_INLINE MDR_CPU_SetClockResult MDR_CPU_SetClock_HSEdiv2_def(MDR_CLK_Source freqSource, bool fromLowerFreq) { MDR_CPU_CfgHSE cfgHSE = MDR_CPU_CFG_HSE_SRC_DIV_DEF(freqSource, true); return MDR_CPU_SetClock_HSE(&cfgHSE, HSE_TIMEOUT, fromLowerFreq); }
Здесь все аналогично уже описанным структурам. Для задания тактирования по умолчанию с параметрами из MDR_CongifVExx.h можно использовать функции MDR_CPU_SetClock_HSE_def(freqSource, fromLowerFreq) и MDR_CPU_SetClock_HSEdiv2_def(freqSource, fromLowerFreq). Дополнительно необходимо указать в freqSource тип внешнего источника сигнала - резонатор или генератор.
// ==== 1986VE8x, 1923VK014, ESila ==== typedef struct { uint8_t mulN_3_75; // Множитель PLL от 3 до 75 uint8_t divQ_0_15; // Делитель PLL от 0 до 15, 0 соответствует делению на 1 MDR_PLL_DV_Div divOut; // Делитель выходной частоты PLL } MDR_CLK_CfgPLL; typedef struct { MDR_CLK_HSI_TRIM freqTrim; // Подстройка частоты генератора HSI bool selDiv2; // Делитель на 2 выхода с генератора uint32_t timeoutCycles_HSI; // Таймаут ожидания готовности генератора, бит HSI_READY MDR_CLK_CfgPLL cfgPLL; // Настройки PLL uint32_t timeoutCycles_PLL; // Таймаут ожидания готовности PLL, бит PLL_READY uint16_t divMaxToCpu_0; // Делитель частоты Max_Clock в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к ОТР и настройка LDO под частоту } MDR_CPU_PLL_CfgHSI; // ==== Остальные МК ==== typedef struct { MDR_CLK_HSI_TRIM freqTrim; // Подстройка частоты генератора HSI bool selDiv2; // Делитель на 2 выхода с генератора uint32_t timeoutCycles_HSI; // Таймаут ожидания готовности генератора, бит HSI_READY MDR_MUL_x16 pllMul; // Множитель PLL uint32_t timeoutCycles_PLL; // Таймаут ожидания готовности PLL, бит PLL_READY MDR_Div256P divC3; // Делитель частоты от делителя C3 в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к флеш и настройка LDO под частоту } MDR_CPU_PLL_CfgHSI;
Разница в структурах для микроконтроллеров не значительна:
// Тактирование ядра от внутреннего генератора HSI через PLL, ~8МГц (6МГц .. 10МГц) // ==== 1986VE8x, 1923VK014, ESila - варианты с PLL0, PLL1, PLL2==== MDR_CPU_SetClockResult MDR_CPU_SetClock_HSI_PLL0(const MDR_CPU_PLL_CfgHSI *cfgPLL_HSI, bool fromLowerFreq) // ==== Остальные МК ==== MDR_CPU_SetClockResult MDR_CPU_SetClock_PLL_HSI(const MDR_CPU_PLL_CfgHSI *cfgPLL_HSI, bool fromLowerFreq); // Варианты с параметрами по умолчанию из MDR_ConfigXX.h // ==== 1986VE8x, 1923VK014, ESila - варианты с PLL0, PLL1, PLL2 ==== MDR_CPU_SetClockResult MDR_CPU_SetClock_HSI_PLL0_def(MDR_CLK_CfgPLL *cfgPLL, MDR_CLK_FreqSupport *freqSupp, bool fromLowerFreq) // ==== Остальные МК ==== MDR_CPU_SetClockResult MDR_CPU_SetClock_PLL_HSI_def(MDR_MUL_x16 pll_Mul, MDR_CLK_Delay_EEPROM delayAccessEEPROM, MDR_CLK_LDO_LowSRI lowSRI, bool fromLowerFreq)
// ==== 1986VE8x, 1923VK014, ESila ==== typedef struct { MDR_CLK_Source freqSource; // Выбор что снаружи - генератор или резонатор bool selDiv2; // Делитель на 2 выхода с генератора uint32_t timeoutCycles_HSE; // Таймаут ожидания готовности генератора, бит HSE_READY MDR_CLK_CfgPLL cfgPLL; // Настройки PLL uint32_t timeoutCycles_PLL; // Таймаут ожидания готовности PLL, бит PLL_READY uint16_t divMaxToCpu_0; // Делитель частоты Max_Clock в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к ОТР и настройка LDO под частоту } MDR_CPU_PLL_CfgHSE; // ==== Остальные МК ==== typedef struct { MDR_CLK_Source freqSource; // Выбор что снаружи - генератор или резонатор bool selDiv2; // Делитель на 2 выхода с генератора uint32_t timeoutCycles_HSE; // Таймаут ожидания готовности генератора, бит HSE_READY MDR_MUL_x16 pllMul; // Множитель PLL uint32_t timeoutCycles_PLL; // Таймаут ожидания готовности PLL, бит PLL_READY MDR_Div256P divC3; // Делитель частоты от делителя C3 в CPU_CLock MDR_CLK_FreqSupport freqSupp; // Задержка доступа к флеш памяти и настройка LDO под частоту } MDR_CPU_PLL_CfgHSE;
// Тактирование ядра от внешнего генератора HSE через PLL // ==== 1986VE8x, 1923VK014, ESila - варианты с PLL0, PLL1, PLL2 ==== MDR_CPU_SetClockResult MDR_CPU_SetClock_HSE0_PLL0(const MDR_CPU_PLL_CfgHSE *cfgPLL_HSE, bool fromLowerFreq) MDR_CPU_SetClockResult MDR_CPU_SetClock_HSE1_PLL0(const MDR_CPU_PLL_CfgHSE *cfgPLL_HSE, bool fromLowerFreq) // ==== Остальные МК ==== MDR_CPU_SetClockResult MDR_CPU_SetClock_PLL_HSE(const MDR_CPU_PLL_CfgHSE *cfgPLL_HSE, bool fromLowerFreq);
Данные структуры содержат все необходимое для запуска тактирования от сигнала с внешнего генератора HSE пропущенного через PLL. Все эти поля уже содержались в ранее описанных структурах.
В качестве примеров можно использовать наборы настроек собранные в файле MDR_CPU_ClockSelect.h Здесь уже объявлены наборы параметров для получения некоторых частот тактирования. Параметры для 1986ВЕ8Т и подобных МК уже согласованы с требованием по выходной частоте PLL и errate по джиттеру PLL.
В качестве примера настройки различных частот тактирования представлен проект Проект для перебора вариантов тактирования ядра в 1986ВЕ1, 1986ВЕ91Т, 1986ВЕ8Т, 1923ВК014, "Электросила". В этом проекте заведено несколько профилей тактирования, можно дописать свои профили или внести изменения в текущие. Проект позволяет переключаться между вариантами тактирования и смотреть как отрабатывают функции выставления частоты.
Функции настройки тактирования "под ключ" работают на основе элементарных функций по управлению узлами блока тактирования. Т.е. можно как в привычном SPL реализовать свою настройку частоты. Эти функции в большинстве инлайновые чтобы не терять производительность при их вызове. Поэтому функции "под ключ" не должны сильно проигрывать привычной реализации настройки тактирования. Кратко перечислим эти функции (Функции делают ровно то, что означает перевод):
__STATIC_INLINE void MDR_LSE_Enable(MDR_CLK_Source freqSource) {...} __STATIC_INLINE void MDR_LSE_Disable (void) {...} bool MDR_LSE_GetReady(void); __STATIC_INLINE bool MDR_LSE_EnableAndWaitReady(MDR_CLK_Source freqSource, uint32_t timeoutCycles) {...}
__STATIC_INLINE void MDR_LSI_EnableAndTrim(MDR_CLK_LSI_TRIM freqTrim) {...} __STATIC_INLINE void MDR_LSI_Enable (void) {...} __STATIC_INLINE void MDR_LSI_Disable (void) {...} bool MDR_LSI_GetReady(void); __STATIC_INLINE bool MDR_LSI_EnableAndWaitReady(MDR_CLK_LSI_TRIM freqTrim, uint32_t timeoutCycles) {...}
Для включения внутренних генераторов есть две функции, одна включает с подстройкой Trim по умолчанию из файла MDR_ConfigXX.h, во втором варианте Trim можно задать явно.
__STATIC_INLINE void MDR_HSI_EnableAndTrim(MDR_CLK_HSI_TRIM freqTrim) {...} __STATIC_INLINE void MDR_HSI_Enable (void) {...} __STATIC_INLINE void MDR_HSI_Disable (void) {...} bool MDR_HSI_GetReady(void); __STATIC_INLINE bool MDR_HSI_EnableAndWaitReady(MDR_CLK_HSI_TRIM freqTrim, uint32_t timeoutCycles) {...}
// ==== 1986VE8x, 1923VK014, ESila ==== __STATIC_INLINE void MDR_HSE0_Enable(MDR_CLK_Source freqSource) {...} __STATIC_INLINE bool MDR_HSE0_GetReady(void) {...} __STATIC_INLINE void MDR_HSE0_Disable(void) {...} bool MDR_HSE0_WaitReady(uint32_t timeoutCycles); __STATIC_INLINE bool MDR_HSE0_EnableAndWaitReady(MDR_CLK_Source freqSource, uint32_t timeoutCycles) {...} __STATIC_INLINE void MDR_HSE1_Enable(MDR_CLK_Source freqSource) {...} __STATIC_INLINE bool MDR_HSE1_GetReady(void) {...} __STATIC_INLINE void MDR_HSE1_Disable(void) {...} bool MDR_HSE1_WaitReady(uint32_t timeoutCycles); __STATIC_INLINE bool MDR_HSE1_EnableAndWaitReady(MDR_CLK_Source freqSource, uint32_t timeoutCycles) {...} // ==== Остальные МК ==== __STATIC_INLINE void MDR_HSE_Enable(MDR_CLK_Source freqSource) {...} __STATIC_INLINE void MDR_HSE_Disable(void) {...} bool MDR_HSE_GetReady(void); __STATIC_INLINE bool MDR_HSE_EnableAndWaitReady(MDR_CLK_Source freqSource, uint32_t timeoutCycles) {...}
// ==== 1986VE8x, 1923VK014, ESila ==== void MDR_PLLx_Enable(MDR_RST_PLL_Type *PLLx, MDR_PLL_IN_SEL inpSrc, const MDR_CLK_CfgPLL *cfgPLL); __STATIC_INLINE bool MDR_PLLx_GetReady(MDR_RST_PLL_Type *PLLx) {...} __STATIC_INLINE void MDR_PLLx_Disable (MDR_RST_PLL_Type *PLLx) {...} bool MDR_PLLx_WaitReady(MDR_RST_PLL_Type *PLLx, uint32_t timeoutCycles); __STATIC_INLINE bool MDR_PLLx_EnableAndWaitReady(MDR_RST_PLL_Type *PLLx, MDR_PLL_IN_SEL inpSrc, const MDR_CLK_CfgPLL *cfgPLL, uint32_t timeoutCycles) где, PLLx - это структура с регистрами блоков PLL0, PLL1, PLL2. // ==== Остальные МК ==== void MDR_PLL_Enable(MDR_MUL_x16 pllMul); bool MDR_PLL_GetReady(void); __STATIC_INLINE void MDR_PLL_Disable(void) {...} __STATIC_INLINE bool MDR_PLL_EnableAndWaitReady(MDR_MUL_x16 pllMul, uint32_t timeoutCycles) {...}
Параметров, которые необходимо выставлять в зависимости от частоты тактирования, всего два. Это:
Есть функции которые возвращают необходимые параметры поддержки, в зависимости от частоты которая будет выставляться:
// Рассчет задержки доступа к EEPROM от частоты CPU MDR_CLK_Delay_EEPROM MDR_FreqCPU_ToDelayEEPROM(uint32_t CPU_FregHz); // 1986ВЕ8, 1986ВЕ81 - Рассчет задержки доступа к EEPROM от частоты CPU MDR_CLK_Delay_OTP MDR_FreqCPU_ToDelayOTP(uint32_t CPU_FregHz) // Рассчет подстройка LDO от частоты MDR_CLK_LDO_LowSRI MDR_FreqCPU_ToLowSRI(uint32_t CPU_FregHz);
На вход этих функций подается будущая частота в герцах, на выходе получаем значение из перечислений MDR_CLK_Delay_EEPROM и MDR_CLK_LDO_LowSRI, которые необходимо установить. Значения перечислений MDR_CLK_Delay_EEPROM и MDR_CLK_LDO_LowSRI для микроконтроллеров отличаются. Поэтому необходимо либо пользоваться приведенными функциями, либо осознанно выбирать значения для данных параметров из списка допустимых вариантов.
В 1923ВК014 нет ни flash памяти, ни ОТР памяти, поэтому для данного МК нет параметра задержки доступа. Код загружается в память SRAM, которой задержка не нужна.
Функцию MDR_FreqCPU_ToDelayOTP() необходимо вызывать только из ОЗУ! Другими словами, нельзя перестраивать тайминги доступа к ОТР исполняя код из самой ОТР.
Чтобы применить эти параметры реализованы следующие функции. Для удобства есть возможность отдельного выставления каждого параметра, либо обоих сразу:
// Применение параметров в микроконтроллер void MDR_CLK_ApplyFreqSupport_LDO(MDR_CLK_LDO_LowSRI lowSRI); void MDR_CLK_ApplyFreqSupport_EEPROM(MDR_CLK_Delay_EEPROM delayEEPROM); __STATIC_INLINE void MDR_CPU_ApplyFreqSupportF(MDR_CLK_LDO_LowSRI lowRI, MDR_CLK_Delay_EEPROM delayEEPROM) { MDR_CLK_ApplyFreqSupport_LDO(lowRI); MDR_CLK_ApplyFreqSupport_EEPROM(delayEEPROM); } __STATIC_INLINE void MDR_CPU_ApplyFreqSupport(const MDR_CLK_FreqSupport *freqSupp) { MDR_CLK_ApplyFreqSupport_LDO(freqSupp->lowSRI); MDR_CLK_ApplyFreqSupport_EEPROM(freqSupp->delayAccessEEPROM); }
Пользуясь функциями драйвера RST_Clock можно настраивать частоту отдельными функциями, как это предлагается в текущей SPL, либо использовать готовые функции уже реализующие все необходимое. Как правило все настраивают частоту более-менее одинаково, поэтому есть смысл пользоваться готовыми функциями. Для понимания того, что делает функция "под ключ" приведу "внутренности" одной из таких функций для 1986ВЕ8Т:
// Тактирование ядра от внешнего генератора HSЕ (1МГц - 30МГц) через PLL MDR_CPU_SetClockResult MDR_CPU_SetClock_PLL_srcHSE(MDR_RST_PLL_Type *PLLx, MDR_RST_HSE_Type *HSEx, MDR_MAXCLK_SEL selMaxClk, const MDR_CPU_PLL_CfgHSE *cfgPLL_HSE, MDR_PLL_IN_SEL inpPll, bool fromLowerFreq) { // Нельзя перестраивать частоту на которой в текущий момент работаешь if (selMaxClk == MDR_CLK_GetMAXCLK()) return MDR_SET_CLOCK_ERR__SRC_USING; // Включение генератора if (!MDR_HSEx_EnableAndWaitReady(HSEx, cfgPLL_HSE->freqSource, cfgPLL_HSE->timeoutCycles_HSE)) return MDR_SET_CLOCK_ERR__GEN_NotReady; // Выбор частоты для PLL и настройка PLL if (cfgPLL_HSE->selDiv2) inpPll += 1; if (!MDR_PLLx_EnableAndWaitReady(PLLx, inpPll, &cfgPLL_HSE->cfgPLL, cfgPLL_HSE->timeoutCycles_PLL)) return MDR_SET_CLOCK_ERR__PLL_NotReady; // Переключение на новую частоту MDR_CPU_SetClock_srcMAXCLK(selMaxClk, cfgPLL_HSE->divMaxToCpu_0, &cfgPLL_HSE->freqSupp, fromLowerFreq); return MDR_SET_CLOCK_OK; } // Переключение тактирования ядра мультиплексором MAXCLK void MDR_CPU_SetClock_srcMAXCLK(MDR_MAXCLK_SEL selMaxClk, uint16_t divMaxToCpu_0, const MDR_CLK_FreqSupport *freqSupp, bool fromLowerFreq) { if (fromLowerFreq) MDR_CPU_ApplyFreqSupport(freqSupp); MDR_CLK_SetMAXCLK(selMaxClk); MDR_CLK_SetDiv_MaxToCPU(divMaxToCpu_0); if (!fromLowerFreq) MDR_CPU_ApplyFreqSupport(freqSupp); }
Как видно по листингу, это все то, что обычно самостоятельно реализуется пользователем в коде. Основная разница лишь в том, что многие забывают выставлять настройку LDO, или задержки доступа к памяти, или переключают компоненты в неправильной последовательности. Готовая функция исключает подобные ошибки и запрашивает все необходимые параметры, которые необходимо выставить для той или иной частоты тактирования. Это гарантирует что частота и сопутствующие параметры буду выставлены правильно!