Как стать автором
Обновить

Почему может не работать отладка на плате XIAO BLE

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров1.3K
Как обычно, ничто не предвещало веселья. Шла рутинная работа. Надо было освоить работу с ОС Zephyr в контроллере NRF52 на примере забавной платы из семейства «Сяо» (а именно XIAO BLE). Вообще, с этой платой принято работать из среды Arduino, но задача была использовать именно Zephyr, а значит — среду VS Code плагином NRF Connect Plugin. Заказанная плата приехала, к точкам для доступа по SWD был припаян разъём программатора… Потом я немножко похулиганил… В итоге, содержимое флэшки в контроллере было стёрто.

Но что нам стоит восстановить загрузчик? С сайта производителя был скачан актуальный HEX-файл, он был залит в плату… Дальше был собран типовой демо проект Blinky… И вечер перестал быть томным, так как проект не запустился на отладку.

Почему именно он не запустился, как я это выявлял, и как с этим бороться, будет рассказано в этой статье.



Первые шаги, и сразу проблема


Итак. Был создан типовой проект на базе Blinky. Его создают десятки блогеров в своих видео. Но у них всё работает, а у меня не заработало! Уже странно. Но почему бы и нет? Осталось понять, чем мои действия отличаются от типовых. Вроде, я всё делал верно, в рамках той задачи, которую собирался решать. Собирал проект и запускал его на отладку. Но ведь у них-то всё работает!

Хорошо. Давайте присмотримся к проблеме чуть более внимательно. Вот я запускаю проект. Отладчик не выдаёт никаких сообщений об ошибках! Он сообщает, что идёт исполнение кода. Но обычно при входе в функцию main() срабатывает точка останова. А тут – нет.

Надо сказать, что я впервые столкнулся с этим контроллером, поэтому ещё не знал, должен произойти останов или нет, так что для надёжности поставил точку останова на первых строках функции main().



Нет, чуда не произошло. Останов не сработал даже с ней. Но отладчик работает, работает, работает! Мало того, если нажать на паузу, он остановится, но неизвестно где.





Кто виноват?


Я так подробно рассказываю симптомы, потому что они характерны для любого контроллера. Если отладчик работает, но не останавливается в функции main(), значит «прошивка» была запущена, но работа ведётся где-то на этапе инициализации. Так как мы перед началом работ прошивали загрузчик – значит, он запустился, но не нашёл причин, почему следует передать управление основному коду. А раз управление не было передано, то и точки останова в функции main() не сработали. Ни та, что на входе, ни та, что была поставлена мной.

С этими знаниями, осмотримся вокруг. Загрузчик создаёт виртуальный диск. Ну-ка? Ага! Вот он!



Итак, у нас загрузчик получает управление при старте контроллера даже без двойного нажатия на Reset, но не отдаёт его нашей прикладной программе. Осталось понять, почему.

Проверяем адреса


Первое, что приходит на ум – проверить правильность адресов. Допустим, загрузчик хочет передать управление на один адрес, а приложение расположено по-другому. Я с таким уже сталкивался. Давайте осмотрим map-файл нашего приложения.



Есть мнение, что оно расположено по адресу 0x2700.

Немного поиска в сети, и оказывается, что исходный код нашего загрузчика расположен вот здесь:
GitHub — adafruit/Adafruit_nRF52_Bootloader: USB-enabled bootloaders for the nRF52 BLE SoC chips

Из описания следует, что адрес приложения, в зависимости от обстоятельств, может быть 0x26000 или 0x27000. Я специально оперирую терминами запутавшегося новичка, у которого глаза разбегаются. Сейчас-то я знаю, что это за обстоятельства. Но статья же про меня времён разборок. Оцените диапазон возможных адресов, который вылили на нас:



Итак, мы запутались в выданной нам горе информации. Какой же из двух адресов у нас? На самом деле, есть несколько способов проверить это. Я проверил несколькими, но давайте в статье просто рассмотрю HEX-файл загрузчика, который был скачан с сайта производителя платы. Вот этот участок:

:020000040002F8

:10644000B1490200D74902001C0500402005004068
:10645000001002007464020008000020E80100003F
:106460004411000098640200F0010020D8110000DF
:10647000A011000001181348140244200B440C061C
:106480004813770B1B2034041ABA0401A40213101A
:08649000327F0B744411C000BF

Первая строка читается так:

02 – два байта полезных данных в строке,
0000 – для этой строки не используются (а так – это адрес),
04 – тип строки. В ней задаются старшие 16 бит адреса,
0002 – последующие строки будут содержать данные для адресов 0x0002XXXX,
F8 – контрольная сумма строки.

В следующих строках нас интересует только начало. Рассмотрим вторую строку
10 – в строке задаётся 0x10 полезных байт,
6440 – младшие 16 бит адреса. С учётом старшей части, данная строка содержит данные для адреса 0x00026440.

Далее идут сами данные и контрольная сумма. Но для нас главное, что адрес 0x26440 занят телом загрузчика. Ну, и последняя строка соответствует адресу 0x26490.

Поэтому вопрос 0x26000 или 0x27000 закрыт. Не может приложение пользователя располагаться по адресу 0x26000. Там ещё идёт тело загрузчика… Как потом выяснилось, не совсем загрузчика, а SoftDevice, но сейчас нам не до того, нам бы наше приложение разместить. Тело какого-то штатного кода…

В общем, исходя из увиденного, автоматически предложенный нам адрес приложения 0x27000 считаем верным. Он, скорее всего, не приводит к проблемам, из-за которых приложение не получает управления. Да и если бы управление передавалось на неверный адрес, то всё бы падало, и виртуальный диск терял бы работоспособность. Если просто надолго поставить приложение на паузу в отладке – диск отвалится. Нельзя надолго USB-устройство останавливать. Он работает! Значит, просто загрузчик работает и не хочет никому управление отдавать!

Попытка понять логику кода загрузчика с треском провалилась. Он весьма увесистый. Его логика слишком сложна, чтобы пытаться постичь её за короткое время, а много времени тратить я был не готов. Хорошо. Адрес верный, остальное – пытаемся выяснить другим путём.

Успешное решение проблемы при помощи бубна


Вообще, меня очень смущало, что вот я вижу проблему, а поисковики ничего такого не находят. У подавляющего большинства авторов вопросов на форумах, не работает JLINK в принципе. Один автор получил точно такую же проблему, как у меня (ну хоть что-то), но этот автор скомпоновал приложение на адрес 0, и всё заработало. Читер несчастный! Он просто убрал загрузчик. Это не наш метод. Мало того, сейчас я знаю, что в итоге он потерял возможность работы с Bluetooth, так как затёр код SoftDevice. А без Bluetooth можно использовать и контроллеры попроще.

Но у остальных-то пользователей всё работало без проблем!!! Никто не оставался внутри загрузчика!!! КАК??? ПОЧЕМУ???

Возник и у меня соблазн на момент забыть об отладке и начать загружать приложение, как все Ардуинщики и даже некоторые Зефирщики: копируя файл с ним на виртуальный диск. Ничему же не противоречит! Я же только попробую! Вот этот файл! Почему не попробовать-то?



Копирую… Виртуальный диск тут же исчезает, а светодиод на устройстве начинает приветливо моргать, как того требует логика программы Blinky. Ну хоть что-то! Однако, как же проводить JTAG отладку?

А давайте попробуем провести её, когда приложение скопировано на диск? Ой! Пока я тут рассуждал, уже сработала точка останова на входе в функцию! Управление добралось до рабочего приложения!



Дальше начинается магия. Я пробую модифицировать программу и запускать её из среды разработки. Все модификации послушно прошиваются, и трассировка всегда работает! Работает, будто и не было неразрешимых проблем. Это точно работает не то, что я скопировал через файл, а новая залитая версия. Я специально вносил существенные изменения. Все они учитывались!

Магия! Но что это было?

Следствие пошло по неверному пути


Сначала я пошёл по пути наименьшего сопротивления. Что за файл CURRENT.UF2 на виртуальном диске? О-о-о-о! Давайте его откроем!

Вот его начало.



А вот – второй сектор.



Уже отсюда можно предположить, что по адресу 0x0C располагается адрес, на который должно копироваться тело сектора. И да, в секторе как раз 0x100 байт.



Но в целом, незачем разбирать формат интуитивно. Описание файла uf2 легко ищется:
GitHub — microsoft/uf2: UF2 file format specification

Само собой, я сделал конвертор из этого формата в обычный двоичный. Преобразовал файл, когда ничего не работает (но приложение уже залито), и файл, когда приложение уже работает. И оказалось, что различий не так и много:

Сравнение файлов data.bi~ и DATA.BIN
00032D8C: FF 00
00032D8D: FF 00
00032D8E: FF 00
00032D8F: FF 00
00032D90: FF 00
00032D91: FF 00
00032D92: FF 00
00032D93: FF 00
00032D94: FF 00
00032D95: FF 00

00032DF8: FF 00
00032DF9: FF 00
00032DFA: FF 00
00032DFB: FF 00
00032DFC: FF 00
00032DFD: FF 00
00032DFE: FF 00
00032DFF: FF 00

Собственно, последний сектор добит нулями до конца…

Так вот… Не делайте так! Не проверяйте пользовательские данные. Нули – это хорошо, но это – просто технологическая вещь. Поднимем глаза чуть вверх, и увидим, что первый сектор, который описывает данный файл, имеет смещение во флэшке 0x1000:



Что располагается до него (а сейчас я знаю, что там располагается область MBR) – из этого файла не узнать. И что расположено после тела файла – тоже узнать не удастся. А загрузчик и его параметры не входят в это тело. Так что этот файл – не для нашего случая, нам надо более сложное решение, которое накроет весь образ флэшки. Но вот как этот файл накрывает пользовательский код – мне очень понравилось. Надо будет пользоваться им в реальной жизни, тем более, это формат поддерживает сама Microsoft.

Читаем всю область флэша


Стандартный программатор для среды разработки NRF Connect – это J-LINK. Само собой, у меня к плате XIAO тоже подключён J-LINK EDU. Все, кто работал с именно этой модификацией адаптера (EDU), знают, что прекрасная утилита J-FLASH из штатной поставки от разработчика программатора с ним не дружит. Но есть одна тонкость. Она не дружит на запись! А на чтение вполне себе работает! Поэтому запускаем её, выбираем чтение области.



Задаём, скажем, первый мегабайт (то есть, от 0 до 0xfffff):



И всё читается! Считанный дамп сохраняем:



Не забыв выбрать двоичный формат:



Сохранив два дампа (с неработающим и работающим кодом, у меня это файлы full_first.bin и full_working.bin), сравниваем их штатной командой Windows, перенаправив весь вывод в файл full_cmp.txt, для чего в консоли (я использую FAR, но подойдёт и cmd) вводим строку:
fc /b full_first.bin full_working.bin >full_cmp.txt

Собственно, в рабочем файле есть участок, который в нерабочем затёрт FF-ами:



Остальное (ну, кроме того самого занулённого конца сектора пользовательского кода) у этих файлов идентично. Что же это такое?

Постигаем физику проблемы


Итак. У нас есть конкретный адрес, который надо изучить поподробнее. Давайте вобьём его на удачу в поиск по каталогу с загрузчиком. Зря что ли мы его нашли в сети? Ищем строку 0xff000 по всем файлам.



Первый же файл (makefile) показывает, что мы на верном пути, но не раскрывает деталей. У нас же как раз по адресу 0xff000 располагается DWORD 0x00000001:



Строки закомментированы… Но адрес!!! Адрес совпадает! И данные там наши! Отлично. Ищем дальше. И удача улыбается нам. Скрипт компоновщика (файл nrf52840.ld) гласит:
/** Location of bootloader setting in flash. */
BOOTLOADER_SETTINGS (rw): ORIGIN = 0xFF000, LENGTH = 0x1000

Отлично! Теперь ищем по всем файлам строку BOOTLOADER_SETTINGS. И вот она (я опустил несколько скучных косвенностей, это уже финальная):



Уже не теплее! Уже горячее! А что за тип bootloader_settings_t? Вот он!



Та-а-а-а-ак! Накладываем это дело на то, что мы уже видели!



Ещё бы константы 01 и FF раскрыть. Так вот же они! Рядом!



Прекрасно! Перед нами описание таблицы разделов! Сначала раздел «приложение» с занулённой контрольной суммой, потом – раздел «Неверное приложение». А когда флэшка стёрта, оба байта заполнены константой 0xFF. Приложений нет!

HEX-файл, скачанный с сайта, это дело не инициализирует. А при первой же записи на виртуальный диск, всё заполняется. И после этого, всё начинает работать.

Вот почему почти у всех всё работает! Сразу после получения платы, файловая система инициализирована! Это мои шаловливые ручки привели к краху файловой системы, потребовавшему её перезаписи. Если бы я хоть раз скопировал файл, оно бы починилось само… Но я не копировал. И ещё один автор не копировал, но он и разбираться не стал, просто от загрузчика отказался. Потеряв SoftDevice, а значит – возможность работы с Bluetooth. Но это его проблемы.

Теперь причина полного молчания в сети о проблеме понятна. Её получить не так просто. Но если получили – мы уже научились её локализовывать и устранять при помощи бубна. А точнее, единичного копирования файла с расширением uf2 на виртуальный диск.

Но можно ли автоматизировать устранение проблемы? Глобально – нет. Но для себя – да.

Правим фирменный HEX-файл


Почему бы нам не заставить автоматику записывать эту таблицу разделов вместе с основным HEX-файлом? HEX-файл же текстовый, мы его легко можем подправить! Если бы не контрольные суммы, то вообще в текстовом редакторе бы сформировали нужные строки. Но в целом… У нас же есть J-Flash! Давайте заставим его работать на нас!

Вот мы читали мегабайт. А давайте считаем только нужные байты!



Получаем вот такую красоту:



А теперь сохраняем не в двоичном виде, а в виде Intel Hex:



Вот он:



Последняя строчка – это маркер конца. Она нам не интересна. А вот первую и вторую берём в буфер обмена и вставляем до маркера конца в тот HEX-файл, который был скачан с сайта производителя плат XIAO. Например, сюда:



Теперь, если стереть содержимое флэшки в контроллере и прошить такой загрузчик, проблемы уже не будет… Уффф! Проблема решена? Да, но появилась новая проблема. Мы отметили, что есть приложение, но самого приложения не прошили. Но ведь мы сразу же его прошьём, правда? Вот починили плату, и сразу начали её отлаживать, пока снова не испортили. Так что это не страшно. Иначе придётся добавить в тело HEX-файла ещё и какое-то приложение-пустышку. Но это уже – творческая часть, которую каждый сделает сам, если ему это будет нужно.

Заключение


В статье рассмотрена достаточно редкая проблема, связанная с платой XIAO BLE. Однако, по ходу развития, в статье показаны методы выявления случаев не выхода за пределы начального загрузчика во время отладки, которые возникают довольно часто, так что умение замечать их является полезным. Кроме того, показаны методы быстрого и детального устранения конкретной проблемы, которые могут быть применены и в других случаях. Также в статье вскользь упомянут очень интересный файл с расширением uf2. При разработке обновлений «прошивок» через виртуальный диск этот формат позволит как удобно прошивать сектора, так и производить контрольное обратное считывание.
Теги:
Хабы:
Всего голосов 16: ↑15 и ↓1+18
Комментарии11

Публикации