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

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

Рабочие файлы: [ЦСУ 4 ЛЭМД Модель] [ЦСУ 4 ЛЭМД int32] [ЦСУ 4 ЛЭМД [-1, +1]]
[ЦСУ 4 ЛЭМД Шум] [ЦСУ 4 ЛЭМД Си-код]

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

Ключевые слова: ЛЭМД, электромагнитный, электропривод, Си-код, IQ-Math, IQ-арифметика, относительный, цифровой, дискретный, ПИД-регулятор, САР, АЛУ, микроконтроллер, ШИМ, моделирование в Jigrein4WEB.

Введение

В настоящей статье представлены и документированы листинги Си-кода цифровой управляющей системы (ЦСУ) линейного электромагнитного привода (следящей системы с машиной постоянного тока), который мало чем отличается от аналогичного программного кода для DC/DC-преобразователей [1]. Инженеру настоятельно рекомендуется скопировать листинги кода упомянутых систем управления в файлы. Разместить их в двух разных директориях (в разных папках). И воспользоваться инструментами файлового менеджера Total Commander "Синхронизация каталогов" и "Сравнение содержимого файлов". Различия выявят неизменные и настраиваемые фрагменты кода. В документе [2] доступен программный код векторной системы управления для синхронного двигателя. Он сложнее, но структура программы, методика разработки и стиль написания кода совпадают.

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

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

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

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

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

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

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

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

Второй линейный блок кода программы – это функция обработки прерывания, реализующая управление электромагнитом (машиной постоянного тока). В зависимости от микроконтроллера и его ШИМ-драйвера это может быть прерывание АЦП, таймера, или контроллера ПДП. Источник прерывания не столь важен. Важно чтобы при обработке результатов измерений сигналов обратных связей использовался правильно заданный шаг дискретизации цифровой системы управления – параметр 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). А во-вторых, легче не допустить ошибку при выборе параметров системы управления. Поскольку в относительных величинах, для контролируемых объектов с отличающимися на несколько порядков паспортными данными, параметры регуляторов будут принадлежать одной декаде.

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

...

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

Для реализации цифровой системы управления линейного электромагнитного привода из модулей микроконтроллера необходимо собрать обслуживающие задачу аппаратные машины состояний. Листинг файла mcu-s.c содержит код конфигурации машины состояний для обслуживания одного привода. Но при необходимости его можно дополнить, поскольку 32-х разрядный ARM-процессор STM32F303xB / STM32F303xC (ядро Cortex-M4, 72 МГц) имеет необходимую периферию и может контролировать движение двух независимых приводов с линейными электромагнитными двигателями.

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

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

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

Приложение. Листинги файлов исходного кода двухконтурной, цифровой системы управления прецизионного электромагнитного привода для малых линейных перемещений рабочего органа, реализованной на 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: 27 В, 6 * 170 А
#define PRIVATE_KIT_3 // Самодельный Power KIT для ЛЭМД: 24 В, 1.0 А

#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 для ЛЭМД: 24 В, 1.0 А

#define PWM_FREQUENCY   44000   // Частота переключения стоек силового моста, (Гц)
#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.4256  // Смещение нуля датчика тока      (доля шкалы АЦП)
#define VS_10           0.0000  // Смещение нуля датчика положения (доля шкалы АЦП)
// ************************************************************************** //
// Исходные данные к расчету параметров системы управления преобразователя
// ************************************************************************** //
#define BASE_VOLTAGE    24.0    // Напряжение dc-шины, соответствующие шкале АЦП, (В)
#define BASE_CURRENT    0.95    // Ток дросселя,  соответствующий  1/2 шкалы АЦП, (А)
#define BASE_POSITION   4.00    // Отклонение РО, соответствующий  1/2 шкалы АЦП, (мм)
#define LD      0.01            // Индуктивность соленоида (ЛЭМД), (Гн)
#define RD      25.0            // Сопротивление ЛЭМД + ключей + шунта + ШИН, (Ом)
#define C2      0.02            // Момент инерции якоря и РО, (кг)
#define KK_C    0.74            // Коэффициент усиления контура тока:  1 .. L/R / (RC)
#define KPWM    (1.0 * 22)      // Kпн = KPWM = Uдр_MAX (тут мостовая схема)
// ************************************************************************** //
// Параметры системы управления DC/DC-преобразователя в относительных величинах
// ************************************************************************** //
// Коэффициент усиления P-канала Регулятора Тока: Kрт = Kкт Rдр / Kдт / Kпн
#define KP_C    (KK_C * RD / (1.0 / BASE_CURRENT) / KPWM)
// Постоянная  времени  I-канала Регулятора Тока: Tдр = Lдр / (Rдр + ..), сек
#define TI_C    (LD / RD)
// Коэффициент усиления P-канала РП: Kрн = Kкн Kдт / Rдр / Kдн
#define KP_V    (0.25 / BASE_CURRENT * BASE_POSITION)
#define TI_V    0.03            // Постоянная  времени  I-канала РП, сек
#define TD_V    0.003           // Постоянная  времени  D-канала РП, сек
#define LEV     0.05            // Ограничение ошибки (по модулю) в И-канале: 4..10%
#define LDV     0.03            // Ограничение приращения ошибки  в D-канале: 4..10%

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

// ...

#endif

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

/******************************************************************************
* File Name :   powerlib.h
* Author :      Клиначев Николай Васильевич
* Version :     1.1.0
* Date :        201512
* Description : Библиотеки IQ-Math library и Power Control library
*       Библиотека математических преобразователей, необходимых при
*       реализации цифровых систем управления электромагнитных приводов
*       и необходимые макросы целочисленной арифметики (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

// ************************************************************************** //
// Глобальные уставки Цифровой системы управления для ЛЭМД
typedef struct
{
    _iq gP;    // Output: Уставка напряжения в ??? !абсолютных! ??? ед.
    _iq gI;    // Output: Уставка тока в относительных ед. (pu), [0.00, +1.0]
    unsigned int gP_TicCounter;   // счетчик тиков программной машины состояний
}
SETPOINTS_TypeDef;

// ************************************************************************** //
// Измеряемые величины на силовом модуле электромагнитного приводова
typedef struct
{
    _iq yP;     // Output: Сигнал датчика положения  (сигнал ОС), [0.0, ?1.0]
    _iq yI;     // Output: Ток электромагнита (ЛЭМД) (сигнал ОС), [-1.0, 1.0]
    _iq I0;     // Const:  Смещение нуля датчика тока ЛЭМД,       0.5 +/-10 %
    _iq yR;     // Output: Аналоговое задание (резистор),         [0.0, ?1.0]
}
PWKIT_TypeDef;

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

#define PID_P_MACRO(pr)                                                        \
{                                                                              \
    _iq pr_Lu = *pr.gI;                         /* Предел для вых. сигнала   */\
    _iq pr_zi;                                  /* Вход регистра задержки    */\
    _iq pr_dX;                                  /* Приращение ошибки         */\
    pr_zi = *pr.gP - *pr.yP;                    /* Ошибка регулятора         */\
    pr_dX =  pr_zi -  pr.zx;                    /* Приращение ошибки         */\
    pr_dX = pr_dX > pr.Ld ? pr.Ld : pr_dX < -pr.Ld ? -pr.Ld : pr_dX;           \
    pr.uI = pr.zo + _IQmpy(pr_zi, pr.Kp)        /* Выход = I-канал + P-канал */\
                  + _IQmpy(pr_dX, pr.Kd);       /*                 + D-канал */\
    pr.uI = pr.uI > pr_Lu ? pr_Lu : pr.uI < -pr_Lu ? -pr_Lu : pr.uI;           \
    /* ---------------------------------------------------                   */\
    pr.zx = pr_zi;                              /*  s : Обновили выход 1/z   */\
    pr_zi = pr_zi > pr.Lx ? pr.Lx : pr_zi < -pr.Lx ? -pr.Lx : pr_zi;           \
    pr_zi = pr.zo + _IQmpy(pr_zi, pr.Ki);       /* 1/s: Значение на входе    */\
    pr_zi = pr_zi > pr_Lu ? pr_Lu : pr_zi < -pr_Lu ? -pr_Lu : pr_zi;           \
    pr.zo = pr_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  uN;    // 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.uN = -ir.uV;                           /* Инверсный Выход регулятора  */\
    ir.uV = ir.uV >= _IQ(+1.0) ? _IQ(+1.0) - 1           /*   ..00011111..   */\
          : ir.uV <  _IQ(-1.0) ? _IQ(-1.0) : ir.uV;      /*   ..11100000..   */\
    ir.uN = ir.uN >= _IQ(+1.0) ? _IQ(+1.0) - 1           /*   ..00011111..   */\
          : ir.uN <  _IQ(-1.0) ? _IQ(-1.0) : ir.uN;      /*   ..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;                                                             \
}

/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
#ifndef GL_Q            // Точность типа данных для IQ-вычислений: 20 22 24
#define GL_Q 22         // Определять здесь. В *.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 / 2)
// PWM_RESOLUTION делим на два для настройки таймера с реверсируемым счетчиком
// Для симметрии ШИМ-а PWM_RESOLUTION должно быть нечетным числом
//#define PWM_RESOLUTION  ((((unsigned int)(CPU_SYSCLK / PWM_FREQUENCY)) \
                                                               >> 1) | 1)
// В данном проекте оно без остатка должно делиться на 8
#define PWM_RESOLUTION  ((((unsigned int)(CPU_SYSCLK / (8 * PWM_FREQUENCY)) \
                                                         >> 1) << 3) | 1)
#define FSM_TIMESTEP    (((unsigned int)(0.02 / TIMESTEP)) * TIMESTEP)

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
// Переменные цифровой системы управления для DC/DC преобразователя
SETPOINTS_TypeDef       SetPnt; // Глобальные уставки ЦСУ для ЛЭМД
PWKIT_TypeDef           PwKit;  // Измеряемые величины на силовом модуле
PID_P_TypeDef           pid_p;  // ПИД-регулятор контура положения
PID_I_TypeDef           pid_i;  //  ПИ-регулятор контура тока

// Переменные низкоприоритетной Машины Состояний
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; }
    SetPnt.gP  = _IQmpy(_IQ(6 /* мм */ / BASE_POSITION), PwKit.yR);
    // Опрос кнопки включения
    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.gP_TicCounter) {
        SetPnt.gP_TicCounter -= 1;
    } else {
        SetPnt.gP_TicCounter = (unsigned int) (0.125 /* сек */ / FSM_TIMESTEP);
        SetPnt.gP = SetPnt.gP < _IQ(1.9 /* мм */ / BASE_POSITION)
            ? _IQ(2.2 / BASE_POSITION) : _IQ(1.2 / BASE_POSITION);
    }
    // Декрементируем счетчик времени блокировки кнопки
    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.gP = _IQ(2.5 /* мм */ / BASE_POSITION);
    SetPnt.gP_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.gP = _IQ(2.0 /* мм */ / BASE_POSITION);            //             *
    SetPnt.gI = _IQ(0.52 /* А */ / BASE_CURRENT);             //             *
    SetPnt.gP_TicCounter = 0;                                 //             *
    // Измеряемые величины на силовом модуле *********************************
    PwKit.I0  = _IQ(CS_10);                                   //             *
    // PID-Регулятор контура положения c Ограничителем ***********************
    pid_p.Kp  = _IQ(KP_V);                                    // мастер      *
    pid_p.Ki  = _IQ(KP_V / TI_V * TIMESTEP);                  // мастер      *
    pid_p.Kd  = _IQ(KP_V * TD_V / TIMESTEP);                  // мастер      *
    pid_p.Lx  = _IQ(LEV);                                     // тюнинг LEV  *
    pid_p.Ld  = _IQ(LDV);                                     // тюнинг LDV  *
    // Подчиненный PI-регулятор контура тока *********************************
    pid_i.Kp  = _IQ(KP_C);                                    // тюнинг KK_C *
    pid_i.Ki  = _IQ(KP_C / TI_C * TIMESTEP);                  // не меняется *
}

