Teensy 4.1 через MCUXpresso. Часть 2. Осваиваем GPIO и UART

В прошлой статье мы начали работать с платой Teensy 4.1 не через сцепку из её «родных» среды разработки и библиотек (совместимых с Arduino), а через среду разработки и SDK, «родные» для установленного на ней микроконтроллера фирмы NXP. Мы убедились, что примеры от совершенно другой макетной платы, в принципе, могут быть запущены и на Teensy. После проделанных опытов нас уже есть USB-устройство, работающее по стандарту CDC, то есть виртуальный COM-порт.

image-loader.svg

Но пока что мы просто учились пользоваться всем готовым. Внесённая правка была чисто символической. Сегодня мы научимся работать с UART (это очень важно, так как других средств отладки у платы Teensy 4.1 нет), поиграем с GPIO, разгоним работу с ним в десятки раз, просто подвигав «мышкой», а на закуску — уберём некоторые особенности примера виртуального COM-порта, о которых я говорил в конце прошлой статьи. Приступаем.

Как открыть нужный инструмент


Я, как любитель контроллеров STM32, прекрасно знаю про систему создания и настройки проектов Cube MX. Дальше у ST-шников появился CubeIDE, куда всё это конфигурирование встроено. Не знаю, кто был первым, но в целом, фирма NXP пошла тем же путём: они встроили подсистему для конфигурирования в среду разработки. Эклипса позволяет переключать перспективы. Вот одна из перспектив как раз и предназначена для конфигурирования портов. Иконки, соответствующие перспективам, традиционно располагаются в правом верхнем углу Эклипсы. Вот они:

image-loader.svg

Нажимаем на ту, где изображена микросхема с ногами. Мы же будем настраивать ноги.

Работаем с UART


В открывшейся перспективе мы видим три представления: таблица слева, таблица снизу и картинка справа. Я не буду подробно рассказывать о таблицах. Есть документация, есть учебные видео. Все желающие могут посмотреть их. Документация живёт тут: MCUXpresso Config Tools User’s Guide (IDE).

В нижней таблице мы уже видим две готовые ножки UART. Где на плате Teensy их искать? Давайте разбираться.

image-loader.svg

Мы видим, что линия RX подключена к ножке L14, которой соответствует порт AD_B0_13, а TX — к ножке K14, порт AD_B0_12.

Запрашиваем у Гугля Teensy 4.1 wiring diagram, получаем ссылку сюда: Teensy and Teensy++ Schematic Diagrams.

Первое, на что стоит обратить внимание: есть порты B0_XX, а есть AD_B0_XX. Это разные порты!!! Я сначала перепутал! Не повторяйте моих ошибок! Теперь, зная об этом, находим хоть нужные порты, хоть нужные ножки. Вот они:

image-loader.svg

Получается, что нас интересуют контакты платы Teensy 4.1, отмаркированные как 24 и 25. Именно к ним подключены линии TX и RX соответственно. Берём какой-нибудь сторонний переходник USB-UART и подключаем его к этим ножкам.

Теперь надо узнать скорость порта, ведь это — настоящий UART. Наверное, она спрятана где-то в районе инициализации. Возвращаемся в обычную перспективу:

image-loader.svg

И начинаем смотреть процесс старта. Вот что-то похожее на правду:

image-loader.svg

Идём в эту функцию:

image-loader.svg

Всё понятно. Открываем вновь подключённый переходник в терминале со скоростью 115200. Перезапускаем Teensy. Видим следующее:

image-loader.svg

Это сработала следующая строка:

image-loader.svg

usb_echo — это макрос, который раскрывается в DbgConsole_Printf. А в прошлой статье я специально ставил галочку, чтобы можно было работать и через обычный printf. Давайте проверим, работает ли этот механизм. Добавим вывод в функцию main:

image-loader.svg

Собираем, «прошиваем», запускаем…

image-loader.svg

Ну, собственно, этого следовало ожидать. Переходим к более творческой части, к работе с GPIO.

Медленный GPIO


Давайте просто попробуем пошевелить ножкой платы, отмаркированной как 32. Почему ею? Она с краю, её легко найти.

image-loader.svg

Сначала я повторю свою ошибку, чтобы потом эффектно её исправить. Итак. Смотрим схему.

image-loader.svg

Это ножка контроллера C10, порт B0_12. Прекрасно. Идём в ногастую перспективу, в левой таблице в поиск вбиваем B0_12:

image-loader.svg

Кстати, тот случай. Я первый раз впал в ступор, увидев, что порт B0_12 уже занят. Но занят не B0_12, а AD_B0_12. Это видно по номерам ног. Щёлкаем по флажку слева от C10, получаем вот такой перечень возможных альтернатив:

image-loader.svg

И вот тут я сейчас и допущу ошибку. Я вижу GPIO2_IO12. Вот около этого порта и ставлю флажок. Я же просто к GPIO подключаюсь.

Новая ножка появилась в нижней строке

image-loader.svg

Но она жёлтая. Её надо дозаполнить. Я сделаю это так:

Identifier — MyPin

Direction — Output

GPIO Initial State — Logical 0

Speed — Max

Slew Rate — Fast

Остальное оставлю без изменений.

image-loader.svg

image-loader.svg

Теперь надо щёлкнуть по кнопке Update Code

image-loader.svg

По завершении обновления исходников нас автоматически выкинут в перспективу для программиста. Если интересно — в функции Main есть вызов функции BOARD_InitPins ():

image-loader.svg

Перейдя к этой функции, можно убедиться, что новая ножка действительно инициализируется:

image-loader.svg

Ну и прекрасно. Давайте сразу после инициализации сделаем бесконечный цикл, шевелящий этой ножкой:

int main(void)
#else
void main(void)
#endif
{
    BOARD_ConfigMPU();

    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();

    while (1)
    {
    	GPIO_PinWrite(BOARD_INITPINS_MyPin_PERIPHERAL, BOARD_INITPINS_MyPin_CHANNEL, 0);
    	GPIO_PinWrite(BOARD_INITPINS_MyPin_PERIPHERAL, BOARD_INITPINS_MyPin_CHANNEL, 1);
    }


Компилим, загружаем, запускаем…

image-loader.svg

6 мегагерц. Давайте попробуем разогнать…

Традиционный разгон


Привычным движением руки делаем ассемблерный код и смотрим, как выглядит генератор нашего меандра:

    while (1)
    {
    	GPIO_PinWrite (BOARD_INITPINS_MyPin_PERIPHERAL, BOARD_INITPINS_MyPin_CHANNEL, 0);
60002a94:	2200      	movs	r2, #0
60002a96:	210c      	movs	r1, #12
60002a98:	4804      	ldr	r0, [pc, #16]	; (60002aac )
60002a9a:	f000 fff1 	bl	60003a80 <__GPIO_PinWrite_veneer>
    	GPIO_PinWrite (BOARD_INITPINS_MyPin_PERIPHERAL, BOARD_INITPINS_MyPin_CHANNEL, 1);
60002a9e:	2201      	movs	r2, #1
60002aa0:	210c      	movs	r1, #12
60002aa2:	4802      	ldr	r0, [pc, #8]	; (60002aac )
60002aa4:	f000 ffec 	bl	60003a80 <__GPIO_PinWrite_veneer>

60002aa8:	e7f4      	b.n	60002a94 


Никакой оптимизации! Переключаемся в режим Release:

image-loader.svg

В свойствах проекта включаем максимальную оптимизацию, так как по умолчанию там была оптимизация по размеру, которая не подразумевает максимальной вставки inline повсюду и чуть-чуть ещё. Заодно ставим флажок Enable Link Time Optimization

image-loader.svg

Не забываем поправить Post Build Step, как мы делали для Debug сборки в прошлой статье. Ведь для Debug и Release сборок настройки разные! В прошлый раз мы это делали для Debug.

Собираем, смотрим новый ассемблерный код:

    186c:	4a1f      	ldr	r2, [pc, #124]	; (18ec )
    186e:	f44f 5380 	mov.w	r3, #4096	; 0x1000
    1872:	f8c2 3088 	str.w	r3, [r2, #136]	; 0x88
    1876:	f8c2 3084 	str.w	r3, [r2, #132]	; 0x84
    187a:	e7fa      	b.n	1872 


Отлично! Лучше уже не сделать! «Прошиваем», запускаем. Смотрим осциллограмму…

image-loader.svg

Нет, втрое мы, конечно, разогнали… Но для контроллера, работающего на частоте 600 МГц, это маловато. Напомню, что в одной из предыдущих статей я добавил разгона путём замены механизма «чтение-модификация-запись» на «сброс» и «установка». Правда, судя по ассемблерному коду, тут за нас это сделал оптимизатор. Но давайте я прекращу держать интригу и раскрою карты.

Использование порта, подключённого к другой шине


Шина — великое дело. Ещё в статье про DMA я показал, как латентность шины портит всё впечатление от хорошего механизма. Но в статьях про процессорную систему NIOS II мы уже встречались с такой удивительной шиной, которая подключается напрямую от процессорного ядра к устройству. Например, тут. Вот в контроллере, стоящем в Teensy 4.1, есть такие же шины. Их несколько. И порты могут быть подключены к обычной шине, а могут — к такой удивительной. Это я узнал отсюда.

Я выбрал порт номер два. Надо добавить пять… То есть, должен быть порт номер семь. Выбираем его (не забываем снять галку со второго, иначе будет конфликт):

image-loader.svg

Заново заполняем все поля порта в нижней таблице. Всё-таки это новое назначение… Не забываем сделать Update Code. Ну, и собираем, «прошиваем»…

image-loader.svg

Мой осциллограф показывает 142 мегагерца. Я обещал разгон в десятки раз? Вот он! Начинали мы с шести… А это у нас меандр 142 мегагерца, значит частота записи в порт у нас вдвое больше. Каково?

Пара доработок USB


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

image-loader.svg

Просто закомментируем этот цикл, выполняющий перенос…

image-loader.svg

Попутно я изменю VID/PID, чтобы устройство стало садиться на драйвер WinUSB, установленный при опытах для позапрошлой статьи:

image-loader.svg

Будет:

image-loader.svg

Можете запустить… Не будет оно работать. Вот в этой функции обратного вызова:

image-loader.svg

Запрос следующего блока прописан не по событию «Принят блок», а по событию «Блок ушёл». Если точнее, то на приём тоже стоит, но только при нулевой длине. Логика такая: новый блок будет принимать, только если освободился буфер. Но если пришёл нулевой пакет — значит, буфер не занят, значит можно и при приёме запустить новый. Прекрасно. Закомментируем соответствующий код в районе отправки:

image-loader.svg

И уберём условие «если длина нулевая» в районе приёма:

image-loader.svg

Терминал после этого виснуть перестанет, а вот моя тестовая программа — продолжит. Они сделали так, что данные не обрабатываются, пока не взведён сигнал DTE:

image-loader.svg

Может, они и правы, но в STM такого нет, и всё работает. Поэтому я во всём файле virtual_com.c закомментировал все условия:

(1 == s_cdcVcom.startTransactions)


Делаю замер скорости, получаю недостаточно радующий глаз результат:

image-loader.svg

Половина теоретической скорости (примерно 25 мегабайт в секунду). Но причины всё те же, что и в позапрошлой статье. Пакет приходит, пока мы не подготовились к его приёму. Поэтому хост получает NAK. И библиотека NXP такова, что больше одного запроса отправить невозможно. Там в двух местах стоит защита от этого! Но к счастью, зато размер запроса может достигать вплоть до шестнадцати килобайт! Поставим, скажем, восемь.

Меняем:

image-loader.svg

На:

image-loader.svg

Получаем совсем другую картину, которой я в своё время хвастался:

image-loader.svg

Собственно, у меня всё.

Заключение


Мы научились работать с UART средствами библиотеки NXP. Также мы освоили GPIO и при оптимизации его работы выяснили, что в контроллере, установленном на плате Teensy 4.1 имеются не только медленные, но и сильно связанные с ядром шины. В следующий раз мы займёмся размещением кода и данных в памяти, подключённой именно через них, чтобы программа работала с максимально возможной скоростью.

© Habrahabr.ru