Comments 73
Где-то после размера блока 4 килобайта, скорость упирается в 5.6 килобайт в секунду. Давайте я грубо умножу это на 8. Получаю условные 4.5 мегабита в секунду.
5.6КиБ * 8 = 44,5Киб. Кибибайты при умножении должны превратиться в кибибиты. 44,5 кибибита это примерно 0,0434 мибибита. Понятно, то там NRZI да ещё и приправлен битстаффингом, но не на три порядка же.
560 * 8 = 4 480 килобит, грубо 4.5 мегабита.
Сейчас поправлю текст. Ещё раз спасибо.
PS
560 * 8 = 4 480 килобит, грубо 4.5 мегабита.
А если точно: 4480 кибибита / 1024 = 4,375 мибибита.
Было бы интересно сравнить с ChibiOS, так как там свой USB-стек, не завязаный на кривоубогенький STM-овский HAL.
ТС наваял USB на основе калокуба и пытается доказать, что в проблемах виновато железо, а не рукожопые разработчики калокуба!
Реальные тесты нужно проводить: на «голом» CMSIS со своей реализацией USB. Тогда и всплывут реальные ограничения железа.
Я находил альтернативную реализацию, лишённую ряда слоёв. Но копирайты там были те же самые. Так что это просто другой вариант развития той же идеологии. И разумеется, за требуемое время из обработчика прерывания там выйти тоже ничего не успевало. Со всеми теми же самыми проблемами.
В общем, единственные, кто целенаправленно давил эту проблему — те самые китайские товарищи. Но тот самый USB 2.0 хаб испортил бы результаты и им… И это в статье доказано.
Это было (для меня) нелегко, но я справился со сборкой Вашего проекта /F1-nolib/CDC_ACM под Windows. Результат при прямом подключении к материнке — просто замечательный. Через хаб — предсказуемо такой же, какой был.
Сделаю несколько замеров — дополню статью ссылкой на российского автора полезной библиотеки в Вашем лице…
Ибо от куба готовы отказаться многие, а вот пересмотреть код usb-драйвера, желающих думаю будет гораздо меньше.
В целом, согласен с Вами. Но в частности, комментарии под статьями — это здорово! В комментариях под этой сатьёй собрались авторы сразу трёх USB библиотек, написанных с нуля! Я даже сделал статью-экспромт, в которой погонял две из их начерно и указал, где поглядеть про третью.
Что до постепенного разгона — вопрос интересный. По крайней мере, я замерял для нескольких мегабайт.
А так — выше я в комментариях отписался (и новый раздел ближе к концу текста вставил), как удалось разогнать на любых объёмах. Тот же метод, что и у китайцев, но не абстрактное «Мы сделали», а скачана конкретная библиотека от отечественного автора. И получившиеся осциллограммы соответствуют предсказанным. Ну, и с USB 2.0 хабом, предсказуемо ничего не улучшилось.
Интересно, как вы это сделали, если стандарт USB 2.0 приводит максимальную пропускную способность для Bulk и 64-байтных пакетов равную 1216000, что есть 9.728 Mbit/s. Что-то тут не сходится...
EHCI — вот эта плата, процессор NXP IMXRT1062. Скоро выложу несколько статей со своими первыми впечатлениями о работе с нею. Но по цене это совсем разные весовые категории… И отладочного порта на этой плате нет. А так — забавная штучка.
2) HAL зло, написанное криворукими программистами, которые даже не пытаются сделать совместимость чипов на уровне софта, хотя на уровне железа STM такую совместимость декларирует (можно заменить один контроллер на более мощный той-же распиновки).
Возьмите 2 HAL разных контроллеров, совместимых по выводам и периферии — ВСЕ переименовано. Чтобы сменить контроллер, приходиться тупо проходить по коду и менять имена констант.
3) HAL зло, написанное криворукими программистами, которые получают оплату за кол-во изменившегося кода от версии к версии. и STM на это пофиг.
Берем 2 HAL разных версий: и видим например в коде настройки пинов замену for (int i...) цикл на int i =0; while (i<)… цикл.
Итог: совместимости нет. Оптимизации нет. Святая вера в оптимизации компилятора это бред. Если Вам нужна производительность, надо писать короткий и ПРАВИЛЬНЫЙ код, который будет оптимизироваться. А надеяться что компилятор заинлайнит стек из 30 вызовов — это бред.
П.С. Прошу прощения. Накипело. STM делает прекрасные контроллеры и полное Г. в библиотеках. Может соберемся и сделаем open-source CMSIS+HAL спроектированное и реализованное прямыми руками?
STM делает прекрасные контроллеры
Не обольщайтесь. Скажем, стек USB там проектировали те еще наркоманы:
1. Регистр USB_EPxR: часть битов доступна только на чтение (это нормально). Часть — на чтение и запись (тоже пока нормально). Часть игнорирует запись единицы с сбрасывается записью нуля. А часть игнорирует ноль и перебрасывает значение при записи единицы.
2. Размер буфера под конечные точки нигде в инклюдах не прописан — только если догадаться, что он не часть оперативки и залезть в memory map (ссылок туда из соответствующего раздела тоже нет)
3. Организация буфера конечных точек: 256 штук 16-битных слов с 32-битной адресацией. Хочешь копировать побайтно — шиш тебе, перекодируй на лету.
4. Двойная буферизация. Она как бы есть — в изохронных и bulk точках. При этом переключение буферов на передачу осуществляется битом DTOG_RX. Буферов на передачу битом на чтение!
Но как человек, живший при дефиците, я несколько десятков Пилюль купил давно. Для дома, для семьи… Уж больно они замечательные.
У NXP библиотеки лучше сделаны. Да и сами они неплохи. Но ценаааааа… Я просто сейчас Teensy 4.1 по проектной необходимости изучаю. Но когда надо круть — можно и переплатить. Когда не надо — зачем переплачивать?
Слева сверху зелёненькая надпись Active. Чуть правее от неё монетка десятикопеечная. Наведите на неё курсор…
- Все эти режимы сделаны намеренно и нужны в разных ситуациях.
- Так он не фиксирован. Догадываться не надо, в Reference Manual написано про эту память.
- Не очень удобно, но исправлено в более новых контроллерах. Впрочем, DMA должен уметь делать такую конвертацию.
- Только DTOG_RX для такого endpoint называется SW_BUF, если Reference Manual почитать. А в остальном — почему бы и нет? Поскольку такой endpoint не может одновременно работать на чтение и на запись, часть битов не имеет смысла и может в теории использоваться как угодно.
Все эти режимы сделаны намеренно и нужны в разных ситуациях.Что им мешало сделать инверсию не по 1, а по 0 или наоборот чтобы запись 1 сбрасывала другой бит? Претензия-то не к тому, что разные способы доступа, а что маски кривые накладывать приходится
Так он не фиксирован. Догадываться не надо, в Reference Manual написано про эту память.Написано. Вот только ссылки туда из соответствующего раздела нет. И в инклюдниках ее размер не задефайнен.
Не очень удобно, но исправлено в более новых контроллерах. Впрочем, DMA должен уметь делать такую конвертацию.DMA умеет. Про новые контроллеры ничего сказать не могу.
Только DTOG_RX для такого endpoint называется SW_BUF, если Reference Manual почитать.Так кто ж спорит что они это задокументировали. Но зачем было заставлять дергать бит приема при передаче? Если уж «часть битов можно использовать как угодно», так и привязали бы бит передачи к передаче.
Правда, я давно не заглядывал, что у него там сейчас. Лет 5 назад скачал и развивал свой вариант. В столе даже есть пара статей про эту библиотеку, но знакомые отговорили от их публикации. Она же не моя. И типа нечего тут…
USB — приходилось пользоваться тем, что есть… Но буду изучать библиотеки, с которыми познакомился сегодня. Сейчас вставлю дополнение в текст…
А то по вашей логике получается, что мы должны писать статьи только про свой код. Про linux/windows/appache/e.t.c. низя… ШТА? О_О А как учиться?
делает простой работу со сложными блоками (я использовал GPIOGPIO как раз штука простая, не знаю зачем в том же HAL или Arduino ее настолько изуродовали. К примеру, в моем варианте делается примерно так:
#define LED_RED B, 10, GPIO_PP_VS
GPIO_config( LED_RED );
GPO_ON( LED_RED );
GPO_OFF( LED_RED );
Где-то объявляем
typedef Mcucpp::IO::Pa0 oscOut;
Потом можно всегда поименять на другое
Затем — где надо, инициализируем
oscOut::ConfigPort::Enable();
oscOut::SetDirWrite();
Первая строка нужна, чтобы на порт тактирование включить, оно там само определит, на какой GPIO подать, для более сложных блоков — и на GPIO ног, и на блоки подаст.
Ну, и работаем
oscOut::Set();
oscOut::Clear();
Обратите внимание, что никаких переменных не заводится. Там работа через статические функции идёт. Как макросы, только легко читаются реализующие функции, и всё строго типизировано. И при вводе текста часто подсказки IntelliSense хорошие выдаёт, не надо всё помнить.
Оптимизируются так, что руками на ассемблере лучше не написать. Я проверял.
А можно при таком подходе хранить пины в массиве и в цикле проходить по всем?
2) А вот у самого автора той библиотеки зато есть более интересная штука — PinList. Ноги, объявленные таким методом, объединяются в группу, которую можно заполнять занесением двоичной константы. Я в своих статьях как раз извращаюсь с их заполнением и смотрю, что выдаёт оптимитзатор. А оптимизатор там даёт идеальный код. На ассемблере с этой группой контактов обычно лучше не получится работать. Правда, в Кейле надо включить обязательно оптимизацию по времени. Если по размеру — всё будет хуже.
Собственно, из статьи про эти группы контактов я и узнал о существовании той библиотеки. Но когда скачал с Гитхаба, оказалось, что она — это не только GPIO, а ещё и масса других вещей. Надеюсь, за ссылку на очень старую статью на другой ресурс, меня не расстреляют. Она реально старая. Тогда библиотека ещё называлась avrcpp. Сегодня её надо гуглить по имени mcucpp. Вот, нагуглил текущее состояние.
О, я помню эту статью! С PinList понятно, что это очень эффективный метод, но я не понимаю, как вообще подход с шаблонами кошерно применить к такой задаче (недавно как раз решал): прочитать два пина (или N пинов), сделать со значениями какие-то одинаковые операции, записать результат в другие два пина.
template<typename...>
struct IndexOf;
template<typename _Need, typename... _Tail>
struct IndexOf<_Need, std::tuple<_Need , _Tail...>>
{
static const int value = 0;
};
template<typename _Need, typename _Head, typename... _Tail>
struct IndexOf<_Need, std::tuple<_Head, _Tail...>>
{
static const int value = IndexOf < _Need, std::tuple<_Tail...>>::value + 1;
};
template <typename... _Pins>
uint16_t ReadAll()
{
using PinList = std::tuple<_Pins...>;
return ((_Pins::Read() << IndexOf<_Pins, PinList>::value) | ...);
}
Записать +- аналогично, циклы заменяются на раскрытие variadic-ов.
Спасибо за статью!
Вопрос - а что со случаем, когда данные отправляются из МК в ПК? Какие подводные камни там?
Но в целом — с точки зрения самой шины в обратную сторону всё намного лучше. Главный на шине же хост. И когда он хочет послать данные на скорости FS — он всегда их посылает. Сколько раз пытается, столько посылает весь пакет, тратя время. А обратно — он просто посылает запрос, не занимая шину ради прокачки самих данных. Уже в этом лучше.
Ещё могу точно сказать, что правило «чем больше размер запрошенного блока, тем быстрее бежит поток» соблюдается и там. Собственно, эти графики зависимости скорости от размера блока я впервые построил именно при приёме данных, когда читалку NAND Flash 12 лет назад делал.
Остальное — надо пробовать. Я обязательно попробую со вновь найденной библиотекой. Её надо будет изучить вдоль и поперёк. Если она устойчивая — переходить на неё.
Вот на STM32F4 у меня был забавный подводный камень. Я перетащил прошивку MARLIN для 3D принтера на него (дело было давно, тогда ещё не было готовых вариантов). Работаю. Даю её знакомым. У них всё регулярно виснет. У меня обменом занимается Simplify3D, у них — CuRa. У них виснет. В общем, после долгих опытов выяснил, что при большой нагрузке на порт прекращается передача данных. Даже написал свою стресс-программу, которая за 10-20 посылок всё роняет.
Погуглил — были такие вопросы на форуме ST Ответ — «А вы не гоните много данных». Легко сказать! Долго ли, коротко ли, а опытным путём выяснил, как сделать так, чтобы не висло. Надо было отправлять данные как раз из контроллера в хост не в любой момент, а когда пришло прерывание SOF. Копишь-копишь в кольцевом буфере, пришёл SOF — отправил. Зависания ушли. Почему — не знаю. Является это проблемой конкретно F4 или всех STM — не знаю. На всякий случай, я на всех STM32 по SOFу отправку инициирую, благо там не так много в моих проектах обычно уходит.
Как же это соотносится с тем, что данные спокойно идут во время обработки прерывания?А они идут? А то, может, игнорируются из за ошибки переключения буферов.
Как вариант, можно провести тест чтобы контроллер принимал поток байтов, что-то с ними делал и отсылал обратно, а комп проверял правильно ли данные пришли. Если тест только на прием, то, скажем, вернуть последовательность 0x00 — первый_принятый_байт — сумма — последний_принятый_байт — 0xFF. Просто чтобы в однородном потоке байтов надежно определять пакет и иметь возможность определить что счел пакетом контроллер.
Но это вопрос не для одного дня. Начерно — NAKов нет. А они вырабатываются аппаратурой. Вот все ли данные доходят до потребителя — вопрос интересный.
Можно просто контрольную сумму считать. Чтобы были заметны перепутанные буфера — CRC. Там от последовательности результат зависит.Внутри пакета данные не путаются. Да можно просто передавать нарастающие значения, а контроллер пусть отслеживает что они именно нарастают и именно по одному. Правда, я не знаю есть ли на Пилюле еще светодиоды, на которые можно вывести ошибку.
Кейл ещё позволяет отслеживать память через SWD, не останавливая процессор и не внося ощутимых задержек. Я просто генерил меандр на ноге, ставил осциллографу запуск по повышенной длительности и мониторил память Кейлом. Осциллограф факты задержек бы отследил. Всё было просто замечательно. Записывая данные в эту память, можно много чего наотслеживать хоть в дампе, хоть через просмотр переменных в процессе исполнения программы.
Вообще, в этом плане Кейл просто замечательный. Я там обожаю порты (UART, SPI, прочие) отслеживать на лету. Все проблемы настройки выявляешь в момент. Вот бы научиться так же в Эклипсе делать! А то ребята как попросят помочь, а у них Эклипса. И придумывай, как выкручиваться… Наверняка же тоже так можно, надо просто знать, как…
Насколько я понимаю, там сначала запускается openocd, который поднимает gdb-сервер. А уже к серверу коннектится Эклипс.
Главная беда известных мне методов отладки через OpenOCD/GDB — они для любого обращения к процессору стопорят его. Если я ничего не путаю, через настоящий JTAG по-другому нельзя. Но у нас же не он, а SWD!!! А Кейл через SWD работает, не трогая ядро. Вот надо разобраться, можно ли так же всё настроить при работе через Эклипсу. Это первое. А второе — Кейл классно svd файлы подглатывает и порты дешифрует через них очень красиво. Не останавливая ядро! Как в Эклипсе это делать — тоже пока не знаю.
если всё собрать с отладочной информацией DWARF2, а потом просто открыть обычную Эклипсу и в ней настроить проект на elf файл, то можно будет всё отлаживать, бегая по исходникам. Удобно!Кх-х-хм… Оно как бы для этого и нужно. Еще дизассемблировать можно чтобы сравнить выхлоп компилятора со строкой, из которой он получен.
Можно было бы оформить в виде статьи, попутно расписав весь набор требуемых вещей (как раз OpenOCD, GDB), зачем они нужны и как проверяется, что они зацепились (показать мои любимые команды для проверки и ответы для случая, когда всё работает). Но есть опасение, что будет много выкриков «Я это и так знал». Поэтому руки опускаются. Были у меня такие статьи…Ну вообще да, наличие в файле отладочной информации известно многим. Но если сделать упор не на этом, а на туториале по настройке окружения, может и взлетит.
Только без восторгов, как в вашем посте :) Не поймут.
Если я ничего не путаю, через настоящий JTAG по-другому нельзя. Но у нас же не он, а SWD!!! А Кейл через SWD работает, не трогая ядро. Вот надо разобраться, можно ли так же всё настроить при работе через Эклипсу.Это не Эклипса забота, а openocd, ведь через что подключаться решает именно он. Ну и программатор еще. Например, мой самодельный st-link в JTAG не умеет вообще.
Кейл классно svd файлы подглатываетЭто честно говоря не знаю что такое
Это не Эклипса забота, а openocd, ведь через что подключаться решает именно он. Ну и программатор еще. Например, мой самодельный st-link в JTAG не умеет вообще.Но работа идёт через Эклипсу
XMLник, в котором все порты подробно расписаны. Я, было дело, играл в Altera Cyclone V SoC (ПЛИС с ARM Cortex A на борту). Так чтобы в том отладчике можно было всё красиво смотреть, даже ручками эти файлы для портов, которые в ПЛИС описал, делал. Муторно, но результат того стоит!Кейл классно svd файлы подглатываетЭто честно говоря не знаю что такое
Поищите файлы *.svd там, куда Кубик (CubeIDE) или ещё чего для компиляции STM установлено. Вот я сейчас нашёл — D:\ST\STM32CubeIDE_1.3.0\STM32CubeIDE\plugins\ com.st.stm32cube.ide.mcu.productdb.debug_1.4.0.202007081208\ resources\cmsis\ STMicroelectronics_CMSIS_SVD\STM32F103.svd. Найдёте подобное у себя — загляните внутрь. Красота! А Кейл этим пользуется, чтобы пояснять, что там где в портах. Остальные просто обязаны делать то же самое, просто не на поверхности оно, скорее всего!
Хотя не факт… заклинания для прошивки у разных контроллеров все же отличаются. Возможно, openocd не обеспечивает той абстракции, на которую я так надеюсь.
Поищите файлы *.svd там, куда Кубик (CubeIDE) или ещё чего для компиляции STM установлено.Вы забываете, что я Кубом не пользуюсь и не уважаю. Текстовый редактор, консоль — большего и не надо.
Но уговорили, запущу виртуалку посмотрю. Вдруг и впрямь для чего-нибудь сгодится.
И да, народ ругающий HAL. Сам его не люблю, но как понял из статьи, HAL тут особо ни при чём. Собака порылась не в HAL-е, в usb-драйвере. И именно его код надо смотреть. Что куда менее приятно.
Предельная скорость USB на STM32F103, чем она обусловлена?