Собираем проект для STM32 с помощью Clang/LLVM
Небольшое описание процесса сборки проекта для микроконтроллера STM32 с помощью clang/llvm.

Казалось бы зачем использовать Clang/LLVM
для проектов ориентированных на микроконтроллеры, лучше ли он GCC
?
Сразу скажу, здесь это не рассматривается, мы будем учиться использовать, а не разбираться зачем это и почему. Раз уж вы это читаете, значит у вас есть свои причины.
Первое с чего стоит начать это то, что у clang
параметры командной строки почти аналогичны gcc
. Почти - это большинство параметров, исключение лишь указание архитектуры для которой будет генерироваться бинарник, но это скорее уже к бэкенду, нежели к самомму clang
.
Ну и сам Си код совместим c GCC
.
Компилируем сишный исходник в объектный файл
Типичная строка для компиляции кода с помощью GCC
под Cortex-M0
будет выглядеть следующим образом:
arm-none-eabi-gcc.exe -c -std=c2x -mcpu=cortex-m0 -mthumb $(C_DEFS) $(C_INCLUDES) -O2 -Wall -fdata-sections -ffunction-sections -Wno-implicit-fallthrough -fshort-enums file.c -o file.o
Папки с заголовками и предопределенные макросы специально нес тал указывать, и ... да-да, использую язык будущего стандарта для Си - -std=c2x
, об изменениях можно почитать подробнее здесь: https://en.cppreference.com/w/c/language/history
А теперь посмотрим как это будет выглядеть для clang
clang.exe -c -std=c2x --target=thumbv6m-none-none-eabi $(C_DEFS) $(C_INCLUDES) -Ic:/gcc-arm/arm-none-eabi/include -Ic:/gcc-arm/lib/gcc/arm-none-eabi/9.2.1/include -O2 -Wall -fdata-sections -ffunction-sections -Wno-implicit-fallthrough -fshort-enums file.c -o file.o
Вот тут уже побольше, давайте разбираться, что же у нас тут добавилось:
-
--target=thumbv6m-none-none-eabi
Указание цели для которой будет генерироваться код, нужно выбрать из следующих вариантов:
thumbv6m-none-none-eabi
для ARM Cortex-M0 и Cortex-M0+;thumbv7m-none-none-eabi
для ARM Cortex-M3;thumbv7em-none-none-eabi
для ARM Cortex-M4 и Cortex-M7 (без FPU);thumbv7em-none-none-eabihf
для ARM Cortex-M4F и Cortex-M7F (с FPU).
-
-Ic:/gcc-arm/arm-none-eabi/include
и-Ic:/gcc-arm/lib/gcc/arm-none-eabi/9.2.1/include
А это пути (в вашем случае они могут, и скорее всего будут другие) к заголовочным файлам и файлам библиотек из пакета
GCC
. Да его тоже нужно будет установить. Это, по крайней мере до тех пор, пока не появятся соответсвующие файлы в самомLLVM
.
В принципе ничего сложного, добавились несколько параметров, с этим можно жить.
Компилируем ассемблерный исходник в объектный файл
Как это выглядит в gcc
:
arm-none-eabi-gcc.exe -c -x assembler-with-cpp -mcpu=cortex-m0 -mthumb $(ASM_DEFS) $(ASM_INCLUDES) -O2 -Wall -fdata-sections -ffunction-sections file.s -o file.o
А теперь как это будет выглядеть в clang
- а никак, ну по крайней мере я не нашел как это сделать, он ругается на каждую инструкцию, каждую запятую и всё остальное в ассемблерном файле. Вроде должен быть компилятор для ассемблера llvm-as.exe
, но не в моем установочном пакете, собирать из исходников не стал, не думаю, что это будет интересно.
Так что будем жить без ассемблерных исходников. Можно, конечно, обернуть все ассемблерные функции в сишные и спокойно пользоваться встроеным ассемблером - здесь всё отлично работает.
Что делать с "startup.s" файлом
Ответ ожидаем, переписать его на Си, и выглядеть он будет примерно так:
#define NULL (void*)0
extern void * _estack;
extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;
void Reset_Handler();
void NMI_Handler();
void HardFault_Handler();
void SysTick_Handler();
/* Тут прототипы для остальных векторов
* ...
*/
extern void SystemInit ();
extern int main();
void * vectors[] __attribute__((section(".isr_vector"), used)) =
{
&_estack
, &Reset_Handler
, &NMI_Handler
, &HardFault_Handler
, NULL
/* тут остальные векторы прерываний
* ...
* */
};
void Reset_Handler()
{
// копирование и инициализация данных в оперативке
void **pSource, **pDest;
for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++) *pDest = *pSource;
for (pDest = &_sbss; pDest != &_ebss; pDest++) *pDest = 0;
// полезные функции
SystemInit ();
main();
}
void __attribute__((weak)) NMI_Handler() { for(;;){}; }
void __attribute__((weak)) HardFault_Handler() { for(;;){}; }
void __attribute__((weak)) SysTick_Handler() { }
/* Тут остальные функции обработки прерываний, помеченные как "weak"
* ...
*/
Всё что написано выше - это немного переделанный ассемблерный код, при большом желании можно сделать конвертер с помощью питона или ещё чего-нибудь.
Линковка нескольких объектных файлов
Компилировать мы уже научились, теперь необходимо слинковать несколько файлов и получить заветный HEX
, ну точнее сначала ELF
файл, ну а затем уже HEX
.
Как выглядит линковка с использованием GCC
:
arm-none-eabi-gcc.exe $(CFLAGS) file1.o file2.o -specs=nano.specs -TSTM32F042K6Tx_FLASH.ld -lc -lm -lnosys -Wl,-Map=target.map -Wl,--gc-sections -nostdlib --output target.elf
Ничего примечательного, указываются объектные файлы, скрипт для линкера, используемые библиотеки, путь для MAP
файла (очень полезная штука) и имя готового ELF
файла.
Теперь посмотрим, что же у нас происходит при использовании clang
:
clang.exe $(CFLAGS) file1.o file2.o -TSTM32F042K6Tx_FLASH.ld -lc -lm -lgcc -Lc:/gcc-arm/lib/gcc/arm-none-eabi/9.2.1/thumb/v6-m/nofp -Lc:/gcc-arm/arm-none-eabi/lib/thumb/v6-m/nofp -Wl,-Map=target.map -Wl,--gc-sections -nostdlib --output target.elf
Почти всё осталось без изменений:
-specs=nano.specs
удалено;-Lc:/gcc-arm/lib/gcc/arm-none-eabi/9.2.1/thumb/v6-m/nofp
и-Lc:/gcc-arm/arm-none-eabi/lib/thumb/v6-m/nofp
, - это пути до библиотек, их обязательно нужно указывать, так как сам LLVM ничего о них не знает. С этими путями не всё так просто, они индивидуальны под каждую архитектуру.
В принципе всё.
Что изменилось
- Время сборки проекта уменьшилось раза в два;
- Можно делать так:
enum NAME : uint8_t { VAL1, VAL2, VAL3 };
, то есть указывать какому типу будет соответсвовать перечисление, вgcc
это делается не так просто. - Можно использовать
[[nodiscard]]
- Можно использовать
[[fallthrough]]
- Можно использовать
[[clang::optnone]]
- ...
По сути я и не знаю чем один лучше другого, и даже не хочу сравнивать, мне просто нравиться clang
.