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

—

от автора

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

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

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

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

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

Работаем с UART

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

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

Мы видим, что линия 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. Это разные порты!!! Я сначала перепутал! Не повторяйте моих ошибок! Теперь, зная об этом, находим хоть нужные порты, хоть нужные ножки. Вот они:

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

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

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

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

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

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

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

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

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

Медленный GPIO

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

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

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

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

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

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

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

Identifier — MyPin

Direction – Output

GPIO Initial State – Logical 0

Speed – Max

Slew Rate – Fast

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

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

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

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

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

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);     } 

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

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

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

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

    while (1)     {     GPIO_PinWrite (BOARD_INITPINS_MyPin_PERIPHERAL, BOARD_INITPINS_MyPin_CHANNEL, 0); 60002a94:2200      movsr2, #0 60002a96:210c      movsr1, #12 60002a98:4804      ldrr0, [pc, #16]; (60002aac <main+0x2c>) 60002a9a:f000 fff1 bl60003a80 <__GPIO_PinWrite_veneer>     GPIO_PinWrite (BOARD_INITPINS_MyPin_PERIPHERAL, BOARD_INITPINS_MyPin_CHANNEL, 1); 60002a9e:2201      movsr2, #1 60002aa0:210c      movsr1, #12 60002aa2:4802      ldrr0, [pc, #8]; (60002aac <main+0x2c>) 60002aa4:f000 ffec bl60003a80 <__GPIO_PinWrite_veneer>  60002aa8:e7f4      b.n60002a94 <main+0x14> 

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

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

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

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

    186c:4a1f      ldrr2, [pc, #124]; (18ec <main+0xcd0>)     186e:f44f 5380 mov.wr3, #4096; 0x1000     1872:f8c2 3088 str.wr3, [r2, #136]; 0x88     1876:f8c2 3084 str.wr3, [r2, #132]; 0x84     187a:e7fa      b.n1872 <main+0xc56> 

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

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

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

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

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

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

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

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

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

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

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

Будет:

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

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

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

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

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

(1 == s_cdcVcom.startTransactions)

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

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

Меняем:

На:

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

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

Заключение

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


ссылка на оригинал статьи https://habr.com/ru/articles/569222/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *