Разработка ПО для DSP TMS320F28 motor control

В своей первой статье я обмолвился о данном семействе контроллеров, мне в лс написал не один десяток людей с вопросами о нем, хотя это и не являлось темой статьи. В гугл народ категорически не хотел идти, говоря об отсутствии информации. Я немного удивился и решил проверить — действительно на русском языке по семейству C2000 нет практически ничего (на фоне AVR, STM), а главное нет понятных стартовых гайдов. Информацию можно найти на английском языке, но опять же ее мало. Для меня это несколько удивительно, учитывая, что данному семейству лет уже не мало. Поэтому решено было в меру своих возможностей повлиять на ситуацию.

Кому эти контроллеры в принципе нужны… Вы хотите собрать себе сварочный инвертор? Источник бесперебойного питания? Выпрямитель для гальванической ванны? Частотник? Инвертор для альтернативной энергетики? Станок с ЧПУ? Если хотя бы один пункт про вас, то статья посвящается именно вам!

Остальным читателям тоже будет интересно узнать о «новом-старом» контроллере, зачем он нужен и как с ним работать. Данное семейство очень простое (сильно проще STM, LPC и прочих Cortex-ов), камни легко купить (на Али тоже есть), позволяют реализовать сверхнадежные промышленные решения, на их основе можно построить практически любую промышленную систему управления.

Вы уже решили, что данный контроллер ваша мечта и готовы ринуться в бой? Тогда покупаем за 17$ вот такую отладку F28027-LaunchPad:

edcdcvjwp5ewu_jl0jbnwwzsfyc.png


Купили? Вот теперь можно и в бой. Если возник вопрос где купить «получше» и «подешевле», то идем в официальный магазин. Переходим сюда и видим ценник 17$. За эту сумму вы получите оригинальную отладочную плату и доставку курьером до двери. Я заказывал один раз в Китае на сдачу, вышло 16$ и это со скидкой и купоном, так еще и как «бонус» поход на почту. Поэтому рекомендую именно официалов. Поехали!

Обзор серии C2000


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

Для начала пару слов о C2000 вообще. Отличительными чертами семейства, которые связаны с его основным назначением motor control, является наличие HRPWM (высокоточный ШИМ) и CLA (сопроцессор). Последний правда отсутствует в самых младших Piccolo TMS320F2802x, но оно там и не нужно, главное HRPWM на месте. Что это такое… Собственно HRPWM это обычный ШИМ, только очень точный и время записи и установки нового значения коэффициента заполнения происходит ощутимо быстрее. Это позволяет получить, например, отличный по форме синус в DC/AC инверторе или с очень высокой точностью управлять шаговыми двигателями в станке с ЧПУ.

CLA — это по сути полноценное ядро, вот только доступа к периферии у него нет, только к основному ядру и памяти. Служит оно для разгрузки основного ядра от вычислений. Очень легко и непринужденно данный сопроцессор переваривает данные float, что важно при реализации различных алгоритмов, фильтров и прочего.

5ouilh691kdhoflkcrtcs4tmwqy.png

Я буду рассказывать о двух основных семействах с которыми вам вероятно придется столкнуться:

  • Piccolo. Самые младшие контроллеры, но и самые дешевые. Мой TMS320F28027 как раз из этого семейства. Если вы надумаете разрабатывать силовую электронику в коммерческих целях, то это будут ваши основные камни — они достаточно дешевые, паябельные (LQFP, QFN, TSSOP) и позволяют реализовать почти все. Например, их производительности хватит для двухфазного PFC, инвертора для солнечных панелей, частотника до 10 кВт с векторным управлением и т.д. Как видите это сегмент продукции который покупают и обычные люди, и предприятия, а значит очень востребовано. Основные ограничения — частота 60 МГц, ограниченное число ШИМ-каналов.
  • Delfino. Идеологически это все те же Piccolo, только накаченные мельдонием. В чем это выражается — частота до 200 МГц, в старших камнях уже 2 полноценных ядра + 2 сопроцессора, большие корпуса и соответственно много ног, много каналов ШИМ, много АЦП и всего в общем-то много. То есть в старших камнях мы имеем 4 ядра с частотой 200 МГц, производительность в 800 MIPS, что согласитесь весьма внушительно. Такую мощу использовать можно по разному, но основное применение — алгоритмически сложные системы, например, выпрямитель «Виенна» или еще чего. Так же на одном таком контроллере можно реализовать всю систему управления для станка с ЧПУ, например, фрезерного или газопламенной резки металла.


Доработка отладочной платы


Когда вы получите коробку с отладочной платой, то обнаружите отсутствующий кварц и конденсаторы к нему. Их не обязательно, но желательно допаять. Кварцев в корпусе HC-49 у меня уже не водится, поэтому пришлось одолжить у знакомого. Он ковыряет AVR и STM, поэтому только 8 МГц у него нашлись. Запаял. Конденсаторы 22 пФ добавил по старой памяти, вроде так на мегах в школе делал.

Данное решение не самое лучшее и связано это с настройкой PLL. Максимальный множитель для PLL составляет х12 (можно и больше, но не рекомендовано и криво работает). Максимальная частота 60 МГц. Максимальное значение делителя на выходе PLL составляет 3. Частота кварца 8 МГц. Я не могу умножить на 8 на целое число и получить 60. Ищем ближайшее общее значение, в данном случае это 240. То есть умножит 8 на 30, а потом разделив на 4 я получу заветные 60 МГц, но тут беда — х30 множитель PLL недопустим, делитель /4 тоже недоступен. Тут есть 2 выхода:

  • Плохой: частоту 8 МГц умножить на 7 и поделить на 1 и получим 56 МГц. Можно умножить и на 8 и получить 64 МГц, работать будет стабильно, но в обоих случаях частота не максимальная 60 МГц. Не хватает перфекционизма, увы. У меня именно такой вариант, запаял 8 МГц и сделал частоту 56 МГц.
  • Хороший: пойти и купить кварц на 10 или 20 МГц (лучше 10) и умножить на 6, ну и поделить на 1, получаем заветные 60 МГц. Живу я за городом и честно говоря лень было ехать в местный магазин, в котором не факт что есть кварцы на 10 МГц. Для обучения пойдет конечно и внутренняя RC-цепь, и кварц на 8 МГц, но в свои проекты будущие все таки ставьте кварцы на 10 МГц, только не мамонтов-мутантов в корпусе HC-49.


Архитектура и особенности периферии


Все, что будет происходить далее относится к контроллеру TMS320F28027. Для начала давайте просто посмотрим на структуру, которая взята из даташита:

srdahbswvkrs9zjzbnvevwgqvho.png

Первое на что стоит обратить внимание — ОЗУ разделена на 3 сектора: M0, M1 и SARAM. Объемы этих секторов памяти в нашем случае 1к, 1к и 4к. Стоит заметить, что в данном случае слово не 8 битное, а 16 битное, то есть в более привычной форме это 2 кБайт + 2 кБайт + 8 кБайт. Особенность данного модуля в том, что сектора M0 и M1 более шустрые, чем оставшиеся 8 кБайт ОЗУ. Формально можно воспринимать это как кэш. В секторах M0 и М1 обычно хранят наиболее часто используемые данные, а так же данные, которые наиболее критичны к производительности памяти. По умолчанию мы можем указать с помощью линкера что и где хранится, но эту тему в данной статье я поднимать не буду, тут отдельная статья нужна как минимум.

Второй важной особенностью является то, что вся периферия тактируется от системной шины, то есть от 60 МГц (в моем случае 56 МГц). В качестве примера приведу микроконтроллеры STM32, там частота ядра составляет, например, 180 МГц для F4, но вот периферия тактируется через ряд делителей. Данный подход я для себя именую «фальшивые мегагерцы», хотя это весьма утрировано. Поэтому 60 МГц у TMS320F28 и 180 МГц у stm32 не так уж сильно отличаются, а если вспомнить о наличии CLA, то 60 + 60 МГц уже как минимум сопоставимы. Понятно, что такое сравнение не правильное, но явно дает понять, что не одними мегагерцами сыты.

Так же интересный момент — обратите внимание на общую структуру: HRPWM, АЦП, компараторы с внутренним ЦАП, модуль обработки энкодера (eCAP)… Готовый преобразователь частоты с векторным управлением в чистом виде! В этом вся суть данного семейства — минимализм. С одной стороны периферия достаточно бедная на фоне Cortex-ов, но с другой стороны достаточная, чтобы реализовать и частотник, и dc/dc, и dc/ac, да и драйвер шагового двигателя. За счет этого работа с контроллерами TMS320F28 очень простая, понятная и не перегружена лишними действиями. Но если вам вдруг нужно 3 UART-а, а к ним пару i2c и еще 3 SPI, то эти контроллеры точно не для вас — у них задачи другие.

Окружение и среда разработки


5vvcivdcbwzulr9uocxmccv-ba0.png


Посмотрели на логотип-заставку? Запоминаем ее. Если вы решили начать применять в разработке героев статьи, то данная софтина ваше все, а называется софтина как видите — controlSUITE.

Данное приложение является собранием и библиотекой всего, что понадобится для разработки ПО для контроллеров семейства C2000. Установка данного приложения — это первое, что нужно сделать и посмотреть его состав, в нем находятся:

  • Описание всех существующих контроллеров и отладочных плат на их основе. Исходники схем, печатных плат, BOM-ы в основном в Altium Designer
  • Примеры схемотехники и проектирования печатных плат
  • Основные библиотеки для разработки встроенного ПО
  • Библиотеки математики и DSP, в том числе для использования с CLA
  • Примере проектов ПО для каждого вида периферии
  • Большое количество апноутов по реализации большинства алгоритмов управления двигателями, dc/dc преобразователями, MPPT-контроллерами и прочими системами
  • Набор программ, которые позволяют создать систему управления двигателем без программирования вообще, просто используя графическую среду
  • Сама IDE в которой и будет вестись разработка


Все, что я описал выше — очень кратко и скромно. На то, чтобы все просмотреть и пролистать хотя бы по диагонали, вам потребуется пару недель. Конечно большинство из этого объема данных на старте вам не понадобится, но нужно помнить куда идти, если что-то для вас непонятно и в диковинку.

Теперь то, с чем мы сегодня поработаем — IDE, графическая часть, которой основана на базе всем известного Eclipse. Компилятор не GCC, а свой от Texas, который по моему субъективному мнению определенно лучше первого. Хотя есть подозрения, что это основательно допиленный все тот же gcc. Называется среда разработки — Code Composer Studio, актуальная на данный момент версия 7.4.

Создание проекта


Вначале я хотел реализовать задачу идентичную первой статье, то есть нарисовать синус. В принципе в рамках одной статьи это можно было бы сделать, оставив за кадром очень большой объем мелочей, но как известно суть как раз в мелочах. В интернете есть несколько статей по TMS, но все они весьма поверхностны и сводятся к виду «вот это копируем и опа все работает», то есть сам процесс и идеология не рассматриваются вообще. Поэтому в рамках данной статьи мы создадим проект, почистим его от лишних компонентов, настроим прошивку во flash память контроллера и научимся работать с GPIO, а они тут весьма интересные.

Скачиваем CCS7 с сайта производителя, устанавливаем и приступаем к созданию проекта привычным способом: File → New → CCS Project…

wbv0jyccatp8jysn2nckh7qaooc.png


Видим данное окно и в нем нам необходимо выбрать интересующий нас контроллер, в моем случае это TMS320F28027, указать имя проекта и прописать путь, где он будет храниться. Предварительно нужно создать папку, где будет храниться проект. Имя проекта и имя папки могут не совпадать. Жмем кнопку Finish и наш проект создан.

Теперь необходимо наполнить наш проект содержимым и подключить его. Перед этим для улучшения структуры проект, создаем вот такой набор папок:

eecgerskm0jeso2ocebvaheeyrc.png


  • inc — папка, которая содержит все заголовочные файлы
  • system_inc — в данном подразделе будут хранится заголовочные файлы стандартных библиотек, файлы же, которые мы сами будет создавать, например, main.c находятся в папке inc. Это позволит не поломать что-то бездумно или удалить лишнего
  • src — папка со всеми исходниками
  • system_src — папка с исходными файлами для стандартных библиотек


Прошу обратить внимание, что данная структура не является какой-то догмой, а является лишь моим представлением об упорядоченности. Хотя людям с минимальным опытом я все же ее порекомендую, со временем вы ее поменяете под свое мировоззрение, а пока это минимизирует количество косяков.

Теперь создаем в папке inc файл main.h и подключаем его в main.c. Через него будет происходить подключения базовых библиотек. И перед тем как начать переносить библиотеки и прочие файлы, давайте в настройках проекта пропишем пути к папкам, где будут храниться наши будущие заголовочные файлы. Для этого в дереве проекта правой кнопкой жмем на проект (Test) и в само низу нажимаем Properties или просто нажимаем Alt+Enter. В открывшем окне идем Build → C2000 Compiler → Include Options и тут нам необходимо к двум уже имеющимся путям — прописать путь к папкам inc и system_inc. Нажимаем Add, далее Workspace и далее до нужной папке inc, затем проделываем тоже самое и цепляем вторую папку. Таким образом мы прописали относительные пути, при переносе проекта ничего перенастраивать не придется. В итоге у нас получается вот такая картина и жмем Ok:

2xndqlhcm5szyalytbw6oeseyka.png

Теперь мы имеем пустой проект с прописанными путями и прочими настройками, остается его только заполнить библиотеками. Единственное, что нужно сделать — это проверить все ли подключено. По идее у вас должен получиться такой код и картина, компилируем проект. Для этого нажимаем CTRL+B или наверху идем в Project → Build All. Проект должен скомпилироваться без ошибок и выглядеть так (картинка кликабельна):

0bhvurabfuhr1-6lqjgzn4svzp0.png

Теперь давайте немного о линковщике поговорим. Изначально при создании проекта IDE генерирует файл 28027_RAM_lnk.cmd, он размещает нашу программу в ОЗУ в процессе отладки и прошивки. Это удобно, когда мы занимаемся отладкой, т.к. не тратятся ресурсы flash памяти да и вход в отладку в ОЗУ ощутимо быстрее происходит. Но что делать, если мы хотим шить во флеш? Для этого есть другой файл линковки, который разместит нашу программу именно во флеш. Этот вариант я и покажу.

Первым делом удаляем файл 28027_RAM_lnk.cmd. Как я уже говорил — controlSUITE наше все. Открываем его. Теперь идем English → Device → Piccolo F2802x → Supporting Libraries → Header Files for F28027x. Справа мы видим папки — это и есть стандартные библиотеки и все, что нужно, включая линковщики. Теперь идем в папку f2802x_common → cmd и тут мы видим набор линковщиков для всех камней линейки. Как не сложно догадаться файлы _RAM для заливки кода в ОЗУ, а без данного тега для заливки по флеш. Берем файл F28027.cmd и копируем в наш проект вместо старого удаленного линковщика.

Теперь пришло время перенести сами библиотеки. Идем в папку f2802x_common → source и видим кучу файлов. Тут два вида библиотек: стандартные регистры (аналог CMSIS) и некое подобие SPL. В данном случае нас интересует только первый вид, то есть файлы с префиксом f2802x_. Конечно можно все их перетащить в наш проект, но зачем засорять его если мы не все использовать будем? Если что-то понадобится, то в дальнейшем просто добавим. Пока ограничимся следующим наборов файлов:

  • f2802x_codestartbranch.asm
  • f2802x_defaultisr.c
  • f2802x_piectrl.c
  • f2802x_pievect.c
  • f2802x_sysctrl.c


Копируем данные файл и вставляем в нашу папку system_src. Теперь идем в папку f2802x_headers → source и забираем оттуда файл F2802x_GlobalVariableDefs.c и опять же копируем в нашу папку system_src. Далее идем в папку f2802x_headers → cmd и копируем оттуда файл F2802x_Headers_nonBIOS.cmd в ту же самую папку. На этом наполнение папки system_src закончено и переходим к заголовкам.

Идем в папку f2802x_headers → include и копируем оттуда все файлы в нашу папку system_inc. Теперь идем в папку f2802x_common → source и копируем оттуда файлы:

  • f2802x_examples.h
  • f2802x_globalprototypes.h
  • f2802x_i2c_defines.h
  • f2802x_epwm_defines
  • f2802x_swprioritizedisrlevels.h
  • f2802x_defaultisr.h


У нас должна получиться такая картина в дереве проекта:

jwtuh-pssz-pn5_ynxlxugmijtw.png


Теперь необходимо подключить базовые библиотеки, файл main.h обретает следующий вид:

#pragma once

#include "F2802x_Device.h"
#include "F2802x_examples.h"


Пробуем компилировать. Если проект скомпилировался без ошибок и варнингов — значит все правильно подключено. Если этого не произошло, то все 10 раз перепроверьте, а уж если совсем не получается, то пишите в лс — помогу, как говорила Сова: «Бэз-воз-мЭз-дно, то есть даром».

Стартовая инициализация контроллера и системы тактирования


В данном разделе мы напишем функцию, которая проинициализирует сторожевой таймер и вектора прерываний, обнулит флаги прерываний. Так же настроим систему тактирования в результате чего источником тактирования станет внешний кварц, а не внутренняя RC-цепочка, настроим PLL и включим тактирование для все периферии.

Для аккуратности кода предлагаю вынести все базовые инициализации в отдельный файл, фронтендом которого станет функция void InitStartSystem (void). Для этого создаем файлы systemInitStart.h и systemInitStart.c. Я сразу напишу функцию и дальше просто разберем ее содержимое:

void InitStartSystem (void) {

    DisableDog();

    XtalOscSel();                                   
    InitPll(TMS320_PLLCR, TMS320_DIVSEL);           
    InitPeripheralClocks();                        

    InitPieCtrl();
    InitPieVectTable();

}


Все функции, которые вызываются в InitStartSystem (), являются стандартными. Советую вам посмотреть подробно как они реализованы, для этого можно зажать CTRL и кликнуть по интересующей функции. Посмотрели? Теперь кратко пробежимся…

  • DisableDog () — функция выключает «собаку». Это обязательное действие при настройке основной части критичной периферии, например, системы тактирования. В коде библиотек вы часто увидите это, оно будет дублироваться и дублироваться
  • XtalOscSel () — данная функция реализует переключение с внутреннего источника тактирования на внешний кварц. Важный момент! В стандартной библиотеке с данной функцией есть ошибка — она не объявлена. Идем в файл f2802x_globalprototypes.h и среди всех прочих объявление дописываем строку extern void XtalOscSel (void)

    3lvvyvwdfpwbqpek6bphfkheapu.png

    Второй важный момент! Идем в функцию XtalOscSel и удаляем функцию задержки.

    veiqiwa2locaclbj2-xfbpiakpe.png

    Третий важный момент! Идем в файл f28027x_exmaples.h и закомментируем функцию реализации задержки.

    t7jkjpu3nbgcqlqt5aolaniwqxg.png

  • InitPll (TMS320_PLLMUL, TMS320_DIVSEL) — функция производит настройку PLL. В нее передаются 2 значения: множитель и делитель. Их значение прописаны дефайнами в заголовочном файле. Важный момент! Открываем данную функцию в библиотеке и нужно закомментировать задержку в самом низу

    mgupgi_vhx8hvcvg3tgaqsxaxas.png

  • InitPll (TMS320_PLLMUL, TMS320_DIVSEL) — функция производит настройку PLL. В нее передаются 2 значения: множитель и делитель. Их значение прописаны дефайнами в заголовочном файле. Важный момент! Открываем данную функцию в библиотеке и нужно закомментировать задержку в самом низу
  • InitPeripheralClocks () — данная функция просто включает тактирование для всей периферии. Да, именно для всей. C2000 — это не решение для железок с питанием от батарейки,
    это решение на единицы-десятки-сотни киловатт и жалкие 2–3 мА роли тут не сыграют. Ну, а вам не надо каждый раз вспоминать включили вы тактирование на какой нибудь SPI или нет
  • InitPieCtrl () — функция выключает все прерывания и сбрасывает флаги прерываний
  • InitPieVectTable () — функция заполняет таблицу с векторами прерываний


Собственно вот она вся инициализация. Думаю многие заметили «важные моменты», связанные с функцией Delay. Почему мы ее выпилили на корню? Да все просто — это костыль.

Инженеры TI добавили в часть функций эти совершенно ненужные задержки, добавили в недавних обновлениях. Зачем — загадка не только для меня. Регистры и прочие критичные записи и так защищены, поэтому не будет заставлять наш контроллер тупить. Кстати при инициализации в силовой электронике «тупить» вообще нельзя, а то будут бабахи. Поэтому забудьте навсегда функции delay и прочую бесовщину, только таймеры! Задержки допустимы только в каких-то учебных целей, например, быстро светодиодом помигать.

Для того, чтобы убедиться в работоспособности кода, мы вызываем функцию инициализации в main, компилируем, прошиваем и цепляемся осциллографом на GPIO18. Это вывод аналогичен MCO у STM32, то есть он выводит системную частоту. Осциллографом должны увидеть сигнал с частотой 56 МГц. Если осциллограф хороший, то вы увидите меандр, если китаец (даже хороший), то скорее всего это будет что-то ближе к синусу. Настройка GPIO18 на вывод системной частоты можно увидеть в функции InitPeripheralClocks (). Сначала нужно «подключить» gpio к выходу частоты, а затем установить делитель, равный 1:

   GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 3; // GPIO18 = XCLKOUT
   SysCtrlRegs.XCLK.bit.XCLKOUTDIV=2; // Set XCLKOUT = SYSCLKOUT/1


Настройка GPIO


Для работы с данным семейством нам потребуется только референс мануал, который разработчики из TI разбили на несколько файлов, каждый из которых описывает определенную периферию, что очень удобно. Скачиваем даташит тут и идем в раздел Documentation Support на страницу 126. Тут мы видим набор ссылок на документацию с ее кратким описанием: errata, пособие для старта управления двигателем и гайды по каждой периферии. Нас интересует документ с названием TMS320F2802x/TMS320F2802xx Piccolo System Control and Interrupts Reference Guide, именно в нем находится описание интересующих нас GPIO и прочих базовым системных настроек. Смотрим на структурную схему GPIO:

vpuu29qhryyngpm2thgtvtquoeo.png

Мы видим достаточно привычную картину устройства портов ввода-вывода. Тут и возможность включения внутренней подтяжки, и использование сигнала с GPIO для прерываний и прочие прелесть. Главная фича данной периферии у C2000 — это возможность аппаратного подавления помех, например, дребезга механических контактов кнопки. Давайте посмотрим на интересную диаграмму:

guzjymm5gq08m1gar1284zobjx8.png

На ней показан принцип считывания состояния входов. В большинстве контроллеров состояние входа считывается с частотой тактирования данной периферии, то есть по умолчанию с частотой 56 МГц в нашем случае, а у тех же stm в старших семействах эти частоты еще выше. Думаю всем понятно, что при такой частоте контроллер успевать «увидеть» любые помехи и шумы. Иногда такая частота нужна, а иногда нет, например, если нам нужно опрашивать кнопку. Зачем нам ее опрашивать каждые 18 нс? Поэтому реализовали возможность уменьшения частоты тактирования конкретного порта с помощью регистра CTRL и битов QUALPRDx, где X принимает значение от 0 до 3: QUALPRD0 отвечает за GPIO0…7, QUALPRD1 отвечает за GPIO8…15 итак далее. По сути это обычный делитель частоты с коэффициентом от 1 до 510.

a5ro9eq9hvvmomonnkm8ggkxzcq.png

Кнопку опрашивать сильно часто смысла нет, поэтому настраивать делитель будем на значение 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» команда игнорируется


Вот такой простенький набор регистров. Даже из описания выше уже можно понять, что нужно сделать для настройки порта, но предусмотрительный инженеры или технические писатели из TI сделали еще пошаговую инструкцию:

lytxmkk_nwnp8qzigdzblyywwx8.png

Скажу сразу, что шаги 6 и 7 для нас не нужны, т.к. ни собаку, ни прерывания мы не используем в данной статье. Остальные шаги опишу кратко для людей, которые в школе учили немецкий:

  • Шаг 1 — определяемся с функционалом вывода: что это будет вход или выход,
    gpio или выход другой периферии и прочее
  • Шаг 2 — включаем или отключаем внутреннюю подтяжку к питанию
  • Шаг 3 — настраиваем тактирование и защиту от дребезга для конкретного порта
  • Шаг 4 — выбираем нужную функцию: gpio или периферия
  • Шаг 5 — настраиваем направление работы вывода: вход или выход


Вот и вся настройка, как видите она элементарная и логически понятная. Сразу хочу отметить, что не обязательно именно в таком порядке производить настройку, например, настроить направление (вход или выход) можно самым первым шагом. Это не имеет значения.

Супер важно!

При работе с регистрами в семействе C2000 необходимо учесть момент, что они защищены. Все далее описанное касается в основном только регистров группы настройки. Если вы внимательно смотрели стандартные функции, то наверняка увидели там непонятные команды: EALLOW; и EDIS; . Команда EALLOW — снимает защиту и открывает доступ для работы с системными регистрами. Команда EDIS — включает обратно защиту и открывает доступ для работы с системными регистрами. То есть любая работа с системными регистрами должна ВСЕГДА выглядеть следующим образом:

EALLOW;

// Работаем с системными регистрами, меняем их значения, настраиваем

EDIS;


Такая операция не требуется, если мы работаем с регистрами данных, например, если мы выставляем с помощью регистра GPxSET наш выход в »1», то с него не надо снимать защиту и соответственно и включать ее обратно. В документации везде написано, что нужно защищать, а что нет, например, так:

lytxmkk_nwnp8qzigdzblyywwx8.png

Исходя из всего выше описанного, давайте настроим 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();

}


Теперь вызываем в функцию InitStartSystem в основном теле программы в main и на этом настройка завершается. Получаем вот такой код:

#include "main.h"

int main (void) {

    InitStartSystem();

    while(1)
    {

    }
}


Настало время написать нашу первую тестовую программу и проверить все это дело. Алгоритм такой: моргает светодиодом, который сидит на 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». Предупреждаю, что видео на час, муторно, долго, но максимально подробно и все видно:

Файлы из статьи


  • Архив с проектом для CCS7 скачиваем тут
  • Посмотреть код можно на github


Итог


На данном этапе я заканчиваю сегодняшнюю статью. Думаю вы поняли, что если бы я сходу решил показать реализацию DC/AC инвертора, то объем статьи был бы в несколько раз больше или же много важных мелочей остались бы просто за кадром, что на мой взгляд недопустимо.

Надеюсь моя статья поможет всем желающим начать освоение данного семейства контроллеров и начать разработку в области силовой электроники и станкостроения. В будущем я наверняка напишу еще что-то на данную тему, например, хотелось бы рассмотреть работу с ШИМ или реализовать какой-то алгоритм. Главное чтобы было время.

Если у вас остались какие-либо вопросы или у вас что-то не получается, то вы можете написать мне в личные сообщения и я постараюсь ответить на ваши вопросы и оказать посильную помощь в изучение. Успехов вам в обучение!

© Geektimes