Н.В. Клиначев

Си-код цифровой системы управления многофазного импульсного DC/DC преобразователя для 32-х разрядного микроконтроллера с целочисленным арифметико-логическим устройством

Рабочие файлы: [Модель DC/DC] [ЦСУ 4 DC/DC double] [ЦСУ 4 DC/DC int32] [ЦСУ 4 DC/DC Си-код]
[Terminal Modbus RS-485]

Описаны структура, алгоритм работы программы и порядок настройки Си-кода цифровой системы управления многофазного импульсного DC/DC преобразователя. Система управления реализует подчиненный принцип контроля движения координат. Дискретная реализация опирается на IQ-макросы 32-х разрядной целочисленной арифметики.

Ключевые слова: DC-DC, преобразователь импульсный, микроконтроллер, ШИМ, IQ-арифметика, Си-код, step-down, buck converter, моделирование в Jigrein4WEB.

Введение

Разработка программного кода цифровой управляющей системы (ЦСУ) для DC/DC-преобразователя с одной стороны – это тривиальная задача, с другой – достаточно объемная. Ошибиться, решая такую задачу, можно легко. Цена ошибки высока. Но еще раз повторим, программный код ЦСУ для DC/DC-преобразователя во многом шаблонный и с минимальными модификациями может быть адаптирован к преобразователю с любой топологией. В настоящей статье представлены и документированы листинги Си-кода ЦСУ многофазного DC/DC-преобразователя. Для настройки программы (т.е. для выбора значений параметров регуляторов) приведена численная интерактивная модель и даны методические указания, к её использованию.

Структура программного кода и замечания к построению проекта

Программный код цифровой системы управления для импульсного, многофазного источника вторичного электропитания представлен в листинге файла main.c. Стандартные (для цифровых систем управления) математические преобразования определены в форме макросов в библиотеке powerlib.h. (Макросы используются с целью достижения предельного быстродействия). Программа дополнена двумя базами данных. В первой определены параметры силовых модулей DC/DC-преобразователей с предрайверами и датчиками (файл powerkits.h). Во второй – специфичный для заданного микроконтроллера программный код ШИМ-драйверов фаз преобразователя (файлы mcu-s.h и mcu-s.c).

В каждой базе инженеру необходимо раскомментировать определение каталожного идентификатора силового модуля преобразователя и микроконтроллера соответственно. Или определить собственный идентификатор, дополнив базу по образцу. База данных ШИМ-драйверов содержит шаблон драйвера (программный интерфейс). Если не определить каталожный идентификатор микроконтроллера, то активируется код интерфейса и любой Си-компилятор должен без ошибок выполнить построение проекта, поскольку основная программа системы управления ни одной сторонней библиотеки не использует.

ШИМ-драйвер написанный для заданного микроконтроллера должен контролировать любой импульсный источник (количество фаз, тип выхода, активные уровни, и пр.). С этой целью, в базе данных силовых модулей (powerkits.h) определены параметры для настройки драйвера. Но код драйверов недоработан. И функционирует правильно лишь с теми преобразователями, которые имеются в базе.

Для некоторых микроконтроллеров справедливо утверждение о том, что код, размещенный в ОЗУ, исполняется в 8..10 раз быстрее. В таких случаях разработчику необходимо самостоятельно планировать перемещение кода отдельных функций программы из ПЗУ (из флэш-памяти) в ОЗУ после включения питания. Данные о времени исполнения главного блока программного кода приведены для микроконтроллеров в листинге файла main.c (5..10 мкс в зависимости от числа фаз преобразователя). Размер программы, в зависимости от микроконтроллера, составляет от 6 до 8 КБ.

Алгоритм работы программы

Программа системы управления состоит из двух линейных блоков кода. Первый начинается в функции main. Здесь настраивается периферия микроконтроллера, конфигурируется ШИМ-драйвер, определяется вектор прерывания, выполняется построение и настройка кода системы управления. Завешается блок линейного кода бесконечным циклом, в котором реализована низкоприоритетная машина состояний. Последняя переводит DC/DC-преобразователь от одного состояния к другому. Либо по нажатию кнопки, либо по истечению временного интервала.

Состояний – три. Первое – источник выключен. Силовые ключи не переключаются. Микроконтроллер измеряет смещение нуля датчиков тока. Второе состояние – рабочий режим – наступает при нажатии кнопки. Если нагрузка соответствует уставке по току, то источник стабилизирует напряжение на выходе. В противном случае – ограничивает потребляемый нагрузкой ток. Третье состояние предназначено для целей отладки системы управления. (Переход к этому состоянию заблокирован комментарием). В этом состоянии, через временной интервал соответствующий длительности переходного процесса, меняется уставка для выходного напряжения между двумя значениями. В результате емкость выходного фильтра циклически перезаряжается. Заряд и разряд конденсатора сопровождается реверсом и ограничением потребляемого тока. Из этого состояния (за номером три), при нажатии кнопки, программа переходит к состоянию за номером один.

Второй линейный блок кода программы – это функция обработки прерывания, реализующая управление DC/DC-преобразователем. В зависимости от микроконтроллера и его ШИМ-драйвера это может быть прерывание АЦП, таймера, или контроллера ПДП. Источник прерывания не столь важен. Важно чтобы при обработке результатов измерений сигналов обратных связей использовался правильно заданный шаг дискретизации цифровой системы управления – параметр TIMESTEP.

Библиотеки IQ-Math library и Power Control library

Цифровые системы управления могут быть реализованы либо с применением абсолютных, либо относительных единиц (когда все координаты принадлежат диапазону от 0 до 1). Оба подхода предполагают определение в программе переменных типа float или double и наличие в микроконтроллере математического сопроцессора с плавающей точкой (FPU). Но на самом деле, особой потребности в нем нет. Оцифрованные значения сигналов датчиков обратных связей описываются целым типом без знака (unsigned int). Такой же тип данных достаточен для контроля выходной периферии (регистров сравнения таймера). А системы управления, в большинстве случаев, линейные – это означает, что для их реализации необходимы лишь операции сложения, вычитания, и масштабирования сигналов (умножение на константу). Поэтому нет особого смысла в переходе от целого типа к типу с плавающей точкой и обратно. Более того операции с плавающей точкой всегда выполняются медленней, чем с целыми числами (даже при наличии математического сопроцессора). Производители микроконтроллеров / микропроцессоров предоставляют пользователям библиотеки для выполнения высокоскоростных операций цифровой обработки сигналов (DSP). Одна из лучших библиотек – IQ-Math library (эмулирующая выполнение операций с плавающей точкой на микроконтроллере с целочисленной арифметикой) была опубликована фирмой Texas Instruments. Она и быстрее любого сопроцессора и точнее (в пределах погрешности типа данных float).

Идея IQ-арифметики очень простая. Предположим нам надо умножить два числа с плавающей точкой, с какой-то погрешностью $a·b=c$. И, допустим, мы имеем дело с числами 2 и 3.141592653589793. Если мы непосредственно воспользуемся целым типом данных, то результат будет иметь большую погрешность $2·3=6$. Но каждое из чисел мы можем умножить на большое число $Q$. Очевидно, что и результат будет больше $(a·Q)·(b·Q)=c·Q·Q$. Воспользуемся целочисленным вычислительным устройством, посмотрим каким будет результат и насколько удобно нам будет его воспринимать: $(2·1000)·(3.141592653589793·1000)= 2000·3142=6284000$. Да, результирующе число большое. Разделим его на $Q$ и посмотрим на три числа, с которыми работали: 2000, 3142, 6284. Хм. Это же наши исходные числа: 2, 3.14 и 6.28, – закодированные в целом типе данных! И точность вычисления – 1/1000-ая – вполне достаточна для большинства приложений.

При написании кода цифрвой управляющей системы использовалась следующая стратегия в отношении математических преобразований. Для работы с периферией принято соглашение о том, что полным шкалам устройств ввода вывода (АЦП, регистры сравнения таймера, ЦАП) соответствует единица, закодированная в целом типе данных специальным макросом _IQ(1.0), см. листинг файла powerlib.h. Для выполнения операции умножения определен макрос _IQmpy(A, B). Отличие реальной IQ-арифметики от приведенного примера заключается в том, что в качестве большого числа $Q$ используется число кратное степени двойки. Человеку легко делить числа на 10, а компьютеру – на 2. Основной тип данных – _iq24. В котором цифры кодируются 32-мя битами. Старшие 8 бит – это знак и целая часть числа. Младшие 24 бита – это дробная часть числа. Подобным образом 32-мя битами могут быть представлены разные типы данных: _iq30, _iq29, .., _iq1. Используя операцию сдвига можно переходить от одного типа данных к другому. Для чего, например, определен макрос _IQ24toIQ15. Каждому из названных типов данных соответствуют: погрешность представления числа и предельные значения (не вызывающие перегрузку мантиссы).

Библиотека Power Control library (см. листинг файла powerlib.h), так же как и схожая библиотека Motor Control library [2] содержит определение типов структур (для объявления связанных по назначению групп переменных) и макросов (последовательностей математических вычислений, реализующих регуляторы системы управления). Никаких модификаций кода для этой библиотеки не предполагается. Поскольку последовательность математических вычислений (реализующая тот или иной регулятор) от масштаба обрабатываемых сигналов ни как не зависит. В связи с чем, регуляторы могут быть настроены для обработки сигналов, как в абсолютных, так и в относительных величинах. Но все же предпочтительна обработка сигналов приведенных к относительным единицам. В этом случае, во-первых, отпадает необходимость контроля выхода значений параметров и сигналов за диапазон свойственный типу данных (см. константу GL_Q). А во-вторых, легче не допустить ошибку при выборе параметров системы управления. Поскольку в относительных величинах, для контролируемых объектов с отличающимися на несколько порядков паспортными данными, параметры регуляторов будут принадлежать одной декаде.

Порядок настройки программного кода системы управления

Существуют лишь две схемы замещения для импульсных преобразователей, какую бы топологию они не имели. Первая подходит преобразователям, у которых дроссель подключается непосредственно и к первичному источнику и к нагрузке. Вторая – тем, у которых на одном из коммутационных интервалов дроссель включен последовательно с нагрузкой. К последним, например, относиться реверсивный, синхронный, полумостовой, понижающий преобразователь (Synchronous Buck DC/DC). На чертеже 1 представлена его линейная непрерывная динамическая модель с двухконтурной системой управления, позволяющая настроить регуляторы и определить запись в базе данных преобразователей (см. секцию #define для PRIVATE_KIT_1, файл powerkits.h).

Линейная непрерывная динамическая модель для настройки
двухконтурной системы управления импульсного, реверсивного,
полумостового, понижающего преобразователя (Buck DC/DC converter)

Сколь бы ни был правилен подход к проектированию систем управления с применением относительных единиц – инженеры пытаются его игнорировать. Составляют модели технических систем в абсолютных величинах. Впоследствии так и переносят решения в программный код. Поэтому модель, представленная на чертеже, представляет собой компромисс. Движение всех координат она описывает в абсолютных величинах. Но, благодаря одному единственному коэффициенту, параметры регулятора тока соответствуют относительному масштабу обработки координат. Другими словами, в одном из двух контуров, реализованных в виде программного кода, перегрузка мантиссы или недостаточное разрешение практически исключены. Ну а в случае, если выходное напряжение источника или коэффициент регулятора контура напряжения выйдут за пределы диапазона значений типа данных _iq24, то инженер должен будет самостоятельно, по образцу, добавить соответствующий коэффициент приведения.

Рассмотрим порядок настройки системы управления источника:

  1. Определение исходных данных к расчету параметров системы управления: BASE_VOLTAGE, BASE_CURRENT, LD, RD, C2, KPWM. Сравните цифровые значения параметров в секции #define для PRIVATE_KIT_1 и в модели. BASE_VOLTAGE – это коэффициент приведения, отсутствующий в модели в силу компромисса. Остальные значения параметров соответствуют друг другу.
  2. Выбор шага симуляции для модели через коэффициент усиления контура тока KK_C. См. так же комментарий к параметру TIMESTEP. Значения периода дискретизации ЦСУ в модели и в программе могут быть разные. В программе – период может быть меньше. Поскольку, как правило, частоту для ШИ-модулятора выбирают выше минимально возможной (параметр PWM_FREQUENCY). Повторим сказанное другими словами. Значение выражения (1/PWM_FREQUENCY) должно быть меньше или равно шагу симуляции.
  3. Расчет параметров регуляторов системы управления (см. комментарии и формулы в секции #define для PRIVATE_KIT_1): KK_C, KP_C, TI_C, TI_V, LEV. Перенос значений в модель (чертёж 1) и подбор единственно оставшегося коэффициента: KP_V. Тюнинг – TI_V.
  4. Компиляция проекта без определения идентификатора микроконтроллера (в базе mcu-s.h). Установка брейкпоинта в конце процедуры DCDC_CntrlUnit_SetBlkParam. Контроль отсутствия выхода параметров за диапазон значений типа данных _iq24. Компиляция проекта с параметрами для другого источника. Контроль отсутствия существенных отклонений параметров (для источников с любыми паспортными данными параметры системы управления обрабатывающей сигналы в относительных величинах должны принадлежать одной декаде).

Примечание 1. Параметр ISR_PRESCALER предназначен для прореживания вызовов прерывания, код которого реализует ЦСУ. Однако у импульсных источников питания, в типовом случае электромагнитная постоянная времени дросселя в несколько раз больше постоянной времени заряда конденсатора. Поэтому прореживание прерываний и усреднение выборок сигналов обратных связей обычно не требуется (ISR_PRESCALER=1). (Для электропривода типовая ситуация с постоянными времени обратная. А код шаблонизирован [2]).

Примечание 2. Настраивая систему управления импульсного источника, инженер должен помнить. За шесть лет, из-за окислительно-восстановительных реакций в феррите марки 2000НМ1-А (постоянна времени старения 1 .. 1.5 года) индукция насыщения падает с 0.56 до 0.26 Тл [#]. Емкость электролитических конденсаторов, при высыхании, так же падает.

Конфигурация аппаратных машин состояний

Для реализации цифровой системы управления импульсного преобразователя из модулей микроконтроллера необходимо собрать обслуживающие задачу аппаратные машины состояний. Семейство ARM-процессоров с набором периферии от фирмы STM плохо подходит для построения многофазных импульсных источников вторичного электропитания (мало подходящих таймеров [1]). Но для шестифазного источника можно и процессоры скаскадировать. В связи множеством возможных решений, листинг файла mcu-s.c приводиться исключительно для образца. Инженеру необходимо существенно переработать данный файл. В нем приведен код конфигурации машины состояний для обслуживания однофазного преобразователя.

Машину состояний ведет таймер TIM1, чей счетчик работает в реверсивном режиме, формируя сигнал треугольной формы для ШИ-модулятора. В моменты реверса счетчика таймер формирует синхросигнал (переворачивает аппаратный триггер), который запускает два аналогово-цифровых преобразователя в синхронном режиме (ADC3 – master и ADC4 – slave). Последние, выполняют по два преобразования, оцифровывая ток дросселя фазы преобразователя и выходное напряжение. По завершению, ведущий АЦП формирует запрос на обслуживание контроллеру ПДП (DMA2). Последний, активирует один из своих каналов (Channel5) и переносит результаты оцифровки в ОЗУ, формируя массив данных (выборок). По завершению цикла передачи контроллер инициирует прерывание (DMA2_Channel5_IRQn), исполняемое ядром процессора. В прерывании обрабатываются результаты измерений, обновляется значение для ШИ-модулятора таймера – формируется новое управляющее воздействие для фазы преобразователя.

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

Рассмотрим порядок настройки аппаратной части:

  1. Компиляция проекта с определением идентификатора микроконтроллера (в базе mcu-s.h). Подача питания на датчики тока. Измерение смещения нуля датчиков (см. макрос ADC_OFFSET_MACRO). Фиксация измеренных значений в секции #define для источника пользователя (PRIVATE_KIT_#): CS_10 = PwKit.I10 / (1 << GL_Q)
    CS_20 = PwKit.I20 / (1 << GL_Q)
    ...
  2. Экспериментальное уточнение базовых величин BASE_VOLTAGE и BASE_CURRENT. Запуск одной фазы источника.
  3. Компиляция проекта с функцией режима отладки Task_DCDC_debug. Юстировка коэффициентов усиления регуляторов контура тока (KP_C) и контура напряжения (KP_V).
  4. Монтаж и подключение конденсаторов в количестве соответствующем числу одновременно включаемых фаз. Запуск источника в многофазном режиме.
  5. Монтаж и подключение конденсаторов для удвоения общей емкости, в расчете на высыхание электролита за 15 лет эксплуатации источника. Наведение порядка в коде. Создание резервных копий.

Приложение. Листинги файлов исходного кода двухконтурной, цифровой системы управления многофазного, реверсивного, импульсного источника вторичного электропитания (Synchronous Buck DC/DC converter), реализованной на ARM-процессоре с ядром Cortex-M4

Листинг 1. Файл powerkits.h

/*************************************************************************
* File Name :   powerkits.h
* Author :      Клиначев Николай Васильевич
* Version :     1.0.1
* Date :        201512
* Description :
*      Ниже представлена база данных силовых модулей импульсных источников
*      вторичного электропитания (Power KIT), для которых конфигурировалась
*      цифровая система управления.
*      Для определения параметров DC/DC-преобразователя, необходимо
*      раскомментировать определение его каталожного идентификатора в списке.
*************************************************************************/
#define PRIVATE_KIT_1 // Самодельный Power KIT: 30 / 12 В, 10 А
//#define PRIVATE_KIT_2 // Самодельный Power KIT
//#define PRIVATE_KIT_2 // Самодельный Power KIT

#if defined PRIVATE_KIT_1       // Самодельный Power KIT: 30 / 12 В, 10 А

#define PWM_FREQUENCY   20000   // Частота переключения стоек силового моста, (Гц)
#define PWM_DEADBAND    20      // Величина бестоковой паузы в циклах CPU, (Q0)
#define SW_H_POLARITY   0       // Активный уровень предрайверов верхних ключей
#define SW_L_POLARITY   0       // Активный уровень предрайверов  нижних ключей
#define SW_H_IDLESTATE  1       // Уровень сигнала для верхних ключей в IDLE-режиме
#define SW_L_IDLESTATE  1       // Уровень сигнала для  нижних ключей в IDLE-режиме
#define MCU_OPEN_DRAIN  1       // 1, если питание у MCU и у Моста раздельное
#define CS_10           0.5000  // Смещение нуля датчика тока фазы 1  (доля шкалы АЦП)
#define CS_20           0.5000  // Смещение нуля датчика тока фазы 2  (доля шкалы АЦП)
#define CS_30           0.5000  // Смещение нуля датчика тока фазы 3  (доля шкалы АЦП)
#define CS_40           0.5000  // Смещение нуля датчика тока фазы 4  (доля шкалы АЦП)
#define CS_50           0.5000  // Смещение нуля датчика тока фазы 5  (доля шкалы АЦП)
#define CS_60           0.5000  // Смещение нуля датчика тока фазы 6  (доля шкалы АЦП)
#define VS_10           0.0000  // Смещение нуля датчика напряжения 1 (доля шкалы АЦП)
// ************************************************************************** //
// Исходные данные к расчету параметров системы управления преобразователя
// ************************************************************************** //
#define BASE_VOLTAGE    35.0    // Напряжение dc-шины, соответствующие шкале АЦП, (В)
#define BASE_CURRENT    15.0    // Ток дросселя,  соответствующий  1/2 шкалы АЦП, (А)
#define LD      0.0005          // Индуктивность дросселя, (Гн)
#define RD      0.05            // Сопротивление дросселя + ключей + шунта + ШИН, (Ом)
#define C2      0.004           // Емкость на выходе преобразователя, (Ф)
#define KK_C    50              // Коэффициент усиления контура тока:  1 .. L/R / (RC)
#define KPWM    (0.5 * 30)      // Kпн = KPWM = V1_MAX / 2 = Uдр_MAX
// ************************************************************************** //
// Параметры системы управления DC/DC-преобразователя в относительных величинах
// ************************************************************************** //
// Коэффициент усиления P-канала Регулятора Тока: Kрт = Kкт Rдр / Kдт / Kпн
#define KP_C    (KK_C * RD / (1.0 / BASE_CURRENT) / KPWM)       // 4.5
// Постоянная  времени  I-канала Регулятора Тока: Tдр = Lдр / (Rдр + ..), сек
#define TI_C    (LD / RD)                                       // 0.01
// ************************************************************************** //
// Параметры системы управления DC/DC-преобразователя в абсолютных величинах
// ************************************************************************** //
#define LEV     1.0     // Ограничение ошибки (по модулю) в И-канале: 4..10% от Uн, В
#define KP_V    10.0    // Коэффициент усиления P-канала РН: Kрн = Kкн Kдт / Rдр / Kдн
#define TI_V    (TI_C)  // Постоянная  времени  I-канала РН: Tф  = Cф * (Rдр + ..), сек

#elif defined PRIVATE_KIT_2     // Самодельный Power KIT: 27 В, 6 * 170 А

#define PWM_FREQUENCY   10000   // Частота переключения стоек силового моста, (Гц)
#define PWM_DEADBAND    54      // Величина бестоковой паузы в циклах CPU, (Q0)
#define SW_H_POLARITY   0       // Активный уровень предрайверов верхних ключей
#define SW_L_POLARITY   0       // Активный уровень предрайверов  нижних ключей
#define SW_H_IDLESTATE  1       // Уровень сигнала для верхних ключей в IDLE-режиме
#define SW_L_IDLESTATE  1       // Уровень сигнала для  нижних ключей в IDLE-режиме
#define MCU_OPEN_DRAIN  1       // 1, если питание у MCU и у Моста раздельное
#define CS_10           0.5000  // Смещение нуля датчика тока фазы 1  (доля шкалы АЦП)
#define CS_20           0.5000  // Смещение нуля датчика тока фазы 2  (доля шкалы АЦП)
#define CS_30           0.5000  // Смещение нуля датчика тока фазы 3  (доля шкалы АЦП)
#define CS_40           0.5000  // Смещение нуля датчика тока фазы 4  (доля шкалы АЦП)
#define CS_50           0.5000  // Смещение нуля датчика тока фазы 5  (доля шкалы АЦП)
#define CS_60           0.5000  // Смещение нуля датчика тока фазы 6  (доля шкалы АЦП)
#define VS_10           0.0000  // Смещение нуля датчика напряжения 1 (доля шкалы АЦП)
// ************************************************************************** //
// Исходные данные к расчету параметров системы управления преобразователя
// ************************************************************************** //
#define BASE_VOLTAGE    52.0    // Напряжение dc-шины, соответствующие шкале АЦП, (В)
#define BASE_CURRENT    720.0   // Ток дросселя,  соответствующий  1/2 шкалы АЦП, (А)
#define LD      0.00006         // Индуктивность дросселя, (Гн)
#define RD      0.01            // Сопротивление дросселя + ключей + шунта + ШИН, (Ом)
#define C2      0.015           // Емкость на выходе преобразователя, (Ф)
#define KK_C    5.5             // Коэффициент усиления контура тока:  1 .. L/R / (RC)
#define KPWM    (0.5 * 260)     // Kпн = KPWM = V1_MAX / 2 = Uдр_MAX
// ************************************************************************** //
// Параметры системы управления DC/DC-преобразователя в относительных величинах
// ************************************************************************** //
// Коэффициент усиления P-канала Регулятора Тока: Kрт = Kкт Rдр / Kдт / Kпн
#define KP_C    (KK_C * RD / (1.0 / BASE_CURRENT) / KPWM)       // 0.3
// Постоянная  времени  I-канала Регулятора Тока: Tдр = Lдр / (Rдр + ..), сек
#define TI_C    (LD / RD)                                       // 0.006
// ************************************************************************** //
// Параметры системы управления DC/DC-преобразователя в абсолютных величинах
// ************************************************************************** //
#define LEV     1.0     // Ограничение ошибки (по модулю) в И-канале: 4..10% от Uн, В
#define KP_V    40.0    // Коэффициент усиления P-канала РН: Kрн = Kкн Kдт / Rдр / Kдн
#define TI_V    (TI_C)  // Постоянная  времени  I-канала РН: Tф  = Cф * (Rдр + ..), сек

#elif defined PRIVATE_KIT_3     // Самодельный Power KIT

// ...

#endif

Листинг 2. Файл powerlib.h

/******************************************************************************
* File Name :   powerlib.h
* Author :      Клиначев Николай Васильевич
* Version :     1.1.0
* Date :        201512
* Description : Библиотеки IQ-Math library и Power Control library
*       Библиотека математических преобразователей, необходимых
*       при реализации цифровых систем управления DC/DC преобразователей
*       и необходимые макросы целочисленной арифметики (IQ-математика)
*       http://model.exponenta.ru/k2/Jigrein/JS/fwlink.htm#B447
*******************************************************************************/
/* Защита от рекурсивных подключений библиотеки-------------------------------*/
#ifndef __PWLIB_H
#define __PWLIB_H

/* Includes ------------------------------------------------------------------*/

/* Exported types ------------------------------------------------------------*/
// ************************************************************************** //
typedef long    _iq;    // это _iq24, _iq22 или _iq20 в зависимости от GL_Q

// ************************************************************************** //
// Глобальные уставки Цифровой системы управления для Synchronous Buck DCDC
// Примечание. Если преобразователь не многофазный удалить коэффициенты РТ
typedef struct
{
    _iq gV;    // Output: Уставка напряжения в ??? !абсолютных! ??? ед.
    _iq gI;    // Output: Уставка тока в относительных ед. (pu), [0.00, +1.0]
    _iq Kpc;   // Const: Коэффициент усиления пропорционального канала РТ
    _iq Kic;   // Const: Коэффициент усиления интегрирующего    канала РТ
    unsigned int gV_TicCounter;   // счетчик тиков программной машины состояний
}
SETPOINTS_TypeDef;

// ************************************************************************** //
// Измеряемые величины на силовом многофазном модуле DC/DC преобразователя
typedef struct
{
    _iq yVo;    // Output: Напряжение на выходе DC/DC (сигнал ОС), [0.0, ?1.0]
    _iq yI1;    // Output: Ток фазы "1"         DC/DC (сигнал ОС), [-1.0, 1.0]
    _iq yI2;    // Output: Ток фазы "2"         DC/DC (сигнал ОС), [-1.0, 1.0]
    _iq yI3;    // Output: Ток фазы "3"         DC/DC (сигнал ОС), [-1.0, 1.0]
    _iq yI4;    // Output: Ток фазы "4"         DC/DC (сигнал ОС), [-1.0, 1.0]
    _iq yI5;    // Output: Ток фазы "5"         DC/DC (сигнал ОС), [-1.0, 1.0]
    _iq yI6;    // Output: Ток фазы "6"         DC/DC (сигнал ОС), [-1.0, 1.0]
    _iq I10;    // Const: Смещение нуля датчика тока фазы "1",     0.5 +/-10 %
    _iq I20;    // Const: Смещение нуля датчика тока фазы "2",     0.5 +/-10 %
    _iq I30;    // Const: Смещение нуля датчика тока фазы "3",     0.5 +/-10 %
    _iq I40;    // Const: Смещение нуля датчика тока фазы "4",     0.5 +/-10 %
    _iq I50;    // Const: Смещение нуля датчика тока фазы "5",     0.5 +/-10 %
    _iq I60;    // Const: Смещение нуля датчика тока фазы "6",     0.5 +/-10 %
}
PWKIT_TypeDef;

// ************************************************************************** //
// ПИ-регулятор контура напряжения (с Ограничителем на выходе)
typedef struct
{
    _iq *gI;  // Input: Указатель на Уставку тока (для ораничителя), [0.0, 1.0]
    _iq *gV;  // Input: Указатель на Уставку  напряжения (задание   для РН), В
    _iq *yV;  // Input: Указатель на выходное напряжение (сигнал ОС для РН), В
    _iq  zo;  // sPrivate: Выход регистра задержки интегратора
    _iq  uI;  // Output: Выход РН (задание с ограничением для РТ),  [-1.0, 1.0]
    _iq  Kp;  // Const: Коэффициент усиления пропорционального канала РН
    _iq  Ki;  // Const: Коэффициент усиления интегрирующего    канала РН
    _iq  Lx;  // Const: Предел ограничения ошибки (по модулю) на входе И-канала, В
}
PID_V_TypeDef;

#define PID_V_MACRO(vr)                                                        \
{                                                                              \
    _iq vr_Lu = *vr.gI;                         /* Предел для вых. сигнала   */\
    _iq vr_zi;                                  /* Вход регистра задержки    */\
    vr_zi = *vr.gV - *vr.yV;                    /* Ошибка регулятора         */\
    vr.uI = vr.zo + _IQmpy(vr_zi, vr.Kp);       /* Выход = I-канал + P-канал */\
    vr.uI = vr.uI > vr_Lu ? vr_Lu : vr.uI < -vr_Lu ? -vr_Lu : vr.uI;           \
    /* ---------------------------------------------------                   */\
    vr_zi = vr_zi > vr.Lx ? vr.Lx : vr_zi < -vr.Lx ? -vr.Lx : vr_zi;           \
    vr_zi = vr.zo + _IQmpy(vr_zi, vr.Ki);       /* 1/s: Значение на входе    */\
    vr_zi = vr_zi > vr_Lu ? vr_Lu : vr_zi < -vr_Lu ? -vr_Lu : vr_zi;           \
    vr.zo = vr_zi;                              /* 1/s: Обновили выход 1/z   */\
}

// ************************************************************************** //
// ПИ-регулятор контура тока (без канала комбинированного управления)
typedef struct
{
    _iq *gI;    // Input: Указатель на Уставку (задание) для РТ, [-1.0, 1.0]
    _iq *yI;    // Input: Указатель на сигнал ОС с датчика тока, [-1.0, 1.0]
    _iq  zo;    // sPrivate: Выход регистра задержки интегратора
    _iq  uV;    // Output:   Выход РТ (скважность для ШИМ),      [-1.0, 1.0]
    _iq *Kp;    // Const: Коэффициент усиления пропорционального канала РТ
    _iq *Ki;    // Const: Коэффициент усиления интегрирующего    канала РТ
}
PID_I_TypeDef;

#define PID_I_MACRO(ir)                                                        \
{                                                                              \
    _iq ir_zi;                                /* Вход регистра задержки или  */\
    ir_zi = *ir.gI - *ir.yI;                  /* ... Ошибка регулятора тока  */\
    ir.uV = ir.zo + _IQmpy(ir_zi, *ir.Kp);    /* Выход = I-канал + P-канал   */\
    ir.uV = ir.uV >= _IQ(+1.0) ? _IQ(+1.0) - 1           /*   ..00011111..   */\
          : ir.uV <  _IQ(-1.0) ? _IQ(-1.0) : ir.uV;      /*   ..11100000..   */\
    /* ---------------------------------------------------                   */\
    ir_zi = ir.zo + _IQmpy(ir_zi, *ir.Ki);    /* 1/s: Значение на входе      */\
    ir_zi = ir_zi >  _IQ(+1.0) ? _IQ(+1.0)               /*   ..00100000..   */\
          : ir_zi <  _IQ(-1.0) ? _IQ(-1.0) : ir_zi;      /*   ..11100000..   */\
    ir.zo = ir_zi;                                                             \
}

// ************************************************************************** //
// Блок указателей на выходы регуляторов тока и ограничения их значений
typedef struct
{
    _iq *Ph1;   // Input: Указатель на выход регулятора тока фазы "1"
    _iq *Ph2;   // Input: Указатель на выход регулятора тока фазы "2"
    _iq *Ph3;   // Input: Указатель на выход регулятора тока фазы "3"
    _iq *Ph4;   // Input: Указатель на выход регулятора тока фазы "4"
    _iq *Ph5;   // Input: Указатель на выход регулятора тока фазы "5"
    _iq *Ph6;   // Input: Указатель на выход регулятора тока фазы "6"
    _iq  uV1;   // Output: Управляющее значение для ШИМ фазы "1", [-1.0, 1.0]
    _iq  uV2;   // Output: Управляющее значение для ШИМ фазы "2", [-1.0, 1.0]
    _iq  uV3;   // Output: Управляющее значение для ШИМ фазы "3", [-1.0, 1.0]
    _iq  uV4;   // Output: Управляющее значение для ШИМ фазы "4", [-1.0, 1.0]
    _iq  uV5;   // Output: Управляющее значение для ШИМ фазы "5", [-1.0, 1.0]
    _iq  uV6;   // Output: Управляющее значение для ШИМ фазы "6", [-1.0, 1.0]
}
PWM_L_TypeDef;

#define PWM_PH_LIMIT_MACRO(pPh, uV)                                            \
{                                                                              \
    uV = *pPh;                                                                 \
    if (uV >= _IQ(+1.00)) uV = _IQ(+1.00) - 1;  /* ..00011111.. */             \
    if (uV <  _IQ(-1.00)) uV = _IQ(-1.00);      /* ..11100000.. */             \
}

/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
#ifndef GL_Q            // Точность типа данных для IQ-вычислений: 20 22 24
#define GL_Q 24         // Определять здесь. В *.h много мороки
#endif
#if GL_Q == 24          // _iq24:   -128 .. +127.999 999 940    0.000 000 060
#define _IQ(A)          (long) ((A) * 16777216.0L)      // _IQ24
#elif GL_Q == 22        // _iq22:   -512 .. +511.999 999 76     0.000 000 24
#define _IQ(A)          (long) ((A) * 4194304.0L)       // _IQ22
#elif GL_Q == 20        // _iq20:  -2048 .. 2047.999 999 0      0.000 001
#define _IQ(A)          (long) ((A) * 1048576.0L)       // _IQ20
#else
#error "Wrong IQ type. Use: IQ24 or IQ22 or IQ20"
#endif                  // Если в *.h, то так надо все IQ-макросы

// _iq15:    -1 .. +1 - 0.000030517578125
#define _IQ15(A)        (s16) ((A) * 32768.0L)
/*******************************************************************************
* Description :  Макрос преобразования биполярного значения
*                к униполярному с масштабированием
* Input : x    - число в формате _Q15: 8000..FFFF-0000..7FFF
* Param : BASE - униполярное число: 0000..FFFF, на 1 большее MAX значения
* Return :       0x0000 .. (base - 1)
*/
#define _Q15toBASE(x, BASE) ((unsigned long) ((  signed short) (x) + 0x8000)   \
                                           * ((unsigned short) (BASE)) >> 16)
#define _IQtoIQ15(A)        ((long) (A) >> (GL_Q - 15))
#define _IQdiv2(A)          ((A) >> 1)
#define _IQmpy(A, B)        (long) (((long long) (A) * (long long) (B)) >> GL_Q)
#define _IQmpy2(A)          ((A) << 1)

/* Exported functions ------------------------------------------------------- */

#endif /* __PWLIB_H */

Листинг 3. Файл main.c

/******************************************************************************
* File Name :   main.c
* Author :      Клиначев Николай Васильевич
* Version :     1.1.0
* Date :        20150707
* Target :      Любой 32-битный микроконтроллер с power-control-периферией
*               от 48 МГц, FPU:off/none, см. уточнение в mcu-s.h
* Description : Программа двухконтурной цифровой системы управления для
*          многофазного, реверсивного, понижающего, импульсного источника
*          вторичного электропитания (Synchronous Buck DC/DC converter).
*
*     Соглашение о префиксах для координат САР:
*          g – задающее воздействие (Ref),
*          y – регулируемая координата (Fbk),
*          x – ошибка регулирования (Err),
*          u – управление на объект (Out),
*          p – верхний предел координаты (upper, pozitive),
*          n – нижний предел координаты (lower, negative),
*          zo# – значение хранящееся в регистре задержки (1/z),
*          zi# – значение на входе регистра задержки (input of 1/z),
*          u# – внутренняя координата регулятора (private coordinate).
*
*     Алгоритм работы с отладчиком:
*          1. Запустить отладчик в свободном режиме
*          2. Нажать кнопку для запуска преобразователя
*          3. Нажать кнопку для остановки преобразователя
*          4. Остановить отладчик
*          5. Изменить значения переменных в Watch window
*          6. Перейти к шагу 1
*******************************************************************************/
// TODO:

/* Includes ------------------------------------------------------------------*/
#include "powerkits.h"  // Подключаем базу с параметрами силовых мостов
#include "mcu-s.h"      // Подключаем базу данных плат микроконтроллеров
#include "powerlib.h"   // Подключаем библиотеки IQ-Math и Power Control

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

// TIMESTEP - Период вызова главного прерывания программы - MainISR.
// Определяет шаг дискретизации ЦСУ == ISR_PRESCALER * (1 / PWM_FREQUENCY), (сек)
// Внимание! Должно соблюдаться условие: TIMESTEP < TI_C / KK_C
#define TIMESTEP        ((((unsigned int)(CPU_SYSCLK / PWM_FREQUENCY)) \
                              & 0xfffe) / CPU_SYSCLK * ISR_PRESCALER)
// PWM_RESOLUTION делим на два для настройки таймера с реверсируемым счетчиком
// Для симметрии ШИМ-а PWM_RESOLUTION должно быть нечетным числом
#define PWM_RESOLUTION  ((((unsigned int)(CPU_SYSCLK / PWM_FREQUENCY)) >> 1) | 1)
#define FSM_TIMESTEP    (((unsigned int)(0.02 / TIMESTEP)) * TIMESTEP)

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
// Переменные цифровой системы управления для DC/DC преобразователя
SETPOINTS_TypeDef       SetPnt; // Глобальные уставки ЦСУ для Buck DCDC
PWKIT_TypeDef           PwKit;  // Измеряемые величины на силовом модуле
PID_V_TypeDef           pid_v;  // ПИ-регулятор контура напряжения
PID_I_TypeDef           pid_i1; // ПИ-регулятор контура тока фазы "1"
PID_I_TypeDef           pid_i2; // ПИ-регулятор контура тока фазы "2"
PID_I_TypeDef           pid_i3; // ПИ-регулятор контура тока фазы "3"
PID_I_TypeDef           pid_i4; // ПИ-регулятор контура тока фазы "4"
PID_I_TypeDef           pid_i5; // ПИ-регулятор контура тока фазы "5"
PID_I_TypeDef           pid_i6; // ПИ-регулятор контура тока фазы "6"
PWM_L_TypeDef           pwm;    // Блок ограничения скважности для фаз

// Переменные низкоприоритетной Машины Состояний
static void (*Alpha_FSM_Task)(void);           // указатель на задачу
static unsigned long VirtualTimer     = 0;     // виртуальный таймер
static unsigned int FSM_TicCounter    = 0;     // счетчик тиков
static FlagStatus isIRQHandlerLocked  = SET;   // флаг блокировки вычислений

/* Private function prototypes -----------------------------------------------*/
static void DCDC_CntrlUnit_SetBlkParam(void);
static void DCDC_CntrlUnit_CreateWires(void);
static void DCDC_CntrlUnit_Reset_IC(void);
static void Task_DCDC_off(void);
static void Task_DCDC_run(void);
static void Task_DCDC_debug(void);

/*******************************************************************************
* Function Name : main
* Description :   Main program
*******************************************************************************/
#pragma optimize=none  // 4 main Иначе Оптимизатор кода отключает Alpha_FSM_Task
void main(void) {
    PWMDRV_InitTypeDef  pwm;
    // Определим количество циклов CPU, которые считает Таймер формируя ШИМ, (Q0)
    // PWM_RESOLUTION число нечетное. А а таймеры должны считать до четного числа
    pwm.PeriodMax   = PWM_RESOLUTION - 1; // === четное число 0 1 2 1 0 1 2 ...
    // Определим полу-период ШИМ-а в циклах CPU, (Q0)
    pwm.HalfPerMax  = (PWM_RESOLUTION - 1) >> 1;
    // Определим величину бестоковой паузы в циклах CPU, (Q0)
    pwm.Deadband    = PWM_DEADBAND; // == PWM_DEADBAND / CPU_SYSCLK, uS
    pwm.CnfgReg.all =
        (SW_H_POLARITY  << 0) + // Активный уровень предрайверов верхних ключей
        (SW_L_POLARITY  << 1) + // Активный уровень предрайверов  нижних ключей
        (SW_H_IDLESTATE << 2) + // Уровень сигнала для верхних ключей в IDLE-режиме
        (SW_L_IDLESTATE << 3) + // Уровень сигнала для  нижних ключей в IDLE-режиме
        (MCU_OPEN_DRAIN << 4) + // 1, если питание у MCU и у Моста раздельное
        (/*SVPWM_ON*/ 0 << 5);  // 0, если мала полоса ОУ (не работает на ХХ)

    MCU_Init(&pwm);

    //DCDC_CntrlUnit_CreateBlock(); // Создаем матем. блоки ЦСУ для DCDC
    DCDC_CntrlUnit_SetBlkParam();   // Устанавливаем параметры блоков
    DCDC_CntrlUnit_CreateWires();   // Определяем схему передачи аргументов
    DCDC_CntrlUnit_Reset_IC();      // Предустанавливаем Начальные Условия

    MCU_Start();

    unsigned long zVirtualTimer = 0;    // Регистр задержки для тика таймера
    unsigned int ISR_TicCounter = 0;    // Счетчик тиков таймера
    Alpha_FSM_Task = &Task_DCDC_off;    // Предустановили задачу для FSM

    // Машина состояний для низкоприоритетных операций:
    // опрос копок, индикация, сетевая коммуникация и тп.
    while (1) {
        zVirtualTimer = VirtualTimer;             // запомнили  тик таймера
        while (VirtualTimer == zVirtualTimer) {}  // ждем новый тик таймера
         // Через каждые 20 mS (FSM_TIMESTEP) обслуживаем Машину Состояний
        if (++ISR_TicCounter >= (unsigned int)(FSM_TIMESTEP / TIMESTEP)) {
            ISR_TicCounter = 0; (*Alpha_FSM_Task)();
        }
    }
}

static void Task_DCDC_off(void) {
    // Декрементируем счетчик времени блокировки кнопки
    if (FSM_TicCounter) { FSM_TicCounter -= 1; return; }
    // Опрос кнопки включения
    if (IS_BUTTON_PRESS == 0) return;

    // Переход к следующей задаче

    // Планируем блокировку кнопки на 1 сек
    FSM_TicCounter = (unsigned int) (1.0 / FSM_TIMESTEP);
    // Модифицируем блок-схему
    // ...
    DCDC_CntrlUnit_Reset_IC();
    // Снимаем флаг блокировки вычислений
    isIRQHandlerLocked = RESET;
    // Включаем двигатель
    EPWM_on();
    // Меняем указатель на задачу
    Alpha_FSM_Task = &Task_DCDC_run;
}

static void Task_DCDC_run(void) {
    // Декрементируем счетчик времени блокировки кнопки
    if (FSM_TicCounter) { FSM_TicCounter -= 1; return; }
    // Опрос кнопки включения
    if (IS_BUTTON_PRESS == 0) return;

    // Переход к следующей задаче

    // Планируем блокировку кнопки на 1 сек
    FSM_TicCounter = (unsigned int) (1.0 / FSM_TIMESTEP);
    // Поднимаем флаг блокировки вычислений
    isIRQHandlerLocked = SET;
    // Выключаем двигатель
    EPWM_off();
    // Меняем указатель на задачу
    Alpha_FSM_Task = &Task_DCDC_off;
    //Alpha_FSM_Task = &Task_DCDC_debug;
}

static void Task_DCDC_debug(void) {
    // Периодически меняем уставку напряжения для настройки регуляторов
    // при визуальном контроле переходного процесса осциллографом
    if (SetPnt.gV_TicCounter) {
        SetPnt.gV_TicCounter -= 1;
    } else {
        SetPnt.gV_TicCounter = 2;
        SetPnt.gV = SetPnt.gV < _IQ(12.0) ? _IQ(17.0) : _IQ(7.0);
    }
    // Декрементируем счетчик времени блокировки кнопки
    if (FSM_TicCounter) { FSM_TicCounter -= 1; return; }
    // Опрос кнопки включения
    if (IS_BUTTON_PRESS == 0) return;

    // Переход к следующей задаче

    // Планируем блокировку кнопки на 1 сек
    FSM_TicCounter = (unsigned int) (1.0 / FSM_TIMESTEP);
    // Поднимаем флаг блокировки вычислений
    isIRQHandlerLocked = SET;
    // Выключаем двигатель
    EPWM_off();
    // Модифицируем блок-схему
    SetPnt.gV = _IQ(12.0);
    SetPnt.gV_TicCounter = 0;
    // Меняем указатель на задачу
    Alpha_FSM_Task = &Task_DCDC_off;
}


/*******************************************************************************
* Function Name : DCDC_CntrlUnit_SetBlkParam
* Description :   ...
*     Процедура установки параметров математических блоков
*     (объектов программы), которые составляют ЦСУ для DC-DC преобразователя.
*     Не беспокойтесь о делениях! Подобные выражения вычислит препроцессор
*     компилятора. Тут просто константы будут присваиваться переменным.
*******************************************************************************/
#pragma optimize=none
static void DCDC_CntrlUnit_SetBlkParam(void) {
    // Глобальные уставки Цифровой системы управления ************************
    SetPnt.gV  = _IQ(12.25 /* В */ );                         //    12.25 В  *
    SetPnt.gI  = _IQ(10.00 /* А */ / BASE_CURRENT);           // ?6? * 10 А  *
    SetPnt.gV_TicCounter = 0;                                 //             *
    // Измеряемые величины на силовом модуле *********************************
    PwKit.I10  = _IQ(CS_10);                                  //             *
    PwKit.I20  = _IQ(CS_20);                                  //             *
    PwKit.I30  = _IQ(CS_30);                                  //             *
    PwKit.I40  = _IQ(CS_40);                                  //             *
    PwKit.I50  = _IQ(CS_50);                                  //             *
    PwKit.I60  = _IQ(CS_60);                                  //             *
    // PI-Регулятор контура напряжения c Ограничителем ***********************
    pid_v.Kp   = _IQ(KP_V / BASE_CURRENT);                    // мастер      *
    pid_v.Ki   = _IQ(KP_V / BASE_CURRENT / TI_V * TIMESTEP);  // мастер      *
    pid_v.Lx   = _IQ(LEV);                                    // тюнинг LEV  *
    // Подчиненный PI-регулятор контура тока DC/DC преобразователя ***********
    SetPnt.Kpc = _IQ(KP_C);                                   // тюнинг KK_C *
    SetPnt.Kic = _IQ(KP_C / TI_C * TIMESTEP);                 // не меняется *
}

/*******************************************************************************
* Function Name : DCDC_CntrlUnit_CreateWires
* Description :   ...
*     Процедура выполняет построение цифровой системы управления
*     из вычислительных модулей - экземпляров объектов.
*     Суть построения ЦСУ - определение схемы передачи аргументов.
*     Выход - это атрибут модуля (переменная принадлежит объекту).
*     Вход - не является атрибутом модуля (объект имеет указатели
*     для подключения к аргументам).
*******************************************************************************/
static void DCDC_CntrlUnit_CreateWires(void) {
    // ПИ-регулятор контура напряжения (РН) (с Ограничителем на выходе) ********
    pid_v.gV  = &SetPnt.gV;     // Подключили Уставку напряжения (задание для РН)
    pid_v.yV  = &PwKit.yVo;     // Подключили вых. напряжение (сигнал ОС для РН)
    pid_v.gI  = &SetPnt.gI;     // Подключили Уставку тока (момента)
    // ПИ-регуляторы подчиненных контуров тока (РТ) ****************************
    pid_i6.Kp =                 // Определили указатель на коэффициент Kp РТ
    pid_i5.Kp =                 // Определили указатель на коэффициент Kp РТ
    pid_i4.Kp =                 // Определили указатель на коэффициент Kp РТ
    pid_i3.Kp =                 // Определили указатель на коэффициент Kp РТ
    pid_i2.Kp =                 // Определили указатель на коэффициент Kp РТ
    pid_i1.Kp = &SetPnt.Kpc;    // Определили указатель на коэффициент Kp РТ
    pid_i6.Ki =                 // Определили указатель на коэффициент Ki РТ
    pid_i5.Ki =                 // Определили указатель на коэффициент Ki РТ
    pid_i4.Ki =                 // Определили указатель на коэффициент Ki РТ
    pid_i3.Ki =                 // Определили указатель на коэффициент Ki РТ
    pid_i2.Ki =                 // Определили указатель на коэффициент Ki РТ
    pid_i1.Ki = &SetPnt.Kic;    // Определили указатель на коэффициент Ki РТ
    pid_i6.gI =                 // Подключили к регулятору тока задающий сигнал
    pid_i5.gI =                 // Подключили к регулятору тока задающий сигнал
    pid_i4.gI =                 // Подключили к регулятору тока задающий сигнал
    pid_i3.gI =                 // Подключили к регулятору тока задающий сигнал
    pid_i2.gI =                 // Подключили к регулятору тока задающий сигнал
    pid_i1.gI = &pid_v.uI;      // Подключили к регулятору тока задающий сигнал
    pid_i1.yI = &PwKit.yI1;     // Подключили ОС - ток индуктора фазы "1" DC-DC
    pid_i2.yI = &PwKit.yI2;     // Подключили ОС - ток индуктора фазы "2" DC-DC
    pid_i3.yI = &PwKit.yI3;     // Подключили ОС - ток индуктора фазы "3" DC-DC
    pid_i4.yI = &PwKit.yI4;     // Подключили ОС - ток индуктора фазы "4" DC-DC
    pid_i5.yI = &PwKit.yI5;     // Подключили ОС - ток индуктора фазы "5" DC-DC
    pid_i6.yI = &PwKit.yI6;     // Подключили ОС - ток индуктора фазы "6" DC-DC
    // Блок ограничения управляющих значений для ШИ-модуляторов ****************
    pwm.Ph1   = &pid_i1.uV;     // Подключили регулятор I фазы "1" к блоку огр.
    pwm.Ph2   = &pid_i2.uV;     // Подключили регулятор I фазы "2" к блоку огр.
    pwm.Ph3   = &pid_i3.uV;     // Подключили регулятор I фазы "3" к блоку огр.
    pwm.Ph4   = &pid_i4.uV;     // Подключили регулятор I фазы "4" к блоку огр.
    pwm.Ph5   = &pid_i5.uV;     // Подключили регулятор I фазы "5" к блоку огр.
    pwm.Ph6   = &pid_i6.uV;     // Подключили регулятор I фазы "6" к блоку огр.
}

/*******************************************************************************
* Function Name : DCDC_CntrlUnit_Reset_IC
* Description :   ...
*     Процедура сброса / предустановки Начальных Условий для элементов
*     с эффектом памяти (регистров задержки и интеграторов).
*     Если программа ЦСУ по блок-схеме собрана корректно, то для сброса
*     к начальному состоянию достаточно предустановить начальные условия
*     на регистрах задержки (на интеграторах). Остальные координаты будут
*     корректно рассчитаны от источников Уставок до Силового модуля.
*******************************************************************************/
static void DCDC_CntrlUnit_Reset_IC(void) {
    pid_v.zo  =
        _IQ(0.00);      // Начальное состояние интегратора контура напряжения
    pid_i1.zo =         // Безопасное состояние интегратора контура тока
    pid_i2.zo =         // Безопасное состояние интегратора контура тока
    pid_i3.zo =         // Безопасное состояние интегратора контура тока
    pid_i4.zo =         // Безопасное состояние интегратора контура тока
    pid_i5.zo =         // Безопасное состояние интегратора контура тока
    pid_i6.zo =         // Безопасное состояние интегратора контура тока
        _IQ(-1.0);      // вот оно Самое.
                        // Но если заряжаем АКБ - исключаем реверс при Вкл.
        //_IQ(-1.0 + 2.0 * (12.0 / 24.0)); // Uвых / Uвх
}

/*******************************************************************************
* Function Name : DMA2_Channel5_IRQHandler
* Description :   Самая главная процедура программы (обработка прерывания)
*     Лучше всего сконфигурировать функционирование Таймера, АЦП,
*     и контроллера ПДП, так чтобы прерывание, которое формирует последний,
*     не приходилось прореживать программно. Т.е. чтобы период вызова
*     соответствующей функции совпадал с требуемы шагом дискретизации
*     цифровой системы управления (TIMESTEP).
*     Поглощающий прерывания / события счетчик реверсов Таймера лучше
*     не использовать. Рекомендуется увеличить буфер АЦП для результатов
*     оцифровки сигналов. И корректно настроить котроллер ПДП.
*     В этом случае результаты оцифровки можно усреднять.
*******************************************************************************/
#ifdef STM32_F3_DISCOVERY
ADCRESULT_TypeDef       adc;
#pragma optimize=speed
void DMA2_Channel5_IRQHandler(void)
#else
signed short    adc[6 + 1]; // Оцифрованные значения датчиков тока и напряжения
void MainISR(void)
#endif
{
    CLEAR_IT_PENDING_BIT();
    GPIO_TOGGLE_MACRO();    // Формируем синхросигнал (инвертируем бит порта)

    // -------------------------------------------------------------------------
    //  Инкрементируем виртуальный таймер и ограничиваем счет 15-ю битами
    // -------------------------------------------------------------------------
    VirtualTimer++;         // VirtualTimer &= 0x00007FFF;
    // -------------------------------------------------------------------------
    //  Вызываем макрос усреднения АЦ-преобразований и приведения к отн. ед.
    // -------------------------------------------------------------------------
    ADC_MEAS_MACRO(PwKit);  // I = [-1.0, +1.0], U = [0.0, +1.0] (вся шкала АЦП)

    if (isIRQHandlerLocked) {
        ADC_OFFSET_MACRO(PwKit); // Макрос компенсации смещений нуля датчиков
        return;
    }
    // -------------------------------------------------------------------------
    //  Приведём: U - к абсол. [0.0, +BASE_VOLTAGE]
    // -------------------------------------------------------------------------
    PwKit.yVo = _IQmpy(_IQ(BASE_VOLTAGE), PwKit.yVo);   // Полная шкала 52 В
    // -------------------------------------------------------------------------
    //  Вызываем макрос ПИ-регулятора контура напряжения
    // -------------------------------------------------------------------------
    PID_V_MACRO(pid_v);
    // -------------------------------------------------------------------------
    //  Вызываем макрос ПИ-регулятора контура тока для каждой фазы
    // -------------------------------------------------------------------------
    PID_I_MACRO(pid_i1);
    PID_I_MACRO(pid_i2);
    PID_I_MACRO(pid_i3);
    PID_I_MACRO(pid_i4);
    PID_I_MACRO(pid_i5);
    PID_I_MACRO(pid_i6);
    // -------------------------------------------------------------------------
    //  Ограничиваем сигналы (чтоб не было перегрузки при преобразовании к Q0)
    // -------------------------------------------------------------------------
    PWM_PH_LIMIT_MACRO(pwm.Ph1, pwm.uV1);
    PWM_PH_LIMIT_MACRO(pwm.Ph2, pwm.uV2);
    PWM_PH_LIMIT_MACRO(pwm.Ph3, pwm.uV3);
    PWM_PH_LIMIT_MACRO(pwm.Ph4, pwm.uV4);
    PWM_PH_LIMIT_MACRO(pwm.Ph5, pwm.uV5);
    PWM_PH_LIMIT_MACRO(pwm.Ph6, pwm.uV6);
    // -------------------------------------------------------------------------
    //  Обновляем Регистры Сравнения Таймеров или каналов одного Таймера
    // -------------------------------------------------------------------------
    PWM_MACRO(pwm);

    GPIO_TOGGLE_MACRO(); // Формируем синхросигнал (инвертируем бит порта)
    // STM32_F3_DISCOVERY:
    //      С оптимизацией  по скорости время исполнения функции:  9.2 uS
    //      Без оптимизации по скорости время исполнения функции: 27.4 uS
}

/************************ END OF FILE *****************************************/

Листинг 4. Файл mcu-s.h

/******************************************************************************
* File Name :   mcu-s.h
* Author :      Клиначев Николай Васильевич
* Version :     1.0.0
* Date :        20150707
* Target 1 :    ARM Cortex-M4 (STM32F303xC), FPU:Off
*               MCU KIT: STM32F3DISCOVERY
* Target 2 :    ___________________________, FPU:none
*               MCU KIT: __________________
*
* Description : База данных плат микроконтроллеров (MCU KITs)
*     В базе определены ШИМ-драйверы силового модуля многофазного
*     DC/DC-преобразователя.
*     Для определения драйвера необходимо раскомментировать определение
*     одного каталожного идентификатора платы микроконтроллера в списке.
*     На первом этапе допустимо оставить закомментированными все определения.
*     Код содержит шаблон ШИМ-драйвера и будет успешно скомпилирован
*     без подключения библиотек производителя микроконтроллера.
*******************************************************************************/
/* Защита от рекурсивных подключений библиотеки-------------------------------*/
#ifndef __MCU_S_H
#define __MCU_S_H

//#define STM32_F3_DISCOVERY      // ARM Cortex-M4 - STM32F303xC / 72 МГц
//#define LAUNCHXL_F28027F        // TI C2000 - TMS320F28027F / 60 МГц

// Структура для конфигурации ШИМ-драйвера силового моста **********************
typedef struct {
    unsigned int PeriodMax;  // Период      реверса Таймера в циклах CPU, (Q0)
    unsigned int HalfPerMax; // Полу-период реверса Таймера в циклах CPU, (Q0)
    unsigned int Deadband;   // Величина бестоковой паузы   в циклах CPU, (Q0)
    union {
        unsigned int all;
        struct {
            unsigned int SW_H_Polarity:  1; // Активный уровень предрайверов верхних ключей
            unsigned int SW_L_Polarity:  1; // Активный уровень предрайверов  нижних ключей
            unsigned int SW_H_IdleState: 1; // Уровень сигнала для верхних ключей в IDLE-режиме
            unsigned int SW_L_IdleState: 1; // Уровень сигнала для  нижних ключей в IDLE-режиме
            unsigned int MCU_Open_DRAIN: 1; // 1, если питание у MCU и у Моста раздельное
            unsigned int SVPWM_on:       1; // 0, если мала полоса ОУ (не работает на ХХ)
            unsigned int rsvd:          10; // 15:6 Зарезервированы
        } bit;
    } CnfgReg;               // Регистр конфигурации ШИМ-драйвера силового моста
} PWMDRV_InitTypeDef;


#if defined STM32_F3_DISCOVERY  // Публичные определения ШИМ-драйвера MCU
/******************************************************************************
* Target :      ARM Cortex-M4 (STM32F303xC), FPU:Off
* MCU KIT :     STM32 F3 Discovery
* DAC :         PA.05
* Sinxro :      PB.11
* Description : ...
*******************************************************************************/
#include "stm32f30x.h"  // Подключаем библиотеки производителя процессора
#define CPU_SYSCLK      72e6    // SystemCoreClock
#define ISR_PRESCALER   2       // Количество усредняемых выборок
/* Область памяти, для обращения к которой можно использовать:
* либо имя массива в котором хранятся 32-х разрядные слова (4 шт.),
* либо имя массива в котором хранятся 16-и разрядные полуслова (8 шт.).
*
* adc.Dual[0] == { adc.Single[0], adc.Single[1] }
* adc.Dual[1] == { adc.Single[2], adc.Single[3] }
* adc.Dual[2] == { adc.Single[4], adc.Single[5] }
* adc.Dual[3] == { adc.Single[6], adc.Single[7] }
*
* adc.Single[0] ==  АЦП3, 1-ая выборка без знака (     ток фазы DC/DC I1)
* adc.Single[1] ==  АЦП4, 1-ая выборка без знака (выходное напряжение V1)
* adc.Single[2] ==  АЦП3, 2-ая выборка без знака (     ток фазы DC/DC I1)
* adc.Single[3] ==  АЦП4, 2-ая выборка без знака (выходное напряжение V1)
* adc.Single[4]  -  значение, полученное при самокалибровке АЦП3
* adc.Single[5]  -  значение, полученное при самокалибровке АЦП4
* adc.Single[6]  -  среднее по двум выборкам АЦП3, тип данных: _iq13
* adc.Single[7]  -  среднее по двум выборкам АЦП4, тип данных: _iq13
*/
typedef union {
    uint32_t   Dual[ISR_PRESCALER     + 2];   // массив 32-х разрядных слов
    uint16_t Single[ISR_PRESCALER * 2 + 4];   // массив 16-х разрядных полуслов
} ADCRESULT_TypeDef;                          // сырые данные АЦП3 и АЦП4
extern ADCRESULT_TypeDef        adc;          // Массив оцифрованных значений
// Объявление прототипа функции обработки прерывания ***************************
void DMA2_Channel5_IRQHandler(void);
// Функция инициализации микроконтроллера **************************************
void MCU_Init(PWMDRV_InitTypeDef* pwm);
// Функция запуска микроконтроллера (разрешения прерываний) ********************
void MCU_Start(void);
// Макрос опроса кнопки пользователя на плате микроконтроллера *****************
#define IS_BUTTON_PRESS         ( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)     )
// Макрос переключения GPIO-выхода для контроля / или синхронизации осцилл-а ***
#define GPIO_TOGGLE_MACRO()     { GPIOE->ODR ^= GPIO_Pin_8;                    }
// Макрос записи данных в ЦАП **************************************************
#define DAC_MACRO(data)         { DAC_SetChannel2Data(DAC_Align_12b_L, data);  }
// Макрос  включения предрайвера силового моста ********************************
#define EPWM_on()               { TIM_CtrlPWMOutputs(TIM1, ENABLE);            }
// Макрос выключения предрайвера силового моста ********************************
#define EPWM_off()              { TIM_CtrlPWMOutputs(TIM1, DISABLE);           }
// Макрос очистки бита подтверждения обслуживания прерывания *******************
#define CLEAR_IT_PENDING_BIT()  { DMA_ClearITPendingBit(DMA2_IT_TC5);          }
// Макрос обновления Регистров Сравнения таймер[а/ов] **************************
//      Вариант 1 (32*32): _IQmpy(_IQdiv2(pwm.uV + _IQ(1.0)), PWM_RESOLUTION);
//      Вариант 2 (16*16): _Q15toBASE(_IQtoIQ15(pwm.uV), PWM_RESOLUTION));
#define PWM_MACRO(pwm)          {                                              \
    TIM_SetCompare1(TIM1, _Q15toBASE(_IQtoIQ15(pwm.uV1), PWM_RESOLUTION));     \
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV2), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV3), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV4), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV5), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV6), PWM_RESOLUTION));   */\
}
// Макрос измерения смещения нуля датчиков тока фаз DC/DC-преобразователя ******
#define ADC_OFFSET_MACRO(PwKit) {                                              \
    /* Оцифрованные, униполярные, 13-ти битные значения преобразуем          */\
    /* к относительным величинам в диапазоне от _IQ(0.00) до _IQ(+1.0).      */\
    /* Усредним сигналы апериодическим звеном 1-ого порядка с большой        */\
    /* постоянной времени (T = 1/0.2 сек). Ожидаемый результат для           */\
    /* токов близок к _IQ(0.5000). Фиксируем его в powerkits.h               */\
    PwKit.I10 += _IQmpy(((long) adc.Single[6] << (GL_Q - 13))                  \
                         - PwKit.I10, _IQ(0.2 * TIMESTEP));                    \
/*  PwKit.yV0 += _IQmpy(((long) adc.Single[7] << (GL_Q - 13))                  \
                         - PwKit.yV0, _IQ(0.2 * TIMESTEP));                  */\
}
// Макрос измерения тока фаз DC/DC-преобразователя и выходного напряжения  *****
#define ADC_MEAS_MACRO(PwKit)   {                                              \
    /* Сложим пары униполярных значений (для усреднения по каналам)          */\
    adc.Dual[3] = adc.Dual[0] + adc.Dual[1]; /* тип данных результата: _iq13 */\
    /* Оцифрованные, униполярные, 13-ти битные значения преобразуем          */\
    /* к относительным величинам: I == [_IQ(-1.0), _IQ(+1.0)]                */\
    /*                            U == [_IQ(0.00), _IQ(+1.0)]                */\
    PwKit.yI1 = _IQmpy2((((long) adc.Single[6]) << (GL_Q - 13)) - PwKit.I10);  \
    PwKit.yVo =         (((long) adc.Single[7]) << (GL_Q - 13)) - _IQ(VS_10);  \
}

#elif defined LAUNCHXL_F28027F  // Публичные определения ШИМ-драйвера MCU
/******************************************************************************
* Target :      С2000: TMS320F2802x
* MCU KIT :     LAUNCHXL-F28027F
* DAC :         none
* Sinxro :      J1.5 (GPIO-34)
* Description : ...
*******************************************************************************/
#include "DSP28x_Project.h" // Подключаем библиотеки производителя процессора
// ...

#else                           // Шаблон определений ШИМ-драйвера MCU
/******************************************************************************
* Target :      _________________, FPU:Off
* MCU KIT :     _________________
* DAC :         P?.##
* Sinxro :      P?.##
* Description : Шаблон определений ШИМ-драйвера MCU
*******************************************************************************/
//#include "*.h"        // Подключаем библиотеки производителя процессора
typedef enum { DISABLE = 0, ENABLE = !DISABLE } FunctionalState;
typedef enum { RESET = 0, SET = !RESET } FlagStatus;
#define CPU_SYSCLK      72e6            // SystemCoreClock
#define ISR_PRESCALER   1               // Количество усредняемых выборок
extern signed short     adc[6 + 1];     // Массив оцифрованных значений
// Объявление прототипа функции обработки прерывания ***************************
void MainISR(void);
// Функция инициализации микроконтроллера **************************************
#define MCU_Init(pwm)           {                                              }
// Функция запуска микроконтроллера (разрешения прерываний) ********************
#define MCU_Start()             {                                              }
// Макрос опроса кнопки пользователя на плате микроконтроллера *****************
#define IS_BUTTON_PRESS         ( 0 == 0                                       )
// Макрос переключения GPIO-выхода для контроля / или синхронизации осцилл-а ***
#define GPIO_TOGGLE_MACRO()     {                                              }
// Макрос записи данных в ЦАП **************************************************
#define DAC_MACRO(data)         {                                              }
// Макрос очистки бита подтверждения обслуживания прерывания *******************
#define CLEAR_IT_PENDING_BIT()  {                                              }
// Макрос  включения предрайвера силового моста ********************************
#define EPWM_on()               {                                              }
// Макрос выключения предрайвера силового моста ********************************
#define EPWM_off()              {                                              }
// Макрос обновления Регистров Сравнения таймер[а/ов] **************************
//      Вариант 1 (32*32): _IQmpy(_IQdiv2(pwm.uV + _IQ(1.0)), PWM_RESOLUTION);
//      Вариант 2 (16*16): _Q15toBASE(_IQtoIQ15(pwm.uV), PWM_RESOLUTION));
#define PWM_MACRO(pwm)          {                                              \
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV1), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV2), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV3), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV4), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV5), PWM_RESOLUTION));   */\
/*  TIM_SetCompareX(TIMx, _Q15toBASE(_IQtoIQ15(pwm.uV6), PWM_RESOLUTION));   */\
}
// Макрос измерения смещения нуля датчиков тока фаз DC/DC-преобразователя ******
#define ADC_OFFSET_MACRO(PwKit) {                                              \
    /* Оцифрованные, униполярные, 12-ти битные значения преобразуем          */\
    /* к относительным величинам в диапазоне от _IQ(0.00) до _IQ(+1.0).      */\
    /* Усредним сигналы апериодическим звеном 1-ого порядка с большой        */\
    /* постоянной времени (T = 1/0.2 сек). Ожидаемый результат для           */\
    /* токов близок к _IQ(0.5000). Фиксируем его в powerkits.h               */\
    PwKit.I10 += _IQmpy(((long) adc[0] << (GL_Q - 12))                         \
                         - PwKit.I10, _IQ(0.2 * TIMESTEP));                    \
    PwKit.I20 += _IQmpy(((long) adc[1] << (GL_Q - 12))                         \
                         - PwKit.I20, _IQ(0.2 * TIMESTEP));                    \
    PwKit.I30 += _IQmpy(((long) adc[2] << (GL_Q - 12))                         \
                         - PwKit.I30, _IQ(0.2 * TIMESTEP));                    \
    PwKit.I40 += _IQmpy(((long) adc[3] << (GL_Q - 12))                         \
                         - PwKit.I40, _IQ(0.2 * TIMESTEP));                    \
    PwKit.I50 += _IQmpy(((long) adc[4] << (GL_Q - 12))                         \
                         - PwKit.I50, _IQ(0.2 * TIMESTEP));                    \
    PwKit.I60 += _IQmpy(((long) adc[5] << (GL_Q - 12))                         \
                         - PwKit.I60, _IQ(0.2 * TIMESTEP));                    \
 /* PwKit.yV0 += _IQmpy(((long) adc[6] << (GL_Q - 12))                         \
                         - PwKit.yV0, _IQ(0.2 * TIMESTEP));                  */\
}
// Макрос измерения тока фаз DC/DC-преобразователя и выходного напряжения  *****
#define ADC_MEAS_MACRO(PwKit)   {                                              \
    /* Оцифрованные, униполярные, 12-ти битные значения преобразуем          */\
    /* к относительным величинам: I == [_IQ(-1.0), _IQ(+1.0)]                */\
    /*                            U == [_IQ(0.00), _IQ(+1.0)]                */\
    PwKit.yI1 = _IQmpy2((((long) adc[0]) << (GL_Q - 12)) - PwKit.I10);         \
    PwKit.yI2 = _IQmpy2((((long) adc[1]) << (GL_Q - 12)) - PwKit.I20);         \
    PwKit.yI3 = _IQmpy2((((long) adc[2]) << (GL_Q - 12)) - PwKit.I30);         \
    PwKit.yI4 = _IQmpy2((((long) adc[3]) << (GL_Q - 12)) - PwKit.I40);         \
    PwKit.yI5 = _IQmpy2((((long) adc[4]) << (GL_Q - 12)) - PwKit.I50);         \
    PwKit.yI6 = _IQmpy2((((long) adc[5]) << (GL_Q - 12)) - PwKit.I60);         \
    PwKit.yVo =         (((long) adc[6]) << (GL_Q - 12)) - _IQ(VS_10);         \
}

#endif

#endif /* __MCU_S_H */

Листинг 5. Файл mcu-s.c

/******************************************************************************
* File Name :   mcu-s.c
* Author :      Клиначев Николай Васильевич
* Version :     1.0.0
* Date :        20150707
* Description : ...
*       ...
*******************************************************************************/
#include "mcu-s.h"

#if defined STM32_F3_DISCOVERY

// ADC common regular data register for dual mode = ADC1/3 base address + 0x30C
// DM00043574.pdf, p.410, pp 15.6.3.
// Адрес регистров данных АЦП1 и АЦП2
#define ADC12_CDR_ADDRESS         ((uint32_t)0x5000030C)
// Адрес регистров данных АЦП3 и АЦП4
#define ADC34_CDR_ADDRESS         ((uint32_t)0x5000070C) // (ADC3_BASE + CDR)

/*******************************************************************************
* Function Name : RCC_Configuration
* Description :   Reset and Clock Control.
*******************************************************************************/
static void RCC_Configuration(void) {
#ifdef NONE_SYSTEM_STM32F30X_C
    // Сбрасываем состояние системного генератора к начальному
    RCC_DeInit();       // RCC system reset (for debug purpose)
    // Включаем внешний высокочастотный кварцевый генератор
    RCC_HSEConfig(RCC_HSE_ON);

    // Ожидаем выхода генератора на рабочий режим
    if (RCC_WaitForHSEStartUp() == !SUCCESS) {
        while (1) {} // неисправен тактовый генератор
    }
    // Активируем буфер упреждающей выборки для флеш-памяти
    FLASH_PrefetchBufferCmd(ENABLE);
    // Определим задержку для Флеш-памяти
    FLASH_SetLatency(FLASH_Latency_2);

    // Настроим тактовый генератор процессора,
    //      см. док: DM00043574.pdf, стр. 127, Figure 13
    // Конфигурируем делитель для: ядра, памяти, AHB-шины, ПДП
    RCC_HCLKConfig(RCC_SYSCLK_Div1);        // HCLK = SYSCLK
    // Конфигурируем делитель для высокоскоростной периферии
    RCC_PCLK2Config(RCC_HCLK_Div1);         // PCLK2 = HCLK
    // Конфигурируем делитель для низкоскоростной периферии
    RCC_PCLK1Config(RCC_HCLK_Div2);         // PCLK1 = HCLK/2

    // Установим коэффициент умножения частоты кварца
    // для системного генератора PLLCLK = 8MHz * 9 = 72 MHz
    RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9);
    // Включаем контур ФАПЧ (PLL) системного генератора
    RCC_PLLCmd(ENABLE);
    // Ожидаем завершения переходного процесса в контуре ФАПЧ
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {}
    // Выбираем контур ФАПЧ в качестве системного генератора
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // SYSCLK = PLLCLK
    // Ожидаем выхода на рабочий режим всех делителей
    while (RCC_GetSYSCLKSource() != 0x08) {}
#endif

    // Настроим делитель частоты системного генератора для АЦП12 и 34
    //RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div1); // ADCCLK = PLLCLK
    RCC_ADCCLKConfig(RCC_ADC34PLLCLK_Div1); // ADCCLK = PLLCLK

    // Включаем тактирование устройств на системной AHB-шине с высокой
    // пропускной способностью (Advanced High-performance Bus)
    RCC_AHBPeriphClockCmd(
        RCC_AHBPeriph_GPIOA |   //
        RCC_AHBPeriph_GPIOB |   //
        RCC_AHBPeriph_GPIOD |   //
        RCC_AHBPeriph_GPIOE |   //
      //RCC_AHBPeriph_ADC12 |   //
      //RCC_AHBPeriph_DMA1  |   //
        RCC_AHBPeriph_DMA2  |   // контроллер ПДП для АЦП3 и АЦП4
        RCC_AHBPeriph_ADC34,    // АЦП3 и АЦП4    для  I[] и U[]
        ENABLE);
    // Включаем тактирование устройств на APB2-шине
    // высокоскоростной периферии
    RCC_APB2PeriphClockCmd(
        RCC_APB2Periph_TIM1,    // Таймер ШИМ-драйвера
        ENABLE);
    // Включаем тактирование устройств на APB1-шине
    // низкоскоростной периферии
    RCC_APB1PeriphClockCmd(
      //RCC_APB1Periph_TIM2 |   //
      //RCC_APB1Periph_TIM3 |   //
        RCC_APB1Periph_DAC,     // ЦАП для отладки
        ENABLE);
}

/*******************************************************************************
* Function Name : NVIC_Configuration
* Description : Определяем таблицу векторов прерываний и настраиваем приоритеты
*******************************************************************************/
static void NVIC_Configuration(void) {
    NVIC_InitTypeDef NVIC_InitStructure;

#ifdef NONE_SYSTEM_STM32F30X_C
#ifdef VECT_TAB_SRAM
    // Разместим таблицу векторов прерываний в ОЗУ: 0x20000000
    NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else
    // Разместим таблицу векторов прерываний в ПЗУ: 0x08000000
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
#endif

    // Определим, как прерывания будут сгруппированы по приоритетам
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    // Определим вектор прерывания для контроллера ПДП (DMA2_IT_TC1)
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel5_IRQn;//IRQn.
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

/*******************************************************************************
* Function Name : GPIO_Configuration
* Description : Configure the TIM1 Pins.
*******************************************************************************/
static void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    // Запросим параметры по умолчанию для терминалов
    GPIO_StructInit(&GPIO_InitStructure);
    // Подправим одно из значений по умолчанию
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    // Порт A: Терминал 4 - аналоговый выход для DAC1_OUT1
    // Порт A: Терминал 5 - аналоговый выход для DAC1_OUT2 (Осцилл)
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // GPIO_Pin_4 |
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт D: Терминал 11 - аналоговый вход для PD11 === ADC3_IN8 ( I1 )
    // Порт B: Терминал 15 - аналоговый вход для PB15 === ADC4_IN5 ( U1 )
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // Порт A: Терминал 1 - цифровой вход подключенный к кнопке
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт A: Терминал 10 - цифровой вход (закоротим с PB13)
    //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    //GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт E: Терминал 8 для контроля осциллографом при отладке
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOE, &GPIO_InitStructure);
    // Порт A: Терминал 8 конфигурируем для вывода PWM
    //GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    //GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт B: Терминал 13 конфигурируем для вывода PWM
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // Подключаем терминалы к альтернативным источникам сигнала (TIM1_PWM)
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_6);  // TIM1_CH1
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_6); // TIM1_CH1N
}

/*******************************************************************************
* Function Name : TIM_Configuration
* Description :   Конфигурируем тоймер TIM1
*     Генерируем 3 пары комплементарных ШИМ-сигналов с бестоковой паузой (для
*     контроля состояния ключей стоек полупроводникового 3-х фазного моста).
*     При изменении частоты ШИМ-драйвера меняется максимальное значение,
*     до которого таймер считает тактирующие импульсы. Уставки для регистров
*     сравнения должны учитывать разрешение ШИ-модулятора (должны быть меньше).
*     Для их масштабирования предложен макрос _Q15toBASE, которому, в качестве
*     аргументов, передается выборка и параметр PWM_RESOLUTION.
*******************************************************************************/
static void TIM_Configuration(PWMDRV_InitTypeDef* pwm) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;
    TIM_BDTRInitTypeDef     TIM_BDTRInitStructure;

    // Настроим Таймер ШИМ-драйвера
    //
    // 1. Настроим двоичный делитель тактирующей частоты
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    // 2. Настроим поглощающий счетчик счетных импульсов
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    // 3. Активируем режим реверсивного счёта (inc/dec)
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
    // 4. Установим максимальное число для счёта
    TIM_TimeBaseStructure.TIM_Period = pwm->PeriodMax;
    // 5. Настроим поглощающий прерывания счетчик (реверсов)
    TIM_TimeBaseStructure.TIM_RepetitionCounter = (2) - 1;
    // и как ... сконфигурируем!
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    // Настроим выходы каналов Захвата / Сравнения таймера
    //
    // 1. Установим режим сравнения CCR >= CNT или CCR <= CNT
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1
    // 2. Активируем выход OCx Регистра Сравнения (верхний ключ)
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 3. Активируем выход OCNx Регистра Сравнения (нижний ключ)
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
    // 4. Установим полярность сигнала OCx для верхнего ключа
    TIM_OCInitStructure.TIM_OCPolarity = pwm->CnfgReg.bit.SW_H_Polarity
        ? TIM_OCPolarity_High : TIM_OCPolarity_Low;
    // 5. Установим полярность сигнала OCNx для нижнего ключа
    TIM_OCInitStructure.TIM_OCNPolarity = pwm->CnfgReg.bit.SW_L_Polarity
        ? TIM_OCNPolarity_High : TIM_OCNPolarity_Low;
    // 6. Определим состояние выходов OCx  для IDLE режима
    TIM_OCInitStructure.TIM_OCIdleState = pwm->CnfgReg.bit.SW_H_IdleState
        ? TIM_OCIdleState_Set : TIM_OCIdleState_Reset;
    // 7. Определим состояние выходов OCNx для IDLE режима
    TIM_OCInitStructure.TIM_OCNIdleState = pwm->CnfgReg.bit.SW_L_IdleState
        ? TIM_OCNIdleState_Set : TIM_OCNIdleState_Reset;
    // Установим число в Регистра Сравнения = половину от ARR
    TIM_OCInitStructure.TIM_Pulse = pwm->HalfPerMax;
    // TIM1: Конфигурируем каналы: 1, 2, 3 (заполение 50%)
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);
    //TIM_OC2Init(TIM1, &TIM_OCInitStructure);
    //TIM_OC3Init(TIM1, &TIM_OCInitStructure);

    // Настроим защиту моста (ST.com: DM00080497.pdf DM00042534.pdf)
    //
    // 1. Определим величину бестоковой паузы
    TIM_BDTRInitStructure.TIM_DeadTime = pwm->Deadband;
    // 2. [Де]Активируем Компаратор Защиты
    TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
    // 3. Укажем активный уровень для Компаратора Защиты
    TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
    // 4. Установим уровень срабатывания Компаратора Защиты
    TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
    // 5. Не используем однотактный режим управления мостом (Run-режим)
    TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
    // 6. Не отключаем таймер от выходов при выключении ШИМ-а (Idle-режим)
    TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
    // 7. Не используем автоматическое включение после срабатывания защиты
    TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable;
    // и как ... сконфигурируем!
    TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);

    // TIM1: Подаем на Триггер Событий (TRGO) сигнал обновления
    TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
}

/*******************************************************************************
* Function Name : ADC_Configuration
* Description :   Измеряемые величины: I[], U[].
*     Два АЦП мК конфигурируем для выполнения "Регулярных" измерений.
*     Запуск АЦП3 и АЦП4 производиться синхронно, по триггеру таймера
*     TIM1_TRGO, который срабатывает при обновлении счетчика.
*     По готовности результата оба АЦП формируют запрос на обслуживание
*     контроллеру ПДП. Который пересылает результаты в ОЗУ (в массив).
*******************************************************************************/
static void ADC_Configuration(void) {
    ADC_CommonInitTypeDef   ADC_CommonInitStructure;
    ADC_InitTypeDef         ADC_InitStructure;

    // АЦП3 и АЦП4: I[], U[]
    // Выполним процедуру калибровки АЦП3 и АЦП4
    //
    // Включим регулятор напряжения ???
    ADC_VoltageRegulatorCmd(ADC3, ENABLE);
    ADC_VoltageRegulatorCmd(ADC4, ENABLE);
    // Подождем 10 uS
    for (u32 i = 720; i != 0; i--); // Delay(10);
    // Выберем режим калибровки для АЦП3
    ADC_SelectCalibrationMode(ADC3, ADC_CalibrationMode_Single);
    ADC_StartCalibration(ADC3);
    // Выберем режим калибровки для АЦП4
    ADC_SelectCalibrationMode(ADC4, ADC_CalibrationMode_Single);
    ADC_StartCalibration(ADC4);
    // Ожидаем завершения калибровки АЦП3
    while (ADC_GetCalibrationStatus(ADC3) != RESET);
    adc.Single[4] = ADC_GetCalibrationValue(ADC3);
    // Ожидаем завершения калибровки АЦП4
    while (ADC_GetCalibrationStatus(ADC4) != RESET);
    adc.Single[5] = ADC_GetCalibrationValue(ADC4);

    // АЦП3 и АЦП4: сконфигурируем для работы в паре
    //  DM00043574.pdf, стр. 358, 15.3.29 Dual ADC modes
    // Режим преобразований: Независимый или Альтернативы ...
    //ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_RegSimul;
    // Выберем тактовый генератор высокоскоростной AHB-шины
    ADC_CommonInitStructure.ADC_Clock = ADC_Clock_SynClkModeDiv1;
    // Конфигурируем выдачу данных для контроллера ПДП
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;
    // Укажем режим выдачи данных контроллеру ПДП
    ADC_CommonInitStructure.ADC_DMAMode = ADC_DMAMode_Circular;
    // Укажем величину задержки между стадиями Выборки
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = 10;
    // и как ... настроим!
    ADC_CommonInit(ADC3, &ADC_CommonInitStructure);

    // Триггеры для запуска АЦП перечислены в документе
    // DM00043574.pdf, стр. 329, Table 89.
    // См. так же параграф 20.3.25 ADC synchronization, стр. 563

    // Для АЦП Настроим:
    ADC_StructInit(&ADC_InitStructure);
    // 1. Разрешение в битах
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    // 2. Выравнивание данных
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    // 3. Триггер внешней синхронизации запуска (TIM1_TRGO)
    ADC_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_9;
    // 4. Запускающий АЦП фронт триггера
    ADC_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_RisingEdge;
    // 5. Число каналов для преобразования (перечисленных в секвенсоре)
    ADC_InitStructure.ADC_NbrOfRegChannel = 1;  // = числу фаз DC/DC
    // 6. Режим преобразования: по событиям или непрерывный
    ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
    // 7. Режим автоинжекции преобразований после Регулярной группы
    ADC_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable;
    // 8. Режим блокирования новых результатов, если старые не считаны
    ADC_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable;
    // и как ... настроим!
    ADC_Init(ADC3, &ADC_InitStructure);
    //ADC_InitStructure.ADC_NbrOfRegChannel = 1;
    ADC_Init(ADC4, &ADC_InitStructure);

    // ADC3: Составим очередь преобразований (заполним секвенсор)
    // Укажем источник сигнала, позицию в секвенсоре, и время выборки
    // PD11 === ADC3_IN8 ( I1 ), позиция в секвенсоре, и время выборки
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 1, ADC_SampleTime_7Cycles5);
    //ADC_RegularChannelConfig(ADC3, ADC_Channel_#, 1, ADC_SampleTime_7Cycles5);
    //ADC_RegularChannelConfig(ADC3, ADC_Channel_#, 1, ADC_SampleTime_7Cycles5);
    //ADC_RegularChannelConfig(ADC3, ADC_Channel_#, 1, ADC_SampleTime_7Cycles5);
    //ADC_RegularChannelConfig(ADC3, ADC_Channel_#, 1, ADC_SampleTime_7Cycles5);
    //ADC_RegularChannelConfig(ADC3, ADC_Channel_#, 1, ADC_SampleTime_7Cycles5);

    // ADC4: Составим очередь преобразований (заполним секвенсор)
    // Укажем источник сигнала, позицию в секвенсоре, и время выборки
    // PB15 === ADC4_IN5 ( U1 ), позиция в секвенсоре, и время выборки
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 1, ADC_SampleTime_7Cycles5);
    //ADC_RegularChannelConfig(ADC4, ADC_Channel_#, 1, ADC_SampleTime_7Cycles5);
    // ...

    // Включаем режим разбиения очереди преобразований на группы
    //ADC_DiscModeCmd(ADC3, ENABLE);
    // Укажем количество преобразований в группе
    //ADC_DiscModeChannelCountConfig(ADC3, 1);
    // Конфигурируем режим запроса канала ПДП
    ADC_DMAConfig(ADC3, ADC_DMAMode_Circular);
}

/*******************************************************************************
* Function Name : DMA_Configuration
* Description :   Настройка 5-ого канала 2-ого контроллера ПДП (DMA2_Channel5)
*     Контроллер прямого доступа в память настраиваем в режим кольцевого
*     буфера для результатов преобразования АЦП. При каждом запросе от
*     ведущего АЦП, контроллер ПДП переносит в память одно 32-х разрядное
*     слово (в котором результаты преобразования двух АЦП), инкрементирует
*     адрес ОЗУ и переходит в режим ожидания следующего запроса.
*     Когда буфер заполняется, передача данных считается завершенной,
*     контроллер формирует прерывание. Можно кратно увеличить размер
*     буфера (см. ISR_PRESCALER). Накапливать результаты и усреднять.
*******************************************************************************/
static void DMA_Configuration(void) {
    DMA_InitTypeDef DMA_InitStructure;

    // Настроим контроллер ПДП для обслуживания ADC3 и ADC4
    // Источник запроса: ADC3, DM00043574.pdf, p.276, Table 79.
    DMA_DeInit(DMA2_Channel5);
    // Укажем адрес периферийного устройства (УВВ) т.е. АЦП
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &ADC3_4->CDR;
    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC34_CDR_ADDRESS;
    // Укажем адрес массива в ОЗУ (для результатов преобразования)
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) &adc;
    // Укажем направление передачи данных: из АЦП в ОЗУ
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    // Укажем размер массива для результатов преобразования
    DMA_InitStructure.DMA_BufferSize = ISR_PRESCALER; // * число фаз
    // Укажем, что адрес УВВ (АЦП) инкрементировать не нужно
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    // Укажем, что адрес в ОЗУ необходимо инкрементировать
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    // Укажем размер данных по указанному адресу периферии
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    // Укажем размер данных по указанному адресу в ОЗУ
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    // Укажем режим кольцевого буфера для канала ПДП
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    // Установим приоритет для канала ПДП
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    // Укажем, что канал не используется для передачи данных из ОЗУ в ОЗУ
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    // Инициализируем канал контроллера ПДП
    DMA_Init(DMA2_Channel5, &DMA_InitStructure);
}

/*******************************************************************************
* Function Name : DAC_Config
* Description :   Конфигурируем 2-ой канал ЦАП-а
*******************************************************************************/
static void DAC_Configuration(void) {
    DAC_InitTypeDef     DAC_InitStructure;
    // Запросим параметры по умолчанию для ЦАП-а
    DAC_StructInit(&DAC_InitStructure);
    // DAC_DeInit();

    // Настроим 2-ой канал 1-ого ЦАП-а для отладки СУ
    //
    // Укажем триггер внешней синхронизации
    DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
    // Деактивируем встроенный генератор шума и треугольника
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
    // Определим количество разрядов для встроенного генератора
    DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;
    // Деактивируем буферизирующий усилитель на выходе ЦАП-а
    DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    // Инициализируем 2-ой канал 1-ого ЦАП-а
    DAC_Init(DAC_Channel_2, &DAC_InitStructure);
}

void MCU_Init(PWMDRV_InitTypeDef* pwm) {
    // Настраиваем тактовый генератор процессора
    RCC_Configuration();
    // Заполняем таблицу векторов прерываний
    NVIC_Configuration();
    // Конфигурируем порты ввода / вывода
    GPIO_Configuration();
    // Конфигурируем контроллеры ПДП
    DMA_Configuration();
    // Конфигурируем ЦАП-ы
    DAC_Configuration();
    // Конфигурируем АЦП12 и АЦП34
    ADC_Configuration();
    // Конфигурируем Таймеры
    TIM_Configuration(pwm);
}

void MCU_Start(void) {
    // Включаем АЦП3
    ADC_Cmd(ADC3, ENABLE);
    // Ожидаем флаг готовности АЦП3
    while (!ADC_GetFlagStatus(ADC3, ADC_FLAG_RDY));
    // Дадим волшебный пендель, чтоб все поехало
    ADC_StartConversion(ADC3);
    while(!ADC_GetFlagStatus(ADC3, ADC_FLAG_RDY));

    // Включаем АЦП4
    ADC_Cmd(ADC4, ENABLE);
    // Ожидаем флаг готовности АЦП4
    while (!ADC_GetFlagStatus(ADC4, ADC_FLAG_RDY));
    // Дадим волшебный пендель, чтоб все поехало
    ADC_StartConversion(ADC4);
    while(!ADC_GetFlagStatus(ADC4, ADC_FLAG_RDY));

    // Активируем запросы Устройств к контроллерам ПДП
    ADC_DMACmd(ADC3, ENABLE);
    // Включаем каналы контроллеров ПДП
    DMA_Cmd(DMA2_Channel5, ENABLE); // ADC3
    //DMA_Cmd(DMA1_Channel5, ENABLE); // TIM1
    //DMA_Cmd(DMA2_Channel1, ENABLE); // TIM8
    //DAC_Cmd(DAC_Channel_2, ENABLE); // DAC1
    // ...

    // Включаем прерывания Устройств
    // Сбрасываем флаг прерывания по завершению передачи
    DMA_ClearFlag(DMA2_FLAG_TC5);
    // Разрешаем прерывание по завершению передачи
    DMA_ITConfig(DMA2_Channel5, DMA_IT_TC, ENABLE);
    // ...

    // Включаем Таймеры стробирования Устройств
    // Включаем счетчик таймера ШИМ-драйвера
    TIM_Cmd(TIM1, ENABLE);

    // Не включаем главный ШИМ-выход таймера
    //TIM_CtrlPWMOutputs(TIM1, ENABLE);
    //TIM_CtrlPWMOutputs(TIM8, ENABLE);
    // ...
}

#endif

STM32F303VCT6 | STM32F303K8T6 | 32F3348DISCOVERY

Литература

  1. STM32F0, STM32F1, STM32F2, STM32F4, STM32L1 series, STM32F30x, STM32F3x8, STM32F373 lines timer overview / AN4013 [APPLICATION NOTE] Rev.: 4, DocID022500, January 2015 // STMicroelectronics web site. – URL: http://www.st.com/web/en/resource/ technical/document/ application_note/DM00042534.pdf. Дата обращения: 18.01.2016.
  2. Клиначёв Н.В. Программный код векторной системы управления для синхронного двигателя с обработкой сигналов резольвера. // Моделирующая программа Jigrein: Теория, программа, руководство, модели. – 2006-2016 гг. – URL: http://model.exponenta.ru/k2/ Jigrein/JS/fwlink.htm#AD65. Дата обращения: 18.01.2016.
  3. Клиначёв Н.В., Маргацкая Е.А. Прецизионный электромагнитный привод для малых линейных перемещений рабочего органа. Си-код цифровой системы управления для 32-х разрядного микроконтроллера с целочисленным арифметико-логическим устройством. // Моделирующая программа Jigrein: Теория, программа, руководство, модели. – 2006-2016 гг. – URL: http://model.exponenta.ru/k2/ Jigrein/JS/fwlink.htm#FDEA. Дата обращения: 28.04.2016.

18.01.2016