TMS320 для начинающих или «Секретное оружие для тех, кому STM32 уже тесен»
Какой уважающий себя КИПовец не грезит на досуге о том, чтобы шагнуть из мира реле и датчиков в светлое цифровое будущее? Стать программистом, поднять категорию, пугать коллег умными словами, а то и чего греха таить поднять свое ЧСВ на форуме выложив фотки с платой с крутым камнем или видео со своим ПЧ? Путь джедая наверное выглядит примерно так: Arduino для затравки, ESP32 для вай-фая и интернета вещей, MQQT и все такое, STM32 для серьёзных глаз. Но мы-то с вами не ищем лёгких путей, и если уже и идти то сразу на Эверест! Поэтому предлагаю заглянуть на огонёк к святому Граалю Motorcontrol — контроллерам TMS320.
Что за зверь и с чем его едят?
Перед нами детище Texas Instruments — цифровой сигнальный процессор (DSP) с живым сопроцессором внутри. Его фишка не в гигагерцах (хотя измеряя в MIPS/MFLOPS он вполне гепард в своей нише), а в умении считать синусы и косинусы с такой скоростью, что математика просто не успевает создать задержку. Это отчасти магия библиотеки IQMath, которую инженеры TI выстрадали кровью, а отчасти конечно работа сопроцессора. На голом STM32 вы такое чудо не найдёте (хотя умельцы и пытались адаптировать IQMath, но это уже шаманство). Если вы думали, что весь сыр-бор только из-за названия бренда, то спешу развенчать миф. У семейства C2000 есть «фишки», ради которых адепты прощали ему капризный CodeComposer и танцы с бубном вокруг лицензий.
На борту у этого камня — полный фарш: куча ШИМ-модулей, заточенных под трёхфазные инверторы, причём не простых, а умных; модули захвата/сравнения для энкодеров и АЦП, которые синхронизируются по работе еPWM, да и сами еPWM тоже можно синхронизировать, в общем, всё, как у взрослых, но есть и особенные фишки.
HRPWM — это ШИМ на стероидах. Звучит как обычный шим-сигнал, не так ли? Но нет! Приставка High Resolution означает, что время установки нового коэффициента заполнения и сам шаг изменения длительности импульса здесь настолько малы, что обычный ШИМ курит в сторонке. Представьте: вы рисуете синусоиду для DC/AC-инвертора — с HRPWM она получается такой гладкой, хоть на выставку достижений народного хозяйства. Или управляете шаговым двигателем на ЧПУ-станке — точность позиционирования вырастает на глазах, детали выходят как по маслу. В младших контроллерах Piccolo (например, TMS320F2802x) CLA может и отсутствовать (но это и не критично), а вот HRPWM есть всегда — и это главный козырь!
CLA — ваш личный математический раб. CLA (Control Law Accelerator) — это по сути второе ядро, которое живёт внутри кристалла. Только вот хитрость: к периферии (портам, АЦП, таймерам) у него прямого доступа нет, зато он на полной скорости общается с основной памятью и главным ядром. Зачем он нужен? Чтобы разгрузить «начальника» от тяжёлых вычислений. Вы ему — float-циферки, фильтры Калмана (добро пожаловать в ЦОС), ПИД-регуляторы — а он их перемалывает без напряжения, пока основное ядро занимается управлением, обменом по интерфейсам и прочими важными делами. Особенно приятно, что CLA «съест» числа с плавающей точкой легко и непринуждённо, без лишних телодвижений. Это как иметь ассистента, который берёт на себя всю рутину, пока вы блистаете на совещании.
Вот такие два кита. Первый дарит вам идеальный сигнал, второй — незаметно тащит математику в фоновом режиме. И оба делают TMS320 незаменимым в мире серьёзной силовой электроники.
Кому это надо, или Тест на профпригодность
Заряжаем вопрос-детектор: вы хотите собрать сварочный инвертор, который не взорвётся (Скифовскому привет, хотя не факт что наш путь не будет таким же тернистым)? Может нужен источник бесперебойного питания или накопитель энергии, чтобы сервера не плакали? Частотник для станка Нннада, от которого зависит зарплата? Или, может, инвертор для солнечных батарей, чтобы соседи лопнули от зависти? Если кивнули хоть раз — добро пожаловать в клуб. Остальным тоже будет любопытно: почему этот «старый ветеран» до сих пор держит оборону в серьёзной промышленности, а не пылится на полке.
Программатор и отладочная плата
Я, когда-то давно, поразмыслив и почесав репу, приобрёл плату от «Терра-Электроника» с контроллером TMS320F28335, тогда выбора особо не было, да и али не был так популярен. В итоге купив ее вся эта тягомотина с ней показалась мне вообще невообразимо сложной и плата лежала без дела кучу лет, периодически возвращаясь на стол и снова уходя на полку, но как ни странно не теряла актуальность. Сразу оговорюсь: я пишу про флагмана семейства C2000, но модель уже почтенного возраста (F28335). Однако хоть и говорят, что дьявол кроется в деталях, но принципы-то едины, потому думаю данную статью можно будет принять и на других контроллерах, близких линеек. В общем, вам думаю можно заказать любую плату на Алике или еще где-то, камень только лучше чтобы был линеек C2000, и главное что она должна быть, тк программировать без камня все равно что танцевать без партнера, навыки не появятся, а ошибки не будет видно. Что касается программатора, то могу сказать, что оригинальные программаторы от Texas Instruments стоят довольно дорого и не всегда легко доступны. Однако на рынке уже давно представлены китайские аналоги, которые можно приобрести, например, на AliExpress. За примерно 5 000 рублей можно получить не только программатор, а комплект, включающий как сам программатор, так и отладочную плату, но есть нюанс подойдет он скорее всего только под этот МК, так что не стоит думать, что тратя много денег можно получить универсальный прибор на все случаи жизни. Программатор для С2000 поддерживает загрузку прошивки и отладку через интерфейс JTAG. Китайские программаторы судя по отзывам в целом работает стабильно, но возможно он будет чувствителен к длине соединительных проводов, то есть при длине более 10–15 см могут возникать проблемы с надёжностью связи с микроконтроллером.
Проблема: информации — кот наплакал
Про STM32 исписаны горы мануалов, а вот про TMS320 в рунете — информационный вакуум с редкими островками боли. Да, есть статьи, но они построены по принципу: «Бери готовый пример, не дыши, не трогай, а в этой строчке замени 5 на 6 и молись». Порог входа — выше крыши, интерес гаснет быстрее, чем паяльник остывает. А ведь именно такие процессоры стоят в навороченных сервоприводах и ПЧ, значит кто-то осилил, значит и я смогу! Англоязычные форумы полны готовых решений для тех, кто уже смог дорасти, но дорасти-то как раз и сложно, там разбирают проблемы, но наша - «как начать» у них не в почете. Учитывая возраст семейства, это странно. Поэтому я решил исправить несправедливость: попытаюсь разложить всё по полочкам для себя (и для вас), чтобы, когда потребуется срочно глянуть, не рыться в закладках неделю.
Отлично, теперь давайте разберёмся в зоопарке C2000. Чтобы вы не потерялись в этом мире, я вкратце обрисую два главных семейства, с которыми вы почти гарантированно столкнётесь.
Знакомьтесь: Piccolo — маленький, да удаленький
Это младшая линия, бюджетный сегмент, но не думайте, что это «галочка для галочки». Если вы задумали выпускать силовую электронику в коммерческих масштабах (читай: продавать и зарабатывать), то эти камни станут вашей основной рабочей лошадкой. Почему? Всё просто:
- Цена — кусается реже, чем другие.
- Паябельность — корпуса LQFP, QFN, TSSOP, которые можно припаять даже не самым крутым феном, без страха порвать дорожки.
- Функциональность — для большинства задач хватает за глаза.
Что можно на них построить? Да почти всё, что душе угодно: двухфазный PFC, инвертор для солнечных панелей (чтобы соседи завидовали), частотник до 10 кВт с векторным управлением — и всё это будет работать как часы. Это тот самый «народный» контроллер, который востребован и в гараже, и на заводе. Правда, есть и ограничения: тактовая частота «всего» 60 МГц, да и ШИМ-каналов не бесконечно много. Но для 80% задач этого с лихвой хватит.
Delfino — когда хочется ещё больше «вау»
Если Piccolo — это боец средней руки, то Delfino — это тот же боец, но накачанный мельдонием и витаминами (хоть здесь за мельдоний не штрафуют). Идеологически они близки, но разница — как между велосипедом и спорткаром. Что мы имеем?
- Частота — до 200 МГц! Это уже серьёзный прыжок.
- Ядра — в старших моделях целых два полноценных ядра + два сопроцессора (CLA). Итого 4 вычислительных единицы, которые выдают до 800 MIPS совокупной производительности. Впечатляет, правда?
Корпуса становятся больше, ног — больше, ШИМ-каналов и АЦП — тоже больше. В общем, всего становится «больше». И возникает закономерный вопрос: куда деть эту мощь? А вот куда:
- Сложные алгоритмы, например, выпрямитель «Виенна» (это вам не банальный диодный мост).
- Полноценная система управления для станка с ЧПУ — хоть фрезерного, хоть газопламенной резки. Один такой контроллер может заменить целую стойку из нескольких плат.
В двух словах: Piccolo — ваш надёжный друг для массовых проектов, Delfino — выбор гуру, которые любят размах и сложные задачи. И то, и другое — достойно внимания. Выбирайте по потребностям и кошельку, а мы пошли дальше — к практике!
Ну и конечно же давайте глянем на структурку (а лучше на две, чтобы сравнить)


Первое на что стоит обратить внимание — ОЗУ разделена на 3 сектора: M0, M1 и SARAM. Особенность данного модуля в том, что сектора M0 и M1 более шустрые, чем оставшаяся часть ОЗУ, формально можно воспринимать это как кэш. В секторах M0 и М1 обычно хранят наиболее часто используемые данные, а так же данные, которые наиболее критичны к производительности памяти. По умолчанию мы можем указать с помощью линкера что и где хранится, но эту тему в данной статье я поднимать не буду, тут отдельная статья нужна как минимум, пока просто чтобы запомнить, мы же только начинаем.
Вторая ключевая фишка, о которой молчат в рекламных буклетах, – это тактирование периферии. На TMS320F28xxx почти вся периферия (ШИМ, АЦП, таймеры, GPIO) висит прямо на системной шине безо всяких делителей. То есть если ядро стучит на частоте 150 МГц, то и ШИМ-модуль работает с той же скоростью — 150 честных мегагерц, без скидок и компромиссов, но есть конечно и делители, и если нужно, частоту можно снизить. Но нам главное сейчас другое, не сильно углубляясь в настройки и выставив делители в 1 не нужно смотреть, какая же шина тактирования «питает» периферию и сколько мегагерц идет в модуль/подсистему, просто берем SYSCLKOUT.
А теперь возьмём для контраста популярный STM32F4. Ядро у него, допустим, 180 МГц – вроде бы даже больше! Но только вот периферия там тактируется через целый каскад делителей, и реально её частота часто оказывается 45–90 МГц. Я в шутку называю такой подход «фальшивыми мегагерцами» – громко, красиво, а на деле выхлоп не тот. Разумеется, это утрирование, но суть отражает точно.
Поэтому когда вы видите 150 МГц у TMS320 и 180 МГц у STM32, не спешите радоваться за второй (вспомним про MIPS/MFLOPS-ы). В реальных задачах управления силовой электроникой, где важна каждая наносекунда задержки в ШИМ или АЦП, TMS320 ощутимо бодрее. А если добавить к этому ещё и CLA-сопроцессор, который берёт на себя тяжёлую математику, то разрыв становится просто неприличным.
Понимаю, что сравнивать архитектуры напрямую – дело неблагодарное, у каждой свои сильные стороны. Но этот пример наглядно показывает: не всё золото, что блестит, и не всё решают одни мегагерцы. Важно, как эти мегагерцы дойдут до ваших ШИМ-ножек и АЦП-входов. А у TMS320 они доходят без задержек – как курьер с пиццей в час пик, который знает все подворотни.
Так же интересный момент — обратите внимание на общую структуру: HRPWM, АЦП, компараторы с внутренним ЦАП, модуль обработки энкодера (eCAP)… Готовый преобразователь частоты в чистом виде! В этом вся суть данного семейства — минимализм. С одной стороны периферия достаточно бедная на фоне Cortex-ов, но с другой стороны достаточная, чтобы реализовать и частотник, и dc/dc, и dc/ac, да и драйвер шагового двигателя. За счет этого работа с контроллерами TMS320F28ххх очень простая (как оказалось), понятная и не перегружена лишними действиями. Но если вам вдруг нужно 3 UART-а, а к ним пару i2c и еще 3 SPI, то эти контроллеры точно не для вас — у них задачи другие.
Старт: Где копать и чем стучать?
Переходим к матчасти. Создаём проект!
Наш инструмент — CodeComposer Studio (она же CCS). Старая добрая IDE на базе Eclipse, знакомая каждому, кто не боится сложных интерфейсов. Скачиваем версию 12 (или актуальную) с сайта TI. Это бесплатно. Если, конечно, вы обойдёте блокировки и санкции — это отдельный квест на ловкость, но он того стоит (лицензии больше не нужны, ура!).
Качаем, ставим, запускаем. Вас встретит предложение выбрать workspace — папочку для хранения настроек редактора, открытых проектов и истории работы. Не пугайтесь: сам проект может жить где угодно, но workspace — это как «личный кабинет» интерфейса, где IDE запоминает, что вы любите «тёмную тему» и какие проекты открывали.
Это еще не все, теперь вам надо скачать библиотеки, которые у TI называются ControlSuite. Это набор инструментов для разработки программного обеспечения микроконтроллеров C2000. Сейчас этот продукт не обновляется. Его преемником стала платформа C2000Ware, которая включает актуальные версии ПО, примеры приложений и SDK (Software Development Kits) для цифровой силовой электроники (DigitalPower SDK) и управления двигателями (MotorControl SDK), но я до сих пор пользуюсь именно ControlSuite.
Отлично, теперь самый важный шаг – знакомство с ControlSuite/C2000Ware. Если CodeComposer Studio – это ваша мастерская, то ControlSuite/C2000Ware – это ящик с инструментами, где есть всё, включая те самые отвёртки, которые вы даже не знали, что вам понадобятся. Это не просто библиотека, это целый портал в мир C2000. Установка этого приложения – ваш первый долг перед будущим проектом, и вот почему.
Открываем этот «сундук» и видим:
- Полный каталог контроллеров и отладочных плат. Хотите знать, что есть в природе? Схемы, исходники печатных плат, BOM-списки – и всё это в родном для многих Altium Designer. Можно смело брать и копировать, не изобретая велосипед.
- Готовые примеры схемотехники и разводки. Это как готовые чертежи для сборки мебели – только здесь вы собираете не шкаф, а силовой инвертор. Отличная отправная точка, чтобы не наступать на грабли.
- Базовые библиотеки для встроенного ПО. Всё, что нужно, чтобы быстро оживить контроллер – драйверы периферии, инициализация, обработка прерываний. С ними вы не пишете с нуля, вы просто собираете конструктор.
- Математические и DSP-библиотеки, в том числе специально заточенные под CLA. Хотите считать фильтры, ПИДы, преобразования Фурье? Пожалуйста, всё уже прикручено и оптимизировано под аппаратные особенности.
- Примеры проектов под каждый вид периферии. PWM, ADC, eCAP, SPI – для каждого модуля есть готовый работающий код. Берите, меняйте под себя и не парьтесь.
- Гора апноутов (application notes) по реализации алгоритмов управления двигателями, DC/DC-преобразователями, MPPT-трекерами и прочими хитрыми системами. Это не сухие формулы, а живые рецепты от инженеров TI, с объяснениями и нюансами. Настоящая золотая жила.
- Набор программ для «ленивых» гениев. Хотите создать систему управления двигателем, вообще не написав ни строчки кода? Используйте графическую среду – накидали блок-схем, сгенерировали код, и вперёд! Идеально для быстрого прототипирования или для тех, кто программировать не любит, но результат получить хочет.
Короче говоря, ControlSuite/C2000Ware – это ваша путеводная звезда. Установили – считайте, полдела сделали. Дальше останется только выбрать нужный пример, ткнуть пальцем в настройки и получать удовольствие. Не пренебрегайте этим этапом – это сэкономит вам дни, а то и недели нервов.
И вот оно, таинство:
Запустили IDE, отлично, идём в меню File -> New -> CCS Project. И начинается магия. Остальное — дело техники и следующих статей. Погнали!
Видим данное окно и в нем нам необходимо выбрать интересующий нас контроллер (правый верхний угол), в моем случае это TMS320F28335, указать имя проекта и прописать путь, где он будет храниться. Предварительно нужно создать папку, где будет храниться проект. Имя проекта и имя папки могут не совпадать. Жмем кнопку Finish и наш проект создан.


Теперь необходимо наполнить наш проект содержимым и подключить его. Можно для улучшения структуры проекта создать вот такой набор папок:
- inc — папка, которая содержит все заголовочные файлы
- system_inc — в данном подразделе будут хранится заголовочные файлы стандартных библиотек, возможно мы их и менять не будем, файлы же, которые мы сами будет создавать, например, main.h находятся в папке inc. Так вы не натворите дел сгоряча и случайно не сотрёте чего-нибудь жизненно важного.
- src — папка со всеми исходниками, то есть сорцами
- system_src — папка с исходными файлами для стандартных библиотек, а аналогично с заголовочными
Разумеется, я не возвожу эту структуру в ранг догмы — это всего лишь мои тараканы в голове файлы в папках. Однако для тех, кто пока не обзавёлся собственными тараканами, она сойдёт за базовый каркас. Потом вы его переделаете под себя, а на первых порах он спасёт от глупых косяков, а самое главное искать будет проще.
Теперь наш следующий шаг – создать в папке inc файл main.h и сразу подключить его в main.c. Именно через этот заголовочный файл мы будем подключать все базовые библиотеки.
Но прежде чем кидаться переносить библиотеки и прочие файлы, давайте сразу пропишем в настройках проекта пути к папкам с будущими заголовочными файлами. Без этого компилятор просто не найдёт их и устроит истерику с ошибками, а нам это ни к чему.
Для этого кликаем правой кнопкой по нашему проекту (test) в дереве проекта, это корневой элемент дерева и в самом низу выбираем Properties (или нажимаем Alt+Enter – кому как удобнее). В открывшемся окошке идём по цепочке Build > C2000 Compiler > Include Options. Там уже есть стандартные пути, а нам нужно добавить свои: к папкам inc и system_inc. Кстати стандартные пути по типу ${CONTROLSUITE_HVMOTOR_ROOT}….появиться должны после установки ControlSuite и открытии примеров-исходников и большинство файлов, которые вы будете открывать с помощью навигации (ctrl+клик ЛКМ), будут открываться вне папки проекта как раз по тем путям, учитывайте, что TI предоставляет несколько версий библиотек, которые могут сильно отличаться, ну и вообще, если надо будет что-то менять, лучше скопировать их в свою папку, а не править «на живую».


Нажимаем Add, затем Workspace и выбираем папку inc. Повторяем ту же операцию для папки system_inc. Таким образом мы прописали относительные пути – это значит, что при переносе проекта на другой компьютер ничего перенастраивать не придётся.
В итоге должно получиться примерно так, как на картинке (ну или у вас в окне). Жмём Ok – и идём дальше!
Теперь мы имеем пустой проект с прописанными путями и прочими настройками, остается его только заполнить библиотеками. Единственное, что нужно сделать — это проверить все ли подключено. По идее у вас должен получиться такой код и картина, компилируем проект. Для этого нажимаем CTRL+B или наверху идем в Project -> Build Project (а если проект один, можно и Build All). Проект должен скомпилироваться без ошибок и выглядеть так:
Теперь давайте немного о линковщике (или компоновщик, как удобнее) поговорим. Изначально при создании проекта IDE генерирует файл 28335_RAM_lnk.cmd, в дальнейшем IDE размещает программу в ОЗУ при отладке и загрузке через JTAG / отладчик. Это удобно, когда мы занимаемся отладкой, т.к. не тратятся ресурсы flash памяти да и вход в отладку в ОЗУ ощутимо быстрее происходит. Но что делать, если мы хотим сохранить программу во флеш? Для этого есть другой файл линковки, который разместит нашу программу именно во флеш. Этот вариант я и покажу, но первым делом надо определиться, как на плате выбрать Boot Mode, для этого есть специальные пины на процессоре, а на плате часто стоят джамперы, именно они определяют порядок загрузки, а уж потом файл линковщика настраивает программу для загрузки из соответствующей области памяти. Для работы из flash нужно не только изменить CMD-файл, но и, как правило, адаптировать стартовый код (например, скопировать инициализированные данные из flash в RAM, настроить работу с flash-контроллером, перенастроить векторы прерываний). Сам линковщик этого не делает. Вообще это сложный процесс, достойный отдельной статьи, на для начала для понимания это я очень хочу донести именно здесь и сейчас, чтобы сразу видеть так сказать «направление».
Некоторые важные детали:
- В C2000 для flash-проектов часто требуется включить Flash API и использовать функции инициализации (например,
Flash_CPUScaleConfig()). - Также нужно убедиться, что конфигурация тактирования (PLL) позволяет работать с flash (время доступа, wait states).
- В CMD-файле для flash обычно используют отдельные разделы памяти (например,
FLASHвместоRAM), а для секций данных, которые должны исполняться из RAM, применяют механизмramfuncs.
Первым делом удаляем файл 28335_RAM_lnk.cmd. Как я уже говорил — controlSUITE наше все. Открываем его. Теперь идем English > Device > Delfino F2833x > Supporting Libraries > Header Files for F28335x. Справа мы видим папки — это и есть стандартные библиотеки и все, что нужно, включая линковщики. Теперь идем в папку f2833x_common > cmd и тут мы видим набор линковщиков для всех камней линейки. Как не сложно догадаться файлы _RAM для заливки кода в ОЗУ, а без данного тега для заливки по флеш. Берем файл F28335.cmd и копируем в наш проект вместо старого удаленного линковщика. Теперь пришло время перенести сами библиотеки. Идем в папку f2833x_common > source и видим кучу файлов. Тут два вида библиотек: стандартные регистры (аналог CMSIS) и некое подобие SPL. В данном случае нас интересует только первый вид, то есть файлы с префиксом f2833x_. Конечно можно все их перетащить в наш проект, но зачем засорять его если мы не все использовать будем? Если что-то понадобится, то в дальнейшем просто добавим. Пока ограничимся следующим наборов файлов:
- DSP2833x_CodeStartBranch.asm (это точка входа в вашу программу, его задача принять управление от Boot ROM и передать его в вашу C-программу с помощью секции
codestartименно там вы встретите _c_int00) - f2833x_defaultisr.c (содержит слабые (weak) определения для всех векторов, чтобы линковка прошла успешно, даже если вы не реализовали свой ISR, возможно не самый критически нужный файл)
- f2833x_piectrl.c (Этот файл содержит функции и определения для управления PIE (Peripheral Interrupt Expansion) — модулем, который расширяет количество аппаратных прерываний на процессорах C2000)
- f2833x_pievect.c (Этот файл определяет и инициализирует таблицу векторов прерываний PIE в оперативной памяти (ОЗУ). В отличие от PieCtrl, который управляет включением/флагами, этот файл хранит адреса ваших функций-обработчиков (ISR))
- f2833x_sysctrl.c (Этот файл содержит функции инициализации и управления системной тактовой частотой (PLL), сторожевым таймером (Watchdog), периферийными тактовыми сигналами и режимами пониженного энергопотребления. Это один из самых первых файлов, который должен вызвать
main()перед любой работой с периферией) - DSP2833x_usDelay.asm (Этот ассемблерный файл реализует высокоточную программную задержку в микросекундах, не задействуя аппаратные таймеры. Тут хочется сказать, что delay и motorcontrol, не совсем совместимы, тк каждый такт на счету, а delay их просто сливает в мусор, даже при инициализации «неконтролируемая» задержка может выйти боком, потому с уважением и аккуратностью используйте этот инструмент и только по острой необъодимости)
Копируем данные файл и вставляем в нашу папку system_src. Теперь идем в папку f2833x_headers > source и забираем оттуда файл F2833x_GlobalVariableDefs.c и опять же копируем в нашу папку system_src. Далее идем в папку f2833x_headers > cmd и копируем оттуда файл F2833x_Headers_nonBIOS.cmd в ту же самую папку. На этом наполнение папки system_src закончено и переходим к заголовкам.
Тут кстати у новичка обязательно должен встать вопрос, что за BIOS такой? По мнению инженеров TI так должно называться некое подобие ОС. На самом деле тут небольшая игра слов! Когда говорят «TMS320 и BIOS», имеют в виду не классическую BIOS из ПК, а DSP/BIOS — специальную программную основу (ядро реального времени), которую компания Texas Instruments разработала именно для своих цифровых сигнальных процессоров (DSP, к которым относится и TMS320). Иногда в обиходе действительно расшифровывают как «Build In Operating System» (встроенная операционная система), но технически это не совсем ОС в привычном понимании, а специализированное ядро. Почему-то на самом раннем этапе, когда человеку надо выбрать основы проекта, появляется этот камень и человек больно ударяется об него, это сейчас даже Алиса знает, а когда-то в 2012 году ежика бы родили разбираясь в том, что это за хрень…но в любом случае пока этого слова надо опасаться и отмахиваться трусами, пока оно не исчезнет ;)
Идем в папку f2833x_headers > include и копируем оттуда все файлы в нашу папку system_inc. Теперь идем в папку f2833x_common > source и копируем оттуда файлы, приведу пример некоторых, остальные по аналогии:
- f2833x_examples.h (Этот заголовочный файл содержит определения и макросы для настройки тактовой частоты процессора и вспомогательные утилиты, используемые в примерах для F28335)
- f2833x_globalprototypes.h (Этот заголовочный файл содержит прототипы (объявления) всех глобальных функций, которые предоставляются стандартными драйверами периферии и системными модулями для F28335)
- f2833x_epwm_defines (не обязательный файл в нашем случае, но тк мы постепенно придем к ШИМ-контроллерам, он нам будет нужен, этот заголовочный файл содержит макросы, константы и вспомогательные определения для упрощения настройки и управления расширенными ШИМ-модулями (ePWM) на процессорах F28335)
- f2833x_swprioritizedisrlevels.h (Этот заголовочный файл позволяет задать приоритеты прерываний программно, что даёт разработчику гибкость, недоступную при использовании фиксированных аппаратных приоритетов)
- f2833x_defaultisr.h (Этот заголовочный файл содержит прототипы (объявления) всех стандартных функций-обработчиков прерываний (ISR), которые определены в файле
f2833x_defaultisr.c)
Такая вот картина может появиться в дереве проекта:


Ну надо понимать, что само по себе подключение файлов в дереве проекта, это не есть использование, естественно на них надо сослаться директивой #include, но и это не значит, что все содержимое попадет в проект, только использование непосредственно функционала файла будет увеличивать ваш исходник, потому набитое до отказа дерево проекта не всегда признак большого файла.
Теперь необходимо подключить базовые библиотеки, файл main.h обретает следующий вид:
#pragma once #include "F2833x_Device.h" #include "F2833x_examples.h"
Пробуем компилировать. Если проект скомпилировался без ошибок, значит все правильно подключено.
Вообще будет полезно еще раз представить получившуюся конструкцию с помощью такого флоучарта:
Стартовая инициализация контроллера и системы тактирования
Вот ради чего задумывалась изначально эта статья. Недооценить эту тему сложно, тк я уже писал, что все зависит от тактирования, частота ШИМ - период таймера и SYSCLKOUT, частота запуска АЦП - SYSCLKOUT, ну может отчасти HISPCP, SPI-A, SCI-A/B/C - SYSCLKOUT и LOSPCP. Даже dead-band в модуле ШИМ задается числом, которое определяется временем в микросекундах именно умножая на SYSCLKOUT. Короче вот вам картинка:
Поняли что-нибудь? И я нет, давайте вместе разбираться…тут я бы запостил видео, но оно почему-то исчезло с просторов Youtube, но хотя видео и было утеряно, базовая процедура инициализации ядра и настройки тактирования для контроллеров TMS320 семейства C2000 хорошо документирована, только на очень заумном языке и не по порядку, именно так по мнению инженеров TI я должен узнать процесс настройки, то есть открыв аппноут с названием sprs439m.pdf на 57 странице увидеть картинку, которую запостил здесь выше, а потом ниже на следующей странице (о чудо), увидеть саму систему тактирования сигнала SYSCLKOUT/CLKIN (причем не просто так их две, тк на другой блок делителя PLLSTS[DIVSEL] показан как мультиплексор каналов):


А теперь я опишу этот процесс подробнее и покажу все «камешки» и попробую избавить вас от «рождения ежиков» 30-ти сантиметровой длины.
На картинке мы видим, что в самом начале сигнал с кварца или генератора идет либо напрямую (байпас PLL), или через PLL, который может умножить нашу частоту например на 10, а потом на делитель с тремя вариантами коэффициентов /1, /2, /4 и далее к периферии. Тут есть один момент, если у вас стоит кварц на 20МГц, а вы хотите разогнать его на 150Гц, напрашивается коэффициент умножения 15, но он, как и любой другой от 11 до 14 зарезервирован, и на форумах пишут, что гарантии работы на данных настройках TI не дает, так что имейте ввиду заранее при выборе как кварца, так и своей рабочей частоты. Еще одно ограничение 300МГц на выходе OSCCLK или VCOCLKТ, то есть вроде все просто, всего то ничего: умножитель, делитель, пара регистров, но не тут то было, давайте смотреть…
Весь процесс можно разделить на два основных этапа: инициализацию среды C/C++ и настройку системы тактирования.
Точно можно сказать, что процессор можно запустить не только от кварца, но нам нужно для нашей работы точный сигнал, потому будем это делать.
1. Инициализация ядра (C/C++ Environment)
При включении питания или сбросе, ядро процессора начинает выполнение кода с определённого адреса вектора сброса. Этот код обычно ведёт к функции c_int00 (или _c_int00). Эта критически важная функция подготавливает среду для выполнения вашей C/C++ программы.
Она выполняет следующие задачи:
- Настройка стека: Определяет и инициализирует системный стек (секция
.stack), устанавливая указатели стека. - Инициализация переменных: Инициализирует глобальные и статические переменные. Это делается путём копирования начальных значений из специальных таблиц инициализации в память, выделенную для этих переменных (секция
.bss). - Вызов конструкторов C++: Если вы используете C++, здесь вызываются конструкторы для глобальных объектов.
- Запуск
main(): После выполнения всех подготовительных действий, функцияc_int00передаёт управление вашей основной функцииmain(), и ваша программа начинает работать.
В большинстве проектов для CCS (Code Composer Studio) эта настройка происходит автоматически при использовании стандартных библиотек поддержки времени выполнения (rts.src). Вам не нужно писать этот код самостоятельно.
2. Настройка системы тактирования
Это самая важная часть, которую вы программируете в самом начале функции main(). Настройка тактирования — это процесс конфигурирования встроенного ФАПЧ (PLL - Phase-Locked Loop) для генерации стабильной и высокочастотной системной частоты (SYSCLK) из внешнего или внутреннего опорного генератора.
Хотя точные названия регистров и функции могут немного отличаться в зависимости от конкретной модели, алгоритм действий един для всего семейства C2000. В документации TI предлагается следующая типовая последовательность настройки PLL (для новых серий камней):
- Обход PLL: Сначала необходимо перевести PLL в режим "байпаса" (обхода), чтобы ядро работало непосредс
твенно от опорного генератора.Для этого нужно сбросить бит PLLCLKEN в регистре SYSPLLCTL1 и сделать паузу (например, на 120 циклов NOP). В моем проекте из примера почему-то инженер забил на это, но все работало, я же предлагаю проверить ваши примеры на наличие таких вот «приколов», чтобы потом ежики сами выходили по мере возможности. - Отключение питания PLL: Затем необходимо отключить питание PLL, записав
0в полеPLLENтого же регистра, и снова сделать паузу (например, на 60 цикловNOP). - Выбор источника опорной частоты: Настроить, откуда PLL будет брать опорную частоту (например, от внешнего кварца или внутреннего генератора). Это делается записью в регистр
CLKSRCCTL1.OSCCLKSRCSEL. - Настройка делителя: Установить системный делитель на
/1, чтобы получить максимальную гибкость при дальнейшей настройке PLL. - Настройка и включение PLL: Это ключевой шаг. Необходимо выставить коэффициенты умножения (
IMULT), деления опорной частоты (REFDIV) и выходного деления (ODIV), и записать их все одновременно в регистрSYSPLLMULT. Крайне важно убедиться, что итоговая частота не превышает допустимых значений, указанных в документации на ваш конкретный контроллер. - Ожидание захвата PLL: После включения PLL требуется время для стабилизации. Нужно в цикле ожидать, пока не установится бит статуса
LOCKSв регистреSYSPLLSTS, сигнализирующий об успешном захвате частоты. - Верификация частоты (опционально): Для повышения надёжности можно настроить модуль DCC (Digital Clock Comparator) для проверки того, что сгенерированная частота PLL находится в допустимых пределах.
Пример на практике
Вместо того чтобы писать всю эту низкоуровневую работу с регистрами вручную, разработчики TI предоставляют готовые библиотеки, которые значительно упрощают жизнь.
- Библиотека C2000Ware: В ней есть готовые функции. Например,
Device_init()выполняет полную инициализацию устройства, включая настройку тактовой частоты. - Инициализация системы: Часто используется функция
InitSysCtrl(), которая содержит всю логику настройки PLL. - Настройка PLL: Для тонкой настройки можно использовать функцию
InitSysPll()с нужными параметрами.
Типичная структура кода в вашем проекте выглядит так:
#include "driverlib.h"
// ... другие включения
void main(void)
{
// 1. Остановить сторожевой таймер (Watchdog) на время инициализации
SysCtl_disableWatchdog();
// 2. Инициализировать систему тактирования (вызов готовой функции)
// Эта функция внутри себя выполнит всю последовательность с PLL,
// настроив SYSCLK на нужную частоту (например, 120 МГц)
Device_init();
// 3. Инициализировать пины ввода-вывода (GPIO)
Device_initGPIO();
// ... остальная часть вашей программы (настройка таймеров, ШИМ, АЦП и т.д.)
}Вместо универсальной функции Device_init(), в проектах для F28335 принято использовать собственную функцию InitSysCtrl(). Её код обычно находится в файле DSP2833x_SysCtrl.c.
Весь процесс инициализации системы для F28335 выглядит так:
- Остановка сторожевого таймера (Watchdog): Это делается на самом раннем этапе, чтобы предотвратить случайный сброс во время инициализации.
- Настройка PLL (ФАПЧ): Это самая важная часть, в которой мы настраиваем умножитель и делители для получения нужной частоты ядра (
SYSCLKOUT). - Настройка делителей для периферии: Мы настраиваем предделители для высокоскоростной (
HISPCP) и низкоскоростной (LOSPCP) периферии. - Включение тактирования периферийных модулей: Включаем тактовые сигналы для тех модулей, которые планируем использовать (например, ePWM, ADC).
Настройка PLL на F28335: Пошаговое руководство
Самый важный и тонкий момент — это настройка PLL. Типичная частота ядра для F28335 — 150 МГц. Для её достижения используется следующая стандартная процедура:
Шаг 1: Установите делитель DIVSEL в 0 (режим деления на 4)
Перед изменением умножителя PLL необходимо установить делитель DIVSEL в 0. Это временно понижает частоту ядра и защищает контроллер. В общем-то тут наверное тоже не надо делать догмой этот пункт, тк например по умолчанию PLLSTS[DIVSEL] стоит как делитель на /4, а при выборе загрузки из ROM меняется на /2. Но точно могу сказать, что в даташите говорится: значение PLLSTS[DIVSEL] должно быть равно 0 (собственно есть примеры, где туда вообще ничего не пишется) перед записью в PLLCR и должно быть изменено только после того, как PLLSTS[PLLLOCKS] = 1. Да и если кто не понял что значит PLLSTS[PLLLOCKS], то PLLSTS это PLL Status Register, который имеет поле PLLLOCKS это флаг состояния фазовой автоподстройки частоты (ФАПЧ)
SysCtrlRegs.PLLSTS.bit.DIVSEL = 0; // На время настройки ставим делитель /4
или так:
if (SysCtrlRegs.PLLSTS.bit.DIVSEL != 0)
{
EALLOW;
SysCtrlRegs.PLLSTS.bit.DIVSEL = 0;
EDIS;
}Тут важный момент, если кто не заметил, не все можно делать просто так, например запись в регистр PLLSTS происходит так: снимаем защиту командой EALLOW, и потом защиту возвращаем командой EDIS. Кстати именно у TI мне нравится, как сделаны обертки, обращение к регистру идет через UNION-ы, которые совмещают типы данных, так что можно обратиться как через ключ all, так и через ключ half или bit, удобно не так ли?
Шаг 2: Установите умножитель PLLCR[DIV] (умножитель с названием DIV это шедевр!!!)Запишите нужное значение в регистр PLLCR. Это значение умножает входную частоту (OSCCLK). Важно: Максимальное допустимое значение для F28335 — 10 (0x0A), ну я об этом говорил если что, как и не забыть команды EALLOW/EDIS, которые я убрал для экономии текста.
SysCtrlRegs.PLLCR.bit.DIV = 10; // Умножаем частоту на 10
тут кстати перед этим можно было поставить такую строчку:
// Before setting PLLCR turn off missing clock detect logic
SysCtrlRegs.PLLSTS.bit.MCLKOFF = 1;
Из комментария понятно, что это отключение проверки работы системы тактирования, но если сделаете, вернуть не забудьте. Ну и вообще живой пример функции void InitPll(Uint16 val, Uint16 divsel) из библиотеки вполне себе рабочий. Только потом не забудьте вернуть обратно этот бит в 0.
Если у вас внешний кварц на 30 МГц (как на многих отладочных платах), то 30 МГц * 10 = 300 МГц — это частота внутри PLL (VCOCLK), вроде не вышли из ограничений.
Шаг 3: Ожидайте захвата PLL (PLLLOCKS)
После изменения умножителя PLL требуется время для стабилизации. Нужно дождаться, пока не установится флаг PLLLOCKS. Обычно это делается в цикле:
while(SysCtrlRegs.PLLSTS.bit.PLLLOCKS != 1) {}Вот тут важно, может быть стоит раскоментить строку ServiceDog(), если вы не отключали вашу «глазастую собаку».
Шаг 4: Установите делитель DIVSEL в нужное значение
Теперь, когда PLL стабилен, можно установить финальный делитель, чтобы получить SYSCLKOUT:
SysCtrlRegs.PLLSTS.bit.DIVSEL = 2; // Делим частоту VCOCLK на 2
В итоге: VCOCLK (300 МГц) / 2 = 150 МГц. Но не забывайте, настройка делителя не совсем адаптивна под человека, там значение 0 и 1 это делитель на /4, а значение 3 выдаст делитель /1, глядишь и еще один ежик выйдет свободным путем.
Пример функции InitSysCtrl()
Вот как может выглядеть адаптированная функция инициализации для TMS320F28335:
#include "DSP2833x_Device.h" // Заголовочный файл с определениями регистров
#include "DSP2833x_Examples.h" // Вспомогательные макросы
void InitSysCtrl(void)
{
// --- 1. Остановить сторожевой таймер (Watchdog) ---
// Отключаем Watchdog, чтобы он не сбросил контроллер во время инициализации
EALLOW; // Разрешаем доступ к защищённым регистрам
SysCtrlRegs.WDCR = 0x0068; // Команда отключения Watchdog, собака "Фу!"
EDIS; // Запрещаем доступ к защищённым регистрам
// --- 2. Настройка PLL для получения SYSCLKOUT = 150 МГц ---
// Предполагается, что на входе X1/X2 подаётся частота OSCCLK = 30 МГц
EALLOW;
// Шаг 1: Временно ставим делитель /4
SysCtrlRegs.PLLSTS.bit.DIVSEL = 0;
// Шаг 2: Устанавливаем умножитель PLL = 10
// 30 МГц * 10 = 300 МГц (VCOCLK)
SysCtrlRegs.PLLCR.bit.DIV = 10;
EDIS;
// Шаг 3: Ожидаем захвата PLL
while(SysCtrlRegs.PLLSTS.bit.PLLLOCKS != 1) {}
EALLOW;
// Шаг 4: Устанавливаем финальный делитель /2
// 300 МГц / 2 = 150 МГц (SYSCLKOUT)
SysCtrlRegs.PLLSTS.bit.DIVSEL = 2;
EDIS;
// --- 3. Настройка предделителей для периферии ---
// HISPCP - для высокоскоростной периферии (например, ADC)
// LOSPCP - для низкоскоростной периферии (например, SCI, SPI)
EALLOW;
SysCtrlRegs.HISPCP.all = 0x0001; // HSPCLK = SYSCLKOUT / 2 = 75 МГц
SysCtrlRegs.LOSPCP.all = 0x0002; // LSPCLK = SYSCLKOUT / 4 = 37.5 МГц
EDIS;
// --- 4. Включение тактирования для периферийных модулей ---
// Включаем тактирование для всех модулей, которые будут использоваться
// Это делается с помощью регистра PCLKCR0, PCLKCR1, PCLKCR3
EALLOW;
// Пример: Включить тактирование для ePWM1, ePWM2, ePWM3
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1; // Включить ADC
SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1; // Включить ePWM1
SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 1; // Включить ePWM2
SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 1; // Включить ePWM3
// ... аналогично для других модулей
EDIS;
}Затем эта функция вызывается в самом начале main():
void main(void)
{
// Сначала инициализируем систему
InitSysCtrl();
// ... остальная инициализация (GPIO, PIE, периферия) и основной код
}Важные замечания для F28335
- Входная частота (OSCCLK): Код выше рассчитан на внешний кварц 30 МГц. Если на вашей плате стоит другой кварц (например, 20 МГц), вам нужно пересчитать значения умножителя, чтобы не выйти за пределы 150 МГц.
- Максимальный умножитель: Значение
PLLCR[DIV]не должно превышать 10 (0x0A). Установка запрещённых значений (например, 15) может привести к нестабильной работе. - Защита регистров: Обратите внимание на макросы
EALLOWиEDIS. Многие системные регистры в F28335 защищены от случайной записи, и для их изменения требуется предварительно разблокировать доступ. - Библиотека: Весь этот код и файлы, такие как
DSP2833x_SysCtrl.c, являются частью библиотеки C2000Ware (ранее называласьcontrolSUITE). Для работы с F28335 настоятельно рекомендуется использовать её.
Этот адаптированный подход полностью повторяет логику из утерянного видео, но уже применительно к вашей конкретной модели контроллера.
Удачи!!!
неет…конечно же нет, на этом не конец, далеко не конец, мы разобрали только момент с получением тактового сигнала, а потом конечно нужно настроить тактирование периферии (включить её), настроить вектора прерываний, в целом в перечень вызываемых функция можно включить такой список:
void InitStartSystem (void) {
DisableDog();
XtalOscSel();
InitPll(TMS320_PLLCR, TMS320_DIVSEL);
InitPeripheralClocks();
InitPieCtrl();
InitPieVectTable();
}Функция из примера InitStartSystem() «самодельная», а вот все функции, которые вызываются из нее, уже являются стандартными. Советую вам посмотреть подробно как они реализованы, для этого можно зажать CTRL и кликнуть по интересующей функции, редактор откроет вам файл, где имеется реализация функции. Кратко о назначении:
- DisableDog() — функция выключает «смотрящую собаку». Это обязательное действие при настройке основной части критичной периферии, например, системы тактирования. В коде библиотек вы часто увидите это, оно будет дублироваться и дублироваться
- XtalOscSel() — данная функция реализует переключение с внутреннего источника тактирования на внешний кварц. Важный момент! В стандартной библиотеке с данной функцией может скрываться ошибка — она не объявлена. Идем в файл f2833x_globalprototypes.h и среди всех прочих объявление дописываем строку extern void XtalOscSel (void)
- InitPll(TMS320_PLLMUL, TMS320_DIVSEL) — функция производит настройку PLL. В нее передаются 2 значения: множитель и делитель. Их значение прописаны дефайнами в заголовочном файле.
- InitPeripheralClocks() — данная функция просто включает тактирование для всей периферии. Да, именно для всей. C2000 — это не решение для железок с питанием от батарейки, это решение на единицы-десятки-сотни киловатт и жалкие 2-3 мА роли тут не сыграют. Ну а вам не надо каждый раз вспоминать включили вы тактирование на какой нибудь SPI или нет, но если хотите, напишите свою, код потом все равно напишу
- InitPieCtrl() — функция выключает все прерывания и сбрасывает флаги прерываний
- InitPieVectTable() — функция заполняет таблицу с векторами прерываний
Для того, чтобы убедиться в работоспособности кода, мы вызываем функцию инициализации в main, компилируем, прошиваем и цепляемся осциллографом на GPIO18. Это вывод аналогичен MCO у STM32, то есть он выводит системную частоту. Осциллографом должны увидеть сигнал с тактовой частотой. Если осциллограф хороший, то вы увидите меандр, если китаец (даже хороший), то скорее всего это будет что-то ближе к синусу. Настройка GPIO18 на вывод системной частоты можно увидеть в функции InitPeripheralClocks(). Сначала нужно «подключить» gpio к выходу частоты, а затем установить делитель, равный 1:
GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 3; // GPIO18 = XCLKOUT SysCtrlRegs.XCLK.bit.XCLKOUTDIV=2; // Set XCLKOUT = SYSCLKOUT/1
Отладка
Давайте немного отвлечемся от праграммы и займемся отладкой микроконтроллера, она осуществляется в реальном времени через интерфейс JTAG, чтобы перейти к ней нужно нажать на «жука» в панели инструментов. В процессе выполнения программы можно просматривать значения переменных и содержимое памяти без остановки выполнения кода, для этого служат вкладки Expression и Variables. Данные обновляются, их можно редактировать, визуализировать в виде графиков или сохранять в файл для последующего анализа.
Чтобы видеть изменения переменных в Code Composer Studio (CCS) без остановки программы, нужно использовать функцию "Continuous Refresh" (Непрерывное обновление) в окне Expressions (или Watch). Она специально создана для отслеживания значений "онлайн". Вот пошаговая инструкция, как это настроить:
Шаг 1: Откройте окно Expressions
В меню выберите View → Expressions.
Шаг 2: Добавьте нужные переменные
В открывшемся окне Expressions вручную введите имена переменных, которые хотите отслеживать.
Шаг 3: Включите "Continuous Refresh"
На панели инструментов окна Expressions найдите и нажмите кнопку ";Continuous Refresh" (она обычно выглядит как значок с круговой стрелкой).
Шаг 4: Включите "Real-time Mode" (режим реального времени, возможно и не потребуется, тк уже может быть активирован)
Это ключевой шаг. Функция Real-time emulation позволяет обновлять окна, пока микроконтроллер (MCU) работает.
- На верхней панели инструментов CCS найдите и нажмите кнопку "Real-time Mode".
- Если появится диалоговое окно, нажмите
YES, чтобы разрешить отладочные события.
Шаг 5: Запустите программу
Нажмите Resume (F8), чтобы программа начала выполняться. Теперь значения переменных в окне Expressions должны обновляться автоматически, без необходимости ставить программу на паузу.
💡 Важные нюансы
- Частота обновления: Обновление происходит с определенной периодичностью (по умолчанию до 10 Гц). Это не мгновенное обновление, а периодический "снимок" значений.
- Глобальные переменные: Этот метод лучше всего работает с глобальными переменными. Локальные переменные могут отображаться некорректно, так как они "живут" только в рамках своей функции.
- Альтернативный способ: Можно также включить Continuous Refresh в окне Watch Window или использовать Graph для визуализации данных в реальном времени.
- Ограничения: Помните, что это эмуляция реального времени, и она может не идеально работать на всех устройствах или с очень быстрыми изменениями переменных.
Если кнопка ";Continuous Refresh" неактивна, убедитесь, что вы находитесь в режиме отладки (Debug Perspective) и программа запущена (не находится в режиме паузы).
В среде Code Composer Studio доступны два типа точек остановки (breakpoint):
Программные точки остановки
Они вставляются непосредственно в код с помощью специальной ассемблерной инструкции (например, ESTOP0). Такие точки особенно удобны при отладке исключений или аварийных ситуаций.
После останова доступен стек вызовов (call stack), позволяющий проследить цепочку вызовов, приведшую к текущему состоянию. При наведении курсора на переменную в редакторе кода отображается её текущее значение.
Если плата запущена без подключённого отладчика, программные точки останова игнорируются, и выполнение программы продолжается без прерываний.
Аппаратные точки остановки
Устанавливаются щелчком мыши в поле рядом с строкой исходного кода во время выполнения программы. Они работают аналогично программным, но их количество аппаратно ограничено - обычно доступно всего 2 точки.
Попытка установить больше приводит к нестабильности отладчика: он может аварийно завершить работу. В этом случае необходимо вручную удалить все точки останова и перезапустить сессию отладки - это один из самых раздражающих моментов при работе с CCS.
Дополнительные возможности
При останове также доступны:
- Дизассемблированный код - позволяет увидеть текущие машинные инструкции;
- Карта памяти - отображает распределение переменных и функций по адресному пространству;
- Анализ использования стека - даёт оценку глубины рекурсии и потенциальных переполнений.
Память
Немаловажный момент кроется в понимании памяти МК. Оперативная память микроконтроллеров серии TMS320C28xx разбита на страницы и секции. Логическое распределение этих секций по адресному пространству и их назначение (для кода, данных, стека и пр.) описывается в линкер-скрипте - файле с расширением .cmd, который является частью проекта.
В исходном коде вы можете явно указать, в какой секции должна размещаться переменная или функция, используя прагмы компилятора. Например:
#pragma DATA_SECTION(myBuffer, &qquot;) uint16_t myBuffer[128];
uot;mySection&
Это на тот случай, если надо разместить какие-то данные в конкретном месте. Теоретически такая организация памяти позволяет повысить производительность: архитектура C28x поддерживает параллельное обращение к разным банкам памяти в рамках одного цикла выполнения (например, выборка инструкции из одной секции и чтение данных из другой). Однако на практике эта возможность требует тщательного планирования размещения кода и данных. В моём опыте подобное разбиение чаще создавало дополнительные сложности, чем ощутимый выигрыш в скорости — особенно без глубокого понимания архитектуры памяти контроллера.
Размещение функций в оперативной памяти
Для ускорения выполнения критических по времени функций их можно разместить непосредственно в ОЗУ, а не во флеш-памяти. Для этого используется прагма CODE_SECTION:
#pragma CODE_SECTION(fastISR, &") void fastISR(void) { }
quot;ramfun
cs
Однако микроконтроллер не копирует код из флеш-памяти в ОЗУ автоматически. Это необходимо делать вручную - обычно в функции инициализации, сразу после запуска системы. Пример:
memcpy(&ramfuncs_run, &ramfuncs_load, (size_t)&ramfuncs_size);
где ramfuncs_load - адрес образа функций во флеш-памяти, ramfuncs_run - адрес их размещения в ОЗУ, а ramfuncs_size - размер секции. Эти символы генерируются линкером на основе .cmd-файла.
Настройка GPIO
Для работы с данным семейством нам потребуется только референс мануал, который разработчики из TI разбили на несколько файлов, каждый из которых описывает определенную периферию, что очень удобно. Скачиваем даташит тут и идем в раздел Documentation Support. Тут мы видим набор ссылок на документацию с ее кратким описанием: errata, пособие для старта управления двигателем и гайды по каждой периферии. Нас интересует документ с названием TMS320F2833x System Control and Interrupts Reference Guide, именно в нем находится описание интересующих нас GPIO и прочих базовым системных настроек. Смотрим на структурную схему GPIO:


Мы видим достаточно привычную картину устройства портов ввода-вывода. Тут и возможность включения внутренней подтяжки, и использование сигнала с GPIO для прерываний и прочие прелесть. Главная фича данной периферии у C2000 — это возможность аппаратного подавления помех, например, дребезга механических контактов кнопки. Давайте посмотрим на интересную диаграмму:
На ней показан принцип считывания состояния входов. В большинстве контроллеров состояние входа считывается с частотой тактирования данной периферии, то есть по умолчанию с частотой 56 МГц в нашем случае, а у тех же stm в старших семействах эти частоты еще выше. Думаю всем понятно, что при такой частоте контроллер успевать «увидеть» любые помехи и шумы. Иногда такая частота нужна, а иногда нет, например, если нам нужно опрашивать кнопку. Зачем нам ее опрашивать каждые 18 нс? Поэтому реализовали возможность уменьшения частоты тактирования конкретного порта с помощью регистра CTRL и битов QUALPRDx, где X принимает значение от 0 до 3: QUALPRD0 отвечает за GPIO0...7, QUALPRD1 отвечает за GPIO8...15 итак далее. По сути это обычный делитель частоты с коэффициентом от 1 до 510.
Кнопку опрашивать сильно часто смысла нет, поэтому настраивать делитель будем на значение 510, то есть на максимум. Смотрим опять на диаграмму и видим, что сигнал считается установившемся, только когда его уровень был неизменным 6 тактов. Количество тактов, которое требуется для фиксации может быть 1, 3 или 6.
Чем больше делитель и чем больше тактов мы фиксируем, тем стабильнее работа защиты от дребезга.
Когда будет дребезг контактов, то вначале это будут хаотичные переходы из 0 в 1 и обратно, когда дребезг пройдет и сигнал устаканится и не будет изменяться в течение 6 тактов, то это будет означать, что кнопка нажата. Все гениальное просто.
Теперь давайте рассмотрим основные регистры, прерывания затрагивать не будем — только настройки самих портов. Для начала нужно сказать, что регистры делятся на 2 типа: регистры настройки и регистры данных. Первые отвечают за конфигурацию свойств, например, вход это или выход. Вторая группа отвечает за запись и считывания состояния порта.
Регистры настройки:
- GPxCTRL — регистр для записи делителя тактовой частоты. Вместо «x» подставляем букву «А» — если у нас GPIO0...31, «В» — если используем GPIO32...63 и так далее
- GPAQSELx — регистр для установки количество тактов для фиксации значения на входе
- GPAMUX1 — регистр для выбора подключенной периферии, например, он указывает что приходит на ногу GPIO или UART, а может и ШИМ.
- GPADIR — регистр выбора направления работы GPIO: вход или выход. По умолчанию все порты настроены на вход
- GPAPUD — регистр отвечающий за подключение внутренней подтяжки к VCC.
Стоит заметить, что у части портов по умолчанию подтяжка выключена, а у части включена.
Это важно помнить!
Регистры данных:
- GPADAT — регистр состояния вывода. Если вывод настроен на вход, то из него мы читаем состояние входа. Если настроен на выход, то мы можем записать значение, которое этот выход должен принять, то есть 0 или 1
- GPASET — регистр установки выхода в «1». Чтобы выставить в «1», необходимо записать «1», при записи «0» команда игнорируется
- GPACLEAR — регистр установки выхода в «0». Чтобы выставить в «0», необходимо записать «1», при записи «0» команда игнорируется
- GPATOGGLE — регистр, который инвертирует текущее значение состояние выхода. Чтобы инвертировать значение, необходимо записать «1», при записи «0» команда игнорируется
Вот такой простенький набор регистров. Даже из описания выше уже можно понять, что нужно сделать для настройки порта. Еще раз шаги кратко можно описать так:
- Шаг 1 — определяемся с функционалом вывода: что это будет вход или выход, gpio или выход другой периферии и прочее
- Шаг 2 — включаем или отключаем внутреннюю подтяжку к питанию
- Шаг 3 — настраиваем тактирование и защиту от дребезга для конкретного порта
- Шаг 4 — выбираем нужную функцию: gpio или периферия
- Шаг 5 — настраиваем направление работы вывода: вход или выход
Вот и вся настройка, как видите она элементарная и логически понятная. Сразу хочу отметить, что не обязательно именно в таком порядке производить настройку, например, настроить направление (вход или выход) можно самым первым шагом. Это не имеет значения.
И опять хочу напомнить, что при работе с регистрами в семействе C2000 необходимо учесть момент, что они защищены. Все далее описанное касается в основном только регистров группы настройки. То есть любая работа с системными регистрами должна ВСЕГДА выглядеть следующим образом:
EALLOW; // Работаем с системными регистрами, меняем их значения, настраиваем EDIS;
Такая операция не требуется, если мы работаем с регистрами данных, например, если мы выставляем с помощью регистра GPxSET наш выход в «1», то с него не надо снимать защиту и соответственно и включать ее обратно.
Исходя из всего выше описанного, давайте настроим GPIO0...3 со светодиодами на выход. Все настройки GPIO предлагаю поместить в функцию InitLEDgpio и напишем ее:
void InitLEDgpio (void) {
EALLOW;
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO2 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO3 = 1;
EDIS;
}
По умолчанию наши GPIO уже настроены как GPIO, т.к. все значения регистров очищаются, а значит в регистр GPAMUX1 уже записан «0». Для GPIO0...11 подтяжка по умолчанию отключена, поэтому нам и остается только взять и определить направление работы на выход с помощью GPADIR. Если вы помните, то светодиоды к контроллеру подключены катодами, а значит сразу после инициализации они будут светиться. Давайте прям в функции инициализации данные выводы настроим в «1»:
void InitLEDgpio (void) {
EALLOW;
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO2 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO3 = 1;
EDIS;
GpioDataRegs.GPASET.bit.GPIO0 = 1;
GpioDataRegs.GPASET.bit.GPIO1 = 1;
GpioDataRegs.GPASET.bit.GPIO2 = 1;
GpioDataRegs.GPASET.bit.GPIO3 = 1;
}
Как видите я не использую регистр GPADAT для записи, а применяю SET, CLEAR, TOGGLE. Так же обратите внимание, что данную запись я выполнил за пределами защищенной зоны, то есть после команды EDIS. Теперь в этой же функции настроим GPIO12 для работы с кнопкой и допишем нашу функцию:
void InitLEDgpio (void) {
EALLOW;
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO1 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO2 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO3 = 1;
GpioCtrlRegs.GPAPUD.bit.GPIO12 = 1;
GpioCtrlRegs.GPACTRL.bit.QUALPRD1 = 0xFF;
GpioCtrlRegs.GPAQSEL1.bit.GPIO12 = 2;
EDIS;
GpioDataRegs.GPASET.bit.GPIO0 = 1;
GpioDataRegs.GPASET.bit.GPIO1 = 1;
GpioDataRegs.GPASET.bit.GPIO2 = 1;
GpioDataRegs.GPASET.bit.GPIO3 = 1;
}
Первым делом я отключаю внутреннюю подтяжку записью «1» в регистр GPAPUD, т.к. она для GPIO12 по умолчанию включена. Как я ранее писал — все порты после инициализации настроены на вход, т.к. в регистре GPADIR записаны нули, его мы тут не настраиваем.
Остается настроить защиту от дребезга, для этого делители записываем 0xFF, что соответствует значению /510. В регистр GPAQSEL1 запишем значение «10» или 2, что установит значение на выборку в 6 тактов. Готово! Для того, чтобы считать значение конкретного входа, нужно просто считать значение из регистра GPADAT:
if (GpioDataRegs.GPADAT.bit.GPIO12) {
// Если кнопка нажата и подает +3.3В (лог. 1) на вход, то выполняем данные действия
}
Вот таким образом мы и опрашиваем нужные выводы. Теперь давайте вызовем функцию настройки gpio в нашей основной функции и получим ее финальный вид:
void InitStartSystem (void) {
DisableDog();
XtalOscSel();
InitPll(TMS320_PLLMUL, TMS320_DIVSEL);
InitPeripheralClocks();
InitPieCtrl();
InitPieVectTable();
/*********************************/
InitLEDgpio();
}Настало время написать нашу первую тестовую программу и проверить все это дело. Алгоритм такой: моргает светодиодом, который сидит на GPIO3, а при нажатии кнопки на GPIO12 зажигаем просто светодиод GPIO0. Таким образом мы проверим работу портов и на вход и на выход. Пишем следующий код:
#include "main.h"
int main (void) {
InitStartSystem();
while(1)
{
if (GpioDataRegs.GPADAT.bit.GPIO12)
{
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;
}
else
{
GpioDataRegs.GPASET.bit.GPIO0 = 1;
}
GpioDataRegs.GPATOGGLE.bit.GPIO3 = 1;
delay(100000);
}
}
Компилируем, идем в отладчик, запускаем и видим как один светодиод постоянно моргает, а при нажатии кнопки загорается еще один. В конце раздела я прикреплю проект с данным кодом, если что-то не получится, то подсмотрите в него. Специально для тех, кто тяжело воспринимает тексты или не все моменты понял предлагаю посмотреть данное видео по работе с GPIO, там происходит все тоже, что в разделе «GPIO».
Разбираемся с модулем ШИМ (ePWM) на TMS320
Устали, нет? Отлично, погнали дальше! Раз уж мы заговорили о силовой электронике и управлении двигателями, самое время разобраться с главным инструментом, которым мы будем орудовать постоянно. Я, конечно, надеюсь, что читатель уже в курсе, что такое ШИМ, но если кто-то проспал этот урок на лекциях, давайте на пальцах (и с юмором) разжуём. К слову, в зарубежной литературе ШИМ – это Pulse-Width Modulator (PWM).
ШИМ: как обмануть железо и не сжечь транзисторы
ШИМ, или широтно-импульсная модуляция, — это способ регулировки мощности, при котором мы не пытаемся плавно уменьшить напряжение (как если бы вы прикрывали кран с водой), а делаем хитрее: мы тысячи раз в секунду полностью открываем и полностью закрываем ключ. Ключ либо полностью проводит ток (как труба настежь), либо полностью перекрывает его (как заглушка). Никаких промежуточных положений, а значит и потерь!
А управляем мы этим процессом через параметр, который называется скважность – это отношение времени, когда ключ открыт, к полной длительности цикла (открыт + закрыт). Меняя скважность от 0 до 100%, мы меняем среднюю мощность, подаваемую на нагрузку. Скажем, при скважности 50% нагрузка получает как бы «половинное» питание, при 30% – треть, и так далее.
В чём тут магия и почему это круче, чем «плавно прикрутить»?
Всё дело в том, что транзистор – не кран. Если оставить его приоткрытым (в промежуточном состоянии), он будет дико греться, потери мощности улетят в космос, и вы спалите пол-платы раньше, чем успеете сказать «векторное управление». А в режиме ШИМ транзистор всегда либо полностью открыт (потери почти нулевые), либо полностью закрыт (потери опять почти нулевые). Есть, правда, микроскопический момент переключения, но им можно пренебречь – реальные потери обычно не превышают 2–3% от мощности частотника. Красота!
Но не всё так радужно, есть и минусы:
- Шумы – переключения на высокой частоте создают помехи и электромагнитный свист, который иногда слышно даже ухом.
- Постоянная времени – нагрузка не успевает мгновенно реагировать на эти импульсы, приходится учитывать индуктивности и ёмкости, иначе получится не синус, а зубья пилы.
Зато именно ШИМ позволяет достигать высочайшей эффективности и плавности регулировки.
И главное: именно ШИМ – это метод номер один во всём мире силовой электроники: от регулировки оборотов двигателя до управления сварочным инвертором и зарядкой электромобиля. А раз мы взялись за TMS320 – у него в арсенале мощнейшие ШИМ-модули, включая те самые HRPWM, которые позволяют получить невероятно точный и чистый сигнал. Без них ни один серьёзный MotorControl проект не обходится. Так что запасайтесь осциллографами и готовьтесь настраивать скважности – это наша стихия!
Теперь перейдём к самому сердцу управления силовой частью – модулю ePWM (Enhanced Pulse-Width Modulator). Приставка «e» здесь не для красоты – это «Enhanced», то есть усовершенствованный по сравнению с тем, что было в древних моделях типа TMS320F2407 или F2812. И усовершенствован он настолько, что способен генерировать ШИМ-сигналы сложнейшей формы, почти не отвлекая процессор от его важных дел. Это как иметь личного помощника, который крутит ручки на усилителе, пока вы солируете на гитаре.
Как это работает изнутри?
Главная задача ePWM – формировать управляющие сигналы для силовых ключей (транзисторов). По сути, это просто последовательность логических «0» и «1»: высокий уровень – открываем ключ, низкий – закрываем (или наоборот, зависит от того, как у вас сконструирован драйвер, но суть одна).
Для генерации этого сигнала нужны два главных игрока:
- Несущий сигнал – это периодический «пилот», который задаёт такт. Обычно он имеет пилообразную или треугольную форму.
- Сигнал задания – это то, что мы хотим получить на выходе (например, синусоида для инвертора, или просто постоянное значение для регулировки яркости).
Дальше начинается магия сравнения. В тот момент, когда значение несущего сигнала догоняет сигнал задания, выходной сигнал ШИМ меняет своё состояние с «1» на «0» (или наоборот, если вам так удобнее). Когда несущий сигнал достигает своего пика и сбрасывается в ноль – выходной сигнал тоже переключается обратно. Всё просто, как дважды два.
Но это только вершина айсберга!
Модуль ePWM – это не просто компаратор с часами. Он обладает гибкостью, которая заставит плакать от восторга любого схемотехника:
- Можно выбрать форму несущего сигнала: растущий, падающий или треугольный – как вам больше нравится.
- Можно синхронизировать его с внешними событиями – чтобы весь ваш частотник дышал в унисон.
- Можно настроить реакцию на аварийные ситуации – например, моментально закрыть все ключи, если что-то пошло не так (защита от дурака, только аппаратная).
- Можно включить комплиментарный режим для двух выводов – когда один открыт, второй гарантированно закрыт, и между ними автоматически вставляется мёртвое время (чтобы не было сквозных токов и фейерверка из транзисторов).
Цифры для затравки:
На борту TMS320F28035 целых 6 таких модулей, и каждый может управлять двумя выводами. Это значит, что вы можете разруливать до 12 силовых ключей одновременно – хоть для трёхфазного мотора, хоть для многоканального преобразователя. Процессор при этом лишь задаёт начальные параметры, а дальше ePWM крутится сам, как заведённый.
Короче говоря, ePWM – это не просто ШИМ, это швейцарский нож для силовой электроники. Он берёт на себя всю рутину по формированию точных управляющих сигналов, освобождая ваше ядро для сложных алгоритмов, фильтров и прочих радостей программирования.
В цифровом виде роль несущего сигнала выполняет счётчик (таймер), который работает в автоматическом режиме. Счёт можно настроить разными способами: «вверх» (от нуля до периода, как на рисунке выше), «вниз» (от периода до нуля), или «вверх-вниз» (от нуля до периода, а затем обратно до нуля - так называемый симметричный ШИМ или Center Aligned). В качестве сигнала задания выступает какое-либо число, которое задаёт программист, и с которым сравнивается значение счётчика, обычно оно помещается в спец.регистр. Такое число называется уставкой сравнения. При этом у каждого модуля ePWM есть две независимые уставки сравнения A и B, что даёт возможность сформировать сразу два выходных сигнала – EPWMxA и EPWMxB (здесь x – номер ePWM-модуля). Эти выходные сигналы могут быть выведены на выводы микроконтроллера вместо определённых GPIO. Например, для модуля ePWM1 канал A можно вывести вместо GPIO0, а канал B вместо GPIO1. Часто можно видеть в документации на ПЧ, что сравниваются 2 уставки, это немного другое, это реализация пространственно-векторной модуляции, там используются три ШИМ, один канал открыт, остальные два открываются и закрываются по уставке (или наоборот закрываются). В контексте ПЧ же на одном канале ШИМ присутствует только одна уставка, а два выхода ШИМ A и B используются как комплиментарный выход. Эти каналы A и B могут работать совершенно независимо — каждый сам по себе, со своей скважностью и формой. Но если вы строите инвертор или управляете мостом из ключей, вам нужна пара сигналов, которые всегда противоположны друг другу (комплементарны): когда один ключ открыт, второй закрыт. И здесь ePWM делает вам шикарный подарок: канал B может автоматически стать «зеркальным отражением» канала A, причём с аппаратной вставкой мёртвого времени — той самой крошечной задержки, которая не даёт верхнему и нижнему ключу открыться одновременно и устроить короткое замыкание с фейерверком.
На борту TMS320F28035 у нас как мы помним целых 6 модулей ePWM. Все они как близнецы-братья: абсолютно идентичны, независимы друг от друга, но могут работать одновременно, не мешая соседям, а могут наоборот синхронизироваться. При этом вы вольны задействовать ровно столько модулей, сколько требует ваша задача — хоть один, хоть все шесть. Никто не заставляет использовать весь арсенал, если вам достаточно пары каналов для регулировки яркости лампочки.
Итого: 6 модулей × 2 канала = 12 управляющих сигналов. Этого хватит, чтобы разрулить трёхфазный инвертор с запасом, а если нужно больше — всегда можно добавить внешнюю логику. Но главное — простота и автоматизация: не нужно вручную вычислять мёртвое время и инвертировать сигналы, ePWM сделает это за вас, и сделает надёжнее, чем любой программный костыль.
Структура модуля ШИМ
Модуль ePWM состоит из нескольких подмодулей. Его структура и отношения между подмодулями показаны на рисунке (можно листать):



Краткое описание подмодулей:
- счётчик (Time-Base) выполняет функцию создания несущего сигнала для ШИМ;
- подмодуль сравнения (Counter Compare) проверяет значение счётчика и сравнивает его с заданными уставками CMPA и CMPB. При совпадении, подмодуль сравнения генерирует событие сравнения;
- подмодуль действия (Action Qualifier) определяет, какие события в какие действия должны быть преобразованы. См. таблицу;
- подмодуль мёртвого времени (Dead Band) позволяет вносить задержку на переключение каналов. Таким образом можно создать паузу между отключением ключа в нижней стойке инвертора и включением верхнего, чтобы избежать протекания сквозных токов в стойке, считаем что каналы A и B одного модуля ePWM и модуль создает на канале В сигнал, комплементарный А;
- подмодуль запуска событий (Event Trigger) позволяет генерировать прерывания и запускать АЦП по событиям, приходящим от модуля сравнения (достижение счётчиком нуля или периода, сравнение счётчика с уставками). Это похоже на поведение подмодуля действия с той разницей, что вместо изменения состояния каналов A и B запускается АЦП или прерывание;
- подмодуль реакции на аварию (Trip Zone) позволяет мгновенно отключать силовые ключи при появлении определённого сигнала (низкого уровня) на определенном выводе микроконтроллера. На этот вывод обычно заводят сигнал аппаратной аварии с драйвера ключа или с компаратора тока, но есть там особенный вход TZ6, он не связан с физическим входом, он нужен для обработки программной ошибки, такой как проблема в процессоре или исполнении кода, но и остальные события кроме физического получения сигнала, могут быть сгенерированы программно;
- подмодуль PWM Chopper позволяет преобразовать высокий сигнал канала A или B в последовательность коротких импульсов для случая, если драйвера ключей управляются через трансформатор для обеспечения гальванической развязки;
Кроме того модуль имеет такие сигналы:
- EPWMxSYNCI и EPWMxSYNCO сигналы — тут мало что можно рассказать, это сигналы сихронизации, которые могут быть входом или выходом.
- EPWMxSOCA и EPWMxSOCB сигналы — тут всё более чем понятно из названия. Эти события могут задать события SOC для АЦП.
Теперь перейдём к модулям
Time base (TB) модуль — отвечает за время события каждого модуля ePWM. Не будем сильно вдаваться во все настройки этого модуля, думаю достаточно обратить внимание на то, что есть 3 режима работы счётчика:
- Up-Down-Count Mode
- Up-Count Mode
- Down-Count Mode
А так же есть настройка синхронности таймера через выставление бита TBCLKSYNC
Counter compare (CC) модуль — через него мы как-раз таки и задаём нашу скважность.
Action-Qualifier (AQ) модуль — через него можно настроить состояние на событие. А для выходов можно настроить следующие действия:
- Выставить в состояние HI
- Выставить в состояние LO
- Произвести инверсию состояния
- Ничего не делать
Dead-Band Submodule (DB) модуль — с помощью этого модуля можно настроить зоны нечувствительности для каналов ШИМа. Ни для кого не будет секретом то, что ключи транзисторов переключаются не мгновенно и для того, чтобы не было ситуации когда верхний ключ полумоста не успел закрыться, а нижний уже открыт, выставляют задержку на переключение в состояние HI и более раннее включение в состояние LO.
Trip-Zone Submodule (TZ) модуль — как говорилось выше, этот модуль связан с отработкой аварийных состояний. Тут мы можем выбрать 1 из 4 действий.
- Выставить в состояние HI
- Выставить в состояние LO
- Выставить High-impedance состояние
- Ничего не делать
Событие, вызывающее действие TZ модуля может быть вызвано как программно, так и аппаратно. Кроме этого, предусмотрен вызов прерывания.
Теперь перейдем от слов к практике… Для начала необходимо настроить GPIO на альтернативную функцию epwm
EALLOW;
// Включение pull-up
GpioCtrlRegs.GPAPUD.bit.GPIO0 = 0x000;
GpioCtrlRegs.GPAPUD.bit.GPIO1 = 0x000;
// Настройка GPIO как EPWM1A
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0x001;
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0x001;
EDIS;
Далее, когда мы уже настроили GPIO, мы можем приступать к разным настройкам. Для настройки работы ШИМа надо определиться с тем, что мы хотим получить. Начнём с частоты TBCLK. Она определяется по формуле:
Здесь правда стоит ½ потому как я ориентируюсь на Up-Down-Count Mode или симметричный ШИМ (Center Aligned), в случае обычной ШИМ на 2 делить не нужно. Еще необходимо обратить внимание на то, что CLKDIV по умолчанию равен /1, с HSPCLKDIV всё иначе, по умолчанию он равен /2, если говорить про реальное действие коэффициентов-делителей. Это надо держать в уме, поскольку бывают случаи, когда люди об этом забывают. При загрузке программы в RAM часто HSPCLKDIV у нас /1, соответственно, эту проблему замечают не сразу. Вообще лучше не использовать значения по умолчанию, а присваивать свои, тем более это не проблема, это поля регистра TBCTL и можно обратиться к ним как EPwmxRegs.TBCTL.bit.HSPCLKDIV и EPwmxRegs.TBCTL.bit.CLKDIV, где x-номер канала ePWM.
Кстати с точки зрения математики для расчета частоты модуля ШИМ лучше использовать такую формулу:
TBCLK = SYSCLKOUT / ( (2*EPwmxRegs.TBCTL.bit.HSPCLKDIV)*(1<<EPwmxRegs.TBCTL.bit.CLKDIV) )EALLOW;
в ней учтено реальное значение регистра делителя, а то если просто на него делить по первой формуле выше (та формула, где картинкой), точно ошибка будет, в качестве значений для выбора делителя можно использовать макроопределения типа HSPCLKDIV_PRESCALE_X_1 или CLKDIV_PRESCALE_X_1, во второй формуле (2*EPwmxRegs.TBCTL.bit.HSPCLKDIV) это реальное значение настройки делителя HSPCLKDIV, а (1<<EPwmxRegs.TBCTL.bit.CLKDIV) это значение поля CLKDIV из первой формулы выше второй, во второй же формуле кстати нет значения ½, которое нужно для симметричный ШИМ. Надеюсь не запутал ;)
С частотой тактирования TBCLK надеюсь определились. Но надо бы выбрать, то, как у нас будет работать счётчик. По спаду, по нарастающей, а может и так и эдак, для этого надо настроить соответствующий регистр, например:
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;
В дальнейшем, чтобы никто не пугался макросам, определимся откуда они вообще появляются. Эти дефайны определены в файле с названием DSP2833x_EPwm_defines.h.
После чего необходимо определиться с тем, как будут реагировать наши GPIO на достижение определённых значений TBCTR, за это отвечает регистр AQCTLA/B — это 16-битный регистр управления (Action-Qualifier Control Register) или по-русски это регистр управления выходом A/B квалификатора действий. Он определяет, какое действие (установить, сбросить или переключить уровень) должен выполнить выход при наступлении определенных событий, например, при совпадении счетчика времени с регистрами сравнения CMPA или CMPB. Вариантов действий более чем достаточно. Они приведены в таблице из даташита ниже:
Затем необходимо определиться, какого поведения мы хотим от портов А и В, а именно, мы хотим, чтобы они были связаны друг с другом или же могли работать независимо. Если мы хотим, чтобы порт А был ведущим, то мы просто записываем для него действия, например (случай для отсчёта по нарастающей):
EPwm1Regs.AQCTLA.bit.CAU = AQ_SET; EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
если же хотим независимости и для второго порта, то добавляем:
EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET; EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR; EPwm1Regs.AQCTLB.bit.ZRO = AQ_SET; EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
По подробнее о настройках необходимо обратиться к изображению выше за одним лишь дополнением, регистров AQCTLA чуть больше, чем изображено на таблице, сильно картину это не меняет, а лишь вносится конкретика касательно того, в каком случае достиг нужного значения счётчик, например, при отсчете в верх или отсчёте в низ. Более кратко с битами можно ознакомиться в вырезке из системного .h файла
struct AQCTL_BITS { // bits description
Uint16 ZRO:2; // 1:0 Action Counter = Zero
Uint16 PRD:2; // 3:2 Action Counter = Period
Uint16 CAU:2; // 5:4 Action Counter = Compare A up
Uint16 CAD:2; // 7:6 Action Counter = Compare A down
Uint16 CBU:2; // 9:8 Action Counter = Compare B up
Uint16 CBD:2; // 11:10 Action Counter = Compare B down
Uint16 rsvd:4; // 15:12 reserved
};Если у нас 2 порта ePWM работают зависимо и мы хотим выставить мёртвое время, то необходимо выставить регистр в нужное состояние, например:
EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE;
Теперь, когда с описанием периферии определились, можно перейти к конкретным примерам.
Настройка ePWM в режиме отсчёта по нарастающей
Вот пример кода без мёртвого времени и порт A, и порт В работают зависимо. Когда A активен, B не активен.
EPwm1Regs.TBPRD = 150000 / 5; // т.к частота 150Мгц / 5000 Гц
// Выставляем скважность 50%
EPwm1Regs.CMPA.half.CMPA = EPwm1Regs.TBPRD / 2;
EPwm1Regs.TBPHS.half.TBPHS = 0;
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;
EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE;
EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW;
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_DISABLE;
EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0;
EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
EPwm1Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;
EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO;
EPwm1Regs.AQCTLA.bit.PRD = AQ_CLEAR;
EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;
EPwm1Regs.AQCTLB.bit.PRD = AQ_SET;
EPwm1Regs.AQCTLB.bit.CAU = AQ_CLEAR;
Теперь можно попробовать добавить мёртвое время, для этого добавляем:
EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; EPwm1Regs.DBCTL.all = BP_ENABLE + POLSEL_ACTIVE_HI_CMP; // Настройка db EPwm1Regs.DBFED = 600; // Мертвое время = 150Мгц * 4мкс = 600 EPwm1Regs.DBRED = 600;
Отсчёт мёртвого времени производится аналогично частоте, по формуле:
А теперь мы получили мёртвое время таким, каким мы и хотели
А что делать, если нам надо развязать порт А и порт В? Такое тоже имеет место быть. Тут всё просто. Возвращаемся к первому примеру и удаляем последние 4 строчки, и записываем каждому скважность в следующие регистры.
EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET; EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR; EPwm1Regs.AQCTLB.bit.ZRO = AQ_SET; EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR; EPwm1Regs.CMPA.half.CMPA = EPwm1Regs.TBPRD / 2; // Выставляем скважность 50% порта А EPwm1Regs.CMPB = EPwm1Regs.TBPRD / 3; // Выставляем скважность 33% порта В
Теперь мы имеем вот такую вот картину. Можно задавать скважность каждому каналу по отдельности.
Кстати часто бывают и нетривиальные задачи, такие как например инверсия выходов ШИМ-модуля, так например можно настроить модуль на этот режим (куски текста распределите сами по программе, по функционалу понятно что где должно быть, а что закомментировать):
#define AQCTLA_INIT_STATE ( CAU_SET + CAD_CLEAR ) //оригинал
#define AQCTLA_INIT_STATE ( CAU_CLEAR + CAD_SET ) //для инверсии
/* Init Action Qualifier Output A Register for EPWM1-EPWM3*/ \
EPwm1Regs.AQCTLA.all = AQCTLA_INIT_STATE;
EPwm2Regs.AQCTLA.all = AQCTLA_INIT_STATE;
EPwm3Regs.AQCTLA.all = AQCTLA_INIT_STATE;
#define DBCTL_STOP_STATE (BP_DISABLE) //моя добавка
#define DBCTL_INIT_STATE (BP_ENABLE + POLSEL_ACTIVE_HI_CMP) //оригинал
#define DBCTL_INIT_STATE (BP_ENABLE + POLSEL_ACTIVE_LO_CMP) //для инверсии
//wait while with HiStates
EALLOW;
// Включение pull-up
GpioCtrlRegs.GPAPUD.bit.GPIO0 = 0x000;
GpioCtrlRegs.GPAPUD.bit.GPIO1 = 0x000;
// Включение pull-up
GpioCtrlRegs.GPAPUD.bit.GPIO2 = 0x000;
GpioCtrlRegs.GPAPUD.bit.GPIO3 = 0x000;
// Включение pull-up
GpioCtrlRegs.GPAPUD.bit.GPIO4 = 0x000;
GpioCtrlRegs.GPAPUD.bit.GPIO5 = 0x000;
EDIS;
/* Init Dead-Band Generator for EPWM1-EPWM3*/
//EPwm1Regs.DBFED = DBCNT_INIT_STATE; /* 200 */
//EPwm1Regs.DBRED = DBCNT_INIT_STATE; /* 200 */
//EPwm2Regs.DBFED = DBCNT_INIT_STATE; /* 200 */
//EPwm2Regs.DBRED = DBCNT_INIT_STATE; /* 200 */
//EPwm3Regs.DBFED = DBCNT_INIT_STATE; /* 200 */
//EPwm3Regs.DBRED = DBCNT_INIT_STATE; /* 200 */
//Init Dead-Band Generator Control Register for EPWM1-EPWM3
//EPwm1Regs.DBCTL.all = DBCTL_INIT_STATE;
//EPwm2Regs.DBCTL.all = DBCTL_INIT_STATE;
//EPwm3Regs.DBCTL.all = DBCTL_INIT_STATE;
//force states
/* оставить, если надо инвертировать ШИМ
EPwm1Regs.AQCSFRC.bit.CSFA=CSFA_HIGH; // 0x2 - включить принудительно High
EPwm1Regs.AQCSFRC.bit.CSFB=CSFB_HIGH; // 0x8 - включить принудительно High
EPwm2Regs.AQCSFRC.bit.CSFA=CSFA_HIGH;
EPwm2Regs.AQCSFRC.bit.CSFB=CSFB_HIGH;
EPwm3Regs.AQCSFRC.bit.CSFA=CSFA_HIGH;
EPwm3Regs.AQCSFRC.bit.CSFB=CSFB_HIGH;
//release states in work
/* вставка для инверсии ШИМ (AQCSFRC отвечает за непрерывное программное принудительное управление выходом A/B (PWMA))
EPwm1Regs.AQCSFRC.bit.CSFA=CSFA_DISABLE; // 0x0 - отключить принудительное управление
EPwm1Regs.AQCSFRC.bit.CSFB=CSFB_DISABLE; // 0x0 - отключить принудительное управление
EPwm2Regs.AQCSFRC.bit.CSFA=CSFA_DISABLE;
EPwm2Regs.AQCSFRC.bit.CSFB=CSFB_DISABLE;
EPwm3Regs.AQCSFRC.bit.CSFA=CSFA_DISABLE;
EPwm3Regs.AQCSFRC.bit.CSFB=CSFB_DISABLE;Тут есть один интересный кусок кода (видоизменил под описание далее):
EPwm1Regs.AQCSFRC.bit.CSFA=CSFA_LOW; //выключить принудительно EPwm1Regs.AQCSFRC.bit.CSFB=CSFB_LOW; //выключить принудительно
Его можно использовать, когда надо «отключить» выходы (без инверсии, чтобы стойка инвертора ушла в Z- или высокоимпедансное состояние, ну не считая диодов конечно), но есть нюанс, о нем чуть позже. Вообще встречал и такой код, что ПЧ отключали активацией по каналу TZ (опишу ниже), это на мой взгляд эффективно, но не совсем корректно и если вот прям приперло и надо, чтобы TZ оставался в покое, то данный метод самое то, НО имейте ввиду: при настройки модуля DB ваш выход В все еще зависимый и если просто принудительно заставить перейти в состояние low, то нужно отключить его [DeadBand], в отличии от TZ-метода выход А станет отключенным, а вот выход В будет выдавать 1 и ключ будет садить на землю, это не совсем то, что ожидалось, так что просто используйте временно запись в регистр DBCTL нуля предварительно сохранив старую настройку DB-модуля:
EPwm1Regs.DBCTL.all = DBCTL_STOP_STATE; //выключить принудительно
Настройка длительности задержек «мертвого времени» останется не тронутой.
В общем по настройке модуля ePWM можно сказать все, для режима по спаду всё настраивается примерно аналогично. Есть отличие с отсчётом в режиме up-down. Тут частота ШИМ-а в два раза ниже (вспомните писал про этот момент), а вот deadtime считается так же. В общем и целом теперь настроить модуль не трудно.
Наверное, единственно важное, что не было рассмотрено, так это настройка TZ, можно расслабиться, тк там все просто, остановимся на этом модуле чуть чуть по подробнее.
CPU Halt Trip при правильной настройки соответствующего GPIO это у нас идет с GPIO12=TZ1. Регистр TZSEL (Trip-Zone Select) служит для выбора, какие именно источники будут вызывать два типа событий:
- CBC (Cycle-by-Cycle) – событие, которое сбрасывается автоматически в начале каждого нового периода ШИМ, если ошибка устранена. Используется для быстрого ограничения тока (например, при пиковых перегрузках) без полной остановки ШИМ.
- OST (One-Shot) – одноразовое событие, которое блокирует выходы до явного программного сброса (через бит TZCLR).
Применяется модуль для критических ошибок (короткое замыкание, перегрев), требующих остановки и ручного вмешательства.
Биты CBC1…CBC6 в регистре TZSEL отвечают за выбор источника для CBC-события. Номер указывает на конкретный вход Trip Zone (TZ1…TZ6).
EPwm1Regs.TZSEL.bit.CBC6=0x1;
Установка бита CBC6 = 1 означает: разрешить генерацию CBC‑события от источника TZ6. То есть, если возникает условие, связанное с TZ6 (например, CPU останавливается), то ШИМ будет немедленно переведён в безопасное состояние (настройка состояния показана ниже), но при этом после каждого нового периода ШИМ он попытается восстановиться (если событие снято). Это «цикл-за-циклом» защита.
А вот чтобы привязать сигнал TZ1, например, к отключению канал ePWM1, нужна такая строчка кода
EPwm1Regs.TZSEL.bit.OSHT1 = 1; //enable TZ1 for OSHT
Для настройки правильного состояния по выходу в события аварии достаточно настроить следующие регистры:
EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO; //это у нас отключенное состояние EPwm1Regs.TZCTL.bit.TZB = TZ_FORCE_LO; //и это тоже,
//причем я так понял даже DB модуль не в силах что-то поменять
Вызов и обнуление аварии ШИМа можно осуществить и программно с помощью следующих команд:
//Вызов аварии EALLOW; EPwm1Regs.TZFRC.bit.OST = 1; //формирует событие EDIS; //Обнуление сигнала аварии EALLOW; EPwm1Regs.TZCLR.bit.OST = 1; //а это уже сброс флага наступившего события EDIS;
Вот теперь про ePWM точно все.
Думаю стоит описать далее работу с АЦП, так как это как два брата-из-ларца, одинаковых с лица, короче два сапога-пара, которые зависят друг от друга в задачах, связанных с инверторами, тк измерение тока должно проходить в относительно спокойный период времени, без переключений, а реакция на измерение тока должна тут же быть использована в регуляторе и появиться на выходе ШИМ, потому пока не забыли регистры ePWM, сразу пробежимся и по ADC-модулю.
АЦП: Краткий экскурс для уставших, но любопытных
Про АЦП можно писать трактаты, но мы с вами уже порядком утомились, так что будем кратки как выстрел. Микроконтроллеры F28035/F28335 не зря носят гордое звание motor control, например, АЦП умеет синхронизироваться с событиями ШИМ, запускаться по триггерам от ePWM, и делать выборку в нужные моменты времени, чтобы не ловить помехи и получать максимально достоверные данные.
Что за зверь АЦэПэ?
ADC (Analog-to-Digital Converter) – он же АЦП, он же аналого-цифровой преобразователь. Переводит аналоговый мир (напряжения, токи, температуры) в цифру, понятную нашему контроллеру. Подробное описание всех регистров и режимов работы – это уже за рамками нашего короткого разговора. Если захочется нырнуть в детали, добро пожаловать в документацию: [sprui07.pdf, Chapter 7] для F28035 и [sprui10.pdf, Chapter 8] для F28335. Там всё разжёвано до уровня битов, но предупреждаю – затягивает!
Архитектуру модуля можно увидеть и попытаться понять по следующей схеме:



АЦП имеет собственную группу регистров, отображённую (Maped) на память микроконтроллера, и взаимодействие с ним происходит путём записи и чтения значений в память так же, как это делается с остальным периферийными устройствами.
В этом микроконтроллере АЦП является 12-разрядным, что означает, что результат оцифровки представлен двенадцатью битами и может принимать значения от 0x0 до 0x0FFF (0x0FFF↔ 4095 ↔2 12 − 1). Значение 0 результата оцифровки сигнала соответствует значению напряжения 0.0В на входе АЦП, а 4095 – значению 3.3В, хотя входной диапазон аналогового сигнала не обязательно должен быть от 0В до 3.3В, это определяют уровни опорного напряжения АЦП, которые как правило на всех устройствах выведены отдельными пинами и запитываются обычно от ИОН (источников опорного напряжения - неких стабильных и очень точных источников ЭДС). Таким образом мы можем вывести формулу, по которой можно определить уровень напряжения на входе по прочитанному коду результата АЦП (результату оцифровки):
где ADCRESULT – прочитанный код результата оцифровки. На микроконтроллере есть только один модуль АЦП, но каналов, сигналы с которых можно оцифровать – шестнадцать. Один АЦП может оцифровать все эти каналы за счёт наличия устройства мультиплексирования, которое подключает на вход АЦП тот или иной канал в зависимости от заданных программистом настроек. Каналы имеют названия: ADCINA0, ADCINA1, …, ADCINB6, ADCINB7. В рассматриваемом микроконтроллере АЦП имеет смешанную архитектуру – частично это АЦП последовательного приближения и частично конвейерный. Подобное совмещение архитектур позволяет достигнуть баланса между относительной простотой и энергоэффективностью АЦП последовательного типа и скоростью АЦП конвейерного типа.
Вскользь пройдусь по калибровке АЦП: задача калибровки – устранить смещение нуля и мультипликативную погрешность АЦП, то есть чтобы диапазон напряжений «0 В … 3.3 В» соответствовал коду «0 … 4095», причём зависимость должна быть линейной. АЦП калибруется во время загрузки микроконтроллера, когда выполняется код загрузчика (Boot Sequence). Эта часть кода выполняется до пользовательской программы. Во время загрузки вызывается функция калибровки АЦП, которая заключается в записи правильных значений в особые регистры АЦП, определяющие смещение и усиление сигнала АЦП. Эти значения получены на заводе-изготовителе микроконтроллера после калибровки АЦП на опорных сигналах при температуре 25⁰С и хранятся в однократно-программируемом ПЗУ. Пользователю не рекомендуется записывать в регистры калибровки иные значения. Саму же процедуру калибровки можно вызывать и в программе, если нужно восстановить калибровочные значения, например, после перезапуска модуля АЦП.
Емкость выборки и хранения
Будет справедливо заметить, что один из важных моментов в понимании работы АЦП это понимание емкости выборки и хранения. Например можно заметить, что каналы АЦП в TMS320 делятся на группы A и B, а не имеют сквозную нумерацию от 0 до 15. Ответ заключается в том, что данный АЦП имеет две ёмкости выборки и хранения (в англоязычной литературе это называется Sample-and-Hold Cicuit или сокращённо S/H). Ёмкость выборки и хранения – это устройство, которое позволяет захватить сигнал и «заморозить» его на время оцифровки. По сути это устройство представляет из себя конденсатор и транзистор для подключения и отключения этого конденсатора к внешней цепи.
Во время замыкания ключа конденсатор заряжается за счёт напряжения внешней цепи. Чем дольше время заряда этого конденсатора, тем более близко его напряжение будет к напряжению входа, когда ключ разомкнётся. АЦП оцифровывает именно то значение, которое попало в ёмкость выборки и хранения. Это даёт гарантию, что в результате мы получим именно тот сигнал, который был на входе в момент запуска АЦП, даже если в процессе оцифровки он изменялся. Это позволяет одновременно «захватить» два сигнала, которые заведены на разные группы под одним номером, а затем по очереди их оцифровать. Такой подход является весьма полезным, когда нужно получить информацию о двух сигналах в один момент времени – например, токи фаз двигателя A и B, по которым затем можно рассчитать ток фазы C. Если частота токов небольшая, то поочерёдный захват сигналов не внесёт большой погрешности, но с ростом частоты желательно измерять эти сигналы одновременно. Поэтому при разработке инвертора стоит заранее учесть такую возможность АЦП и завести сигналы датчиков токов фаз на каналы из разных групп, но с одинаковым номером – ADCINA0/ADCINB0 или ADCINA5/ADCINB5 и т.п.
Запуск оцифровки (SOC)
Сигнал SOC или Start Of Conversion так же является одним из основных при работе с АЦП ибо является своего рода триггером к запуска процесса оцифровки (разовой оцифровки), а так же основной настройкой этого запуска. Для запуска захвата и оцифровки того или иного канала (входа) АЦП нужно задать три настройки: номер канала для оцифровки, триггер для запуска оцифровки и длительность выборки сигнала. Например, «По триггеру CPU Timer 0 запускать оцифровку канала ADCINB2, осуществлять выборку в течение 10 тактов». В данном контексте триггер – это какое-то событие микроконтроллера, которое приведёт к запуску АЦП, есть определенный перечень допустимых событий, которые могут стать таким триггером, таким образом АЦП можно синхронизировать с другими процессами МК. Сочетание этих трёх параметров (канал, триггер, длительность выборки) объединяется в объект, называемый SOC (Start-of-Conversion, запуск оцифровки) и соответственно регистр настройки. Эти объекты нумеруются, начиная с «0»: SOC0, SOC1, SOC2…За настройку каждого SOC отвечает соответствующий регистр из группы ADCSOCxCTL, который имеет битовые поля: TRIGSEL задаёт триггер, который запустит оцифровку, CHSEL задаёт канал, который будет оцифрован; ACQPS задаётся длительность стадии выборки сигнала в тактах – время, в течение которого будет заряжаться ёмкость выборки и хранения. Каждому SOC поставлен в соответствие бит из регистра ADCSOCFLG. Если этот бит равен 1, то соответствующий SOC запустится, как только получит приоритет. Биты «взводятся» автоматически в соответствии с настроенным для SOC триггером – то есть при программном запуске, или от таймера и т.д. – а сбрасываются, когда начинается оцифровка, а точнее – когда начинается стадия выборки сигнала. Всего в АЦП на F28335 можно задать 16 индивидуальных настроек SOC для запусков каналов. При этом неважно, как SOC будут соотноситься между собой: можно настроить все 16 запусков для оцифровки одного и того же канала по разным триггерам, или наоборот, оцифровывать 16 разных каналов по одному триггеру. Необязательно при этом настраивать все 16 объектов. Если нужно оцифровать только четыре канала, следует настроить четыре SOC. Выше упоминалось, что при определённых настройках АЦП может «захватить» в устройство выборки и хранения одновременно два сигнала, а затем последовательно их оцифровать, это настройка AdcRegs.ADCTRL3.bit.SMODE_SEL = 1. Но если таких настроек программистом не задано и несколько произвольных SOC настроены на запуск оцифровки по одному триггеру, то сигналы будут захватываться и оцифровываться по очереди, причём приоритет имеет SOC с наименьшим номером. То есть каналы будут оцифровываться в порядке возрастания номера: SOC0 →SOC1 →SOC2 →…→SOC15. Программист может задать с какого из SOC должна начаться оцифровка, но все остальные каналы, запущенные по одному триггеру, будут идти в порядке увеличения номера.
Запуск оцифровки может происходить по различным событиям: запись в соответствующий бит значения «1», внешний сигнал прерывания, событие достижения 0 одним из таймеров CPU, команда на запуск от одного из модулей ePWM. В качестве событий от ePWM могут выступать следующие: достижение счётчиком 0 или периода, сравнение счётчика с уставкой CMPAили CMPB. Связь с модулем ШИМ будет подробно рассмотрена в следующей лабораторной работе. При срабатывании любого триггера или при принудительном запуске выставляется соответствующий триггеру сигнал SOC, запускающий выборку сигнала.
Отдельно стоит отметить так называемый Sequencer (планировщик), который настраивается через AdcRegs.ADCTRL1.bit.SEQ_CASC, выбирая два (SEQ1 и SEQ2) по 8 состояний/автоконверсий или объединенный на 16 состояний/автоконверсий секвенсор (SEQ), а каналы привязываются через запись AdcRegs.ADCCHSELSEQ1.bit.CONVxx = 12, где хх заменяется на порядковый номер, а число соответствует номеру канала, где ADCINA0=0, ADCINA1=1, …. ADCINB0=8, ADCINB1=9, итд.
Окончание оцифровки (EOC)
Когда оцифровка канала завершается, модуль АЦП выдаёт короткий (1 такт) сигнал, называемый EOC (End-of-Conversion, конец преобразования). Этот сигнал используется системой для вызова прерывания, в котором можно обработать результат АЦП. Расчёт в прерывании по готовности АЦП позволяет максимально сократить время реакции системы управления на изменение входных состояний. В зависимости от настройки сигнал EOCможет выставляться либо до оцифровки (после захвата сигнала в ёмкость выборки и хранения), либо после оцифровки. Всего в АЦП 16 таких сигналов – по одному на каждый SOC.
Прерывания АЦП
АЦП может вызывать 8 прерываний (ADCINT1, ADCINT2, …, ADCINT8). Первые два прерывания ADCINT1 и ADCINT2 расположены в группе PIE 1 под номерами 1 и 2 соответственно, а также они дублируются в группе PIE 10 под теми же номерами 1 и 2. Программист может сам решить, по какой группе обрабатывать эти прерывания, что позволяет выбрать приоритет прерывания АЦП по сравнению с прерываниями других периферийных блоков. Напомним, что группа с меньшим номером имеет более высокий приоритет. Остальные 6 прерываний ADCINT3…ADCINT8 расположены только в группе PIE 10 под номерами 3 … 8 соответственно. В настройках АЦП можно указать, какое из этих прерываний может возникать, а также выбрать сигнал, по которому оно должно возникать. В качестве сигналов, как сказано выше, выступают импульсы EOC. Программист может выбрать для любого прерывания любой импульс EOC (EOC0 … EOC15).
Встроенный температурный датчик
Как у практически любого уважающего себя производителя АЦП на TMS320 есть свой датчик температуры, который можно использовать для измерения температуры микроконтроллера. По умолчанию датчик отключен от АЦП, но его можно задействовать, задав в регистр ADCCTL[TEMPCONV] нужную настройку. Если датчик температуры задействован, то он подключается к АЦП вместо канала ADCINA5, то есть при включенном датчике температуры нельзя измерять внешний сигнал с этого канала. Код АЦП, полученный в результате оцифровки этого датчика, можно преобразовать в градусы Цельсия с учётом следующих свойств: значению «0⁰С» соответствует значение offset = 1750, а один бит имеет ценность gain = 0.18⁰С.
Временные характеристики (тайминги) АЦП
Под таймингом в цифровой технике понимается отрезок времени, который требуется устройству для выполнения какой-либо операции, или группы операций. В контексте АЦП это временные промежутки, которые требуются для захвата сигнала в ёмкость выборки и хранения, последующей оцифровки и выдачи сигнала готовности EOC. С учётом того, что сигнал EOC может выставляться до или после оцифровки, а также того, что захватываться может либо один, либо два сигнала одновременно, получается четыре варианта таймингов. Длительность всей стадии преобразования складывается из времени преобразования и времени выборки. Помня, что минимальная длительность выборки составляет 7 тактов, легко увидеть, что минимальная длительность всего процесса составляет 20 тактов. Можно также учесть, что выборка начинается через два такта, после появления сигнала ADCSOCFLG, а сигнал EOC выдаётся спустя один такт после окончания преобразования, и еще один такт требуется на выставление флага прерывания.
Настройки АЦП
Все регистры, кроме результатов оцифровки, объединены в группу AdcRegs, через которую осуществляется доступ к ним: AdcRegs.ADCCTL1, AdcRegs.ADCSOC1CTLи т.д.
// Подключить встроенный датчик температуры // вместо канала ADCINA5 AdcRegs.ADCCTL1.bit.TEMPCONV = 1; // Выдавать сигналы EOC после завершения преобразования AdcRegs.ADCCTL1.bit.INTPULSEPOS = 1; /* Настройка SOC0 */ AdcRegs.ADCSOC0CTL.bit.ACQPS = 6; // Время выборки сигнала - 7 тактов // (6 + 1) AdcRegs.ADCSOC0CTL.bit.CHSEL = 5; // Канал для оцифровки, "5" <=> // ADCINA5 AdcRegs.ADCSOC0CTL.bit.TRIGSEL = 0; // Запуск SOC0 производится только // программно /* Настройка флага прерывания */ AdcRegs.INTSEL1N2.bit.INT1SEL = 0; // Флаг прерывания возводится по // импульсу EOC0 AdcRegs.INTSEL1N2.bit.INT1CONT = 0; // Флаг необходимо будет вручную // очищать AdcRegs.INTSEL1N2.bit.INT1E = 1; // Разрешить выставлять флаг // прерывания EDIS; // Запретить изменение защищённых регистровInitSysCtrl(); // Общая инициализация системы InitAdc(); // Инициализация модуля АЦП EALLOW; // Разрешить изменение защищённых
регистров
В самом начале вызывается уже знакомая вам функция «InitSysCtrl()». Затем вызывается функция «InitADC()». Внутри неё происходит повторный вызов функций калибровки АЦП, разрешается работа всех узлов АЦП и выбираются верхний и нижний опорные уровни – 0В и 3.3В.
Итог
На данном этапе я заканчиваю сегодняшнюю статью, к сожалению по большей части из-за ограничения числа знаков у ВК, как соберусь силами, выпущу продолжение. Надеюсь моя статья поможет всем желающим начать освоение данного семейства контроллеров. Если у вас остались какие-либо вопросы, то вы можете комментировать статью и я постараюсь ответить на ваши вопросы и оказать посильную помощь в изучение. Успехов вам в обучение!
