Содержание

Драйвер тактирования, RST_Clock

При подключении драйвера 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 и Электросилы подход такой-же.

Статус MDR_CPU_SetClockResult

Возникновение проблем с настройкой тактирования отслеживаются по статусу который возвращают функции:

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().

Структура MDR_CLK_FreqSupport и fromLowerFreq

Основное потребление микросхемы определяется динамическими токами, которые протекают при переключении КМОП пар транзисторов. Чем выше рабочая частота, тем больше потребляет микросхема. Поэтому при смене частоты требуется подстройка блоков 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 для большей частоты между старой и новой.

Структура MDR_CPU_CfgLSI

// ==== 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().

Настройки тактирования сведены в структуры, рассмотрим их типы:

Структура MDR_CPU_CfgLSE

  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().

Структура MDR_CPU_CfgHSI

// ==== 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 в железо.

Структура MDR_CPU_CfgHSE

// ==== 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 тип внешнего источника сигнала - резонатор или генератор.

Структура MDR_CPU_PLL_CfgHSI

// ==== 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)

Структура MDR_CPU_PLL_CfgHSE

// ==== 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

В качестве примеров можно использовать наборы настроек собранные в файле MDR_CPU_ClockSelect.h Здесь уже объявлены наборы параметров для получения некоторых частот тактирования. Параметры для 1986ВЕ8Т и подобных МК уже согласованы с требованием по выходной частоте PLL и errate по джиттеру PLL.

В качестве примера настройки различных частот тактирования представлен проект Проект для перебора вариантов тактирования ядра в 1986ВЕ1, 1986ВЕ91Т, 1986ВЕ8Т, 1923ВК014, "Электросила". В этом проекте заведено несколько профилей тактирования, можно дописать свои профили или внести изменения в текущие. Проект позволяет переключаться между вариантами тактирования и смотреть как отрабатывают функции выставления частоты.

Отдельные функции управления узлами тактирования

Функции настройки тактирования "под ключ" работают на основе элементарных функций по управлению узлами блока тактирования. Т.е. можно как в привычном SPL реализовать свою настройку частоты. Эти функции в большинстве инлайновые чтобы не терять производительность при их вызове. Поэтому функции "под ключ" не должны сильно проигрывать привычной реализации настройки тактирования. Кратко перечислим эти функции (Функции делают ровно то, что означает перевод):

Настройка LSE

__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) {...}

Настройка LSE

__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 можно задать явно.

Настройка HSI

__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) {...}

Настройка HSE, HSE0, HSE1

// ==== 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) {...}

Настройка PLL

// ==== 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) {...}

FreqSupport - параметры поддержки частоты

Параметров, которые необходимо выставлять в зависимости от частоты тактирования, всего два. Это:

  1. подстройка LDO, формирующего цифровое питание,
  2. и задержка доступа к памяти ОТР или флеш, поскольку память не может работать на такой высокой частоте как ядро.

Есть функции которые возвращают необходимые параметры поддержки, в зависимости от частоты которая будет выставляться:

//  Рассчет задержки доступа к 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, или задержки доступа к памяти, или переключают компоненты в неправильной последовательности. Готовая функция исключает подобные ошибки и запрашивает все необходимые параметры, которые необходимо выставить для той или иной частоты тактирования. Это гарантирует что частота и сопутствующие параметры буду выставлены правильно!