Хабрахабр

Делаем процесс разработки тяжеловесного программного обеспечения под микроконтроллеры более удобным (нет)

Сейчас уже никого не удивить микроконтроллерами с энергонезависимой (чаще всего Flash) памятью объемом 512 килобайт и более. Их стоимость постепенно снижается, а доступность напротив, растет. Наличие такого объема энергонезависимой памяти дает возможность писать «тяжелые» по объему занимаемой памяти приложения, облегчая при этом последующее сопровождение кода за счет использования готовых решений из различных стандартных библиотек. Однако это ведет к росту объема файла прошивки целевого устройства, который требуется каждый раз целиком заново загружать в энергонезависимую память микроконтроллера при малейшем изменении в коде.

А так же показать, почему данный метод не всегда является эффективным решением.
Цель статьи — рассказать о методе построения проекта на C и/или C++, при котором, в случае изменения участка кода, отладка которого производится чаще всего, большая часть проекта не нуждалась в повторной перезаписи.

Требования к читателю

По ходу повествования я буду предполагать, что читатель:

  • владеет на среднем уровне языками C и C++;
  • имеет опыт работы с микроконтроллерами на базе ядер Corex-M3/Corex-M4 (например серией stm32f4);
  • знает, как происходит процесс сборки конечного файла пришивки (elf/bin) из исходников проекта;
  • представляет себе, для чего нужны linker script файлы;
  • имеет представление о секциях text, bss, data и прочих;
  • работал с каким-либо из дистрибутивов linux;
  • минимально владеет bash;
  • имеет опыт работы с gcc под архитектуру процессоров Corex-M3/Corex-M4 (toolchain arm-none-eabi);
  • имеет начальные навыки работы с cmake.

Суть метода

В «классическом» проекте под микроконтроллеры все неизменяемые данные (секции text, rodata, data и прочие) обычно располагаются «подряд», начиная с адреса начала энергонезависимой памяти (в случае микроконтроллера на основе ядра Corex-M3/Corex-M4 — c адреса 0x08000000). В упрощенном виде карта использования энергонезависимой памяти программой микроконтроллера на базе ядра Corex-M3/Corex-M4, написанной с использованием C++, выглядит примерно так:

mem.ld файл для такого проекта, чаще всего, выглядит примерно следующим образом:

MEMORY
{ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 768K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 112K
}

Здесь вся энергонезависимая память является одним единственным разделом с именем «FLASH», а вся оперативная память — раздел с именем «RAM». В таком виде, при изменении какого-то из участков кода, все остальные начинают «смещаться». Дабы этого избежать, можно «разбить» файл прошивки на какие-то логические блоки. Например, следующим образом:

  • таблица векторов прерываний;
  • собственные библиотеки;
  • сторонние библиотеки (в которые не планируется вносить изменения);
  • часто изменяемый код.

В этом случае при изменении какого-то участка кода, в конечном bin файле будет изменен только раздел, код в котором притерпел изменения и раздел, который был с ним как-то связан (например, таблица векторов прерываний если изменилось положение обработчика в каком-то из разделов).

По сути говоря, в проект добавляются статические библиотеки.

Таким образом прошиваться будут лишь измененные участки. После получения bin файла проекта, его можно разбить на разделы и прошить каждый раздел независимо. Это несет в себе еще и отсутствие необходимости в прошивке перед отладкой, поскольку предполагается, что в микроконтроллере сразу после сборки будет находиться актуальная прошивка и можно сразу запускать отладку.

В конце статьи будут приведены плюсы и минусы такого решения. Далее я подробно распишу, как реализовать это на реальном проекте.

Поле для эксперимента

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

Характеристика проекта

