Как сделать свой bootloader. Изучаем работу SPIFI

ejsanyo

Active member
Нет, 64 разумеется. Раз уж есть возможность не ютиться в 3 гигах ОЗУ, которое даёт 32-битная архитектура, то не вижу причин продолжать это делать. Да, насколько я понимаю, и маломальски актуальный софт под 32 бита тоже давно не работает.
 

ejsanyo

Active member
Ну вот, жёстко изнасиловав "stm32-bootloader", наконец получил что-то работоспособное. Держите ссылку на архив с проектом. Будем считать, что "версия 0.1". Да, с Гитхабами не дружу, поэтому можете себе забрать и продолжить доведение сего кода до совершенства. 🦸‍♂️
Как вы наверно уже поняли, это бутлодырь, который если записать в EEPROM Амура, позволит нам прошивать внешнюю флешку довольно быстро через UART. Причём для прошивки потребуется лишь какая-нибудь программа-терминал, умеющая пересылать файлы по протоколу XModem (точнее, "XModem-CRC16").
Посмотрим на main.c : вначале всё как всегда - настройка работы от кварца, тактирование блоков, настройка UART и включение SPIFI. Потом в UART передаётся небольшая "заставка".
В коде после комментария "jump to the user application" идёт проверка "некого условия", при котором заргузчик или пойдёт дальше выполнять код из флеша, или перейдёт к приёму файла для перепрошивки. Наверно этот код вам имеет смысл скорректировать, поскольку у меня это условие ориентировано на мою самодельную приспособу ("обе копки нажаты после включения").
Потом вы можете увидеть, как в терминале медленно пишутся символы "C C C C C....". В этот момент отправьте в терминале файл по протоколу "XModem" или "XModem 1k". Файл прошьётся, и загрузчик перейдёт на выполнение основной прошивки.
Возможно, вам также придётся подправить функцию flash_jump_to_app(), которая включает в себя, по сути, функцию упрощённых бутлодырей, описанных выше. Так что если ваша флешка не поддерживает полноценный QPI-режим, придётся убрать HAL_SPIFI_W25_QPIEnable(), а также скорректировать команду чтения spifi_mem_config. Как это сделать, думаю, вы уже знаете, прочитав эту ветку форума от начала.
 

ejsanyo

Active member
Со включенной оптимизацией -Os результат упёхался в 6,5 кБ, так что в EEPROM поместился.🤕 С -Ofast - уже нет.
Работу проверял с терминалами ExtraPutty и TeraTerm, вроде всё адекватно шьётся. Как доберусь, опробую и классический Hyper Terminal. В режиме XModem-1k прошивка ожидаемо заливается немного быстрее.
Автор "stm32-bootloader" использовал модифицированный "XModem-CRC16", и это правильно: то, что в оригинальном протоколе обзывалось CRC8, даже "контрольной суммой" назвать сложно.
Вообще, нам здесь проще, по крайней мере, в том плане, что основная прошивка и бутлодыть лежат физически на разных носителях информации, и поэтому изворачиваться с перемещением кода в ОЗУ, чтобы "прошить самого себя", не обязательно. При этом положительный момент в том, что кривой прошивкой, внезапным сбоем питания и т.д. мы бутлодырь себе отстрелить не сможем и в "кирпич" контроллер не превратим.
Некоторые подробности про XModem можно почитать здесь.

Что я вижу можно (и нужно) было бы улучшить:
Сделать возврат контроллера после прошивки, по возможности, взад, в дефолтное состояние: выключить GPIO, UART, перейти на RC-генератор...и, наверно, ещё что-нибудь. Наверно, для совместимости со всякими рабочими прошивками это будет полезно. В идеале симитировать аппаратный сброс.
В первый момент после отправки файла бутлодырь напряжённо замирает на секунду-другую, и только потом начинает шустро писать. Это происходит потому, что в этот момент выполняется команда полного стирания флеш-чипа, а она выполняется очень долго, и это норма. Быть может, здесь имеет смысл использовать посекторное стирание, затерев таким образом столько секторов, сколько ожидается, что будет реально занимать прошивка. XModem не передаёт реального размера файла, и это всё усложняет. быть может, кто-нибудь осилит сделать реализацию, скажем ZModem, где всё это будет?
Функции работы с UART-ом, наверно, можно было бы сделать и лучше, особенно в части реализации таймаута на операции. Аналогично и функцию записи во флешку. Но какие-то более продвинутые режимы работы, с прерываниями и DMA, скорее всего, здесь использовать нецелесообразно, да и нежелательно - можно не вписаться в 8 кБ.
 

ejsanyo

Active member
Ещё напоминание для тех, кто будет использовать проверку контрольной суммой по всему объёму флешки: как известно, при передаче файлов XModem-ом длина передаваемого файла приёмнику не сообщается, а пакеты идут фиксированной длины. Так что, если длина файла нацело не укладывается в длину пакета, протокол "добивает" длину последнего пакета холостыми байтами, причём их значение по какой-то причине выбрано 0x1A. Бутлодырь, естественно, честно зашьёт эти байты во флеху. Но если, например, зашивать ту же прошивку через jtag, то, разумеется, никаких лишних байтов зашито не будет, а после стирания по этим адресам будет 0xFF! Неудивительно, что если в том и в другом случае посчитать CRC, они не совпадут. Более того, поскольку длина пакетов "просто" XModem и XModem-1k разная, то и добивать последние пакеты холостыми байтами придётся в разном количестве. А значит, количество "лишних" записанных байтов будет разным, и CRC опять же не совпадут! И даже более того: из различных источников следует, что, якобы, не все программы придерживаются значения 0x1A для добивания пакетов, в каких-то из них оно может быть другим. И в этом случае CRC опять же будет иным! :rolleyes:
Наши проггеры в этой ситуации выкручиваются таким "костылём": при формировании бинарника принудительно добивают его до размера, кратного пакету XModem-1k, т.е. кратного 1 кБайту. Если правильно помню, при помощи утилиты srec_cat.exe При этом в качестве холостых байтов для добивания выставляют, конечно, 0xFF. Таким образом, разница в результатах прошивки нивелируется.🪄
 

ejsanyo

Active member
В продолжение предыдущего поста: такое ощущение, что заливка флешки через jtag тоже добавляет какие-то "лишние данные" при записи. :unsure: Либо же неполное стирание, которое при этом происходит, оставляет какие-то "хвосты" от предыдущего кода.
Вижу это таким образом:
Сначала делаю бинарник из elf-файла
Bash:
objcopy.exe input.elf -O binary output.bin
Потом выравниваю его до 32768 Байт, поскольку он сам получился длиной 29088 байт
Bash:
srec_cat.exe output.bin -bin -fill 0xFF 0x0 0x8000 -o output_full.bin -Binary
Так вот, когда шью output_full.bin через бутлодырь, что в режиме просто XModem, что в XModem-1k, контрольные суммы по всей флешке совпадают, что ожидаемо. Когда шью невыровненный output.bin, контрольные суммы в разных режимах НЕ совпадают, что тоже ожидаемо. А вот когда тот же проект шью прямо из среды через jtag, контрольная сумма получается совсем другой, не совпадающей ни с какими предыдущими.o_O
PS Поскольку я пользую CRC32b, сравнил результат вычислений её непосредственно на контроллере после прошивки и для бинарника, упакованного zip-архивом (не все знают, но zip использует тот же самый алгоритм). И она совпала для output_full.bin, залитого через загрузчик. Вывод - заливка через jtag всё-таки что-то постороннее в код добавляет... 🤐
PPS Флешку отпаять и прочитать на программаторе не пробовал. Но всё же действительно, читаю из основного кода начальные адреса, где код уже закончился (прикинул по размерам получившегося бинарника) и вижу, что после работы бутлодыря там 0xFF, как и ожидалось. А вот после прошивки jtag-ом там внезапно 0x00. Похоже скрипты добивают область прошивки нулями до целой страницы. Не то чтобы всё это было проблемой, просто нюанс, который возможно придётся учитывать.
 
Последнее редактирование:

ejsanyo

Active member
Полистал немного описание ZModem, взгрустнул... :cautious: Даже если это каким-то чудом реализовать, в 8кБ оно ой как вряд-ли влезет. Интересно, а какие ещё есть альтернативы, чтобы не изобретать велосипедов для пекашной стороны, но при этом код загрузчика не разрастался бы до уровня какого-нибудь U-Boot?
 

cryptozoy

Member
Полистал немного описание ZModem, взгрустнул... :cautious: Даже если это каким-то чудом реализовать, в 8кБ оно ой как вряд-ли влезет. Интересно, а какие ещё есть альтернативы, чтобы не изобретать велосипедов для пекашной стороны, но при этом код загрузчика не разрастался бы до уровня какого-нибудь U-Boot?
Примитивный EEPROM-загрузчик принимает файл расширенного RAM-загрузчика, сохраняя его по мере приёма в оперативку, затем передаёт ему управление. RAM-загрузчик может сам проверять целостность своего кода.
 

Евгений

New member
Полистал немного описание ZModem, взгрустнул... :cautious: Даже если это каким-то чудом реализовать, в 8кБ оно ой как вряд-ли влезет. Интересно, а какие ещё есть альтернативы, чтобы не изобретать велосипедов для пекашной стороны, но при этом код загрузчика не разрастался бы до уровня какого-нибудь U-Boot?
Маленькая не обязательная правка к коду. Тактирование шины в загрузчике лучше сделать от внутреннего генератора 32Мгц. А то я уже купился на внешнем кварце 32Мгц с третьей гармоникой - где в итоге получается частота поделенная на 3. И по незнанию этого момента - долгий поиск почему все работает в 3 раза медленнее. Всякое бывает, все кварцы не проверишь. А осадочек у пользователя останется, особенно если на плате не будет установлен разъем JTAG.

Тут полностью согласен 8 кб это очень мало для такого процессора. Как вариант пробовать писать на ассемблере или резать всякие проверки в исходном коде библиотеке HAL. Она оказалась очень прожорливой.
 

ejsanyo

Active member
Тактирование шины в загрузчике лучше сделать от внутреннего генератора 32Мгц.
Можно, конечно. В этом случае достаточно просто не активировать кварцевый. Только следует понимать, что на RC-тактировании уже не факт, что у вас сохранится стабильная связь на 115200 б/с. Придётся настраивать что-нибудь вроде 9600, и тогда прошивка будет литься долго и печально. :cautious: Так что, считаю, лучше сразу разобраться с тактированием, а потом уже всё остальное.
или резать всякие проверки в исходном коде библиотеке HAL. Она оказалась очень прожорливой.
Тут уж скорее RISC-V код сам по себе получается довольно длинным, поскольку это же Ъ-RISC! Т.е. минимально необходимый для работы набор команд.
 

mscs

Member
В этом случае достаточно просто не активировать кварцевый.
После старта МК тактируется от кварцевого генератора, если этот генератор запустился. Поэтому в загрузчике нужно сразу переключиться на тактирование от HSI32M (AHB_Mux = 1). После этого кварцевый генератор можно выключить (CLOCKS_SYS = 0x201).
на RC-тактировании уже не факт, что у вас сохранится стабильная связь на 115200 б/с.
Встроенный генератор HSI32M обладает достаточно высокой температурной стабильностью, выходная частота практически не зависит от изменения питающего напряжения. Т.о., необходимо компенсировать первоначальный разброс. Для этого достаточно единожды подобрать и сохранить в энергонезависимой памяти значение делителя частоты для USART. Это значение будет индивидуальным для каждого образца МК.
 

cryptozoy

Member
Встроенный генератор HSI32M обладает достаточно высокой температурной стабильностью, выходная частота практически не зависит от изменения питающего напряжения.
Используя часовой кварц можно с высокой точностью измерять частоту внутреннего генератора 32 МГц и корректировать её. При этом автоматически заполнять таблицу значениями коэффициентов коррекции частоты в зависимости от индекса температуры. Если коэффициент записан ранее, то повторное измерение частоты на этой отметке температуры уже не потребуется.
 

mscs

Member
в зависимости от индекса температуры
Повторюсь. Выходная частота генератора практически не зависит ни от температуры, ни от напряжения питания. В документации подробно рассмотрено, за счет каких схемотехнических решений это достигнуто (п. 4.2, стр. 265, 266 ТО версии 2.1.5). Все, что требуется, - это один раз измерить частоту при нормальных условиях и зафиксировать делитель для модулей USART. Делать это с помощью часового кварцевого резонатора, - не лучшее решение, поскольку наличие этого резонатора в схеме не является обязательным. Проще при первом запуске подать с другого устройтва последовательность известных байтов (например, 0x55) и измерить длительность битов с помощью таймера Timer16_0, или определить границы устойчивого приема перебором значений делителя, а затем зафиксировать центральное.
 
Последнее редактирование:

mscs

Member
Кстати, начиная с редакции V2 МК, начальным отклонением частоты генератора вообще можно пренебречь.
Согласно ТУ, частота внутреннего высокочастотного генератора HSI32M должна составлять 31,5 ... 32,5 МГц в диапазоне температур от минус 45 до плюс 85. Это значит: 32 МГц ±1,6%. UART работает устойчиво при отклонении опорной частоты ±2% от номинала.
У МК редакции V0 действительно были проблемы: был больше разброс между образцами, и сама частота была порядка 28,5 МГц.
 
Последнее редактирование:

cryptozoy

Member
Согласно ТУ, частота внутреннего высокочастотного генератора HSI32M должна составлять 31,5 ... 32,5 МГц в диапазоне температур от минус 45 до плюс 85. Это значит: 32 МГц ±1,6%. UART работает устойчиво при отклонении опорной частоты ±2% от номинала.
У МК редакции V0 действительно были проблемы: был больше разброс между образцами, и сама частота была порядка 28,5 МГц.
Видимо не лохи сделали коррекцию частоты внутренних генераторов, потому что не все разработчики в своих поделках из периферийных устройств используют лишь один только USART.
 
Последнее редактирование:

mscs

Member
Видимо не лохи сделали коррекцию частоты внутренних генераторов, потому что не все разбработчики в своих поделках из всех периферийных устройств используют лишь один только USART.
Интересно, что это должна быть за поделка такая хитрая, чтобы без кварцевого резонатора, но при этом нужно было подстраивать RC-генератор? Да и набор периферийных устройств у МК К1948ВК018 к изыскам не особо располагает. Сделать ровно-ровно 16 МГц на SPI? Или ровно-ровно 1 МГц на I2C?
А вот если формировать импульсы или измерять скважность сигналов таймерами, по-серьезному, то скорее всего без резонатора будет не обойтись.
 

cryptozoy

Member
А вот если формировать импульсы или измерять скважность сигналов таймерами, по-серьезному, то скорее всего без резонатора будет не обойтись.
Ждём внедрения кварцевого генератора внутрь корпуса микроконтроллера, как это уже сделали западные буржуины ещё в 2019 году.
 
Последнее редактирование:

ejsanyo

Active member
Ждём внедрения кварцевого генератора внутрь корпуса микроконтроллера, как это уже сделали западные буржуины ещё в 2019 году.
А ещё раньше они запихали в чип MEMS-резонатор. Хотя можно было бы вспомнить и более ранний дорогущий DS3232.
Но как по мне, это всё технологический перебор. Повесить снаружи копеечный кварц, если надо, это гораздо дешевле и универсальнее.
 

cryptozoy

Member
Но как по мне, это всё технологический перебор. Повесить снаружи копеечный кварц, если надо, это гораздо дешевле и универсальнее.
В статье упоминается сравнительная таблица потребляемой энергии чипами с внешним и с внутренним кварцевым резонатором. Так вот внешний кварц победил. Думаю именно поэтому подобная продукция не стала популярной и дальше одной единственной модели микроконтроллера не пошла.
 
Последнее редактирование:
Сверху