Программный код ШИМ-драйвера для питания секций статора 3-х фазного двигателя переменного тока реализованный на ARM-процессоре (ядро Cortex-M4)

Рабочие файлы: [Шаг 1: ШИМ-драйвер] [Шаг 2: ШИМ +АЦП +ПДП] [Шаг 3: Регуляторы тока]
[Шаг 4: Вкл. мК и Мост] [Шаг 5: ARM PMSM FOC] [Шаг 6: ARM СДПМ + СКВТ]
[Модель кода SVPWM-драйвера] [IQdiv и IQatan2]

Введение

Специализированные 32-х разрядные микроконтроллеры фирмы TI разрабатывались для решения задачи управления электродвигателями. К сожалению, политика вмешивается в работу инженеров – в России применение микроконтроллеров фирмы ТИ не приветствуется. У разработчиков соответствующих систем управления остается альтернатива – 32-х разрядные ARM-процессоры общего назначения. Они разрабатывались для решения задач цифровой обработки сигналов и связи. Но функциональность, необходимую для управления электродвигателями, предоставляют. К сожалению, открытые специализированные библиотеки группы "motor control" (без "закладок") и примеры соответствующего кода для ARM-процессоров отсутствуют. Специалистам в области цифровых систем управления крайне сложно начать разработку программы. Ниже представлен проект и описывается программа ШИМ-драйвера для питания секций статора 3-х фазного двигателя переменного тока.

В России 32-х разрядные ARM-процессоры выпускает ЗАО "ПКК Миландр" (серия 1986ВЕ9х) и ОАО "НИИЭТ" (К1921ВК01Т | NT32M4F1), но купить и скачать документацию легче у европейцев. Представляемый вниманию ШИМ-драйвер написан для комплекта разработчика "STM32 F3 Discovery" (с процессором STM32F303xC / ядро Cortex-M4 / 72 МГц). Отлажен – с помощью компилятора IAR (EWARM). Математический сопроцессор (FPU) не использовался. Главная процедура обработки прерывания таймера выполняется за 10 мкс. Согласно оценке автора, код векторной системы регулирования скорости вала электродвигателя с датчиком положения данный процессор будет исполнять за 22 мкс. Без датчиков – 30 мкс (без FPU). Следящего привода с параболическим регулятором – 26 мкс (без FPU).

Первая компиляция проекта

Каким бы компилятором вы не воспользовались, первая удачная трансляция и прошивка комплекта с ARM-процессором – это всегда праздник. Тому мешает множество факторов. Каждый производитель процессора пишет, а после выпуска продукта на рынок, обновляет, собственную библиотеку "стандартных" периферийных драйверов. Причем для каждого процессора в семействе "стандартный" периферийный драйвер собственный. Процессоры производят одни фирмы. Компиляторы делают другие. Демонстрационные платы пытаются делать третьи. Над всеми стоит Майкрософт с правилами безопасности для программ установленных в папку %ProgramFiles%. И крепит положение великий и могучий язык C/C++ с детской болезнью переопределения типов и ни как не могущий определиться с тем, как же передавать аргументы: по ссылке по указателю или использовать для решения задачи массивы массивов. Ситуация кратко описывается поговоркой – "У семи нянек дитя без глазу". Посоветую следующий алгоритм действий:

  1. Установите компилятор IAR (EWARM). Убедитесь в том, что вы установили драйвер для прошивки демонстрационной платы (в большинстве случаев его распространяет не изготовитель процессора, а разработчик компилятора). На этом этапе важно понимать – компилятор хоть и имеет стандартные библиотеки для ARM-процессоров, но они, скорее всего, устарели и бесполезны.
  2. Воспользуйтесь "Информационным центром" компилятора, скачайте актуальную библиотеку "стандартных" периферийных драйверов, от вашего производителя, для вашего процессора, и для вашей демонстрационной платы.
  3. Попробуйте выполнить компиляцию самых простых проектов. Обычно это примеры конфигурирования драйвера GPIO (драйвер портов ввода вывода общего назначения). Этот пример чаще всего проверяют разработчики компиляторов при смене форматов файлов рабочего проекта и пространства. Не удивляйтесь, если вся библиотека "стандартных" периферийных драйверов вместе с библиотеками цифровой обработки сигналов и ОС реального времени окажется в папке "Мои документы". Это не вы начудили. Это в инсталляционном пакете любого компилятора нет корректно написанной программы обновления. И после установки, доступ в папку %ProgramFiles% для обновления библиотек компилятору заказан. Убедитесь в том, что плата успешно прошивается, а светодиод на плате мигает. Если фокус не удастся, то проверьте, какой из драйверов компилятор пытается использовать для прошивки и отладки вашей платы. А так же посетите сайт производителя процессора. Проверьте, нет ли новой версии драйвера.
  4. Если какой либо проект вам удалось запустить, то просто замените в нем файл main.c (см. листинг ниже по тексту). Скопируйте в папку проекта файлы mсlib.c и mсlib.h. Добавьте их к проекту. Если в проекте был файл main.h, то проверьте все ли подключаемые в нем библиотеки подключены в файле main.c и удалите main.h. Выполните трансляцию. Не пугайтесь, когда увидите, что компилятор транслирует все подряд. Это из него крутизна прет. Все это прошиваться в процессор не будет. Если появиться ошибки, то ищите в проекте c-файл объявления и обработки стандартных прерываний, закомментируйте переменные, которые не будут найдены.
  5. После удачного построения проекта ШИМ-драйвера измените его настройки. Отключите математический сопроцессор (FPU). И, на период отладки, отключите оптимизацию кода. 7 КБ счастья вы получите.

Библиотеки IQ-Math library и Motor 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). Для выполнения операции умножения определен макрос _IQmpy(A, B). Отличие реальной IQ-математики от приведенного примера заключается в том, что в качестве большого числа $Q$ используется число кратное степени двойки. Человеку легко делить числа на 10, а компьютеру – на 2. Основной тип данных – _iq24. В котором цифры кодируются 32-мя битами. Старшие 8 бит – это знак и целая часть числа. Младшие 24 бита – это дробная часть числа. Подобным образом 32-мя битами могут быть представлены разные типы данных: _iq30, _iq29, .., _iq1. Используя операцию сдвига можно переходить от одного типа данных к другому. Для чего, например, определен макрос _IQ24toIQ15. Каждому из названных типов данных соответствуют: погрешность представления числа и предельные значения (не вызывающие перегрузку мантиссы).

Трехфазный ШИМ-драйвер с пространственно-векторной модуляцией

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

В процедуре обработки прерывания реализованы: интегратор угла, макрос выборки и масштабирования значений синуса и косинуса из таблицы, преобразователи Парка и Кларка, преобразователь формы трехфазной синусоидальной последовательности, осуществляющий векторную модуляцию (SVPWM), блок защитного кода, не допускающий перегрузку мантиссы, и макросы обновления регистров сравнения таймера. Перечисленные преобразователи позволяют двумя переменными (величиной двух катетов) контролировать гипотенузу – начальную амплитуду и фазу трехфазной синусоидальной последовательности. Еще одна переменная позволяет контролировать частоту.


PA8       ┌─────┐  UH  │ │ 0.1           PB13      ┌─────┐  UL  │ │ 0.1
   ───────┤ 3k3 ├──┬───┤ ├──────┐           ───────┤ 3k3 ├──┬───┤ ├──────┐
PA3       └─────┘  │   │ │      │        NC        └─────┘  │   │ │      │
   ─ ─ ─ ─ ─ ─ ─ ─ ┘            │           ─ ─ ─ ─ ─ ─ ─ ─ ┘            │
PA9       ┌─────┐  VH  │ │ 0.1  │        PB14      ┌─────┐  VL  │ │ 0.1  │
   ───────┤ 3k3 ├──┬───┤ ├──────┤           ───────┤ 3k3 ├──┬───┤ ├──────┤
PA4       └─────┘  │   │ │      │        NC        └─────┘  │   │ │      │
   ─ ─ ─ ─ ─ ─ ─ ─ ┘            │           ─ ─ ─ ─ ─ ─ ─ ─ ┘            │
PA10      ┌─────┐  WH  │ │ 0.1  │        PB15      ┌─────┐  WL  │ │ 0.1  │
   ───────┤ 3k3 ├──────┤ ├──────┤           ───────┤ 3k3 ├──────┤ ├──────┤
          └─────┘      │ │      │                  └─────┘      │ │      │
GND or NC                       │        GND or NC                       │
   ─────────────────────────────┘           ─────────────────────────────┘

    a) для проверки управления               б) для проверки управления
       верхних ключей и АЦП                     нижних ключей

Рис. 1. Схема для проверки программы 3-х фазного ШИМ-драйвера. И два способа её подключения к отладочному комплекту с ARM-процессором "STM32 F3 Discovery". На первом этапе, когда драйвер АЦП ещё не написан, выводы к портам PA3, PA4 не подключаются

Для проверки и отладки программы рекомендуется собрать электрическую схему представленную на рис. 1. Используйте навесной монтаж, и, для соединения с платой, провода длинной 12..15 см с одиночными разъемными контактами. Провода, показанные с пунктиром, к плате не подключайте (они потребуются позже для проверки АЦП). Вам потребуется двухканальный аналоговый осциллограф. Схема является маломощной симметричной трехфазной нагрузкой – фильтром низких частот, которую можно подключить непосредственно к выводам процессора. Она необходима для контроля параметров трехфазного напряжения (амплитуды, частоты, фазы и формы), которое будет питать статор двигателя.

Подключите схему к портам ввода / вывода процессора (к контактам разъема платы) PA8, PA9, PA10, и не подключайте провода к контактам PA3, PA4, GND. Осциллограф подключите к выводам любого из конденсаторов (крокодил – к нейтральной точке). Выполните построение проекта и запустите отладчик компилятора в непрерывном режиме. Если вы увидите синусоиду частотой 30 Гц, то результат верный. Подключите второй канал осциллографа к другому конденсатору. Убедитесь в том, что фазовый сдвиг между синусоидами составляет 120 градусов.

Сохраняя условия предыдущего эксперимента, подключите нейтральную точку нагрузки к контакту GND отладочной платы с процессором. Осциллограммы напряжений должны измениться. Форму этих сигналов электронщики не обсуждают, и имени у нее нет. До заземления нейтральной точки осциллограф показывал "напряжения на секциях обмотки статора двигателя", после её заземления – "напряжения на стойках моста". Один из каналов осциллографа подключите к контакту PA8. Убедитесь в том, что одну треть периода каждая из стоек моста переключаться не будет. Это главное положительное качество пространственно-векторной модуляции (SVPWM).

Установите точку остановки (break-point) в конце процедуры обработки прерывания таймера. Компилятор остановит исполнение программы. Добавьте окно контрольных выражений (Watch window). И в нём – переменную SetPnt. Попробуйте отключить / включить векторную модуляцию – SetPnt.SVPWM_Enable (!ENABLE). Увеличить / уменьшить период ШИ-модуляции в два раза – переменная SetPnt.gS.

Измените текст программы. Вместо переменной ab2uvw.u, для обновления регистра сравнения таймера используйте переменную Rotor.Angle. Выполните построение проекта, запустите отладку в непрерывном режиме, синхронизируйте осциллограф по сигналу Rotor.Angle. Две переменные SetPnt.GdV и SetPnt.GqV – это два катета прямоугольного треугольника, которые позволяют контролировать амплитуду и фазу трехфазной синусоидальной последовательности. Меняя величину катетов и сохраняя гипотенузу постоянной, убедитесь в том, что начальная фаза сигналов меняется как положено.

Восстановите текст программы к исходному состоянию. Запустите отладку. Выполните эксперименты, меняя значения переменных SetPnt.GqV и SetPnt.SVPWM_Enable. Убедитесь в возможности контролировать амплитуду напряжения на двигателе. Изучите, как меняется его форма при насыщении ШИ-модулятора в случаях использования синусоидальной и векторной модуляции. Убедитесь в том, что при векторной модуляции силовой мост способен синтезировать синусоидальное напряжение без искажения формы, с амплитудой большей на 15 %. Это еще одно положительное качество пространственно-векторной модуляции (SVPWM).

Проверьте, как процессор управляет нижними ключами в режиме векторной модуляции (см. рис. 1.б).

Приложение. Листинги файлов исходного кода ШИМ-драйвера для питания секций статора 3-х фазного двигателя переменного тока (ARM-процессор, ядро Cortex-M4)

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

/******************************************************************************
* File Name :   mclib.h
* Author :      Клиначев Николай Васильевич
* Version :     1.0.1
* Date :        20150614
* Description : Библиотеки IQ-Math library и Motor Control library
*       Библиотека математических преобразователей, необходимых
*       при реализации цифровых систем управления электропривода
*       и необходимые макросы целочисленной арифметики (IQ-математика)
*******************************************************************************/
/* Защита от рекурсивных подключений библиотеки-------------------------------*/
#ifndef __MCLIB_H
#define __MCLIB_H

/* Includes ------------------------------------------------------------------*/
#include "stm32f30x.h"  // Подключаем библиотеки производителя процессора

/* Exported types ------------------------------------------------------------*/
// ******************************************************************************* //
typedef long    _iq;    // это _iq24:  -128 .. 127.999999940  0.000000060

// ******************************************************************************* //
// Интегратор Угла и другие координаты, связанные с положением ротора
typedef struct {
    _iq *yS;        // Input: Указатель на Скорость вала, [-1.0, 1.0]
    _iq Angle;      // Input: angle in PU [0.0, 0.99999] (_Q15: 0000..7FFF)
    _iq Sine;       // Output _Q24: (8000..FFFF-0000..7FFF) << (24 - 15)
    _iq Cosine;     // Output _Q24: (8000..FFFF-0000..7FFF) << (24 - 15)
}
ROTOR_TypeDef;

// ******************************************************************************* //
// Инверсный преобразователь Парка
typedef struct {
    _iq *d;         // Input: Вещественная составляющая координаты (по оси d)
    _iq *q;         // Input: Мнимая составляющая координаты (по оси q)
    _iq *Sine;      // Input: Указатель на опорный синусоидальный сигнал (pu)
    _iq *Cosine;    // Input: Указатель на опорный косинусоидальный сигнал (pu)
    _iq a;          // Output: Alpha-фаза вращающейся координаты (сдвиг - 0 градусов)
    _iq b;          // Output: Beta-фаза вращающейся координаты (сдвиг - 90 градусов)
}
PIPARK_TypeDef;

// ******************************************************************************* //
// Преобразователь числа фаз (двухфазной системы в трехфазную)
typedef struct {
    _iq *a;         // Input: Alpha-фаза вращающейся координаты (сдвиг - 0 градусов)
    _iq *b;         // Input: Beta-фаза вращающейся координаты (сдвиг - 90 градусов)
    _iq  u;         // Output: U-фаза трехфазной системы UVW
    _iq  v;         // Output: V-фаза трехфазной системы UVW
    _iq  w;         // Output: W-фаза трехфазной системы UVW
}
PICLARKE_TypeDef;

/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
//#define GLOBAL_Q 24   // Для ЦСУ электропривода достаточная точность чисел Q24
// _iq24:  -128 .. 127.999999940  0.000000060
#define _IQ(A)          (long) ((A) * 16777216.0L) // _IQ24
// _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) ((uint32_t) ((s16) (x) + 0x8000) * ((u16) (BASE)) >> 16)
#define _IQtoIQ15(A)        ((long) (A) >> (24 - 15))
#define _IQdiv2(A)          ((A) >> 1)
#define _IQmpy(A, B)        (long) (((long long) (A) * (long long) (B)) >> 24)

/* Exported functions ------------------------------------------------------- */
void ROTOR_GetTrigonometic(ROTOR_TypeDef* Rotor);

#endif /* __MCLIB_H */

Листинг 2. Файл mclib.c

/******************************************************************************
* File Name :   mclib.c
* Author :      Клиначев Николай Васильевич
* Version :     1.0.1
* Date :        20150614
* Description : Библиотеки IQ-Math library и Motor Control library
*       Библиотека математических преобразователей, необходимых
*       при реализации цифровых систем управления электропривода
*       и необходимые макросы целочисленной арифметики (IQ-математика)
*******************************************************************************/
#include "mclib.h"

#define TABSIZE 2048
#define _PU15toBASE(x, BASE) ((x) * (BASE) >> 15)
#define _IQ15toIQ(A) ((long) (A) << (24 - 15))
/**
* Таблица значений выборок функции sin
* формат s16 (_Q15: 8000..FFFF-0000..7FFF)
*/
const s16 sinTbl[TABSIZE] = {
    0x0000, 0x0064, 0x00c9, 0x012d, 0x0192, 0x01f6, 0x025b, 0x02bf,
    0x0324, 0x0388, 0x03ed, 0x0451, 0x04b6, 0x051a, 0x057e, 0x05e3,
    0x0647, 0x06ac, 0x0710, 0x0774, 0x07d9, 0x083d, 0x08a1, 0x0906,
    0x096a, 0x09ce, 0x0a32, 0x0a97, 0x0afb, 0x0b5f, 0x0bc3, 0x0c27,
    0x0c8b, 0x0cef, 0x0d53, 0x0db7, 0x0e1b, 0x0e7f, 0x0ee3, 0x0f47,
    0x0fab, 0x100e, 0x1072, 0x10d6, 0x1139, 0x119d, 0x1201, 0x1264,
    0x12c7, 0x132b, 0x138e, 0x13f2, 0x1455, 0x14b8, 0x151b, 0x157e,
    0x15e1, 0x1645, 0x16a7, 0x170a, 0x176d, 0x17d0, 0x1833, 0x1895,
    0x18f8, 0x195b, 0x19bd, 0x1a20, 0x1a82, 0x1ae4, 0x1b47, 0x1ba9,
    0x1c0b, 0x1c6d, 0x1ccf, 0x1d31, 0x1d93, 0x1df4, 0x1e56, 0x1eb8,
    0x1f19, 0x1f7b, 0x1fdc, 0x203e, 0x209f, 0x2100, 0x2161, 0x21c2,
    0x2223, 0x2284, 0x22e5, 0x2345, 0x23a6, 0x2406, 0x2467, 0x24c7,
    0x2527, 0x2588, 0x25e8, 0x2648, 0x26a7, 0x2707, 0x2767, 0x27c7,
    0x2826, 0x2885, 0x28e5, 0x2944, 0x29a3, 0x2a02, 0x2a61, 0x2ac0,
    0x2b1f, 0x2b7d, 0x2bdc, 0x2c3a, 0x2c98, 0x2cf6, 0x2d55, 0x2db3,
    0x2e10, 0x2e6e, 0x2ecc, 0x2f29, 0x2f87, 0x2fe4, 0x3041, 0x309e,
    0x30fb, 0x3158, 0x31b5, 0x3211, 0x326e, 0x32ca, 0x3326, 0x3382,
    0x33de, 0x343a, 0x3496, 0x34f1, 0x354d, 0x35a8, 0x3603, 0x365e,
    0x36b9, 0x3714, 0x376f, 0x37c9, 0x3824, 0x387e, 0x38d8, 0x3932,
    0x398c, 0x39e6, 0x3a3f, 0x3a99, 0x3af2, 0x3b4b, 0x3ba4, 0x3bfd,
    0x3c56, 0x3caf, 0x3d07, 0x3d5f, 0x3db7, 0x3e0f, 0x3e67, 0x3ebf,
    0x3f17, 0x3f6e, 0x3fc5, 0x401c, 0x4073, 0x40ca, 0x4121, 0x4177,
    0x41cd, 0x4224, 0x4279, 0x42cf, 0x4325, 0x437a, 0x43d0, 0x4425,
    0x447a, 0x44cf, 0x4524, 0x4578, 0x45cc, 0x4621, 0x4675, 0x46c8,
    0x471c, 0x4770, 0x47c3, 0x4816, 0x4869, 0x48bc, 0x490f, 0x4961,
    0x49b3, 0x4a05, 0x4a57, 0x4aa9, 0x4afb, 0x4b4c, 0x4b9d, 0x4bee,
    0x4c3f, 0x4c90, 0x4ce0, 0x4d30, 0x4d81, 0x4dd0, 0x4e20, 0x4e70,
    0x4ebf, 0x4f0e, 0x4f5d, 0x4fac, 0x4ffb, 0x5049, 0x5097, 0x50e5,
    0x5133, 0x5181, 0x51ce, 0x521b, 0x5268, 0x52b5, 0x5302, 0x534e,
    0x539a, 0x53e6, 0x5432, 0x547e, 0x54c9, 0x5514, 0x555f, 0x55aa,
    0x55f5, 0x563f, 0x5689, 0x56d3, 0x571d, 0x5767, 0x57b0, 0x57f9,
    0x5842, 0x588b, 0x58d3, 0x591b, 0x5964, 0x59ab, 0x59f3, 0x5a3a,
    0x5a82, 0x5ac9, 0x5b0f, 0x5b56, 0x5b9c, 0x5be2, 0x5c28, 0x5c6e,
    0x5cb3, 0x5cf8, 0x5d3d, 0x5d82, 0x5dc7, 0x5e0b, 0x5e4f, 0x5e93,
    0x5ed7, 0x5f1a, 0x5f5d, 0x5fa0, 0x5fe3, 0x6025, 0x6068, 0x60aa,
    0x60eb, 0x612d, 0x616e, 0x61af, 0x61f0, 0x6231, 0x6271, 0x62b1,
    0x62f1, 0x6331, 0x6370, 0x63af, 0x63ee, 0x642d, 0x646b, 0x64aa,
    0x64e8, 0x6525, 0x6563, 0x65a0, 0x65dd, 0x661a, 0x6656, 0x6693,
    0x66cf, 0x670a, 0x6746, 0x6781, 0x67bc, 0x67f7, 0x6831, 0x686c,
    0x68a6, 0x68df, 0x6919, 0x6952, 0x698b, 0x69c4, 0x69fc, 0x6a35,
    0x6a6d, 0x6aa4, 0x6adc, 0x6b13, 0x6b4a, 0x6b81, 0x6bb7, 0x6bed,
    0x6c23, 0x6c59, 0x6c8e, 0x6cc3, 0x6cf8, 0x6d2d, 0x6d61, 0x6d95,
    0x6dc9, 0x6dfd, 0x6e30, 0x6e63, 0x6e96, 0x6ec8, 0x6efa, 0x6f2c,
    0x6f5e, 0x6f8f, 0x6fc1, 0x6ff2, 0x7022, 0x7052, 0x7083, 0x70b2,
    0x70e2, 0x7111, 0x7140, 0x716f, 0x719d, 0x71cb, 0x71f9, 0x7227,
    0x7254, 0x7281, 0x72ae, 0x72db, 0x7307, 0x7333, 0x735e, 0x738a,
    0x73b5, 0x73e0, 0x740a, 0x7435, 0x745f, 0x7488, 0x74b2, 0x74db,
    0x7504, 0x752c, 0x7555, 0x757d, 0x75a5, 0x75cc, 0x75f3, 0x761a,
    0x7641, 0x7667, 0x768d, 0x76b3, 0x76d8, 0x76fe, 0x7722, 0x7747,
    0x776b, 0x778f, 0x77b3, 0x77d7, 0x77fa, 0x781d, 0x783f, 0x7862,
    0x7884, 0x78a5, 0x78c7, 0x78e8, 0x7909, 0x7929, 0x794a, 0x7969,
    0x7989, 0x79a9, 0x79c8, 0x79e6, 0x7a05, 0x7a23, 0x7a41, 0x7a5f,
    0x7a7c, 0x7a99, 0x7ab6, 0x7ad2, 0x7aee, 0x7b0a, 0x7b26, 0x7b41,
    0x7b5c, 0x7b77, 0x7b91, 0x7bab, 0x7bc5, 0x7bde, 0x7bf8, 0x7c10,
    0x7c29, 0x7c41, 0x7c59, 0x7c71, 0x7c88, 0x7c9f, 0x7cb6, 0x7ccd,
    0x7ce3, 0x7cf9, 0x7d0e, 0x7d24, 0x7d39, 0x7d4d, 0x7d62, 0x7d76,
    0x7d89, 0x7d9d, 0x7db0, 0x7dc3, 0x7dd5, 0x7de8, 0x7dfa, 0x7e0b,
    0x7e1d, 0x7e2e, 0x7e3e, 0x7e4f, 0x7e5f, 0x7e6f, 0x7e7e, 0x7e8d,
    0x7e9c, 0x7eab, 0x7eb9, 0x7ec7, 0x7ed5, 0x7ee2, 0x7eef, 0x7efc,
    0x7f09, 0x7f15, 0x7f21, 0x7f2c, 0x7f37, 0x7f42, 0x7f4d, 0x7f57,
    0x7f61, 0x7f6b, 0x7f74, 0x7f7d, 0x7f86, 0x7f8f, 0x7f97, 0x7f9f,
    0x7fa6, 0x7fad, 0x7fb4, 0x7fbb, 0x7fc1, 0x7fc7, 0x7fcd, 0x7fd2,
    0x7fd8, 0x7fdc, 0x7fe1, 0x7fe5, 0x7fe9, 0x7fec, 0x7ff0, 0x7ff3,
    0x7ff5, 0x7ff7, 0x7ff9, 0x7ffb, 0x7ffd, 0x7ffe, 0x7ffe, 0x7fff,
    0x7fff, 0x7fff, 0x7ffe, 0x7ffe, 0x7ffd, 0x7ffb, 0x7ff9, 0x7ff7,
    0x7ff5, 0x7ff3, 0x7ff0, 0x7fec, 0x7fe9, 0x7fe5, 0x7fe1, 0x7fdc,
    0x7fd8, 0x7fd2, 0x7fcd, 0x7fc7, 0x7fc1, 0x7fbb, 0x7fb4, 0x7fad,
    0x7fa6, 0x7f9f, 0x7f97, 0x7f8f, 0x7f86, 0x7f7d, 0x7f74, 0x7f6b,
    0x7f61, 0x7f57, 0x7f4d, 0x7f42, 0x7f37, 0x7f2c, 0x7f21, 0x7f15,
    0x7f09, 0x7efc, 0x7eef, 0x7ee2, 0x7ed5, 0x7ec7, 0x7eb9, 0x7eab,
    0x7e9c, 0x7e8d, 0x7e7e, 0x7e6f, 0x7e5f, 0x7e4f, 0x7e3e, 0x7e2e,
    0x7e1d, 0x7e0b, 0x7dfa, 0x7de8, 0x7dd5, 0x7dc3, 0x7db0, 0x7d9d,
    0x7d89, 0x7d76, 0x7d62, 0x7d4d, 0x7d39, 0x7d24, 0x7d0e, 0x7cf9,
    0x7ce3, 0x7ccd, 0x7cb6, 0x7c9f, 0x7c88, 0x7c71, 0x7c59, 0x7c41,
    0x7c29, 0x7c10, 0x7bf8, 0x7bde, 0x7bc5, 0x7bab, 0x7b91, 0x7b77,
    0x7b5c, 0x7b41, 0x7b26, 0x7b0a, 0x7aee, 0x7ad2, 0x7ab6, 0x7a99,
    0x7a7c, 0x7a5f, 0x7a41, 0x7a23, 0x7a05, 0x79e6, 0x79c8, 0x79a9,
    0x7989, 0x7969, 0x794a, 0x7929, 0x7909, 0x78e8, 0x78c7, 0x78a5,
    0x7884, 0x7862, 0x783f, 0x781d, 0x77fa, 0x77d7, 0x77b3, 0x778f,
    0x776b, 0x7747, 0x7722, 0x76fe, 0x76d8, 0x76b3, 0x768d, 0x7667,
    0x7641, 0x761a, 0x75f3, 0x75cc, 0x75a5, 0x757d, 0x7555, 0x752c,
    0x7504, 0x74db, 0x74b2, 0x7488, 0x745f, 0x7435, 0x740a, 0x73e0,
    0x73b5, 0x738a, 0x735e, 0x7333, 0x7307, 0x72db, 0x72ae, 0x7281,
    0x7254, 0x7227, 0x71f9, 0x71cb, 0x719d, 0x716f, 0x7140, 0x7111,
    0x70e2, 0x70b2, 0x7083, 0x7052, 0x7022, 0x6ff2, 0x6fc1, 0x6f8f,
    0x6f5e, 0x6f2c, 0x6efa, 0x6ec8, 0x6e96, 0x6e63, 0x6e30, 0x6dfd,
    0x6dc9, 0x6d95, 0x6d61, 0x6d2d, 0x6cf8, 0x6cc3, 0x6c8e, 0x6c59,
    0x6c23, 0x6bed, 0x6bb7, 0x6b81, 0x6b4a, 0x6b13, 0x6adc, 0x6aa4,
    0x6a6d, 0x6a35, 0x69fc, 0x69c4, 0x698b, 0x6952, 0x6919, 0x68df,
    0x68a6, 0x686c, 0x6831, 0x67f7, 0x67bc, 0x6781, 0x6746, 0x670a,
    0x66cf, 0x6693, 0x6656, 0x661a, 0x65dd, 0x65a0, 0x6563, 0x6525,
    0x64e8, 0x64aa, 0x646b, 0x642d, 0x63ee, 0x63af, 0x6370, 0x6331,
    0x62f1, 0x62b1, 0x6271, 0x6231, 0x61f0, 0x61af, 0x616e, 0x612d,
    0x60eb, 0x60aa, 0x6068, 0x6025, 0x5fe3, 0x5fa0, 0x5f5d, 0x5f1a,
    0x5ed7, 0x5e93, 0x5e4f, 0x5e0b, 0x5dc7, 0x5d82, 0x5d3d, 0x5cf8,
    0x5cb3, 0x5c6e, 0x5c28, 0x5be2, 0x5b9c, 0x5b56, 0x5b0f, 0x5ac9,
    0x5a82, 0x5a3a, 0x59f3, 0x59ab, 0x5964, 0x591b, 0x58d3, 0x588b,
    0x5842, 0x57f9, 0x57b0, 0x5767, 0x571d, 0x56d3, 0x5689, 0x563f,
    0x55f5, 0x55aa, 0x555f, 0x5514, 0x54c9, 0x547e, 0x5432, 0x53e6,
    0x539a, 0x534e, 0x5302, 0x52b5, 0x5268, 0x521b, 0x51ce, 0x5181,
    0x5133, 0x50e5, 0x5097, 0x5049, 0x4ffb, 0x4fac, 0x4f5d, 0x4f0e,
    0x4ebf, 0x4e70, 0x4e20, 0x4dd0, 0x4d81, 0x4d30, 0x4ce0, 0x4c90,
    0x4c3f, 0x4bee, 0x4b9d, 0x4b4c, 0x4afb, 0x4aa9, 0x4a57, 0x4a05,
    0x49b3, 0x4961, 0x490f, 0x48bc, 0x4869, 0x4816, 0x47c3, 0x4770,
    0x471c, 0x46c8, 0x4675, 0x4621, 0x45cc, 0x4578, 0x4524, 0x44cf,
    0x447a, 0x4425, 0x43d0, 0x437a, 0x4325, 0x42cf, 0x4279, 0x4224,
    0x41cd, 0x4177, 0x4121, 0x40ca, 0x4073, 0x401c, 0x3fc5, 0x3f6e,
    0x3f17, 0x3ebf, 0x3e67, 0x3e0f, 0x3db7, 0x3d5f, 0x3d07, 0x3caf,
    0x3c56, 0x3bfd, 0x3ba4, 0x3b4b, 0x3af2, 0x3a99, 0x3a3f, 0x39e6,
    0x398c, 0x3932, 0x38d8, 0x387e, 0x3824, 0x37c9, 0x376f, 0x3714,
    0x36b9, 0x365e, 0x3603, 0x35a8, 0x354d, 0x34f1, 0x3496, 0x343a,
    0x33de, 0x3382, 0x3326, 0x32ca, 0x326e, 0x3211, 0x31b5, 0x3158,
    0x30fb, 0x309e, 0x3041, 0x2fe4, 0x2f87, 0x2f29, 0x2ecc, 0x2e6e,
    0x2e10, 0x2db3, 0x2d55, 0x2cf6, 0x2c98, 0x2c3a, 0x2bdc, 0x2b7d,
    0x2b1f, 0x2ac0, 0x2a61, 0x2a02, 0x29a3, 0x2944, 0x28e5, 0x2885,
    0x2826, 0x27c7, 0x2767, 0x2707, 0x26a7, 0x2648, 0x25e8, 0x2588,
    0x2527, 0x24c7, 0x2467, 0x2406, 0x23a6, 0x2345, 0x22e5, 0x2284,
    0x2223, 0x21c2, 0x2161, 0x2100, 0x209f, 0x203e, 0x1fdc, 0x1f7b,
    0x1f19, 0x1eb8, 0x1e56, 0x1df4, 0x1d93, 0x1d31, 0x1ccf, 0x1c6d,
    0x1c0b, 0x1ba9, 0x1b47, 0x1ae4, 0x1a82, 0x1a20, 0x19bd, 0x195b,
    0x18f8, 0x1895, 0x1833, 0x17d0, 0x176d, 0x170a, 0x16a7, 0x1645,
    0x15e1, 0x157e, 0x151b, 0x14b8, 0x1455, 0x13f2, 0x138e, 0x132b,
    0x12c7, 0x1264, 0x1201, 0x119d, 0x1139, 0x10d6, 0x1072, 0x100e,
    0x0fab, 0x0f47, 0x0ee3, 0x0e7f, 0x0e1b, 0x0db7, 0x0d53, 0x0cef,
    0x0c8b, 0x0c27, 0x0bc3, 0x0b5f, 0x0afb, 0x0a97, 0x0a32, 0x09ce,
    0x096a, 0x0906, 0x08a1, 0x083d, 0x07d9, 0x0774, 0x0710, 0x06ac,
    0x0647, 0x05e3, 0x057e, 0x051a, 0x04b6, 0x0451, 0x03ed, 0x0388,
    0x0324, 0x02bf, 0x025b, 0x01f6, 0x0192, 0x012d, 0x00c9, 0x0064,
    0x0000, 0xff9b, 0xff36, 0xfed2, 0xfe6d, 0xfe09, 0xfda4, 0xfd40,
    0xfcdb, 0xfc77, 0xfc12, 0xfbae, 0xfb49, 0xfae5, 0xfa81, 0xfa1c,
    0xf9b8, 0xf953, 0xf8ef, 0xf88b, 0xf826, 0xf7c2, 0xf75e, 0xf6f9,
    0xf695, 0xf631, 0xf5cd, 0xf568, 0xf504, 0xf4a0, 0xf43c, 0xf3d8,
    0xf374, 0xf310, 0xf2ac, 0xf248, 0xf1e4, 0xf180, 0xf11c, 0xf0b8,
    0xf054, 0xeff1, 0xef8d, 0xef29, 0xeec6, 0xee62, 0xedfe, 0xed9b,
    0xed38, 0xecd4, 0xec71, 0xec0d, 0xebaa, 0xeb47, 0xeae4, 0xea81,
    0xea1e, 0xe9ba, 0xe958, 0xe8f5, 0xe892, 0xe82f, 0xe7cc, 0xe76a,
    0xe707, 0xe6a4, 0xe642, 0xe5df, 0xe57d, 0xe51b, 0xe4b8, 0xe456,
    0xe3f4, 0xe392, 0xe330, 0xe2ce, 0xe26c, 0xe20b, 0xe1a9, 0xe147,
    0xe0e6, 0xe084, 0xe023, 0xdfc1, 0xdf60, 0xdeff, 0xde9e, 0xde3d,
    0xdddc, 0xdd7b, 0xdd1a, 0xdcba, 0xdc59, 0xdbf9, 0xdb98, 0xdb38,
    0xdad8, 0xda77, 0xda17, 0xd9b7, 0xd958, 0xd8f8, 0xd898, 0xd838,
    0xd7d9, 0xd77a, 0xd71a, 0xd6bb, 0xd65c, 0xd5fd, 0xd59e, 0xd53f,
    0xd4e0, 0xd482, 0xd423, 0xd3c5, 0xd367, 0xd309, 0xd2aa, 0xd24c,
    0xd1ef, 0xd191, 0xd133, 0xd0d6, 0xd078, 0xd01b, 0xcfbe, 0xcf61,
    0xcf04, 0xcea7, 0xce4a, 0xcdee, 0xcd91, 0xcd35, 0xccd9, 0xcc7d,
    0xcc21, 0xcbc5, 0xcb69, 0xcb0e, 0xcab2, 0xca57, 0xc9fc, 0xc9a1,
    0xc946, 0xc8eb, 0xc890, 0xc836, 0xc7db, 0xc781, 0xc727, 0xc6cd,
    0xc673, 0xc619, 0xc5c0, 0xc566, 0xc50d, 0xc4b4, 0xc45b, 0xc402,
    0xc3a9, 0xc350, 0xc2f8, 0xc2a0, 0xc248, 0xc1f0, 0xc198, 0xc140,
    0xc0e8, 0xc091, 0xc03a, 0xbfe3, 0xbf8c, 0xbf35, 0xbede, 0xbe88,
    0xbe32, 0xbddb, 0xbd86, 0xbd30, 0xbcda, 0xbc85, 0xbc2f, 0xbbda,
    0xbb85, 0xbb30, 0xbadb, 0xba87, 0xba33, 0xb9de, 0xb98a, 0xb937,
    0xb8e3, 0xb88f, 0xb83c, 0xb7e9, 0xb796, 0xb743, 0xb6f0, 0xb69e,
    0xb64c, 0xb5fa, 0xb5a8, 0xb556, 0xb504, 0xb4b3, 0xb462, 0xb411,
    0xb3c0, 0xb36f, 0xb31f, 0xb2cf, 0xb27e, 0xb22f, 0xb1df, 0xb18f,
    0xb140, 0xb0f1, 0xb0a2, 0xb053, 0xb004, 0xafb6, 0xaf68, 0xaf1a,
    0xaecc, 0xae7e, 0xae31, 0xade4, 0xad97, 0xad4a, 0xacfd, 0xacb1,
    0xac65, 0xac19, 0xabcd, 0xab81, 0xab36, 0xaaeb, 0xaaa0, 0xaa55,
    0xaa0a, 0xa9c0, 0xa976, 0xa92c, 0xa8e2, 0xa898, 0xa84f, 0xa806,
    0xa7bd, 0xa774, 0xa72c, 0xa6e4, 0xa69b, 0xa654, 0xa60c, 0xa5c5,
    0xa57d, 0xa536, 0xa4f0, 0xa4a9, 0xa463, 0xa41d, 0xa3d7, 0xa391,
    0xa34c, 0xa307, 0xa2c2, 0xa27d, 0xa238, 0xa1f4, 0xa1b0, 0xa16c,
    0xa128, 0xa0e5, 0xa0a2, 0xa05f, 0xa01c, 0x9fda, 0x9f97, 0x9f55,
    0x9f14, 0x9ed2, 0x9e91, 0x9e50, 0x9e0f, 0x9dce, 0x9d8e, 0x9d4e,
    0x9d0e, 0x9cce, 0x9c8f, 0x9c50, 0x9c11, 0x9bd2, 0x9b94, 0x9b55,
    0x9b17, 0x9ada, 0x9a9c, 0x9a5f, 0x9a22, 0x99e5, 0x99a9, 0x996c,
    0x9930, 0x98f5, 0x98b9, 0x987e, 0x9843, 0x9808, 0x97ce, 0x9793,
    0x9759, 0x9720, 0x96e6, 0x96ad, 0x9674, 0x963b, 0x9603, 0x95ca,
    0x9592, 0x955b, 0x9523, 0x94ec, 0x94b5, 0x947e, 0x9448, 0x9412,
    0x93dc, 0x93a6, 0x9371, 0x933c, 0x9307, 0x92d2, 0x929e, 0x926a,
    0x9236, 0x9202, 0x91cf, 0x919c, 0x9169, 0x9137, 0x9105, 0x90d3,
    0x90a1, 0x9070, 0x903e, 0x900d, 0x8fdd, 0x8fad, 0x8f7c, 0x8f4d,
    0x8f1d, 0x8eee, 0x8ebf, 0x8e90, 0x8e62, 0x8e34, 0x8e06, 0x8dd8,
    0x8dab, 0x8d7e, 0x8d51, 0x8d24, 0x8cf8, 0x8ccc, 0x8ca1, 0x8c75,
    0x8c4a, 0x8c1f, 0x8bf5, 0x8bca, 0x8ba0, 0x8b77, 0x8b4d, 0x8b24,
    0x8afb, 0x8ad3, 0x8aaa, 0x8a82, 0x8a5a, 0x8a33, 0x8a0c, 0x89e5,
    0x89be, 0x8998, 0x8972, 0x894c, 0x8927, 0x8901, 0x88dd, 0x88b8,
    0x8894, 0x8870, 0x884c, 0x8828, 0x8805, 0x87e2, 0x87c0, 0x879d,
    0x877b, 0x875a, 0x8738, 0x8717, 0x86f6, 0x86d6, 0x86b5, 0x8696,
    0x8676, 0x8656, 0x8637, 0x8619, 0x85fa, 0x85dc, 0x85be, 0x85a0,
    0x8583, 0x8566, 0x8549, 0x852d, 0x8511, 0x84f5, 0x84d9, 0x84be,
    0x84a3, 0x8488, 0x846e, 0x8454, 0x843a, 0x8421, 0x8407, 0x83ef,
    0x83d6, 0x83be, 0x83a6, 0x838e, 0x8377, 0x8360, 0x8349, 0x8332,
    0x831c, 0x8306, 0x82f1, 0x82db, 0x82c6, 0x82b2, 0x829d, 0x8289,
    0x8276, 0x8262, 0x824f, 0x823c, 0x822a, 0x8217, 0x8205, 0x81f4,
    0x81e2, 0x81d1, 0x81c1, 0x81b0, 0x81a0, 0x8190, 0x8181, 0x8172,
    0x8163, 0x8154, 0x8146, 0x8138, 0x812a, 0x811d, 0x8110, 0x8103,
    0x80f6, 0x80ea, 0x80de, 0x80d3, 0x80c8, 0x80bd, 0x80b2, 0x80a8,
    0x809e, 0x8094, 0x808b, 0x8082, 0x8079, 0x8070, 0x8068, 0x8060,
    0x8059, 0x8052, 0x804b, 0x8044, 0x803e, 0x8038, 0x8032, 0x802d,
    0x8027, 0x8023, 0x801e, 0x801a, 0x8016, 0x8013, 0x800f, 0x800c,
    0x800a, 0x8008, 0x8006, 0x8004, 0x8002, 0x8001, 0x8001, 0x8000,
    0x8000, 0x8000, 0x8001, 0x8001, 0x8002, 0x8004, 0x8006, 0x8008,
    0x800a, 0x800c, 0x800f, 0x8013, 0x8016, 0x801a, 0x801e, 0x8023,
    0x8027, 0x802d, 0x8032, 0x8038, 0x803e, 0x8044, 0x804b, 0x8052,
    0x8059, 0x8060, 0x8068, 0x8070, 0x8079, 0x8082, 0x808b, 0x8094,
    0x809e, 0x80a8, 0x80b2, 0x80bd, 0x80c8, 0x80d3, 0x80de, 0x80ea,
    0x80f6, 0x8103, 0x8110, 0x811d, 0x812a, 0x8138, 0x8146, 0x8154,
    0x8163, 0x8172, 0x8181, 0x8190, 0x81a0, 0x81b0, 0x81c1, 0x81d1,
    0x81e2, 0x81f4, 0x8205, 0x8217, 0x822a, 0x823c, 0x824f, 0x8262,
    0x8276, 0x8289, 0x829d, 0x82b2, 0x82c6, 0x82db, 0x82f1, 0x8306,
    0x831c, 0x8332, 0x8349, 0x8360, 0x8377, 0x838e, 0x83a6, 0x83be,
    0x83d6, 0x83ef, 0x8407, 0x8421, 0x843a, 0x8454, 0x846e, 0x8488,
    0x84a3, 0x84be, 0x84d9, 0x84f5, 0x8511, 0x852d, 0x8549, 0x8566,
    0x8583, 0x85a0, 0x85be, 0x85dc, 0x85fa, 0x8619, 0x8637, 0x8656,
    0x8676, 0x8696, 0x86b5, 0x86d6, 0x86f6, 0x8717, 0x8738, 0x875a,
    0x877b, 0x879d, 0x87c0, 0x87e2, 0x8805, 0x8828, 0x884c, 0x8870,
    0x8894, 0x88b8, 0x88dd, 0x8901, 0x8927, 0x894c, 0x8972, 0x8998,
    0x89be, 0x89e5, 0x8a0c, 0x8a33, 0x8a5a, 0x8a82, 0x8aaa, 0x8ad3,
    0x8afb, 0x8b24, 0x8b4d, 0x8b77, 0x8ba0, 0x8bca, 0x8bf5, 0x8c1f,
    0x8c4a, 0x8c75, 0x8ca1, 0x8ccc, 0x8cf8, 0x8d24, 0x8d51, 0x8d7e,
    0x8dab, 0x8dd8, 0x8e06, 0x8e34, 0x8e62, 0x8e90, 0x8ebf, 0x8eee,
    0x8f1d, 0x8f4d, 0x8f7c, 0x8fad, 0x8fdd, 0x900d, 0x903e, 0x9070,
    0x90a1, 0x90d3, 0x9105, 0x9137, 0x9169, 0x919c, 0x91cf, 0x9202,
    0x9236, 0x926a, 0x929e, 0x92d2, 0x9307, 0x933c, 0x9371, 0x93a6,
    0x93dc, 0x9412, 0x9448, 0x947e, 0x94b5, 0x94ec, 0x9523, 0x955b,
    0x9592, 0x95ca, 0x9603, 0x963b, 0x9674, 0x96ad, 0x96e6, 0x9720,
    0x9759, 0x9793, 0x97ce, 0x9808, 0x9843, 0x987e, 0x98b9, 0x98f5,
    0x9930, 0x996c, 0x99a9, 0x99e5, 0x9a22, 0x9a5f, 0x9a9c, 0x9ada,
    0x9b17, 0x9b55, 0x9b94, 0x9bd2, 0x9c11, 0x9c50, 0x9c8f, 0x9cce,
    0x9d0e, 0x9d4e, 0x9d8e, 0x9dce, 0x9e0f, 0x9e50, 0x9e91, 0x9ed2,
    0x9f14, 0x9f55, 0x9f97, 0x9fda, 0xa01c, 0xa05f, 0xa0a2, 0xa0e5,
    0xa128, 0xa16c, 0xa1b0, 0xa1f4, 0xa238, 0xa27d, 0xa2c2, 0xa307,
    0xa34c, 0xa391, 0xa3d7, 0xa41d, 0xa463, 0xa4a9, 0xa4f0, 0xa536,
    0xa57d, 0xa5c5, 0xa60c, 0xa654, 0xa69b, 0xa6e4, 0xa72c, 0xa774,
    0xa7bd, 0xa806, 0xa84f, 0xa898, 0xa8e2, 0xa92c, 0xa976, 0xa9c0,
    0xaa0a, 0xaa55, 0xaaa0, 0xaaeb, 0xab36, 0xab81, 0xabcd, 0xac19,
    0xac65, 0xacb1, 0xacfd, 0xad4a, 0xad97, 0xade4, 0xae31, 0xae7e,
    0xaecc, 0xaf1a, 0xaf68, 0xafb6, 0xb004, 0xb053, 0xb0a2, 0xb0f1,
    0xb140, 0xb18f, 0xb1df, 0xb22f, 0xb27e, 0xb2cf, 0xb31f, 0xb36f,
    0xb3c0, 0xb411, 0xb462, 0xb4b3, 0xb504, 0xb556, 0xb5a8, 0xb5fa,
    0xb64c, 0xb69e, 0xb6f0, 0xb743, 0xb796, 0xb7e9, 0xb83c, 0xb88f,
    0xb8e3, 0xb937, 0xb98a, 0xb9de, 0xba33, 0xba87, 0xbadb, 0xbb30,
    0xbb85, 0xbbda, 0xbc2f, 0xbc85, 0xbcda, 0xbd30, 0xbd86, 0xbddb,
    0xbe32, 0xbe88, 0xbede, 0xbf35, 0xbf8c, 0xbfe3, 0xc03a, 0xc091,
    0xc0e8, 0xc140, 0xc198, 0xc1f0, 0xc248, 0xc2a0, 0xc2f8, 0xc350,
    0xc3a9, 0xc402, 0xc45b, 0xc4b4, 0xc50d, 0xc566, 0xc5c0, 0xc619,
    0xc673, 0xc6cd, 0xc727, 0xc781, 0xc7db, 0xc836, 0xc890, 0xc8eb,
    0xc946, 0xc9a1, 0xc9fc, 0xca57, 0xcab2, 0xcb0e, 0xcb69, 0xcbc5,
    0xcc21, 0xcc7d, 0xccd9, 0xcd35, 0xcd91, 0xcdee, 0xce4a, 0xcea7,
    0xcf04, 0xcf61, 0xcfbe, 0xd01b, 0xd078, 0xd0d6, 0xd133, 0xd191,
    0xd1ef, 0xd24c, 0xd2aa, 0xd309, 0xd367, 0xd3c5, 0xd423, 0xd482,
    0xd4e0, 0xd53f, 0xd59e, 0xd5fd, 0xd65c, 0xd6bb, 0xd71a, 0xd77a,
    0xd7d9, 0xd838, 0xd898, 0xd8f8, 0xd958, 0xd9b7, 0xda17, 0xda77,
    0xdad8, 0xdb38, 0xdb98, 0xdbf9, 0xdc59, 0xdcba, 0xdd1a, 0xdd7b,
    0xdddc, 0xde3d, 0xde9e, 0xdeff, 0xdf60, 0xdfc1, 0xe023, 0xe084,
    0xe0e6, 0xe147, 0xe1a9, 0xe20b, 0xe26c, 0xe2ce, 0xe330, 0xe392,
    0xe3f4, 0xe456, 0xe4b8, 0xe51b, 0xe57d, 0xe5df, 0xe642, 0xe6a4,
    0xe707, 0xe76a, 0xe7cc, 0xe82f, 0xe892, 0xe8f5, 0xe958, 0xe9ba,
    0xea1e, 0xea81, 0xeae4, 0xeb47, 0xebaa, 0xec0d, 0xec71, 0xecd4,
    0xed38, 0xed9b, 0xedfe, 0xee62, 0xeec6, 0xef29, 0xef8d, 0xeff1,
    0xf054, 0xf0b8, 0xf11c, 0xf180, 0xf1e4, 0xf248, 0xf2ac, 0xf310,
    0xf374, 0xf3d8, 0xf43c, 0xf4a0, 0xf504, 0xf568, 0xf5cd, 0xf631,
    0xf695, 0xf6f9, 0xf75e, 0xf7c2, 0xf826, 0xf88b, 0xf8ef, 0xf953,
    0xf9b8, 0xfa1c, 0xfa81, 0xfae5, 0xfb49, 0xfbae, 0xfc12, 0xfc77,
    0xfcdb, 0xfd40, 0xfda4, 0xfe09, 0xfe6d, 0xfed2, 0xff36, 0xff9b,
};

void ROTOR_GetTrigonometic(ROTOR_TypeDef* Rotor) {
    // Внимание! Перегрузка: _IQtoIQ15(_IQ(1.0)) === 0x8000
    long A = Rotor->Angle > _IQ(0.99999) ? _IQ(0.99999) : Rotor->Angle;
    u16 phi = _PU15toBASE(_IQtoIQ15(A), TABSIZE);
    Rotor->Sine = _IQ15toIQ(sinTbl[phi]);
    phi += TABSIZE >> 2;
    phi = phi >= TABSIZE ? phi - TABSIZE : phi;
    Rotor->Cosine = _IQ15toIQ(sinTbl[phi]);
}

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

/******************************************************************************
* File Name :   main.c
* Author :      Клиначев Николай Васильевич
* Version :     1.0.3
* Date :        20150618
* Target :      ARM Cortex-M4 (STM32F303xC / STM32F3DISCOVERY), FPU:Off
* Description : Программа векторной системы управления для СДПМ (PMSM / BLAC)
*               Неполная версия (скелет программы, ШИМ-драйвер)
*       Реализован код ШИМ-драйвера для питания секций статора 3-х фазного
*       двигателя. Счетчик таймера считает тактирующие импульсы в прямом и
*       в обратном направлении, формируя опорный сигнал треугольной формы.
*       Регистры сравнения 3-х каналов таймера формируют сигналы для
*       управления 6-тью ключами 3-х стоек силового моста с бестоковой паузой.
*       Код формирования сигнала для ШИ-модуляции реализован в процедуре
*       обработки прерывания таймера и вызывается при максимальном и при
*       нулевом кодах в счетчике. Для выборки и преобразования табличных
*       значений синусоиды реализованы: интегратор угла, инверсные
*       преобразователи Парка и Кларка. Интегратор позволяет контролировать
*       частоту, а преобразователи амплитуду сигнала для ШИ-модулятора.
*       Для вычислений используется целочисленные операции и макросы
*       IQ-математики.
*******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "stm32f30x.h"  // Подключаем библиотеки производителя процессора
#include "mclib.h"      // Подключаем библиотеки IQ-Math и Motor Control

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define SYSCLK          72000000                // SystemCoreClock
#define PWM_COUNTER     2048                    // 2048 == 17578.125 Гц
#define PWM_FREQUENCY   (0.5 * SYSCLK / PWM_COUNTER) // div2 4 TRANGLE
#define PWM_RESOLUTION  PWM_COUNTER             // Разрешение падает с ростом частоты
#define TIMESTEP        (1.0 / PWM_FREQUENCY)   // Шаг дискретизации ИУ, Ротаторов (сек)
#define BASE_FREQ       200.0                   // Максимальная частота фазных токов (Hz)

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/

typedef struct {
    _iq gS;    // Output: Уставка Скорости, [об/мин/BASE_SPD_RPM], [-1.0, +1.0], (pu)
    _iq GdV;   // Output: d-Катет управляющего значения для ШИМ, [-1.0, 1.0), (pu)
    _iq GqV;   // Output: q-Катет управляющего значения для ШИМ, [-1.0, 1.0), (pu)
    FunctionalState SVPWM_Enable; // Output: флаг ативизации SVPWM
} SETPOINTS_TypeDef;
// Глобальные уставки Цифровой системы управления для СДПМ (PMSM / BLAC)
SETPOINTS_TypeDef SetPnt = { _IQ(0.15), _IQ(0.0), _IQ(0.9), ENABLE };
// Интегратор Угла и другие координаты, связанные с положением ротора
ROTOR_TypeDef           Rotor;
// Инверсный преобразователь Парка (dq2ab == dc2ac)
PIPARK_TypeDef          dq2ab;
// Инвертирующий преобразователь Кларка (преобразователь числа фаз - 2Ph_2_3Ph)
PICLARKE_TypeDef        ab2uvw;

/* Private function prototypes -----------------------------------------------*/
static void RCC_Configuration(void);
static void GPIO_Configuration(void);
static void NVIC_Configuration(void);
static void TIM_Configuration(void);
static void PMSM_CntrlUnit_CreateWires(void);
void TIM1_UP_TIM16_IRQHandler(void);