Проект содержит:

  • код основного проекта на C++14 с использованием виртуальных таблиц, new/delete (работающие через кучу FreeRTOS), shared_ptr (и прочие умные указатели) и другие прелести стандартных библиотек C++14;
  • FreeRTOS с использованием примерно 6 задач на поддержание инфраструктуры аппаратной периферии и порядка 10 на бизнес-логику (библиотека работы с графикой MakiseGUI, обработка нажатий, работа с fat (FatFS) и прочее);
  • 16 репозиториев с собственными библиотеками взаимодействия с аппаратной периферией на плате, заглушки системных вызовов и прочее;

При параметрах сборки -O0 -g3 код в полноценной реализации с поддержкой unicode, кириллиц и прочего занимает порядка 700 КБ. Однако на текущем этапе, когда аппаратная периферия работает стабильно, а в отладке нуждается лишь бизнес логика, то количество изменяемого кода примерно 20 КБ. Именно по этой причине, на первый взгляд, кажется, что текущий подход является идеальным решением проблемы (вариант с симуляцией на компьютере по некоторым причинам не рассматривается).

Перечень действий

Для реализации описанного метода потребуется:

  • собрать все субмодули как статические библиотеки (описание этого пункта не входит в перечень разбираемых пунктов данной статьи);
  • переписать mem.ld;
  • переписать section.ld;
  • добавить в основной проект утилиту для извлечения разделов из конечного bin файла;
  • добавить в проект вызов скрипта обновления энергонезависимой памяти микроконтроллера при обновлении файла прошивки.

Переписываем mem.ld

Первым шагом является доработка «стандартного» mem.ld под текущую концепцию. При доработке следует учитывать, что очистка энергонезависимой памяти происходит по секторам. Подробнее о том, как структурированы сектора в конкретном микроконтроллере следует читать в документации (в случае микроконтроллеров stm32 — в reference manual). Каждый раздел может занимать не менее одного сектора (больше можно), иначе один раздел перепишет другой.

Иначе можно столкнуться с неприятными багами, которые будет крайне трудно отловить. Так же следует учесть, что если библиотека использует глобальные переменные, то под эту библиотеку нужно зарезервировать место в оперативной памяти на этапе компоновки. А значит нужно позаботиться о том, чтобы в оперативной памяти был раздел под поля, которые будет использовать код из ROM_EXTERNAL_LIBRARIES. Например код библиотеки FatFS будет находиться в разделе ROM_EXTERNAL_LIBRARIES, но он требует 4 байта в RAM на этапе компоновки. В данном примере это RAM_EXTERNAL_LIBRARIES.

В него пойдет все то, что не было разложено по соответсвующим разделам ранее, согласно section.ld (о нем далее). Отдельного внимания заслуживает последний раздел в энергонезависимой памяти.

В контексте текущего проекта mem.ld будет выглядить так.

/* Карта памяти микроконтроллера stm32f405rgt6 под проект ChiptunePlayer-2.22-MainBoard-v2-Firmware. */
MEMORY
{ /*-----------------------------FLASH-------------------------------*/ /* Сектора 0-1 отводятся загрузчику. */ ROM_BOOTLOADER (RX) : ORIGIN = 0x08000000, LENGTH = 32K /* Сектора 2 используется для хранения настроек пользователя. */ ROM_SYSCFG_PAGE_1 (R) : ORIGIN = 0x08008000, LENGTH = 16K /* Сектор 3 используется для хранения копии настроек пользователя. */ ROM_SYSCFG_PAGE_2 (R) : ORIGIN = 0x0800C000, LENGTH = 16K /* Сектор 4 зарезервирован. */ ROM_RESERVE (R) : ORIGIN = 0x08010000, LENGTH = 16K /* Сектора 5, 6, 7 предназначены для хранения сторонник библиотек (FATFS, FREERTOS...). */ ROM_EXTERNAL_LIBRARIES (RX) : ORIGIN = 0x08020000, LENGTH = 384K /* Сектора 8, 9 предназначены для хранения собственных библиотек (драйвера дисплеев, графические объекты...). */ ROM_USER_LIBRARIES (RX) : ORIGIN = 0x08080000, LENGTH = 384K /* Сектора 5, 6 предназначены для хранения основного кода приложения. */ ROM_MAIN_PROGRAMM (RX) : ORIGIN = 0x080E0000, LENGTH = 128K /*-----------------------------RAM---------------------------------*/ /* В режиме выполнения загрузчика карта RAM перекрывает карту целевого устройства. */ RAM_PAGE_1 (RW) : ORIGIN = 0x20000000, LENGTH = 112K RAM_PAGE_2 (RW) : ORIGIN = 0x2001C000, LENGTH = 16K /* С этой областью памяти будут работать сторонние библиотеки на манер FATFS или FreeRTOS. */ RAM_EXTERNAL_LIBRARIES (RW) : ORIGIN = 0x20000000, LENGTH = 10K /* С этой областью памяти будут работать библиотеки пользователя. */ RAM_USER_LIBRARIES (RW) : ORIGIN = 0x20002800, LENGTH = 90K /* С этой областью RAM будет работать код пользователя. */ RAM_MAIN_PROGRAMM (RW) : ORIGIN = 0x20019000, LENGTH = 27K /* Немного памяти RAM выделяется под начальный стек. До запуска планировщика FreeRTOS. */ RAM_MAIN_PROGRAMM_STACK (RW) : ORIGIN = 0x2001FC00, LENGTH = 1K
}

Переписываем section.ld

После того, как имеющаяся карта памяти была поделена на разделы, следует описать, что в какой раздел будет помещено. Для каждой библиотеки (при наличии соответствующей секции в библиотеке) следует указать где располагаются секции .text, .rodata, .data, .bss и прочие. Перечень имеющихся в библиотеке секций можно посмотреть с помощью objdump. Например для библиотеки libstdc++_nano.a требуется указать где расположить секции text, ARM.attributes, rodata, data, bss, COMMON.

В контексте текущего проекта section.ld будет выглядить так.

/* Под начальный стек до запуска планировщика у нас есть своя секция в RAM. */
__estack = ORIGIN(RAM_MAIN_PROGRAMM_STACK) + LENGTH(RAM_MAIN_PROGRAMM_STACK); /* Размер начального стека. */
__stack_size = LENGTH(RAM_MAIN_PROGRAMM_STACK); /* Программа начинается с метода Reset_Handler. */
ENTRY(Reset_Handler) /* Описание секций. */
SECTIONS
>ROM_BOOTLOADER /*----------------ROM область под внешние библиотеки-----------------*/ /* Код. */ .section_external_libraries_text : ALIGN(4) { /* Библиотеки компилятора. */ . = ALIGN(4); *libstdc++_nano.a:*(.text .text*); . = ALIGN(4); *libgcc.a:*(.text .text*); . = ALIGN(4); *libg_nano.a:*(.text .text*); /* Библиотеки пользователя */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.text .text*); . = ALIGN(4); *libFATFS.a:*(.text .text*); . = ALIGN(4); *libFREERTOS.a:*(.text .text*); . = ALIGN(4); *libMAKISE_GUI.a:*(.text .text*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /* Секции, созданные компилятором */ .section_external_libraries_required_by_the_compiler : ALIGN(4) { /* Библиотеки компилятора. */ . = ALIGN(4); *libgcc.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libstdc++_nano.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libg_nano.a:*(.ARM.attributes .ARM.attributes*); /* Библиотеки пользователя */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libFATFS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libFREERTOS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMAKISE_GUI.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /* Сущности с неизменяемыми значениями. */ .section_external_libraries_rodata : ALIGN(4) { /* Библиотеки компилятора. */ . = ALIGN(4); *libgcc.a:*(.rodata .rodata*); . = ALIGN(4); *libstdc++_nano.a:*(.rodata .rodata*); . = ALIGN(4); *libg_nano.a:*(.rodata .rodata*); /* Библиотеки пользователя */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.rodata .rodata*); . = ALIGN(4); *libFATFS.a:*(.rodata .rodata*); . = ALIGN(4); *libFREERTOS.a:*(.rodata .rodata*); . = ALIGN(4); *libMAKISE_GUI.a:*(.rodata .rodata*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /*-----------------------Библиотеки пользователя---------------------*/ /* Код. */ .section_user_libraries_text : ALIGN(4) { . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.text .text*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.text .text*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.text .text*); . = ALIGN(4); *libMC_HARDWARE.a:*(.text .text*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.text .text*); . = ALIGN(4); *libUSER_STARTUP.a:*(.text .text*); . = ALIGN(4); *libBUTTONS.a:*(.text .text*); . = ALIGN(4); *libCHIPTUNE.a:*(.text .text*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.text .text*); . = ALIGN(4); *libLCD_DRIVER.a:*(.text .text*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.text .text*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.text .text*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.text .text*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.text .text*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.text .text*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.text .text*); . = ALIGN(4); } >ROM_USER_LIBRARIES /* Секции, созданные компилятором */ .section_user_libraries_required_by_the_compiler : ALIGN(4) { . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMC_HARDWARE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libUSER_STARTUP.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libUSER_CODE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libBUTTONS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libCHIPTUNE.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libLCD_DRIVER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.ARM.attributes .ARM.attributes*); . = ALIGN(4); } >ROM_EXTERNAL_LIBRARIES /* Сущности с неизменяемыми значениями. */ .section_user_libraries_rodata : ALIGN(4) { . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.rodata .rodata*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.rodata .rodata*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.rodata .rodata*); . = ALIGN(4); *libMC_HARDWARE.a:*(.rodata .rodata*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.rodata .rodata*); . = ALIGN(4); *libUSER_STARTUP.a:*(.rodata .rodata*); . = ALIGN(4); *libBUTTONS.a:*(.rodata .rodata*); . = ALIGN(4); *libCHIPTUNE.a:*(.rodata .rodata*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.rodata .rodata*); . = ALIGN(4); *libLCD_DRIVER.a:*(.rodata .rodata*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.rodata .rodata*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.rodata .rodata*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.rodata .rodata*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.rodata .rodata*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.rodata .rodata*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.rodata .rodata*); . = ALIGN(4); } >ROM_USER_LIBRARIES /*-------------------------Основная программа------------------------*/ /* Код. */ .section_user_code_text : ALIGN(4) { . = ALIGN(4); *(.text .text.*) . = ALIGN(4); } >ROM_MAIN_PROGRAMM /* Секции, созданные компилятором */ .sections_user_code_required_by_the_compiler : ALIGN(4) { . = ALIGN(4); *(.glue_7 .glue_7*) /* Собственные методы-прослойки для ARMv7 */ . = ALIGN(4); *(.glue_7t .glue_7t*) . = ALIGN(4); *(.vfp11_veneer .vfp11_veneer*) /* Прочие секции поддержки. */ . = ALIGN(4); *(.v4_bx .v4_bx*) . = ALIGN(4); *(.iplt .iplt*) . = ALIGN(4); *(.rel.dyn .rel.dyn*) . = ALIGN(4); KEEP(*(.eh_frame .eh_frame*)) /* Для использования исключений в CPP. */ . = ALIGN(4); *(.eh_framehdr .eh_framehdr*) . = ALIGN(4); *(.ARM.attributes .ARM.attributes.*) /* Песь оставшийся дополнительный код, созданный компилятором. */ . = ALIGN(4); *(vtable) /* C++ virtual tables */ PROVIDE_HIDDEN (__preinit_array_start = .); /* Список методов, вызываемых перед конструкторами. */ . = ALIGN(4); KEEP(*(.preinit_array_sysinit .preinit_array_sysinit*)) . = ALIGN(4); KEEP(*(.preinit_array_platform .preinit_array_platform.*)) . = ALIGN(4); KEEP(*(.preinit_array .preinit_array.*)) PROVIDE_HIDDEN (__preinit_array_end = .); PROVIDE_HIDDEN (__init_array_start = .); /* Вызов конструкторов глобальных объектов. */ . = ALIGN(4); KEEP(*(SORT(.init_array.*))) . = ALIGN(4); KEEP(*(.init_array)) . = ALIGN(4); PROVIDE_HIDDEN (__init_array_end = .); PROVIDE_HIDDEN (__fini_array_start = .); /* Вызов деструкторов глобальных объектов. */ . = ALIGN(4); KEEP(*(SORT(.fini_array.*))) . = ALIGN(4); KEEP(*(.fini_array)) . = ALIGN(4); PROVIDE_HIDDEN (__fini_array_end = .); . = ALIGN(4); KEEP(*(.cfmconfig)) . = ALIGN(4); *(.after_vectors .after_vectors.*) . = ALIGN(4); } >ROM_MAIN_PROGRAMM /* Сущности с неизменяемыми значениями. */ .section_user_code_rodata : ALIGN(4) { . = ALIGN(4); *(.rodata .rodata.*) . = ALIGN(4); } >ROM_MAIN_PROGRAMM /* Для stack trace. */ .ARM.exidx : { . = ALIGN(4); *(.ARM.extab* .gnu.linkonce.armextab.*) . = ALIGN(4); *(.ARM.exidx* .gnu.linkonce.armexidx.*) . = ALIGN(4); } > ROM_MAIN_PROGRAMM /*-------------------------------RAM-----------------------------*/ /* Начальные значения изменяемых сущностей. */ .section_external_libraries_data : ALIGN(4) { . = ALIGN(4); __external_lib_data_start = . ; /* Библиотеки компилятора. */ . = ALIGN(4); *libgcc.a:*(.data .data*); . = ALIGN(4); *libstdc++_nano.a:*(.data .data*); . = ALIGN(4); *libg_nano.a:*(.data .data*); /* Библиотеки пользователя */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.data .data*); . = ALIGN(4); *libFATFS.a:*(.data .data*); . = ALIGN(4); *libFREERTOS.a:*(.data .data*); . = ALIGN(4); *libMAKISE_GUI.a:*(.data .data*); . = ALIGN(4); __external_lib_data_end = . ; } >RAM_EXTERNAL_LIBRARIES AT> ROM_EXTERNAL_LIBRARIES /* Область с нулевыми начальными значениями в RAM */ .section_external_libraries_bss : ALIGN(4) { . = ALIGN(4); __external_lib_bss_start = .; /* Библиотеки компилятора. */ . = ALIGN(4); *libgcc.a:*(.bss .bss*); . = ALIGN(4); *libstdc++_nano.a:*(.bss .bss*); . = ALIGN(4); *libg_nano.a:*(*COMMON); . = ALIGN(4); *libgcc.a:*(*COMMON); . = ALIGN(4); *libstdc++_nano.a:*(*COMMON); . = ALIGN(4); *libg_nano.a:*(*COMMON); /* Библиотеки пользователя */ . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(.bss .bss*); . = ALIGN(4); *libFATFS.a:*(.bss .bss*); . = ALIGN(4); *libFREERTOS.a:*(.bss .bss*); . = ALIGN(4); *libMAKISE_GUI.a:*(.bss .bss*); . = ALIGN(4); *libSTM32F4_LOW_LEVEL_BY_ST.a:*(*COMMON); . = ALIGN(4); *libFATFS.a:*(*COMMON); . = ALIGN(4); *libFREERTOS.a:*(*COMMON); . = ALIGN(4); *libMAKISE_GUI.a:*(*COMMON); . = ALIGN(4); __external_lib_bss_end = .; } >RAM_EXTERNAL_LIBRARIES /* Начальные значения изменяемых сущностей. */ .section_user_libraries_data : ALIGN(4) { . = ALIGN(4); __user_lib_data_start = . ; . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.data .data*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.data .data*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.data .data*); . = ALIGN(4); *libMC_HARDWARE.a:*(.data .data*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.data .data*); . = ALIGN(4); *libUSER_STARTUP.a:*(.data .data*); . = ALIGN(4); *libBUTTONS.a:*(.data .data*); . = ALIGN(4); *libCHIPTUNE.a:*(.data .data*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.data .data*); . = ALIGN(4); *libLCD_DRIVER.a:*(.data .data*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.data .data*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.data .data*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.data .data*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.data .data*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.data .data*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.data .data*); . = ALIGN(4); __user_lib_data_end = . ; } >RAM_USER_LIBRARIES AT> ROM_USER_LIBRARIES .section_user_libraries_bss : ALIGN(4) { . = ALIGN(4); __user_lib_bss_start = .; . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.bss .bss*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(.bss .bss*); . = ALIGN(4); *libMC_INTERRUPT.a:*(.bss .bss*); . = ALIGN(4); *libMC_HARDWARE.a:*(.bss .bss*); . = ALIGN(4); *libPCB_HARDWARE.a:*(.bss .bss*); . = ALIGN(4); *libUSER_CODE.a:*(.bss .bss*); . = ALIGN(4); *libBUTTONS.a:*(.bss .bss*); . = ALIGN(4); *libCHIPTUNE.a:*(.bss .bss*); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(.bss .bss*); . = ALIGN(4); *libLCD_DRIVER.a:*(.bss .bss*); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(.bss .bss*); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(.bss .bss*); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(.bss .bss*); . = ALIGN(4); *libSHIFT_REGISTER.a:*(.bss .bss*); . = ALIGN(4); *libWAVE_GENERATORS.a:*(.bss .bss*); . = ALIGN(4); *libUSER_FREERTOS_LEVEL.a:*(.bss .bss*); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.bss .bss*); . = ALIGN(4); *libUSER_BSP_LEVEL.a:*(*COMMON); . = ALIGN(4); *libMC_INTERRUPT.a:*(*COMMON); . = ALIGN(4); *libMC_HARDWARE.a:*(*COMMON); . = ALIGN(4); *libPCB_HARDWARE.a:*(*COMMON); . = ALIGN(4); *libUSER_CODE.a:*(*COMMON); . = ALIGN(4); *libBUTTONS.a:*(*COMMON); . = ALIGN(4); *libCHIPTUNE.a:*(*COMMON); . = ALIGN(4); *libDIGITAL_POTENTIOMETER.a:*(*COMMON); . = ALIGN(4); *libLCD_DRIVER.a:*(*COMMON); . = ALIGN(4); *libMAKISE_GUI_ELEMENTS_BY_VADIMATORIK_ELEMENTS_BY_VADIMATORIK.a:*(*COMMON); . = ALIGN(4); *libMC_HARDWARE_INTERFACES_IMPLEMENTATION_FOR_STM32.a:*(*COMMON); . = ALIGN(4); *libMICROSD_LOW_LEVEL_DRIVER.a:*(*COMMON); . = ALIGN(4); *libSHIFT_REGISTER.a:*(*COMMON); . = ALIGN(4); *libWAVE_GENERATORS.a:*(*COMMON); . = ALIGN(4); *libRUN_TIME_LOGGER.a:*(.COMMON*); . = ALIGN(4); __user_lib_bss_end = .; } >RAM_USER_LIBRARIES /* Начальные значения изменяемых сущностей. */ .section_user_code_data : ALIGN(4) { . = ALIGN(4); __user_code_data_start = . ; . = ALIGN(4); *(.data .data.*) . = ALIGN(4); __user_code_data_end = . ; } >RAM_MAIN_PROGRAMM AT> ROM_MAIN_PROGRAMM .section_user_code_bss : ALIGN(4) { . = ALIGN(4); __bss_start__ = .; __user_code_bss_start = .; *(.bss .bss.*) *(COMMON) . = ALIGN(4); __bss_end__ = .; __user_code_bss_end = .; } >RAM_MAIN_PROGRAMM __external_lib_data_in_rom_start = LOADADDR(.section_external_libraries_data); __user_lib_data_in_rom_start = LOADADDR(.section_user_libraries_data); __user_code_data_in_rom_start = LOADADDR(.section_user_code_data); /*-------------------------Отладочная информация-----------------*/ /* Stabs debugging sections. */ .stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .stab.excl 0 : { *(.stab.excl) } .stab.exclstr 0 : { *(.stab.exclstr) } .stab.index 0 : { *(.stab.index) } .stab.indexstr 0 : { *(.stab.indexstr) } .comment 0 : { *(.comment) } /* * DWARF debug sections. * Symbols in the DWARF debugging sections are relative to the beginning * of the section so we begin them at 0. */ /* DWARF 1 */ .debug 0 : { *(.debug) } .line 0 : { *(.line) } /* GNU DWARF 1 extensions */ .debug_srcinfo 0 : { *(.debug_srcinfo) } .debug_sfnames 0 : { *(.debug_sfnames) } /* DWARF 1.1 and DWARF 2 */ .debug_aranges 0 : { *(.debug_aranges) } .debug_pubnames 0 : { *(.debug_pubnames) } /* DWARF 2 */ .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } .debug_abbrev 0 : { *(.debug_abbrev) } .debug_line 0 : { *(.debug_line) } .debug_frame 0 : { *(.debug_frame) } .debug_str 0 : { *(.debug_str) } .debug_loc 0 : { *(.debug_loc) } .debug_macinfo 0 : { *(.debug_macinfo) } /* SGI/MIPS DWARF 2 extensions */ .debug_weaknames 0 : { *(.debug_weaknames) } .debug_funcnames 0 : { *(.debug_funcnames) } .debug_typenames 0 : { *(.debug_typenames) } .debug_varnames 0 : { *(.debug_varnames) } .debug_macro 0 : { *(.debug_macro) } .debug_ranges 0 : { *(.debug_ranges) }
}

Добавляем в основной проект утилиту для извлечения разделов из конечного bin файла

К сожалению, не удалось найти ни в objcopy ни в objdump флагов для извлечения кода между определнными адресами из elf файла. Существует флаг --only-section, однако он не учитывает того факта, что после всех перечисленных в section.ld сущностей раздела в энергонезависимой памяти еще помещается отладочная информация. Без нее конечная прошивка, собранная из кусочков, работать не будет (по понятным причинам). Поэтому пришлось написать простенькую утилиту, которая принимает общий bin файл и по указанному промежутку адресов извлекает в отдельный файл требуемый участок. Однако тут возникает следующий нюанс. По умолчанию objcopy заполняет пространство между секциями 0-ми. Однако пустое пространство в flash памяти — это 0xFF. Для решения этой проблемы требуется компоновать выходной bin файл с флагом --gap-fill=0xff.

Добавить в проект вызов скрипта обновления энергонезависимой памяти микроконтроллера при обновлении файла прошивки

Для отслеживания изменений в проекте следует после каждой пересборки elf файла вызывать скрипт проверки, который извлечет из elf файла итоговый bin файл, из него нужный раздел, сравнвает с ранее извлеченным, если такой был, и в случае возникновения различий, обновляет раздел в памяти микроконтроллера.

Код скрипта сравнения

#!/bin/bash #
# $1 - старый файл. Он может отсутствовать, если происходит первая компиляция.
# $2 - только что извлеченный из elf файл.
# $3 - строка для прошивки требуемого сектора STM32.
# echo "Old file name: $1" echo "New file name: $2" # Предполагаем, что ничего перепрошивать не придется.
flag_rewrite=0 # Если это первая компиляция, то старого файла не существует и можно просто
# переименовать новый, чтобы он назывался как старый (потому что прошивка
# происходит по имени старого файла).
# Если это новая компиляция, то нужно узнать, различаются ли чем-то файлы или нет
# если да, то надо заменить старый новым. Если нет, то удалить новый.
if [ -e $1 ]
then # Если это уже не первая компиляция и файл существовал.
echo "Both files exist."
# Получаем md5 сумму входных файлов, анализируя файл в бинарном формате.
buf=$(md5sum $1 --binary)
md5_old=${buf:0:32} # Из вывода нам нужна только md5 без имени флага. # Это первые 32 символа.
buf=$(md5sum $2 --binary)
md5_new=${buf:0:32} echo "Started file comparison." if [ $md5_old == $md5_new ]
then # Если файл не был обновлен, удаляем новый.
echo "The file has not been updated."
echo "The new file will be deleted."
rm $2
echo "Removed."
else # Если они разные, тогда заменяем старый новым.
echo "The file has been modified."
echo "Old will be replaced by new."
mv $2 $1
echo "Replaced."
flag_rewrite=1 # Придется перепрошивать этот сектор.
fi
else # Если это первая компиляция.
echo "Old file does not exist."
echo "New will be renamed to old."
mv $2 $1 # Переименовываем файл под старый.
flag_rewrite=1 # Придется перепрошивать этот сектор.
echo "Renamed."
fi # Если файл был обновлен или только появился, то им надо прошить контроллер.
if [ $flag_rewrite -eq 1 ]
then
echo "Started flashing."
echo "CMD params: $3"
$3
fi

В самом проекте можно вызвать cmake функцию, которая сделает все необходимое:

Cmake функция обновления

function(write_sector SECTOR ADDR_BASE ADDR_START ADDR_END) add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD COMMAND ${ARM_OBJCOPY} --output-target=binary --gap-fill=0xff ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_all.bin COMMENT "Creating a binary file of the <<${SECTOR}>> sector" COMMAND ${BIN_EXTRACTOR} -p ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_all.bin -o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}_new.bin -b ${ADDR_BASE} -s ${ADDR_START} -e ${ADDR_END} COMMAND cd ${CMAKE_SOURCE_DIR} && ./cmp.sh ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}.bin ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}_new.bin "${STM32PROG} -c port=${STM32PROG_PORT} freq=${STM32PROG_FREQ} -w ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_section_${SECTOR}.bin ${ADDR_START}")
endfunction(write_sector)

Для записи функция использует stm32programmer.

Пример использования функции из кода проекта

if (STM32PROG_USE STREQUAL "ON") write_sector("bootloader" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_SYSCFG_PAGE_1_ADDRESS}) write_sector("external_libraries" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_EXTERNAL_LIB_ADDRESS} ${SECTION_USER_LIBRARIES_ADDRESS}) write_sector("user_libraries" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_USER_LIBRARIES_ADDRESS} ${SECTION_USER_CODE_ADDRESS}) write_sector("main_programm" ${SECTION_BOOTLOADER_ADDRESS} ${SECTION_USER_CODE_ADDRESS} ${ADDR_END_FLASH})
endif ()

Выводы

Плюсы данного подхода:

  1. в 95% случаев обновляется действительно то, что нужно;

Минусы данного подхода:

  1. нет никакого выигрыша в скорости, поскольку перед каждой прошивкой требуется загружать в микроконтроллер загрузчик для прошивки энергонезависимой памяти (это делается автоматически stm32programmer-ом). Как раз напротив, когда проект пересобирается полностью, то зачастую приходиться шить заново все разделы;
  2. размер section.ld отбивает всякое желание добавить или изменить в нем что-то. Если и потребуется применить данную методологию в реальном проекте, то придется писать удобный GUI для редактирования этого файла;
  3. в случае, если устройство управляет собственным питанием, то можно не заметить, что один из разделов не был зашит верно (при просадке напряжения, например) и долго отлаживать разделы из разных сборок :).

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть