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

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Обновить

сегодня в 14:34

В прошлой статье мы начали работать с платой 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      	movs	r2, #0
60002a96:	210c      	movs	r1, #12
60002a98:	4804      	ldr	r0, [pc, #16]	; (60002aac <main+0x2c>)
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 <main+0x2c>)
60002aa4:	f000 ffec 	bl	60003a80 <__GPIO_PinWrite_veneer>
60002aa8:	e7f4      	b.n	60002a94 <main+0x14>

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

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

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

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

    186c:	4a1f      	ldr	r2, [pc, #124]	; (18ec <main+0xcd0>)
    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 <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 имеются не только медленные, но и сильно связанные с ядром шины. В следующий раз мы займёмся размещением кода и данных в памяти, подключённой именно через них, чтобы программа работала с максимально возможной скоростью.

Всего голосов 7: ↑6 и ↓1+5

Просмотры385

  • 23 июля в 12:43
    Всего голосов 4: ↑3 и ↓1+2

    Просмотры2.2K

  • 21 июля в 17:48
    Всего голосов 9: ↑8 и ↓1+7

    Просмотры3.3K

  • 28 июня в 11:35
    Всего голосов 9: ↑7 и ↓2+5

    Просмотры1.1K

  • сегодня в 11:00
    Всего голосов 126: ↑115 и ↓11+104

    Просмотры18K

  • вчера в 22:00
    Всего голосов 51: ↑51 и ↓0+51

    Просмотры2.8K

  • вчера в 19:15
    Всего голосов 43: ↑42 и ↓1+41

    Просмотры9.3K

  • сегодня в 11:33
    Всего голосов 38: ↑36 и ↓2+34

    Просмотры9.1K

  • сегодня в 12:02
    Всего голосов 30: ↑30 и ↓0+30

    Просмотры1.5K

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.