/*******************************************************************************
* Function Name : DCDC_CntrlUnit_CreateWires
* Description :   ...
*     Процедура выполняет построение цифровой системы управления
*     из вычислительных модулей - экземпляров объектов.
*     Суть построения ЦСУ - определение схемы передачи аргументов.
*     Выход - это атрибут модуля (переменная принадлежит объекту).
*     Вход - не является атрибутом модуля (объект имеет указатели
*     для подключения к аргументам).
*******************************************************************************/
static void DCDC_CntrlUnit_CreateWires(void) {
    // ПИ-регулятор контура положения (РП) (с Ограничителем на выходе) *********
    pid_p.gP  = &SetPnt.gP;     // Подключили Уставку напряжения (задание для РП)
    pid_p.yP  = &PwKit.yP;      // Подключили вых. напряжение  (сигнал ОС для РП)
    pid_p.gI  = &SetPnt.gI;     // Подключили Уставку тока (момента)
    // ПИ-регуляторы подчиненных контуров тока (РТ) ****************************
    pid_i.gI  = &pid_p.uI;      // Подключили к регулятору тока задающий сигнал
    pid_i.yI  = &PwKit.yI;      // Подключили ОС - ток ЛЭМД
}

/*******************************************************************************
* Function Name : DCDC_CntrlUnit_Reset_IC
* Description :   ...
*     Процедура сброса / предустановки Начальных Условий для элементов
*     с эффектом памяти (регистров задержки и интеграторов).
*     Если программа ЦСУ по блок-схеме собрана корректно, то для сброса
*     к начальному состоянию достаточно предустановить начальные условия
*     на регистрах задержки (на интеграторах). Остальные координаты будут
*     корректно рассчитаны от источников Уставок до Силового модуля.
*******************************************************************************/
static void DCDC_CntrlUnit_Reset_IC(void) {
    pid_p.zx  =
    pid_p.zo  =
        _IQ(0.00);      // Начальное состояние интегратора контура напряжения
    pid_i.zo  =         // Безопасное состояние интегратора контура тока
        _IQ(0.00);      // вот оно Самое.
}

/*******************************************************************************
* Function Name : DMA2_Channel5_IRQHandler
* Description :   Самая главная процедура программы (обработка прерывания)
*     Лучше всего сконфигурировать функционирование Таймера, АЦП,
*     и контроллера ПДП, так чтобы прерывание, которое формирует последний,
*     не приходилось прореживать программно. Т.е. чтобы период вызова
*     соответствующей функции совпадал с требуемы шагом дискретизации
*     цифровой системы управления (TIMESTEP).
*     Поглощающий прерывания / события счетчик реверсов Таймера лучше
*     не использовать. Рекомендуется увеличить буфер АЦП для результатов
*     оцифровки сигналов. И корректно настроить котроллер ПДП.
*     В этом случае результаты оцифровки можно усреднять.
*******************************************************************************/
#ifdef STM32_F3_DISCOVERY
ADC34RESULT_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;
    }
    // -------------------------------------------------------------------------
    //  Доприводим кривизну в датчиках
    // -------------------------------------------------------------------------
    PwKit.yI = -PwKit.yI;       // сигнал ОС по току не с тем знаком получаем
    PwKit.yP <<= 1;             // сигнал ДП 0.3 .. 1.5 В привели к [0.2, +1.0]
    // -------------------------------------------------------------------------
    //  Вызываем макрос ПИД-регулятора контура положения
    // -------------------------------------------------------------------------
    PID_P_MACRO(pid_p);
    pid_p.uI = -pid_p.uI;       // а можно просто поменять выводы обмотки ДПТ
    // -------------------------------------------------------------------------
    //  Вызываем макрос ПИ-регулятора контура тока со встроенным ограничителем
    // -------------------------------------------------------------------------
    PID_I_MACRO(pid_i);
    // -------------------------------------------------------------------------
    //  Обновляем Регистры Сравнения Таймеров или каналов одного Таймера
    // -------------------------------------------------------------------------
    PWM_MACRO(pid_i);

    #if defined STM32_F3_DISCOVERY
    // ------------------------------------------------------------------------------
    //  Используем ЦАП для контроля координат ЦСУ осциллографом
    // ------------------------------------------------------------------------------
    // Шаблон преобразования целого числа A [0..3600] к полной шкале ЦАПа (12bit)
    // _iq dd = (((long) A) << (GL_Q - 11)) - (_IQ(3600.0 / 4096 / 2) << 1);
    // dd = _IQmpy(dd, _IQ(4096.0 / 3600)) + _IQ(1.0); // результат GL_Q: [0.0, 2.0]
    _iq signal1 = _IQmpy(pid_p.zo,  _IQ(1.0)) + _IQ(1.0);
    _iq signal2 = _IQmpy(pid_p.uI,  _IQ(1.0)) + _IQ(1.0);
    DAC_MACRO(_IQtoIQ15(VirtualTimer & 1 ? signal1 : signal2));
    #endif

    GPIO_TOGGLE_MACRO(); // Формируем синхросигнал (инвертируем бит порта)
    // STM32_F3_DISCOVERY:
    //      С оптимизацией  по скорости время исполнения функции:  4.0 uS
    //      Без оптимизации по скорости время исполнения функции:  ?.? 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   16      // Количество усредняемых выборок
/* Область памяти, для обращения к которой можно использовать:
* либо имя массива в котором хранятся 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.Dual[ 4] == { adc.Single[ 8], adc.Single[ 9] }
* adc.Dual[ 5] == { adc.Single[10], adc.Single[11] }
* adc.Dual[ 6] == { adc.Single[12], adc.Single[13] }
* adc.Dual[ 7] == { adc.Single[14], adc.Single[15] }

* adc.Dual[ 8] == { adc.Single[16], adc.Single[17] }
* adc.Dual[ 9] == { adc.Single[18], adc.Single[19] }
* adc.Dual[10] == { adc.Single[20], adc.Single[21] }
* adc.Dual[11] == { adc.Single[22], adc.Single[23] }
* adc.Dual[12] == { adc.Single[24], adc.Single[25] }
* adc.Dual[13] == { adc.Single[26], adc.Single[27] }
* adc.Dual[14] == { adc.Single[28], adc.Single[29] }
* adc.Dual[15] == { adc.Single[30], adc.Single[31] }
*
* adc.Dual[16] == { adc.Single[32], adc.Single[33] }
* adc.Dual[17] == { adc.Single[34], adc.Single[35] }
*
* adc.Single[0] ==  АЦП3, 1-ая выборка без знака (      ток якоря ЛЭМД)
* adc.Single[1] ==  АЦП4, 1-ая выборка без знака (положение якоря ЛЭМД)
* adc.Single[2] ==  АЦП3, 2-ая выборка без знака (      ток якоря ЛЭМД)
* adc.Single[3] ==  АЦП4, 2-ая выборка без знака (положение якоря ЛЭМД)
* adc.Single[32]  -  значение, полученное при самокалибровке АЦП3
* adc.Single[33]  -  значение, полученное при самокалибровке АЦП4
* adc.Single[34]  -  среднее по 16 выборкам АЦП3, тип данных: _iq16
* adc.Single[35]  -  среднее по 16 выборкам АЦП4, тип данных: _iq16
*/
typedef union {
    uint32_t   Dual[ISR_PRESCALER     + 2];   // массив 32-х разрядных слов
    uint16_t Single[ISR_PRESCALER * 2 + 4];   // массив 16-х разрядных полуслов
} ADC34RESULT_TypeDef;                        // сырые данные АЦП3 и АЦП4
extern ADC34RESULT_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.uV), PWM_RESOLUTION));      \
    TIM_SetCompare2(TIM1, _Q15toBASE(_IQtoIQ15(pwm.uN), PWM_RESOLUTION));      \
}
// Макрос измерения смещения нуля датчиков тока фаз DC/DC-преобразователя ******
#define ADC_OFFSET_MACRO(PwKit) {                                              \
    /* Оцифрованные, униполярные, 16-ти битные значения преобразуем          */\
    /* к относительным величинам в диапазоне от _IQ(0.00) до _IQ(+1.0).      */\
    /* Усредним сигналы апериодическим звеном 1-ого порядка с большой        */\
    /* постоянной времени (T = 1/0.5 сек). Ожидаемый результат для           */\
    /* токов близок к _IQ(0.5000). Фиксируем его в powerkits.h               */\
    PwKit.I0 += _IQmpy(((long) adc.Single[34] << (GL_Q - 16))                  \
                            - PwKit.I0, _IQ(0.5 * TIMESTEP));                  \
/*  PwKit.yV0 += _IQmpy(((long) adc.Single[?] << (GL_Q - 16))                  \
                         - PwKit.yV0, _IQ(0.2 * TIMESTEP));                  */\
}
// Макрос измерения тока фаз DC/DC-преобразователя и выходного напряжения  *****
#define ADC_MEAS_MACRO(PwKit)   {                                              \
    /* Сложим пары униполярных значений (для усреднения по каналам)          */\
    adc.Dual[17] = adc.Dual[ 0] + adc.Dual[ 1]                                 \
                 + adc.Dual[ 2] + adc.Dual[ 3]                                 \
                 + adc.Dual[ 4] + adc.Dual[ 5]                                 \
                 + adc.Dual[ 6] /* + adc.Dual[ 7] */                           \
                 + adc.Dual[ 8] + adc.Dual[ 9]                                 \
                 + adc.Dual[10] + adc.Dual[11]                                 \
                 + adc.Dual[12] + adc.Dual[13]                                 \
                 + adc.Dual[14] + adc.Dual[15];    /* тип результата: _iq16  */\
    /* Одна выборка в канале тока левая (R). Компенсируем её соседней        */\
    adc.Single[34] += adc.Single[12];              /* вместо  adc.Single[14] */\
    adc.Single[35] += adc.Single[15];              /* незабыл adc.Single[15] */\
    /* Оцифрованные, униполярные, 16-ти битные значения преобразуем          */\
    /* к относительным величинам: I == [_IQ(-1.0), _IQ(+1.0)]                */\
    /*                            U == [_IQ(0.00), _IQ(+1.0)]                */\
    PwKit.yI = _IQmpy2((((long) adc.Single[34]) << (GL_Q - 16)) -  PwKit.I0);  \
    PwKit.yP =         (((long) adc.Single[35]) << (GL_Q - 16)) - _IQ(VS_10);  \
    PwKit.yR =         (((long) adc.Single[14]) << (GL_Q - 12)) - _IQ(0.0  );  \
    PwKit.yR = _IQmpy(PwKit.yR, _IQ(1.5));                                     \
}

#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.I0  += _IQmpy(((long) adc[0] << (GL_Q - 12))                         \
                         - PwKit.I0 , _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.yI  = _IQmpy2((((long) adc[0]) << (GL_Q - 12)) - PwKit.I0 );         \
/*  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

/*******************************************************************************
* 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_GPIOE |   //
        //RCC_AHBPeriph_ADC12 |   //
        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_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 = 0;
    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);
    // Порт A: Терминал  3 - аналоговый вход для PA3 === ADC1_IN4  ( Pos )
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт D: Терминал 11 - аналоговый вход для PD11 === ADC3_IN8  ( I[] )
    // Порт B: Терминал 15 - аналоговый вход для PB15 === ADC4_IN5  ( Pos )
    // Порт B: Терминал  0 - аналоговый вход для PB0  === ADC3_IN12 (  R  )
    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);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    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_Pin_9;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт B: Терминал 13 конфигурируем для вывода PWM
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // Подключаем терминалы к альтернативным источникам сигнала (TIM1_PWM)
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_6);  // TIM1_CH1
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_6);  // TIM1_CH2
  //GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_6); // TIM1_CH3
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_6); // TIM1_CH1N
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_6); // TIM1_CH2N
  //GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_4); // TIM1_CH3N
}

/*******************************************************************************
* 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 = (1) - 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[ISR_PRESCALER * 2 + 0] = ADC_GetCalibrationValue(ADC3);
    // Ожидаем завершения калибровки АЦП4
    while (ADC_GetCalibrationStatus(ADC4) != RESET);
    adc.Single[ISR_PRESCALER * 2 + 1] = 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 = ISR_PRESCALER;
    // 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  (  I  ), позиция в секвенсоре, и время выборки
    // PB00 === ADC3_IN12 (  R  ), позиция в секвенсоре, и время выборки
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  1, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  2, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  3, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  4, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  5, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  6, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  7, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_12, 8, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8,  9, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 10, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 11, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 12, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 13, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 14, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 15, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_8, 16, ADC_SampleTime_7Cycles5);

    // ADC4: Составим очередь преобразований (заполним секвенсор)
    // Укажем источник сигнала, позицию в секвенсоре, и время выборки
    // PB15 === ADC4_IN5  ( Pos ), позиция в секвенсоре, и время выборки
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  1, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  2, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  3, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  4, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  5, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  6, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  7, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  8, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5,  9, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 10, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 11, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 12, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 13, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 14, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 15, ADC_SampleTime_7Cycles5);
    ADC_RegularChannelConfig(ADC4, ADC_Channel_5, 16, 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_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);
    //ADC_DMACmd(ADC1, ENABLE);
    // Включаем каналы контроллеров ПДП
    DMA_Cmd(DMA2_Channel5, ENABLE); // ADC3
    //DMA_Cmd(DMA1_Channel1, ENABLE); // ADC1
    //DMA_Cmd(DMA1_Channel5, ENABLE); // TIM1
    //DMA_Cmd(DMA2_Channel1, ENABLE); // TIM8
    //DAC_Cmd(DAC_Channel_2, ENABLE); // DAC1
    // ...

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

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

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

#endif

STM32F303VCT6 | STM32F303K8T6

Литература

  1. Клиначёв Н.В. Си-код цифровой системы управления многофазного импульсного DC/DC преобразователя для 32-х разрядного микроконтроллера с целочисленным арифметико-логическим устройством. // Моделирующая программа Jigrein: Теория, программа, руководство, модели. – 2006-2016 гг. – URL: http://model.exponenta.ru/k2/ Jigrein/JS/fwlink.htm#F923. Дата обращения: 8.02.2016.
  2. Клиначёв Н.В. Программный код векторной системы управления для синхронного двигателя с обработкой сигналов резольвера. // Моделирующая программа Jigrein: Теория, программа, руководство, модели. – 2006-2016 гг. – URL: http://model.exponenta.ru/k2/ Jigrein/JS/fwlink.htm#AD65. Дата обращения: 18.01.2016.

28.04.2016