/*******************************************************************************
* Function Name : main
* Description :   Main program
*******************************************************************************/
#pragma optimize=none
int main(void) {
    // На этой стадии тактовый генератор процессора уже сконфигурирован,
    // это сделано функцией SystemInit(), которая была вызвана в "startup-файле"
    // (startup_stm32f30x.s), прежде чем была вызвана главная функция приложения
    // main. Для реконфигурирования значений по умолчанию обратитесь к файлу
    // system_stm32f30x.c, который создаётся изготовителем процессора.

    // Настраиваем тактовый генератор процессора
    RCC_Configuration();
    // Заполняем таблицу векторов прерываний
    NVIC_Configuration();
    // Конфигурируем порты ввода / вывода
    GPIO_Configuration();
    // Конфигурируем таймер
    TIM_Configuration();

    PMSM_CntrlUnit_CreateWires();

    while (1) {}
}

/*******************************************************************************
* 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
    RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div1); // ADCCLK = PLLCLK

    // Включаем тактирование устройств на системной AHB-шине с высокой
    // пропускной способностью (Advanced High-performance Bus)
    RCC_AHBPeriphClockCmd(
        RCC_AHBPeriph_GPIOA |   // порт A
        RCC_AHBPeriph_GPIOB,    // порт B
        ENABLE);
    // Включаем тактирование устройств на APB2-шине
    // высокоскоростной периферии: TIM1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, 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);
    // Определим вектор прерывания для Таймера (TIM1, TIM_IT_Update)
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_TIM16_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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    // Порт B: Терминал 11 для контроля осциллографом при отладке
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // Порт A: Терминалы 8, 9, 10 конфигурируем для вывода PWM
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Порт B: Терминалы 13, 14, 15 конфигурируем для вывода PWM
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    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(void) {
    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_COUNTER - 1;
    // 5. Настроим поглощающий прерывания счетчик реверсов
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 2 - 1;
    // и как ... сконфигурируем!
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    // Настроим выходы каналов Захвата / Сравнения таймера
    //
    // 1. Установим режим сравнения CCR >= CNT или CCR <= CNT
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_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 = TIM_OCPolarity_High;
    // 5. Установим полярность сигнала OCNx для нижнего ключа
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
    // 6. Определим состояние выходов OCx  для IDLE режима
    TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
    // 7. Определим состояние выходов OCNx для IDLE режима
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    // Установим число в Регистра Сравнения = половину от ARR
    TIM_OCInitStructure.TIM_Pulse = (PWM_RESOLUTION >> 1) - 1;
    // 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 = 54; // 54 / 72e6 = 0.75 uS
    // 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: Сбрасываем флаг прерывания по обновлению
    TIM_ClearFlag(TIM1, TIM_FLAG_Update);
    // TIM1: Разрешаем прерывание по обновлению
    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
    // TIM1: Включаем счетчик таймера
    TIM_Cmd(TIM1, ENABLE);
    // TIM1: Включаем главный ШИМ-выход таймера
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
}

/*******************************************************************************
* Function Name : PMSM_CntrlUnit_CreateWires
* Description :   ...
*     Процедура выполняет построение цифровой системы управления для
*     СДПМ (PMSM) из вычислительных модулей - экземпляров объектов.
*     Суть построения ЦСУ - определение схемы передачи аргументов.
*     Выход - это атрибут модуля (переменная принадлежит объекту).
*     Вход - не является атрибутом модуля (объект имеет указатели
*     для подключения к аргументам).
*******************************************************************************/
static void PMSM_CntrlUnit_CreateWires(void) {
    // Интегратор Угла (Вычислитель Углового положения ротора)
    Rotor.yS     = &SetPnt.gS;      // Подключили модуль к Наблюдателю Скорости вала
    // Инверсный преобразователь Парка
    dq2ab.d      = &SetPnt.GdV;     // Подключили выходной сигнал РТ для d-оси
    dq2ab.q      = &SetPnt.GqV;     // Подключили выходной сигнал РТ для q-оси
    dq2ab.Sine   = &Rotor.Sine;     // Подключили ротатор к опорной синусоиде
    dq2ab.Cosine = &Rotor.Cosine;   // Подключили ротатор к опорной косинусоиде
    // Преобразователь числа фаз
    ab2uvw.a     = &dq2ab.a;        // Подключили Alpha-фазу к преобразователю
    ab2uvw.b     = &dq2ab.b;        // Подключили  Beta-фазу к преобразователю
}

/*******************************************************************************
* Function Name : TIM1_UP_TIM16_IRQHandler 4 TIM_IT_Update
* Description :   Самая главная процедура программы (обработка прерывания)
*     TIM_IT_Update на периоде треугольного ШИМ-а может генерироваться дважды.
*     Перед нулевым и перед максимальным кодами в счетчике таймера.
*     Но поглощающий прерывания счетчик реверсов может их прореживать.
*     Что влияет на шаг дискретизации системы управления (на TIMESTEP).
*******************************************************************************/
//#pragma optimize=speed
void TIM1_UP_TIM16_IRQHandler(void) {

    //if (TIM_GetITStatus(TIM1, TIM_IT_Update) == RESET) return;
    GPIOB->ODR ^= GPIO_Pin_11;
    // 0.18 uS

    // ------------------------------------------------------------------------------
    //  Наблюдатель углового положения ротора
    // ------------------------------------------------------------------------------
    // Angle = Speed * (100 * PI) * (POLES/2) * TIMESTEP * 1/s, [0.0, 2*PI]
    // где: Speed - в относительных единицах (pu) - [-1.0, +1.0]
    //      Angle - в абсолютных единицах (рад)   - [0.0,  2*PI]
    // ИЛИ
    // Angle = Speed * (100 / 2) * (POLES/2) * TIMESTEP * 1/s =
    //       = Speed *       BASE_FREQ       * TIMESTEP * 1/s, [0.0, 1.0]
    // где: Speed - в относительных единицах (pu) - [-1.0, +1.0]
    //      Angle - в относительных единицах (pu) - [ 0.0,  1.0]
    // ------------------------------------------------------------
    Rotor.Angle += _IQmpy(*Rotor.yS, _IQ(BASE_FREQ * TIMESTEP)); // 0.8 uS
    if (Rotor.Angle > _IQ(1.0)) Rotor.Angle -= _IQ(1.0);
    if (Rotor.Angle < _IQ(0.0)) Rotor.Angle += _IQ(1.0);
    // Внимание! Перегрузка: _IQtoIQ15(_IQ(1.0)) === 0x8000
    ROTOR_GetTrigonometic(&Rotor);
    // 2.4 uS

    // ------------------------------------------------------------------------------
    //  Преобразуем управляющие сигналы Регуляторов тока в 2-x фазную систему напряжений
    // ------------------------------------------------------------------------------
    dq2ab.a = _IQmpy(*dq2ab.d, *dq2ab.Cosine) - _IQmpy(*dq2ab.q, *dq2ab.Sine);
    dq2ab.b = _IQmpy(*dq2ab.q, *dq2ab.Cosine) + _IQmpy(*dq2ab.d, *dq2ab.Sine);
    // 3.8 uS

    // ------------------------------------------------------------------------------
    //  Преобразуем 2-x фазную систему напряжений в 3-x фазную (для питания электромотора)
    // ------------------------------------------------------------------------------
    _iq temp_v1 = _IQdiv2(*ab2uvw.a); // 0.8660254037844386 = sqrt(3)/2
    _iq temp_v2 = _IQmpy(*ab2uvw.b, _IQ(0.8660254037844386));
    ab2uvw.u = -(*ab2uvw.a);
    ab2uvw.v = temp_v1 - temp_v2;
    ab2uvw.w = temp_v1 + temp_v2;
    // 4.7 uS

    // ------------------------------------------------------------------------------
    //  Преобразуем 3-x фазную синусоидальную последовательность в ... и будет SVPWM
    // ------------------------------------------------------------------------------
    if (SetPnt.SVPWM_Enable) {
        if (ab2uvw.u < ab2uvw.v) {
            if (ab2uvw.u < ab2uvw.w) {
                ab2uvw.v = ab2uvw.v - (_IQ(1.0) + ab2uvw.u);
                ab2uvw.w = ab2uvw.w - (_IQ(1.0) + ab2uvw.u);
                ab2uvw.u = _IQ(-1.0);
            } else {
                ab2uvw.u = ab2uvw.u - (_IQ(1.0) + ab2uvw.w);
                ab2uvw.v = ab2uvw.v - (_IQ(1.0) + ab2uvw.w);
                ab2uvw.w = _IQ(-1.0);
            }
        } else if (ab2uvw.v < ab2uvw.w) {
            ab2uvw.w = ab2uvw.w - (_IQ(1.0) + ab2uvw.v);
            ab2uvw.u = ab2uvw.u - (_IQ(1.0) + ab2uvw.v);
            ab2uvw.v = _IQ(-1.0);
        } else if (ab2uvw.w < ab2uvw.v) {
            ab2uvw.u = ab2uvw.u - (_IQ(1.0) + ab2uvw.w);
            ab2uvw.v = ab2uvw.v - (_IQ(1.0) + ab2uvw.w);
            ab2uvw.w = _IQ(-1.0);
        } else {
            ab2uvw.u = _IQ(-1.0);
            ab2uvw.v = _IQ(-1.0);
            ab2uvw.w = _IQ(-1.0);
        }
    }
    // SVM: +1.9 .. 2.4 uS = 6.6 .. 7.0 uS

    // ------------------------------------------------------------------------------
    //  Ограничиваем сигналы (чтоб не было перегрузки в макросе _IQtoIQ15)
    // ------------------------------------------------------------------------------
    if (ab2uvw.u > _IQ(0.99999)) ab2uvw.u = _IQ(0.99999);
    if (ab2uvw.u < _IQ(-1.0000)) ab2uvw.u = _IQ(-1.0000);
    if (ab2uvw.v > _IQ(0.99999)) ab2uvw.v = _IQ(0.99999);
    if (ab2uvw.v < _IQ(-1.0000)) ab2uvw.v = _IQ(-1.0000);
    if (ab2uvw.w > _IQ(0.99999)) ab2uvw.w = _IQ(0.99999);
    if (ab2uvw.w < _IQ(-1.0000)) ab2uvw.w = _IQ(-1.0000);
    // +1.5 uS | SVM: +1.6 .. 1.82 uS | PWM: +1.68 .. 1.86 uS
    // ------------------------------------------------------------------------------
    //  Обновляем Регистры Сравнения 3-х каналов Таймера
    // ------------------------------------------------------------------------------
    TIM_SetCompare1(TIM1, _Q15toBASE(_IQtoIQ15(ab2uvw.u), PWM_RESOLUTION));
    TIM_SetCompare2(TIM1, _Q15toBASE(_IQtoIQ15(ab2uvw.v), PWM_RESOLUTION));
    TIM_SetCompare3(TIM1, _Q15toBASE(_IQtoIQ15(ab2uvw.w), PWM_RESOLUTION));
    TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    // PWM: 8.2 uS | SVM: 9.6 .. 10.1 uS
    GPIOB->ODR ^= GPIO_Pin_11;
}

#ifdef USE_FULL_ASSERT
/*******************************************************************************
* Function Name :       assert_failed
* Description :         Отчеты об имени исходного файла и о номере строки,
*                       где в режиме отладки произошла assert_param ошибка.
* Input : - file :      Указатель на имя исходного файла
*         - line :      Номер строки кода с ошибкой assert_param
* Output :              None
* Return :              None
*******************************************************************************/
void assert_failed(u8 * file, u32 line) {
    // Пользователь может определить собственное сообщение об имени файла
    // и номере строки.
    // Пример:
    // printf("Ошибка величины параметра: file %s on line %d\r\n", file, line);
    while (1) {}
}
#endif

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

Листинг 4. Функция генерации таблицы синусов для консоли браузера

(function (n) {
    function p(x) {
        var s = x.toString(16);
        return s.length === 4 ? s : (s.length === 3 ? "0" : s.length === 2 ? "00" : "000") + s;
    }
    n = 0 | n;
    var s = "", x = +0, i = 0 | 0, M2PI = Math.PI * 2;
    for (i; i < n; i += 1) {
        x = 0 | (0xffff * ((Math.sin(i / n * M2PI) + 1) / 2) + 0.5);
        x = (x - 0x8000) & 0xffff;
        s += "0x" + p(x) + ((i & 7) == 7 ? ",\n" : ", ");
    }
    var b = document.body,
    container = document.createElement('DIV');
    container.innerHTML = "<pre>" + s + "<\/pre>";
    b.insertBefore(container.firstChild, b.firstChild);
}(2048));

17.06.2015