Обновить

Самый маленький загрузчик (MBR 324 байта)

Уровень сложностиПростой
Время на прочтение12 мин
Охват и читатели14K
Всего голосов 13: ↑10 и ↓3+9
Комментарии31

Комментарии 31

Прошивка для чего ?

Прошивка для чего ?

Прошивка MBR нужна того лишь для того, чтобы запустить другую прошивку, которая прописана где-то по другому адресу в физической памяти микроконтроллера.

Это загрузчик.

А если MBR потребует вывода статуса в UART CLI, как это уместить в данный размер памяти? Или как понять, что процесс многоступенчатой загрузки проходит успешно?

 как понять, что процесс многоступенчатой загрузки проходит успешно?

На каждом этапе выставлять на наборе из N GPIO бинарные коды.
Процесс контролировать логическим анализатором.

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

А что за код был, который 25 кбайт занял? Тот, что на основе HAL. Там какие флаги применялись?

А что за код был, который 25 кбайт занял?

Вот эта сборка
https://github.com/aabzel/trunk/tree/main/source/projects/jz_f407vet6_mbr_gcc_m

Там какие флаги применялись?

Компилятору были переданы ключи

-c -MD -MD
-ffreestanding -ffunction-sections -fdata-sections -fno-common -fno-printf-return-value -Werror=shadow -fshort-enums -fomit-frame-pointer -Werror=return-local-addr -Werror=implicit-function-declaration -Wno-nonnull-compare -Wall -fdce -fdse -Werror=address-of-packed-member -Werror=missing-field-initializers -Werror=unused-but-set-variable -Werror=unused-variable -g3 -Wall -ffunction-sections -fdata-sections -fshort-enums -fomit-frame-pointer -fno-move-loop-invariants -fdce -fdse -fmax-errors=70 -fmessage-length=0 -fsigned-char -fno-common -fstack-usage -fzero-initialized-in-bss -finline-small-functions -finline-functions -g3 -Werror=all -Werror=extra -Werror=clobbered -Werror=empty-body -Werror=missing-parameter-type -Werror=missing-field-initializers -Werror=old-style-declaration -Werror=override-init -Werror=strict-overflow=1 -Werror=type-limits -Werror=uninitialized -Werror=unused-but-set-parameter -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=switch-default -Werror=init-self -Werror=logical-not-parentheses -Werror=memset-transposed-args -Werror=misleading-indentation -Werror=parentheses -Werror=return-type -Werror=sign-compare -Werror=sequence-point -Werror=uninitialized -Werror=unused-function -Werror=unused-variable -Werror=sizeof-pointer-memaccess -Werror=strict-aliasing -Werror=switch -Werror=tautological-compare -Werror=trigraphs -Werror=array-bounds=1 -Werror=address -Werror=bool-compare -Werror=char-subscripts -Werror=comment -Werror=duplicated-cond -Werror=enum-compare -Werror=logical-op -Werror=return-local-addr -Werror=implicit-function-declaration -Werror=div-by-zero -Werror=duplicated-cond -Werror=enum-compare -Werror=empty-body -Werror=unused-but-set-variable -Werror=logical-op -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=maybe-uninitialized -Werror=missing-parameter-type -Werror=overflow -Werror=pointer-sign -Werror=shift-count-overflow -Werror=unused-but-set-variable -Werror=incompatible-pointer-types -Werror=missing-parameter-type -Werror=old-style-declaration -Werror=pointer-sign -Werror=shift-count-overflow -Werror=shift-negative-value -Werror=missing-braces -Werror=unused-but-set-variable -Werror=unused-function -Werror=unused-value -Werror=pointer-arith -Werror=unused-but-set-variable -Werror=implicit-function-declaration -Werror=unused-but-set-variable -Werror=unused-variable -Werror=duplicate-decl-specifier -Werror=address-of-packed-member -Werror=sizeof-pointer-div -Werror=int-in-bool-context -Werror=bool-operation -Werror=memset-elt-size -Werror=multistatement-macros -Werror=ignored-qualifiers -Werror=float-equal -Werror=unused -Wno-nonnull-compare -Wno-stringop-truncation -Wno-format-truncation -Wno-restrict -Wno-format -Wno-redundant-decls -Wno-discarded-qualifiers -Wno-int-conversion -Wno-switch-bool -Wno-conversion -Wno-cpp -Wno-sign-compare -Wno-unused-parameter -Wno-implicit-fallthrough -Wno-restrict -Werror -mcpu=cortex-m4 -march=armv7e-m -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O0 -std=c11 -MMD -MP -MF"build/system.d" -DHAS_ARM_GCC -Werror=all -Werror=extra -Werror=clobbered -Werror=empty-body -Werror=missing-parameter-type -Werror=missing-field-initializers -Werror=old-style-declaration -Werror=override-init -Werror=strict-overflow=1 -Werror=type-limits -Werror=uninitialized -Werror=unused-but-set-parameter -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=switch-default -Werror=init-self -Werror=logical-not-parentheses -Werror=memset-transposed-args -Werror=misleading-indentation -Werror=parentheses -Werror=return-type -Werror=sign-compare -Werror=sequence-point -Werror=uninitialized -Werror=unused-function -Werror=unused-variable -Werror=sizeof-pointer-memaccess -Werror=strict-aliasing -Werror=switch -Werror=tautological-compare -Werror=trigraphs -Werror=array-bounds=1 -Werror=address -Werror=bool-compare -Werror=char-subscripts -Werror=comment -Werror=duplicated-cond -Werror=enum-compare -Werror=logical-op -Werror=return-local-addr -Werror=implicit-function-declaration -Werror=div-by-zero -Werror=duplicated-cond -Werror=enum-compare -Werror=empty-body -Werror=unused-but-set-variable -Werror=logical-op -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=maybe-uninitialized -Werror=missing-parameter-type -Werror=overflow -Werror=pointer-sign -Werror=shift-count-overflow -Werror=unused-but-set-variable -Werror=incompatible-pointer-types -Werror=missing-parameter-type -Werror=old-style-declaration -Werror=pointer-sign -Werror=shift-count-overflow -Werror=shift-negative-value -Werror=missing-braces -Werror=unused-but-set-variable -Werror=unused-function -Werror=unused-value -Werror=pointer-arith -Werror=unused-but-set-variable -Werror=implicit-function-declaration -Werror=unused-but-set-variable -Werror=unused-variable -Werror=duplicate-decl-specifier -Werror=address-of-packed-member -Werror=sizeof-pointer-div -Werror=int-in-bool-context -Werror=bool-operation -Werror=memset-elt-size -Werror=multistatement-macros -Werror=ignored-qualifiers -Werror=float-equal -Werror=unused -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-implicit-fallthrough -Wno-stringop-truncation -Wno-format-truncation -Wno-restrict -Wno-format -Wno-cpp -Wno-discarded-qualifiers

Такие ключи были у компоновщика
-specs=nosys.specs -Xlinker --gc-sections -Xlinker --nmagic -Wl,–gc-sections -Xlinker --print-memory-usage -mcpu=cortex-m4 -march=armv7e-m -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -T C:/projects/release/trunk/source/microcontroller/stm32f407ve/gcc_arm_mbr.ld -L C:/projects/release/trunk/source/microcontroller/stm32f407ve -lm -t -Wl,–cref -Wl,–gc-sections -Wl,-Map=build/jz_f407vet6_mbr_gcc_m.map --verbose

Я может быть не туда смотрю, но где там исходники?

Надо запустить скрипт build_from_make.bat. начнется построение.

В логе сборки будут фигурировать абсолютные пути к каждому си файлу.

Зачем-то откуда-то там ещё файл .cproject

.cproject - это ж просто настройка текстового редактора eclipse.
Не более того.

Я может быть не туда смотрю, но где там исходники?

Исходники вынесены "за скобки". То есть в папках на уровень выше.

Например, драйвер светодиода используется во всех проектах, поэтому он лежит в
https://github.com/aabzel/trunk/tree/main/source/control/led/led_mono
а не в папке с названием конкретного проекта

Репозиторий построен согласно вот этой методичке.

Что Должно Быть в Каждом FirmWare Pепозитории
https://habr.com/ru/articles/689542/

Я тут печалюсь, что загрузчик с возможностью обновления прошивки по Modbus меньше 2кБ не получается, а ИИ-шечка делает 2кБ кода, который не делает ничего? Есть надежда, что роботы пока людей не заменят.

Вы не правы. ИИ-шечка уже прекрасно заменяет людей, которые пишут 25 кБ кода, который не делает ничего.

Это stm Hal написанный индусами раздувает бинарь.

А зачем загрузчику интерфейсы и протоколы? Сам бинарь приложение может успешно принять.

Загрузчику остаётся только прыгнуть в бинарь.

Не всегда и не везде нужен многоступенчатый загрузчик. Чаще всего достаточно возможности принять обновление прошивки по UART или CAN, и прыгнуть в основной код. Это всё великолепие можно вместить в первые 2 страницы флеша, а иногда и в одну.

Это же не большой arm процессор, где один загрузчик инициализирует ОЗУ, другой некоторую периферию, третий там в безопасную среду прыгает (или не прыгает), и только потом где-то там вдалеке есть ядро Linux.

Микроконтроллер в большинстве случаев работает вообще без загрузчиков, вот он пошел в адрес 0, который смаплен на какой-то адрес флеша (0x08000000), а там указатель стека и за ним reset handler, и дальше уже всё понятно. А если мы идём к многоступенчатой схеме, то нужно понимать, зачем мы это делаем?

В вашем случае я думаю, что хочется оставить первые небольшие страницы под nvram (что написано в таблице), а вторичный загрузчик работает с каким-то сложным интерфейсом типа ethernet, и не поместится в маленькую страницу флеша. Вот и строится Saturn V с множеством ступеней.

Главное чтобы не получился Спейс Шаттл, который весит в два раза больше, чем везёт на себе, и стоит многократно дороже, чем другие аналоги

Писал загрузчик для stm24f407.

В первую страницу флеша (16кб), уложил полновесную прошивку, которая могла обновлять основную прошивку с USB mass storage (fat 16/32 only)...

Вот ссылка - https://github.com/andreili/USB-Keyboard/tree/master/fw_boot

Сам проект давно заброшен и не доведён до ума - он давно потерял смысл.

По размеру загрузчика:

1) Делать его меньше размера страницы флеша нет смысла - стирание идёт минимально страницами.

2) Если там не нужны прерывания, то почти вся таблица прерываний идёт под нож - прилично экономит размер. Главное - запретить прерывания сразу на старте и не забыть оставить системные прерывания, они не маскируются.

3) Да, большую часть драйверов и прочего придётся писать самому - код из HAL от ST-Micro далеко не оптимальный и есть память как браузер Хром...

Вот вы пользуютесь LLM чтобы нагенерить текмт в статью, но можно же вычитать, чтобы одно и то же не повторялось по несколько раз.

Сори, наболело уже.

Можно уменьшить размер прошивки, если отключить использование стандартной библиотеки C, убрав bl __libc_init_array из startup_stm32f407xx.S и раскомментировав LDFLAGS += -nostdlib в Makefile. Также ещё можно убрать неиспользуемые вектора прерываний из таблицы, например, все external. Суммарно размер прошивки сократится до ~350 байт.

Благодарю Вас, Владимир, за отличную подсказку!
В самом деле, с этими двумя улучшениями бинарь загрузчика стал 324 байта.


rm -f main.o system_stm32f4xx.o startup_stm32f407xx.o jz_f407vet6_mbr_light_gcc_m.elf jz_f407vet6_mbr_light_gcc_m.bin jz_f407vet6_mbr_light_gcc_m.hex jz_f407vet6_mbr_light_gcc_m.map
arm-none-eabi-gcc -mcpu=cortex-m4  -mthumb  -mfpu=fpv4-sp-d16  -mfloat-abi=softfp -Os -ffunction-sections -fdata-sections  -nostdlib  -nostartfiles -fno-builtin  -ffreestanding  -fno-exceptions  -fno-rtti -Wl,-gc-sections -Wl,-s -DSTM32F407xx  -DUSE_STDPERIPH_DRIVER -c main.c -o main.o
cc1.exe: warning: command-line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
arm-none-eabi-gcc -mcpu=cortex-m4  -mthumb  -mfpu=fpv4-sp-d16  -mfloat-abi=softfp -Os -ffunction-sections -fdata-sections  -nostdlib  -nostartfiles -fno-builtin  -ffreestanding  -fno-exceptions  -fno-rtti -Wl,-gc-sections -Wl,-s -DSTM32F407xx  -DUSE_STDPERIPH_DRIVER -c system_stm32f4xx.c -o system_stm32f4xx.o
cc1.exe: warning: command-line option '-fno-rtti' is valid for C++/D/ObjC++ but not for C
arm-none-eabi-gcc    -c -o startup_stm32f407xx.o startup_stm32f407xx.S
arm-none-eabi-gcc -nostdlib  -mcpu=cortex-m4  -mthumb  -mfpu=fpv4-sp-d16  -mfloat-abi=softfp -T gcc_arm_mbr.ld -Wl,--print-memory-usage  -Wl,--gc-sections -Wl,--cref  -Wl,-Map=jz_f407vet6_mbr_light_gcc_m.map main.o system_stm32f4xx.o startup_stm32f407xx.o -o jz_f407vet6_mbr_light_gcc_m.elf
Memory region         Used Size  Region Size  %age Used
             RAM:       16392 B       128 KB     12.51%
          CCMRAM:          0 GB        64 KB      0.00%
           FLASH:         324 B         3 KB     10.55%
arm-none-eabi-size jz_f407vet6_mbr_light_gcc_m.elf
   text	   data	    bss	    dec	    hex	filename
    324	      0	  16392	  16716	   414c	jz_f407vet6_mbr_light_gcc_m.elf
arm-none-eabi-objcopy -O binary jz_f407vet6_mbr_light_gcc_m.elf jz_f407vet6_mbr_light_gcc_m.bin
arm-none-eabi-objcopy -O ihex jz_f407vet6_mbr_light_gcc_m.elf jz_f407vet6_mbr_light_gcc_m.hex

Ещё можно выкинуть кучу и уменьшить стек - их зануление идёт в начальной стадии работы фирмвари. По логу выше - целых 16Кб оперативы зануляет, это дохрена для фирмвари, которая этим не пользуется всё равно...

А я правильно понимаю, что в "наивной реализации HAL" присутствует mcal с целой горой всего на свете?

Это в репозитории много чего лежит.
А к конкретной сборке подключается только то, что выбирается переменными окружения в GNU Make скриптах.

Полный состав реально задействованных исходников можно увидеть в логе сборки компилятора.

После сборки образуется файл-отчет build_log.txt , который и рассказывает, что из чего собиралось.

Расскажите, пожалуйста, о тайном смысле этой прошивки, если размер сектора у STM32F407 16кб.

Расскажите, пожалуйста, о тайном смысле этой прошивки, если размер сектора у STM32F407 16кб.

Отличный вопрос. Остаток 15kByte можно отвести на конфигурационные данные, которые можно назначать утилитой TunerPro перед прошивкой загрузчика. Там как раз хранить значение адреса на которое следует прыгать.

Чем меньше загрузчик, тем больше поместится конфигурационных данных.

Обзор утилиты TunerPro (или const volatile)
https://habr.com/ru/articles/965828/

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

Но Вы же понимаете, что эти данные Вы можете записать только один раз?

Зато не надо пере собирать проект MBR компилятором.
Просто попатчил его бинарь и прошил в МК.

Вопрос: раз загрузчик такой маленький, можно быть есть смысл его сразу на asm написать? Или это сложнее, чем кажется?

раз загрузчик такой маленький, можно быть есть смысл его сразу на asm написать? 

Да, Вы права. Вот он.
https://github.com/aabzel/trunk/blob/main/source/projects/jz_f407vet6_mbr_super_light_gcc_m

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации