![Платы модулей на основе чипов W5500 и W7500p Платы модулей на основе чипов W5500 и W7500p](https://webcf.waybackmachine.org/web/20211014070358/https://habrastorage.org/getpro/habr/upload_files/e5c/bd8/944/e5cbd89440e0d1847757d4b8effa0ad5.jpg)
Многие из тех, кто имел дело с микроконтроллерами, наверняка слышали о микросхеме конвертере SPI <-> Ethernet W5500. В свое время эта микросхема стала поистине "народной" по многим причинам, к которым можно отнести как низкую стоимость самих микросхем и готовых модулей для прототипирования на их основе, так и наличие готовых библиотек под разные платформы для легкой интеграции чипа в различные проекты. К тому же, из-за относительно легкой модели взаимодействия между микросхемой и микроконтроллером, не составляло труда взаимодействовать с микросхемой без сторонних библиотек.
Однако времена шли и появлялось все больше дешевых микроконтроллеров, которые содержали внутри себя MAC уровень, требуя лишь снаружи микросхему PHY. А для ленивых производитель давал готовые решения по интеграции в проект LWIP со стороны софта и демо платы и примеры разводки PHY под свой микроконтроллер со стороны железа. Изредко появлялись чипы с PHY прямо на кристалле микроконтроллера.
Именно в этот момент WIZnet сделала следующий шаг - выпустила чип, который должен был сочетать функциональность W5500 с функциональностью обычного микроконтроллера, объединив тем самым в себе 2 микросхемы. Это техническое решение получило название W7500p.
Рассмотрим, что же из себя представляет W7500P в боевых условиях.
Общие сведения о чипе
У WIZnet имеется две реализации чипа линейки W7500. С суффиксом P (W7500p) и без (W7500). Разница лишь в наличии у W7500p внутри отдельного кристалла PHY (вторым этажом), который подключен к основному кристаллу микроконтроллера через выводы, не выведенные на пригодные для внешней пайки контакты микросхемы.
Вариант реализации без встроенного PHY может быть полезен в ряде случаев, в перечень которых входят жесткие требования к используемому PHY и требования к электромагнитной совместимости в целом. Также стоит помнить что PHY относительно сильно греется.
Я же буду рассматривать вариант с встроенным PHY, наиболее подходящим для дешевых интернет вещей.
Документация, ознакомительные примеры и демонстрационные платы
Так как WIZnet не является сильно популярным производителем микроконтроллеров, то на пути освоения их продукции пришлось столкнуться с множеством "детских" болезней начинающих разработчиков.
На официальном сайте W7500p имеется ссылка на покупку официальной демонстрационной платы, а также ссылки на документацию и готовые библиотеки. Однако не все так радужно:
Данная плата отсутствует в продаже как на официальном магазине, так и на AliExpress. Так как я хотел лишь изучить этот чип для себя, то не стал обременять себя с поиском остатков на Mouser или Элитан. Да и даже если бы я нашел ее там, то стоимость с доставкой одной единицы оставляла бы желать лучшего. Вместо нее на AliExpress есть клон этой платы, на которой убрали слот для модуля расширения, конвертер USB<->UART и добавили разъем программирования и разъем под Micro-SD карту, подключенную по SPI. Именно ее я и приобрел. И о ней далее пойдет речь.
Документация очень скудная. На описание работы TCP/IP стека приходится всего 2 страницы. На остальные модули и того меньше. Отдельно заслуживает внимания политика компании. "В документации этого нет, потому что это уже есть в демонстрационной библиотеке и знать вам об этом не надо". В ряде случаев единственное, что вы можете сделать, так это бездумно копировать "магические" строки из библиотеки себе в проект. Или использовать готовую библиотеку "на веру".
Все примеры, которые имеются под плату хоть и собраны под GCC и Keil, но работают только под Keil. Да, там почти одни и те же файлы, но почему-то ни один пример под GCC у меня из коробки так и не взлетел. И, что интересно, размер собранных bin файлов отличается в несколько раз. И рас уж мы говорим про примеры, то отмечу, что примеры из официального хранилища компании с этой платой работать не будут. Ни из под Keil, ни из под GCC. Там магические строки немного другие. Но об этом позже. Нам на помощь приходит хранилище от разработчиков неофициальной платы. Внутри там как obj файлы, так и исходники. К тому же, имена врут. Видно, что делали или в спешке или не очень добросовестно, но рабочий проект под Keil, что я нашел (для GCC по прежнему не работает ничего из коробки даже отсюда) назывался w5500.
Проект к статье
В своих домашних проектах я предпочитаю делать все с пониманием дела и на регистрах. Этот проект не стал исключением. Воодушевившись наличием рабочего проекта (хоть и не рабочего, если собирать GCC) я начал писать демо-проект с нуля. В готовом виде вы можете увидеть его здесь. Я буду подробно останавливаться на том, что касается именно данного чипа и вскользь упомяну инфраструктуру, на основе которой будет построен проект (о некоторых частях планирую написать отдельные статьи). Проект собирается cmake-ом и написан на C.
Проект включает в себя следующие субмодули:
vsrtos – очень маленькая операционная система реального времени собственной разработки без излишков. Использую ее в своих проектах и фриланс-проектах. О ней напишу в другой раз подробно. А для последующей части статьи достаточно знать, что ее внешний функционал похож на функционал FreeRTOS.
lib_service – библиотека с базовыми компонентами, отвязанными от аппаратной части, которые работают совместно на vsrtos. Из нее будет использован механизм мигания светодиодом в потоке ОС.
lib_w7500x – библиотека для работы с периферией микроконтроллеров W7500 и W7500p. За исключением самого проекта, весь аппаратно зависимый код находится здесь. Файлы из данного хранилища будут рассмотрены в этой статье.
Постановка цели проекта и описание требований
Для того, чтобы оценить возможности чипа, я решил написать небольшой демонстрационный проект, который бы использовал ключевую периферию.
Проект должен:
Работать на максимально возможной частоте.
Содержать операционную систему реального времени.
Из потока ОС мигать светодиодом, расположенным на плате, с частотой 1 Гц.
Из потока ОС раз в 500 миллисекунд отправлять на компьютер по UDP значение счетчика uint32_t переменной.
Хоть поставленные цели и звучат легко, но вот чтобы их реализовать на этом чипе пришлось изрядно постараться, но обо всем по порядку.
Пустой проект
Для начала было бы неплохо собрать проект, который просто инициализирует области в RAM и встает на бесконечное ожидание в функции main. Поскольку перед нами микроконтроллер с ядром Cortex-M0, то тут обошлось без приключений. В общем виде потребовалось написать:
mem.ld – файл с описанием деления памяти микроконтроллера на разные блоки (FLASH, RAM, STACK). Для удобства я всегда выделяю начальный стек в отдельный блок памяти. Этот же стек будет использоваться и обработчиками прерываний. В данном файле я использовал лишь 16 килобайт из первого блока RAM. Однако в чипе содержится 2 блока RAM. Один на 16 килобайт, а второй на 32 килобайта. Интегрированный TCP/IP стек использует второй блок памяти (на 32 килобайта) для приема и передачи Ethernet транзакций совместно с ядром (об этом подробнее далее). Так как 16 килобайт для моих задач было более чем достаточно, то я оставил весь второй блок для TCP/IP стека (о том как TCP/IP стек использует этот блок памяти сообщений, также будет сказано далее). Интересно также, что flash расположена не от 0x08000000 как в STM32, а от 0x00000000.
section.ld – файл с описанием расположения секций кода и данных по блокам памяти, описанным в mem.ld. Тут все как обычно: область .isr_vector а начале FLASH, хранящая таблицу векторов прерываний, за ней .text, хранящая код демонстрационной программы и стандартных библиотек компилятора, и области .data с .bss, расположенные в RAM, хранящие глобальные переменные, заполненными ненулевыми (.data) и нулевыми (.bss) значениями.
vector.c – файл с таблицей векторов прерываний для стандартных прерываний от Cortex-M0 + специфичных для W7500/W7500p. Неиспользуемые прерывания, в случае вызова, переводят выполнение процессора в код-заглушку для последующих исследований. Таблица составлена на основе кода из хранилища рабочего проекта разработчика платы и отличается от таблицы, представленной в документации.
loader.c – файл с методами копирования данных из flash в .data, очистки .bss, заполнения стека начальным, удобным для отслеживания переполнений значением, вызова main и, в случае выхода из main, программной перезагрузки ядра.
main.c – main функция с бесконечным циклом.
Данного набора достаточно, чтобы собрать ничего не делающий пустой проект, запустить его в отладчике и убедиться, что все работает.
Отладка проекта
Чип подключается к клону J-Link и отлаживается через pyOCD последней версии. В openocd для него нет скриптов. Для Keil есть pack, включающий в себя отладку. Отладка в обоих случаях происходит через SWD. Используется аппаратный reset вывод.
Для отладки в среде требуется настроить embedded GDB сервер со следующими параметрами:
'target remote' args: localhost:3333.
GDB Server: pyocd.
GDB Server args: gdbserver --target w7500 --connect halt.
В среде CLion настройка будет выглядеть следующим образом:
![Embedded GDB Server для W7500P в CLion Embedded GDB Server для W7500P в CLion](https://webcf.waybackmachine.org/web/20211014070358/https://habrastorage.org/getpro/habr/upload_files/437/aaa/077/437aaa0773c93bdd0b4a3f2e589e9533.png)
Сам процесс прошивки и начала отладки:
pyocd gdbserver --target w7500 --connect halt
0001317:INFO:board:Target type is w7500
0001698:INFO:dap:DP IDR = 0x0bb11477 (v1 MINDP rev0)
0001706:INFO:ap:AHB-AP#0 IDR = 0x04770021 (AHB-AP var2 rev0)
0001728:INFO:rom_table:AHB-AP#0 Class 0x1 ROM table #0 @ 0xe00ff000 (designer=43b part=471)
0001751:INFO:rom_table:[0]<e000e000:SCS-M0+ class=14 designer=43b part=008>
0001768:INFO:rom_table:[1]<e0001000:DWT-M0+ class=14 designer=43b part=00a>
0001786:INFO:rom_table:[2]<e0002000:BPU class=14 designer=43b part=00b>
0001788:INFO:cortex_m:CPU core #0 is Cortex-M0 r0p0
0001790:INFO:dwt:2 hardware watchpoints
0001794:INFO:fpb:4 hardware breakpoints, 0 literal comparators
0001806:INFO:server:Semihost server started on port 4444 (core 0)
0001860:INFO:gdbserver:GDB server started on port 3333 (core 0)
0002593:INFO:gdbserver:Client connected to port 3333!
0002717:INFO:gdbserver:Attempting to load argon
0002717:INFO:gdbserver:Attempting to load freertos
0002718:INFO:gdbserver:Attempting to load rtx5
0002718:INFO:gdbserver:Attempting to load threadx
0002718:INFO:gdbserver:Attempting to load zephyr
[====================] 100%
0006312:INFO:loader:Erased 5120 bytes (20 sectors), programmed 5120 bytes (20 pages), skipped 0 bytes (0 pages) at 1.39 kB/s
Resetting target
Debugger connected to localhost:3333
Мигаем светодиодом
Как говорилось ранее, в lib_service есть готовый модуль, который в потоке vsrtos инвертирует светодиод с заданным периодом. Однако он требует пользовательский метод для инвертирования светодиода. А значит настало время познакомиться с аппаратной периферией микроконтроллера. Заодно и сравним ее с периферией STM32.
Структуры модулей периферии микроконтроллера
На все модули периферии кроме TCP/IP стека имеются структуры с регистрами периферии в демонстрационном коде из хранилища. Данные структуры отличаются от того, что можно составить по документации, так как в документации множество ошибок. Структуры из рабочего демонстрационного проекта являются истиной в последней инстанции.
На модуль TCP/IP стека не было структур. Только макросы, которые оборачивали адреса регистров периферии.
Для единообразия я переписал эту часть на манер структур от более популярных производителей типа STM. Структуры периферии ядра описаны в файле core.h, а периферии микроконтроллера в periph.h библиотеки lib_w7500x.
Clock Reset generator (CRG)
Это аналог RCC у STM32. Однако он имеет ряд следующих отличий:
Вся периферия по умолчанию включена.
PLL по умолчанию включен. В качестве источника используется внутренняя RC цепь на 8 МГц, а коэффициенты деления с последующим умножением составляют 5 и 2 соответственно.
Все линии тактового сигнала после PLL подключены к блокам периферии с делителем 1 (bypass).
Отсутствуют механизмы анализа выхода на требуемую частоту или детектирования отказа внешнего HSE. Вместо этого есть возможность вывести с любой шины тактирования сигнал на вывод GPIO.
Имеется отдельный генератор частоты для MII_RXC и MII_TXC, работающий от отдельного внешнего кварцевого резонатора. Его можно только включить или выключить. Кварцевый резонатор должен быть на 25 МГц. По умолчанию он включен.
Какие-либо рекомендации по последовательности настройки в документации отсутствуют. Также как и механизмы восстановления в случае установки ошибочных коэффициентов. Микроконтроллер просто начнет вести себя неадекватно при неверных коэффициентах HSE или в случае установки некачественного HSE.
Основываясь на выдвинутых требованиях к проекту, требуется настроить PLL на максимальную частоту 48 МГц и при этом переключить источник с HSI на HSE. После чего выключить HSI. Это делается в 3 строчки кода в файле crg.c библиотеки lib_w7500x.
Порты ввода-вывода (GPIO)
Тут все достаточно сложно. Вместо одного модуля GPIO + EXTI, как это сделано у STM32, тут у нас 3 + EXTI:
Alternate Function Controller (AFC) – данный модуль предоставляет функционал аналогичный выбору альтернативной функции AFx в STM32F4 и схожих. В отличии от STM32, все выводы по умолчанию имеют не функцию входов, а альтернативную функцию, соответствующую наиболее вероятному сценарию использования, которую предполагает производитель. В связи с этим, для того, чтобы помигать светодиодом, для начала нужно убедиться, находится ли требуемый вывод в режиме GPIO, а не подключен к какой-либо периферии. Если подключен, то требуется перевести его в режим работы с GPIO.
External Interrupt (EXTI) – в этом модуле примерно то же, что и в модуле EXTI у STM32F4, только проще. Можно выбрать, генерируется ли прерывание по переходу в требуемый уровень шины сигнала или нет, и по переходу в какой уровень (0 или 1) оно произойдет (речь идет именно о сигнале на внутренней шине между EXTI и GPIO, а не сигнале на входе пина).
Pad Controller (PADCON) – этот модуль предназначен для конфигурации параметров выбранного пина. Тут можно настроить:
Нужна или нет подтяжка к питанию или земле.
Ток выходного сигнала (большой/маленький). Конкретные значения для разных конфигураций указаны в reference manual на странице 150, раздел GPIO.
Является ли выход выходом с открытым коллектором или нет (если нет, то, как я понял, это аналог push-pull).
Включен ли встроенный буфер на входе или нет.
Включен триггер Шмитта на входе или нет.
General-purpose I/Os (GPIO) – модуль сочетает в себе часть RCC и часть GPIO из stm32 и позволяет:
Включать/выключать выходы на пины (режим входов всегда включен).
Считывать данные с входов (после прохождения через буферы и триггеры Шмитта, если включены).
Выставлять данные на выходы пинов (будут выведены только если выход на пине включен).
Считывать/очищать состояния наличия прерываний на пинах.
Выбирать режим срабатывания прерывания: по уровню или спаду/подъему.
Выставлять полярность (в случае срабатывания по совпадению уровня) или границу (в случае срабатывания по фронту или спаду) возникновения прерывания.
Работать с модулем через пространство масок.
Вышла достаточно сложная структура. Для упрощения логики работы с ней был написан ряд простых методов, которые позволяют сделать вид, что весь перечисленный функционал находится в одном модуле. Найти эти методы можно в файле gpio.c библиотеки lib_w7500x.
Для наших целей нам нужно было помигать светодиодом. Он расположен на выводе PC13, который по умолчанию является одним из выводов UART. В файле gpio.c демонстрационного проекта можно посмотреть на его инициализацию, а также на функцию установки состояния на выходе. Последняя передается в аппаратно независимый модуль и мигает светодиодом с заданным периодом.
SYSTIC
Для работы vsrtos, как и для любой RTOS с вытесняющей многозадачностью нужен таймер. В данном микроконтроллере имеется стандартный SYSTIC, который при входной частоте 48 МГц, подаваемых на ядро, тактируется от 6 МГц. Однако есть нюанс. В документации отсутствует бит CLKSOURCE из CSR. Я так и не смог понять, откуда идет частота при 0 в этом бите. Но при 1, все сходится с теоретическими расчетами.
Работа с TCP/IP стеком
Вот мы и добрались до основного модуля, ради которого всё и затевали. Чисто теоретически, для того, чтобы начать отправлять сообщения по UDP, достаточно лишь настроить выводы микроконтроллера, настроить PHY, дождаться подключения кабеля, открыть RX сокет и отправлять в TX сокет свои данные, игнорируя данные в RX сокете (запускаются парами). Однако тут возник ряд нюансов:
Так как внутри нашей микросхемы два кристалла, то часть выводов микроконтроллера не выведено наружу и подключено сразу к PHY. Однако какие именно это выводы - нам не сказано. Но изучение исходников библиотеки, которые писали магические константы по несуществующим адресам + немного дедукции навели на мысли в какие именно регистры какой периферии идет запись.
В подключении между двумя кристаллами (MAC на одном и PHY на другом) есть не подключенный контакт, который приводит к тому, что временами в транзакциях ping скачет с 1 мс до 700 мс. Что для отправки UDP пакетов напрямую кабелем в сетевую карту компьютера значительно. К счастью, в errata по этому поводу имеется специальный раздел. Там просят присвоить константы по постоянным адресам, которых нет.. Опять же, почитав код в пункте 1, стало понятно, что на определенные выводы вешается подтяжка к земле, а на другие к питанию. А на один вывод вообще выставляется логическая единица. Сборную солянку этого можно увидеть в функции init_miim_pins файла eth.c библиотеки lib_w7500x.
Инициализация PHY происходит не через интерфейс MDIO. Его тут попросту нет. Или есть, но нам и тем, кто писал демонстрационный код и документацию об этом не сказали. Все происходит средствами ручного управления пинами. Как принято говорить среди разработчиков "ногодрыгом". Попеременно меня назначение пина. Посмотреть на чуть измененный код ногодрыга для MDIO можно в функциях mdio_idle, mdio_out, mdio_turnaround, mdio_in. Эти методы используются методом mdio_get_data, предназначенным для получения содержимого регистров PHY. Метод phy_init инициализирует пины, и средствами метода get_phy_id пытается получить ID интегрированного PHY. В моем случае он был равен 5.
Как я говорил ранее, второй блок RAM предназначен для TCP/IP стека. А именно:
32 килобайта поделены на два. 16 килобайт для отправки и 16 килобайт для приема. Эти значения фиксированы и не могут быть изменены.
Внутри каждого блока по 16 килобайт можно разбить память для используемых сокетов. В зависимости от задачи. По умолчанию 32 килобайта делятся на максимальное количество поддерживаемых сокетов - 8 штук. То есть, по 2 килобайта на сокет для отправки и 2 килобайта для этого же сокета на прием.
Размер буфера для передачи и приема у одного сокета можно задать разный. Однако размеры кратны двойке: 0, 1, 2, 4, 8, 16 килобайт. То есть, в теории, можно создать один сокет, который будет иметь 16 килобайт для приема и 16 для отправки. Я же оставил данные параметры по умолчанию.
Те участки памяти, которые предназначены для каждого сокета для приема и передачи представляют внутри себя кольцевой буфер. Разберу на примере передачи. Пусть у нас сейчас только началась работа системы. Мы хотим передать 4 байта. Для этого нам нужно:
Считать из специального регистра-указателя текущее положение в кольцевом буфере.
Взять адрес начала буфера и прибавить к нему смещение внутри кольцевого буфера.
Скопировать данные для отправки в буфер по полученному адресу в пункте 2. Если буфер закончился в процессе помещения данных для отправки, то продолжить писать сначала.
Положить в регистр-указатель из пункта 1 прежнее значение плюс длина данных, которые мы приготовили для отправки.
Весь TCP/IP стек представляет для разработчика конечный автомат с множеством состояний и флагов. То есть для того, чтобы начать коммуникацию первый раз, сначала понадобится уточнить, что машина состояний сброшена - сокет закрыт. Далее требуется настроить параметры и дождаться, пока машина состояний перейдет в нужное нам состояние - соединено. С отправкой то же самое. Убеждаемся, что машина состояний находится в состоянии ожидания следующей транзакции, настраиваем пакет для отправки, переводим машину состояния в состояние отправки, и ждем либо переход в состояние истечения времени ожидания, либо окончания передачи. За открытие сокета и последующую отправку в него (с возможным закрытием по желанию пользователя) отвечают методы socket_udp_close, socket_udp_open и socket_udp_send файла eth.c библиотеки lib_w7500x.
Копировать в кольцевой буфер данные можно с помощью DMA. Но я для теста делал ручное копирование, поскольку это лишь первый опыт и реального применения пока не было.
У TCP/IP стека имеется прерывание. Его можно настроить как на срабатывание по общим событиям всего модуля, так и по конкретным на каждом из каналов. Однако при передаче мелких пакетов его надобность практически равна нулю. Так что в примере прерывание не применяется.
В файле eth.c демонстрационного проекта есть задача eth_thread, которая настраивает PHY, открывает сокет со статическими параметрами в режиме UDP, и производит отправку значения счетчика каждые 500 миллисекунд. Поток не рассчитан на отключения кабеля в ходе процесса отправки. При отключении кабеля закрытие канала не происходит. И после его подключения обратно отправка продолжится сама.
В хранилище разработчиков используемой платы имеются библиотеки для DHCP, эхо-ответа (тест через утилиту nc), HTTP сервера/клиента. В случае надобности, можно продолжить доработки на их основе.
Подключение к компьютеру
Собрав и зашив проект в микроконтроллер, он после перезагрузки начнет отправлять данные в UDP сокет на статический адрес и порт. Важно, чтобы сеть на компьютере была в той же подсети и была настроена статически. Пример для Ubuntu:
![](https://webcf.waybackmachine.org/web/20211014070358/https://habrastorage.org/getpro/habr/upload_files/ceb/523/ff2/ceb523ff2b10224db4ff863835ea3e6d.png)
В проекте есть python скрипт rx_udp.py, который открывает сокет-приемник и выводит приходящие сообщения в бинарном виде:
$ python3 rx_udp.py
b'00000000'
b'01000000'
b'02000000'
b'03000000'
b'04000000'
b'05000000'
b'06000000'
b'07000000'
Об оставшейся периферии
Периферия, которую заложили в данный микроконтроллер, достаточно специфична, и всем своим видом дает понять, что не предназначена для широкого применения. По всей видимости данный микроконтроллер задумывался исключительно как конвертер интерфейсов ETH <-> SPI/I2C/UART/PWM.
Выводы
Рассмотрев данный микроконтроллер на практике, можно смело сказать, что он не предназначен для широкого применения повсеместно ввиду своей специфичной периферии. Отсутствие возможности самодиагностики и наличие недокументированных обязательных действий в коде не позволяют использовать его в ответственных приложениях. Однако данный микроконтроллер хорошо себя показал как средство агрегации данных с различных периферийных источников (USART/I2C/SPI) с последующей запаковкой и отправкой по UDP/TCP на компьютер для дальнейшего анализа.