Введение
Как обычно, всё начиналось буднично. Заказчик хотел получить «прошивку», обеспечивающую функцию, суть которой настолько тривиальна, что не стоит упоминания в статье. Единственная интересная вещь в проекте заключалась в том, что Заказчик запросил, чтобы всё было реализовано на контроллере семейства GD32F450. Само собой, с полной лицензионной чистотой. То есть на базе GD-шного SDK и собранное с помощью обычного GCC, не имеющего никакого отношения к ST-шной среде разработки.
Плюс в основу проекта обязательно должна была лечь собственная UDP-библиотека, написанная нашим коллегой для STM32F4 в ходе одного из предыдущих проектов для того же Заказчика. От любимого многими LwIP она отличается компактностью и уменьшенным количеством копирований данных в памяти. Сколько раз буфер копируется при типовом внедрении LwIP – страшно посмотреть! А ведь чтение и запись памяти в микроконтроллерах – довольно медленная операция.
Использование этой библиотеки для Заказчика позволяло убедиться, что она работает и на GD32, а для нашего повествования, благодаря ей, нам удалось опуститься на самый нижний уровень программирования сетевых вещей.
Итак. Зевнув, мы перелопатили код библиотеки, заточенный на конкретный проект, раскидав его так, чтобы им можно было пользоваться из произвольной программы. Потом, не просыпаясь, мы сделали проблемно-ориентированную часть проекта. И чисто для проформы прогнали несколько тестов. Вот тут-то рутина и кончилась…
Суть проблемы
Если откинуть функциональные детали и перейти к терминам UDP протокола, обмен с устройством выглядел так:
1) ПК шлёт пакет в устройство;
2) устройство обрабатывает пакет и шлёт ответ;
3) ПК получает ответ, проверяет его содержимое и переходит к шагу 1.
На практике, на шаге 3 ответ иногда не приходил. Наверняка кто-то уже пошёл писать комментарий, что мы ничего не смыслим в UDP, там потери пакетов – это норма. Комментарии – это хорошо, комментарии я люблю. Но дело в том, что устройство было подключено к выделенной сетевой плате прямым кабелем. И, как видим, всегда в каждом направлении идёт один пакет. Очереди переполняться негде! Пакетам теряться негде! В данном конкретном случае, в правильно работающей системе потери исключены.
Мало того, мы собрали из тех же исходников прошивку для платы NUCLEO-F429ZI, на которой коллега делал свою библиотеку. Тот же тест на той плате отработал 10 часов без единой потери пакета! Тогда мы взяли типовой китайский пример для GD32, выполняющий функцию UDP эха на базе LwIP, и убедились, что он на плате с GD32 вполне себе пакеты теряет, как и наш код, собранный для этого контроллера. Так что на GD-шке всё плохо не только у нас. Но что является причиной?
Как STM32/GD32 принимает пакеты
Как я уже говорил, использование условно своей библиотеки и возможность неограниченно приставать к её автору с вопросами – это очень полезная штука в плане образования. Пользуясь случаем, расскажу всем, как принимаются пакеты в контроллере STM32/GD32. Там используется механизм прямого доступа к памяти (ПДП, далее, в терминах из фирменной документации — DMA). Контроллер DMA может работать в одном из нескольких режимов. В нашем случае (справа на рисунке), в памяти выделяется массив структур. Структура – это дескриптор для одного буфера.
Когда аппаратура DMA достигла конца массива дескрипторов, она автоматически переходит к его началу. Слева на рисунке показано, что дескрипторы не обязательно должны храниться в массиве. Они могут образовывать и связный список. Но автор нашей библиотеки сделал массив, поэтому дальше работаем только с ним.
Что мы видим из всего вышесказанного? А видим мы то, что задержки «прошивки» не могут приводить к потере данных. Как я уже говорил, у нас передача данных идёт по принципу запрос-ответ. Всегда в пути не больше одного пакета. Пока он не будет обработан – новый в сеть не уйдёт. Да, там в сети иногда бегают всякие вспомогательные пакеты, запущенные операционной системой. Вот на логах сниффера один вклинился в протокол нашего обмена:
Но повторю, сетевой адаптер в ПК, на котором ведутся опыты, – выделенный. Левые пакеты через него бегают крайне редко. Их количество обычно не превышает 3-5 штук. А буферов в разные периоды отладки было от девяти до семнадцати. Переполнение просто исключено!
Зато знание принципа размещения принимаемых данных позволило здорово упростить отладку. Адреса всех возможных буферов данных в памяти GD32 известны. В каждый пакет мы на уровне протокола добавили его идентификатор. Поэтому мы легко и непринуждённо можем отобразить в отладочной среде Eclipse те слова памяти, куда попадут эти идентификаторы. В итоге, при срабатывании точки останова сразу видно, какие пакеты успели пробежать по сети (если номера странные – ну значит, в соответствующем буфере лежит тот самый левый пакет, но такое бывало крайне редко и выявлялось по отображаемым сигнатурам, которые из рисунка для статьи я выкинул, они только отвлекают). Вот пример:
Когда какого-то идентификатора не хватает – это сразу заметно!
Макетные платы
Опыты велись на двух платах. Первая – очень навороченная плата от самой фирмы GD. Вот такая красавица:
Штука классная, но у её тестового разъёма шаг 2.0 мм. Поэтому Ардуиновскими проводочками к ней толком не подключишься. Пока эта плата была в цепких лапах Почты России, мы вели опыты на плате, изготовленной безвестными китайцами. Сама по себе плата универсальная. На неё могут быть припаяны контроллеры разных семейств и даже разных производителей (STM и GD), что следует из шелкографической маркировки на ней:
У неё нет своего PHY, но на скорую руку ребята сделали лазерно-утюжную нахлобучку с типовой платой PHY от WaveShare. Просто большая плата ожидалась со дня на день, тратить время на полноценный заказ не имело смысла, но каждый день был для нас, программистов важен, так что делать стоило. Получилось как-то так:
Ой, как этот бутерброд нам помог! Но обо всём по порядку…
Добавляем логический анализатор
Итак, у нас теряются данные. Самый главный вопрос: а в каком месте это происходит? Что они ушли из Windows, мы видим по показаниям WireShark. Что они не пришли в контроллер – мы видим по отсутствию оных в буферах DMA. А ушли ли они из драйвера Windows в сетевой адаптер ПК? Ушли ли они из адаптера в кабель? Приняты ли из кабеля в устройстве?
Самое простое, что можно сделать – это подключиться в устройстве к линиям, идущим от микросхемы PHY к микроконтроллеру. Как я уже говорил, к большой плате подключаться не очень сподручно – там шаг разъёма 2.0 мм. А у имеющегося анализатора контакты для штырей с шагом 2.54 мм. Нет, конечно, можно что-то придумать, но это же человеко-часы. Зачем их расходовать зря? Вот тут-то и пригодилась плата с внешним PHY. На неё был установлен дополнительный разъём, к которому сделаны отведения нужных сигналов. Правда, у сигналов частота 50 МГц. Тут нельзя просто так взять и сделать отвод. Отводящие провода будут создавать эхо, что приведёт к жутким помехам на линиях. Поэтому к дорожкам были припаяны отвязывающие резисторы, а уже их второй контакт пошёл к разъёму.
В «прошивку» был добавлен код, который взводит ножку GPIO, если идентификаторы внутри принимаемых пакетов идут не один за другим. По этому сигналу мы останавливаем процесс накопления данных анализатором. В буфере будет снимок, на котором видно, что творилось незадолго до и сразу после этого момента. Ну что, давайте разглядывать времянки?
Проблема номер 1 не в GD32, а в PHY
А времянки – очень красивые. Вот как работа интерфейса RMII выглядит в документации:
А вот – на анализаторе. Найдите хотя бы одно отличие…
Общий вид:
Начало:
Конец:
Красота! Обратите внимание, что все данные меняются по обратному фронту тактовой частоты! Никаких гонок нет в принципе! Вот я добавил пару зелёных курсоров, идущих от обратных фронтов линии CLK.
Но проблемы ловятся моментально! Я просто запускаю вот такую программу на ПК:
int f3()
{
QRandomGenerator rnd (1234);
QUdpSocket socket;
static const int portRemapper [uart_cnt] ={0,1,2,3,4,5};
socket.bind(QHostAddress::Any,27600);
static int reqId = 1;
QElapsedTimer timeOutCatcher;
while (true)
{
uint8_t datagram [2048];
memset (datagram,0,sizeof(datagram));
uart_frame_tx* out = (uart_frame_tx*)datagram;
out->magic_number = 'SELF';
out->request_id = reqId++;
socket.writeDatagram((char*)datagram,1300, QHostAddress("169.254.156.232"), 27500);
QThread::msleep(2);
}
}
И менее, чем за секунду получаю остановку с вот таким набором буферов:
Пропущен буфер номер 112. На самом деле, это ещё удачный вариант попался при подготовке иллюстраций. Иногда при реальной работе по два-три буфера пропущено было!
На времянках ничего не видно… Если точнее, то видно, конечно, потом покажу, что именно. Но их так много, что сразу и не разглядишь! Поэтому мы написали программу, которая восстанавливает из логов логического анализатора данные, пробегающие по шине. И вот тут-то вся правда и открылась. Вот как выглядит хороший буфер:
А вот так — концовка плохого буфера:
Зная это, можно найти проблемный участок и на времянках:
То есть данные переходят в постоянное состояние. И длина пакета увеличивается на один ниббл.
Кстати, если слать данные не от ПК, а замкнуть PHY в режим LoopBack, проблема не возникает никогда. У нас тест молотил почти сутки.
Опущу процесс поиска. Расскажу, почему это происходит уже в форме обнаруженного факта.
Пара слов про эластичный буфер
Что в нашем случае представляет собой микросхема PHY? Это обычный посредник, который преобразует данные из одного формата в другой. С одной стороны у него наш контроллер и интерфейс RMII, с другой – трансформаторы, за которыми тянутся провода витой пары.
И вот при приёме (а у нас ведь сбоит приёмная часть, с точки зрения контроллера) возникает очень интересная проблема. Со стороны витой пары данные идут в соответствии со скоростью, которую задаёт тактовый генератор где-то вдали (в нашем случае, в ПК). Там скорость задана, и данные будут идти в полном соответствии с нею. Ни быстрее, ни медленнее. Мы не можем попросить ПК сделать паузу. Мы обязаны принимать пакет от начала до конца. Давайте как-то подкрепим это рисунком.
А вот из микросхемы PHY данные забираются в соответствии с тактированием от генератора, который расположен где-то на плате, но вне микросхемы. На рисунке в документации это внешний генератор. Ниже мы узнаем, что в теории, это может быть и источник опорной частоты внутри контроллера. В общем, этот генератор – внешний по отношению к PHY, и он тактирует как нашу микросхему, так и блок, ответственный за работу с сетью (обычно уровень MAC) в контроллере. Давайте я подкрашу его красным цветом.
И протокол RMII устроен так, что если мы начали передачу пакета, сделать паузу невозможно. Можно только сказать, что пакет передан полностью.
Итак. Приёмный тракт нашей микросхемы имеет дело с двумя частотами, каждая из которых задана извне. Я долго думал, какую жизненную аналогию привести… Все требуют каких-то натяжек. Давайте рассмотрим аналогию с выплатой кредита. В ней натяжек будет меньше всего.
Допустим, дядя Вася взял кредит и теперь выплачивает его равными порциями. Причём дядя Вася живёт в мире, где каждый месяц равен тридцати дням. Так будет проще. Зарплата у дяди Васи пятого, платёж надо вносить десятого. Получается, что частота входящего сигнала совпадает с частотой исходящего. Просто они чуть разнесены по фазе. Деньги поступают, пять дней лежат у дяди Васи в кубышке, затем – идут на выход. Красота!
Но это – недостижимый идеал. В реальности, частота генератора имеет небольшую погрешность, полученную в процессе производства. Мало того, она зависит от окружающей среды. Да хоть от той же температуры. Поэтому частоты генераторов, задающих входной и выходной сигналы, будут чуть-чуть различаться.
Пусть у дяди Васи выросла частота входящего сигнала. Частота растёт – период уменьшается. Деньги стали поступать не каждые 30, а каждые 29 дней. А выплачивать их надо по-прежнему, каждые 30. Тогда в первый месяц дядя Вася получил деньги пятого и держал в кубышке 5 дней. В следующий месяц – четвёртого и держал в кубышке уже 6 дней. Потом – 7, 8, 9 и т.д. дней. Однажды набег составил целых 30 дней. А ещё через месяц у него в один день появилось сразу две суммы. Одна лежит уже месяц, вторая – получена прямо сегодня. Ещё через месяц двойная сумма будет в наличии уже целый день. Потом размер накоплений ещё вырастет… И так – пока не кончится срок выплаты кредита (а в нашей сетевой задаче — не кончится пакет). Эта проблема устраняется очень просто – достаточно дяде Васе завести сейф подходящего размера и хранить сумму там. С точки зрения аппаратуры – это очередь, она же FIFO.
Интереснее ситуация, если частота входящих данных станет меньше частоты исходящих. Пусть зарплату начали платить каждые 31 день. Тогда первый месяц она будет шестого, потом – седьмого, потом – восьмого, девятого, десятого… А когда она станет одиннадцатого числа, у дяди Васи начнутся проблемы с банком. В нашем же случае, контроллер не получит данных. Мы же уже выяснили, что протокол RMII не предусматривает пауз в передаче данных. Начал передавать – гони весь пакет! Не пришло данных – всё, пакет искажён. Его корректный приём невозможен. Данные обязаны идти!
Чтобы решить эту проблему, дядя Вася должен сначала несколько месяцев просто копить все входящие
Собственно, мы только что познакомились с эластичным буфером. Микросхема PHY сначала с упреждением копит некоторое заданное количество бит в FIFO, а только затем начинает выдавать их в RMII. Если данные приходят быстрее, чем отдаются, очередь будет расти. Если медленнее –уменьшаться. Вот что говорит документация на микросхему DP83848:
Считается, что при точности генератора плюс-минус 100 тактов на миллион, для разной длины буфера надо сделать ту или иную предвыборку, чтобы запаса хватило на весь пакет. Почему не стоит делать всегда большую предвыборку? Чем она больше, тем выше латентность системы. Ведь N тактов данные не передаются в контроллер, а буферизируются. И потом эти же самые N тактов (ну, если генератор точный) они будут выдаваться по линиям RMII, когда в витой паре уже тишина. А зачем нам задержка? Так что чем меньше данных предварительно кладём в эластичный буфер, тем лучше!
Ну, а в нашем случае, частоты различаются сильнее. Не знаю, почему, но сильнее. И двух битов, которые копятся по умолчанию, для нормальной работы не хватает.
Почему всё работает на плате с STM32? Ну, там и генератор иной, и микросхема PHY другой фирмы. Причём я не нашёл, какой размер предвыборки для того PHY. Может просто значение предвыборки по умолчанию там выше.
Почему работает в режиме LoopBack? Потому что в том случае используется один и тот же генератор для приёма и для передачи. Нет разбега частот – нет этой проблемы.
В общем, добавляем в код строку:
phy_write(0x17,0x23);
И проблема уходит для нашего PHY в связке с ПК… Система перестаёт вылетать в тот же миг… Но… Но детальные тесты показывают, что проблема остаётся!
Проблема номер 2 – PLL в GD32 не торт
Радуясь, что можно вернуться к большой плате (а для целевой функциональности у контроллера на малой не хватало ног), мы переключили железо, и у нас возникло ощущение дежавю. Проблема никуда не ушла. Но теперь мы были уже умные, мы уже знали как суть проблемы, так и то, что у PHY есть отладочные регистры, откуда можно считать как число ошибок, так и сведения фактах опустошения буфера. На малой плате ничего такого не возникает, на большой – буфер опустошается даже если его предзаполнять ну совсем по максимуму. Из документации следует, что целых 16 килобайт можно принять. А вот по факту – и полтора килобайта не проскочат.
Собственно, разница между большой и малой плат невелика. На малой мы используем стороннюю плату PHY от WireShark. Она тактируется от генератора:
А в большой плате – всё веселей. Там PHY тактируется от порта A8 контроллера:
На порт PA8 программно подаётся выход PLL, предназначенный специально для тактирования Ethernet. У нас нет высококлассных измерительных устройств. Нет, мой осциллограф имеет полосу пропускания 200 МГц, но знаю я эти китайские мегагерцы! Невозможно с их помощью проверить качество генератора с частотой 50 МГц. Но всё-таки давайте просто проведём сравнение поведения двух плат.
Если взяться моим осциллографом за линию, идущую с генератора, то 95% времени мне показывают частоту 50 МГц:
И только иногда частота вдруг на мгновение спрыгивает на 52,632 МГц:
Но в известном анекдоте заяц смог выбить достаточно большое количество зубов медведю, а медведь зайцу – всего четыре. Потому что у зайца всего столько было. Вот и тут. Похоже, никаких иных значений мой осциллограф на такой частоте уже выдать не в состоянии, хоть там и гигагерц прогрессивной дискретизации. Либо 50, либо 52.632. Причём второе значение мигает так, что его можно поймать только, поставив развёртку на паузу. Ну ладно, запомним это.
Для платы, где частоту формирует GD32, мигают те же самые две частоты, но они сменяют друг друга хаотично и постоянно. Причём смена идёт так часто, что здесь нужно останавливать развёртку, чтобы увидеть любое из них. Нет такого, чтобы какое-то значение долго находилась на экране.
В общем, как фактическое поведение системы, так и результаты грубых измерений — всё говорит о том, что кривая частота на линии A8 получается. Иначе как объяснить, что PHY жалуется и на ложные несущие (False Carrier), и на ошибки в целом, и на опустошение буфера, а точно такая же микросхема на плате с генератором – прекрасно работает? При всём при этом, плата Nucleo тактируется от того же самого порта A8 настоящего ST-шного контроллера. И у неё, как я уже отмечал, с приёмом всё нормально. Так что перед нами вторая проблема именно контроллера GD32.
В общем, если у вас проблемы, проверьте, не PLL ли их создаёт. Возможно, стоит потратиться на внешний генератор именно для PHY.
Проблема номер 3 – контрольная сумма
Ну ладно, откладываем пока большую плату, проверяем малую. На ней же генератор в порядке, предвыборка в буфер достаточная. На ней же всё будет хоро… Ой! Не хорошо! Опять данные теряются! Причём на этот раз нет в логах никаких констант 55! Но теряются, теряются, теряются! Пришлось навалиться на бедную плату уже не вдвоём, а втроём!
Сначала мы шли по ложному пути и прекращали опыты после первого же сбоя, анализируя проблемный пакет. Но там всё просто идеально! Времянки – хоть на ВДНХ посылай! CRC сходится! Мы уж добавили в декодер отображение массы параметров. Например, сдвиг фронта CRS_DV относительно фронта CLK. Просто пару раз эти два сигнала одновременно взлетали для плохого пакета. Оказалось, что это нормально, у тысяч других пакетов такая же разность фаз, но те пакеты не были потеряны. В общем, ну всё у проблемного пакета так же, как у сотен и тысяч других. Никакой параметр не выбивается из статистики!
И вдруг случайно возникла идея собрать сведения не только о первом, а о большом количестве проблемных пакетов. Тут-то вся правда и открылась. Судите сами. Первый проблемный пакет появляется в случайный момент, а вот последующие… Ууууу! Заметите закономерность? Вот лог, где отображаются идентификаторы потерянных пакетов (напомню, идентификатор в каждом следующем пакете увеличивается на единицу):
Lost Pkt! 49711
Lost Pkt! 115247
Lost Pkt! 180783
Lost Pkt! 246319
Lost Pkt! 311855
Lost Pkt! 377391
ID каждого последующего отличается от предыдущего ровно на 65536. То есть, на 0x10000. Масса экспериментов показала, что это выполняется всегда.
Ещё полдня ушло на поиски, что же происходит каждые 65536 пакетов. А происходит следующее. Вот заголовки двух подряд идущих пакетов. Раз:
Два:
Голубым подсвечено поле Identification. Операционная система сама увеличивает его в каждом следующем пакете. Мы на это повлиять не можем. Прочие поля заголовка остаются неизменными. Раз поле Identification увеличивается, а прочие поля остаются, поле контрольной суммы заголовка пакета (подсвечено жёлтым) уменьшается. Вот оно прошло через значения 0002, 0001… В следующем пакете оно станет равно 0000… Вот этот пакет, восстановленный из логов анализатора:
А из памяти контроллера этот пакет взять не удастся! Потому что он будет отброшен, как проблемный! При этом в счётчиках ошибок в недрах GD32 этот факт зафиксирован не будет!
Именно поэтому первый сбой всегда случайный. Мы же не знаем, какое сейчас у системы придумано значение поля Identification для нас. А дальше – если в поток не вклинятся дополнительные пакеты, нулевое поле КС будет для каждого 0x10000-ного пакета. Ларчик просто открывался!
У STM32 такого нет! Только у GD32.
Собственно, отключаем в настройках MAC проверку КС заголовка, и всё становится хорошо. Целостность пакета всё равно будет контролироваться по его CRC. Ну, а КС заголовка, если очень хочется, можно проверить и программно. А можно и не проверять, наверное.
Уффф. А всё начиналось, как очень скучный, рутинный проект, где надо было просто отрефакторить чужой код и добавить абсолютно типовой функционал…
Заключение
В статье рассмотрен случай, когда одно и то же проявление проблемы (теряются пакеты в сети) было вызвано сразу тремя причинами. Устранение двух любых не решает проблемы в целом. Также в статье показано отличие поведения блока ENET контроллера GD32 от блока ETH контроллера STM32. Ну, и указано на ошибку в модуле ENET.
Выявлено, что:
- Микросхеме DP83448 следует увеличить число предвыбираемых битов эластичного буфера.
- На плате GD32450-EVAL тактовый сигнал для микросхемы PHY, вырабатываемый в PLL контроллера GD32, настолько некачественный, что постоянно приводит к опустошению эластичного буфера PHY и состоянию False Carrier.
- Уровень MAC контроллера GD32 считает ошибочными пакеты, для которых контрольная сумма заголовка равна нулю. При этом контрольная сумма корректная. Она действительно равна нулю! У STM32 такой проблемы нет. Для устранения проблемы, следует отключить проверку контрольной суммы заголовков. При этом, целостность пакетов по-прежнему будет осуществляться путём проверки CRC32.
Комментарии (26)
mpa4b
16.08.2022 12:59Уровень MAC там настолько продвинутый, что аппаратно парсит IPv4 заголовки и проверяет их чексуммы?
EasyLy Автор
16.08.2022 13:07В полном соответствии с логикой STM32, с которым эти контроллеры функционально совместимы. Причём настолько совместимы, что вот у STM32F10X MAC намного проще, чем у STM32F4.... Поэтому у GD32F107 MAC похож на STM32F107, а у GD32F450 - на STM32F4. У первой парочки, скажем, нет расширенных дескрипторов.
А так - там на уровне MAC ещё и аппаратная поддержка PTP имеется. У всех выше перечисленных. И опять же, парочка STM/GD32F107 имеет логику PTP попроще, А которые F4 - покруче. Всё совместимо.mpa4b
16.08.2022 13:46Тогда я думаю проблема очень проста. чексум в IP-протоколах считается как one's complement sum и в нём не различаются 0000 и FFFF. Конкретнее, в заголовках и TCP не различаются, а в UDP (чексум тела пакета) 0000 означает что чексум не вычислялся и проверять нечего, а FFFF -- валидный чексум.
EasyLy Автор
16.08.2022 14:53+2Ну STM32 примерно так и работает. А вот GD32, если видит в этом поле входящего пакета ноль, то игнорирует такой пакет. При этом не выявлено каких-либо флагов ошибки. По крайней мере, мы ставили точки останова на участки, где в дескрипторе вернулся признак ошибки. Ни разу не сработали. И те счётчики ошибок в портах, что мы мониторили - не увеличивались. А пакеты именно с таким признаком именно GD32 - теряли.
Что касается флагов ошибок - может не то смотрели, может где-то что-то и отмечалось. Но что настоящий STM32 в тех же ситуациях ничего не терял - факт. Отключение проверки контрольных сумм заголовков решает проблему - тоже факт.
SuperTEHb
16.08.2022 13:08+5Удручает это. Вроде, всем хороши контроллеры, но только на бумаге. А на поверку оказались "китайскими" в не самом лучше смысле этого слова. Пытались заказать контроллеры GD другой серии, так выяснилось, что они вовсе их не производят. Рекламируют, но не производят. Лично я Гигадевайсом несколько огорчён. Могу лишь понадеяться на дальнейшее развитие.
EasyLy Автор
16.08.2022 13:10+2Это что. Мне однажды свалился баннер - купите отечественный RISC-V контроллер. Ну я зашёл. Ага! Не купите, а запишитесь на покупку. И когда он будет доступен, то у него будет 8 килобайт флэша... При том, что по моим ощущениям, RISC-V с Compressed командами требует в среднем вдвое больше памяти для кода, чем Cortex M. А самая базовая голубая пилюля 64К флэша на борту имеет...
В общем, всё познаёмся в сравнении. Тут - пойманные проблемы неприятны, но пока не смертельны.
SuperTEHb
16.08.2022 13:17+2Согласен, раз на раз не приходится. Тот же М3 от российской фирмы до некоторых событий приобрёл вместе с отладкой легко и просто, и совсем недорого. Да и нынче в, так сказать, ручном режиме обещают отгрузить небольшую партию контроллеров по более-менее адекватной цене. Честно говоря, не ожидал ни тогда, ни сейчас, что вообще можно вот так просто взять и купить.
Keroro
17.08.2022 07:01+1Ну тут проблема непосредственно у GD только одна, как я понял (нулевой CRC считает плохим), остальные две - кривая демо-плата (плохой клок, неправильная настройка буффера). Я с Microchip PIC32MZ работаю, там и не такое встречалось... По сравнению с китайцами у нормальных фирм было то преимущество, что нормальная техподдержка и документация была доступна. Но вот недавно вот обратился в их ТП по поводу очередной проблемы, мне сказали: гуляйте лесом, товарищ, ваша страна больше не рукопожата. А доки теперь скачивать только через VPN. Отличий от китайцев становится всё меньше...
EasyLy Автор
17.08.2022 08:17Документация на GD32 весьма толковая и написана явно с нуля (ну, то есть, пересказана, а не просто переведена на китайский и потом обратно на английский). Хотя, разумеется, и близка к оригиналу. Но по крайней мере, ни разу не пришлось кричать: "Да что же там такое?" и открывать оригинал. Только GDшную штудируем в рамках этого проекта, а раньше с блоком Ethernet я не сталкивался для STM32.
И надо отметить, что библиотеки у них написаны даже красивее, чем у STшников. Кто-кто, а программисты у STшников странно пишут. У китайцев стиль, конечно, интересный, но оптимальность у библиотек для доступа к железу - на высоте. Я не со всеми решениями согласен, но то, что в рамках проекта более, чем в 50% случаев работаю через их библиотеки - это о многом говорит. Кто их писал, тот молодец.
Там путаницы в коде много. Как они тактовую частоту настраивают - это эталон путаницы. Там всё пришлось выкинуть и написать заново. Но доступ к железу, да на втором уровне оптимизации, да со включённой оптимизацией во время компоновки... Конфетка! По крайней мере, по сравнению с оригинальными STшными решениями.
RTFM13
17.08.2022 23:13с Microchip PIC32MZ работаю, там и не такое встречалось...
А что там такого чего нет в errata?
У меня больше проблем было с библиотеками, когда примеры из Harmony не работали, но говорят там уже стало получше. Помогло использование самого нижнего уровня абстракции, с тех пор я так ими и пользуюсь, т.к. код написан уже почти на все случаи жизни.
juramehanik
16.08.2022 15:09+4Хорошее описание поиска и анализа проблемы, спасибо! Но.
в большой плате – всё веселей. Там PHY тактируется от порта A8 контроллера:
на серийных устройствах с STMF4 была та же самая проблема , на большинстве устройств (но не всех) были потери при прямом соединении с пк. Так что вам повезло с платой ST. Она же в единичном экземпляре была.
Джиттер PLL в ST порядка 0.5-1нс ( на выходе MCO а не значения из даташита! ),
, замерял осцилом в рамках системы DPLL, построенной на этих мк (на входе таймера импульс 1гц асинхронный, на выходе точные частота и фаза кварца, что управляется этим мк, и его же тактирует ).
Требования по джиттеру у входного сигнала микросхемы физики повыше (см страницу 77 дш dp83848 , мы использовали micrel, там все тоже самое)
лучше даже вот тут смотреть, все микросхемы езернета весьма чувствительны к качеству клока.
https://www.ti.com/lit/an/snla091b/snla091b.pdf?ts=1660652493295
Так что только лишь из этого утверждать что PLL в GD хуже нельзя
и вероятно не DP83448 а DP83848 ?aptahar
16.08.2022 17:03Проблемы большого джиттера на выходе MCO не только у STMF4, на моем опыте это проявлялось на STMF107. Так же пытались тактовать от PLL микроконтроллера связь по RMII и в итоге пришли к схеме внешнего генератора.
EasyLy Автор
16.08.2022 18:38Cпасибо, маркировку поправил.
Про то, что STM32 тоже может дурить - понятно. У нас две NUCLEO просто работали, на том и остановились. Но в целом, вывод в тексте всё равно универсальный, так что его править не нужно.
В общем, если у вас проблемы, проверьте, не PLL ли их создаёт. Возможно, стоит потратиться на внешний генератор именно для PHY.
А про то, что это не только у GD32 бывает - пусть в комментариях будет.
quaer
17.08.2022 20:51А там нет возможности после PLL добавить в цепочку делитель на 2? Если причина в дробном делителе может помочь, наверно.
EasyLy Автор
17.08.2022 21:38Конкретно у GD32 с делителем всё хорошо. В отличие от STM32, она поддерживает частоты до 200 МГц. Само собой, в проекте установлена именно эта частота. Так что для получения 50 МГц, деление целое.
/* choose DIV2 to get 50MHz from 200MHz on CKOUT0 pin (PA8) to clock the PHY */ rcu_ckout0_config(RCU_CKOUT0SRC_PLLP, RCU_CKOUT0_DIV4); syscfg_enet_phy_interface_config(SYSCFG_ENET_PHY_RMII);
В свою очередь, на той конкретной плате 200 делается из кварца на 25 МГц.
Но спасибо за технологию. В будущем может пригодиться где-нибудь.
sami777
16.08.2022 15:19Боюсь спросить! А поменять алгоритм расчета CRC заголовка, чтобы возвращал не ноль при любом значении, нельзя? Просто вы шлете корректные пакеты, а микроконтроллер их отбрасывает считая некорректными. Кто то явно неправ!
EasyLy Автор
16.08.2022 16:041) CRC (он же контрольный циклический код) - у всего пакета. А у заголовка - арифметическая контрольная сумма, она же CS
2) Заголовок формируется на уровне Операционной Системы. В обычной ситуации, мы на него не можем повлиять . Для исследований, можно слать пакеты через PCAP драйвер, как я описывал тут. В обычной же ситуации, мы открыли сокеты, и шлём данные. А оборачивает в пакеты их ОС. Как я показал, посылая абсолютно идентичные данные, мы получали потерю каждого 65536-го пакета, потому что одно из полей в каждом следующем пакете увеличивает именно ОС. Прочие поля (и их тоже заполняет ОС) не меняются. Поэтому плохая КС получается раз в 65536 посылок (поле, которое увеличивается - 16 битное, КС - тоже).
3) К счастью, контроль CRC всего пакета и CS заголовка - разные операции. Поэтому на уровне контроллера, мы отключили проверку CS заголовка, но оставили контроль CRC пакета. Поэтому если данные исказились при передаче, с очень большой вероятностью, такой пакет будет отброшен.
Borjomy
18.08.2022 00:13Для пакетов с одинаковым набором данных проблема контрольной суммы будет явно существовать. Но если в данные вы добавите пару случайных чисел, то... Опять, же, если дублируется пакет с разной солью, то вы чисто теоретически 100% избавляетесь от данного бага.
У вас ещё поле 2E меняется. Возможно, контрольная сумма от него зависит. Имеет смысл проверить
EasyLy Автор
18.08.2022 07:41+1Какая соль? Это контрольная сумма заголовка. Заголовок формирует операционная система. Мы туда только IPшники и порты передаём. Остальное (включая MAC адреса и даже наш IPшник) заполняет ОС. Мало того. Если я ничего не путаю, при прохождении через маршрутизаторы, порт и даже наш адрес могут поменяться. Правда, это я просто нутром чую. В рамках нашего проекта такое не проверялось.
Одинаковые данные получались, так как мы слали всегда на один адрес и один порт.
А в целом - довольно удивительно подстраивать протокол под ошибки конкретного контроллера. Сколько этих контроллеров в мире? Вот мы сейчас по указанию. Заказчика переходим от опытов с GD32 к CH32. Если там найдётся какая-то ошибка, учитывать и её?
Мы просто нашли, как устранить проблему на уровне прошивки и успокоились. А как ответственный человек, я поделился знаниями со всеми.
NickViz
а вы как-либо сумели с производителем связаться и сообщить о таких ошибках? ну хоть в errata добавят (нет).
EasyLy Автор
У меня такой печальный опыт общения с первыми линиями разных техподдержек, что я без нужды к ним не обращаюсь больше. Обычно никому это не надо у производителя. А если кому-то и надо, то пока до него прорвёшься... А смысл?
Конечным пользователям будет полезно - я задокументировал. Заказчик в Штатах тоже в курсе теперь. Возможно, он с производителем свяжется.