Спустя долгое время, я решил узнать как обстоят дела с поддержкой STM8 в SDCC. Последней версией SDCC которой я пользовался был 3.6. За это время OpenOCD официально обзавелся поддержкой STM8. И т.к. в Linux я пользуюсь преимущественно IDE QtCreator, был соблазн перетащить туда проекты на STM8. Главная сложность возникла с системой управления сборки. QtCreator умеет работать только с qmake, qbs и cmake проектами. Qmake не поддерживает sdcc никоим образом. У CMake поддержка заявлена, но она чисто декларативная. Таким образом нам остается только Qbs.
Ранее для STM8 я пользовался STVD+Cosmic, но это требовало работы из виртуальной машины. STVD - отличная среда разработки, но в визуальном плане она устарела уже давно. Отсутствие темной темы и линтера конечно не принципиально, но без этого уже не комфортно работать. Кроме того Cosmic для проверки лицензии постоянно стучится в интернет. Я же предпочитаю отключать интернет в виртуалке.
Ок, но какие сложности нас ожидают?
Итак, софт который нам понадобится для программирования микроконтроллеров STM8:
Из аппаратного обеспечения нам достаточно будет платы c чипом STM8S103F3 и ST-Link V2 с поддержкой SWIM протокола.
Руководств и мануалов по использованию Qbs в baremetal-проектах почти нет. Есть несколько примеров для STM32, но так как там используется GCC, скрипт проекта будет отличаться от такового для SDCC.
Прежде чем начать что-то делать, настоятельно рекомендуется пройти по последним трем ссылкам, и изучить их содержимое.
Содержание:
I. Консольный Qbs проект для STM8+SDCC
II. Работа с проектом в Qt Creator
III. Бонус
IV. Добавленно позже
Для начала нам нужно убедится, что конкретно наш Qbs поддерживает SDCC:
$ qbs config --list|grep sdcc
profiles.sdcc-4_1_0-mcs51.cpp.toolchainInstallPath: "/usr/bin"
profiles.sdcc-4_1_0-mcs51.qbs.architecture: "mcs51"
profiles.sdcc-4_1_0-mcs51.qbs.toolchainType: "sdcc"
profiles.sdcc-4_1_0-stm8.cpp.toolchainInstallPath: "/usr/bin"
profiles.sdcc-4_1_0-stm8.qbs.architecture: "stm8"
profiles.sdcc-4_1_0-stm8.qbs.toolchainType: "sdcc"
У меня старая версия Qbs, и здесь мы видим поддержку только STM8 и MCS51. В новых версиях была добавлена также поддержка HC08.
В examples с Qbs постовляется пример проекта для STM8S103F3:
$ tree /usr/share/qbs/examples/baremetal/stm8s103f3/ /usr/share/qbs/examples/baremetal/stm8s103f3/ ├── redblink │ ├── README.md │ ├── gpio.c │ ├── gpio.h │ ├── main.c │ ├── redblink.qbs │ └── system.h └── stm8s103f3.qbs
Давайте скопируем этот пример куда-нибудь в домашний каталог, и посмотрим что это такое.
Файл "main.c"
#include "gpio.h" #include "system.h" static void some_delay(unsigned long counts) { unsigned long index = 0u; for (index = 0u; index < counts; ++index) system_nop(); } int main(void) { gpio_init_red_led(); while (1) { gpio_toggle_red_led(); some_delay(20000u); } }
Файл "gpio.c"
#include "gpio.h" #include "system.h" // A LED is connected to the pin #5 of Port B. #define GPIO_RED_LED_PIN_POS (0x20u) void gpio_init_red_led(void) { PB_ODR = 0x00; // Turn off all pins. PB_DDR = GPIO_RED_LED_PIN_POS; // Configure Pin as output. PB_CR1 = GPIO_RED_LED_PIN_POS; // Set Pin to Push-Pull. PB_CR2 = GPIO_RED_LED_PIN_POS; // Set Pin to Push-Pull. } void gpio_toggle_red_led(void) { PB_ODR ^= GPIO_RED_LED_PIN_POS; }
Пробуем собрать проект:
$ qbs build profile:sdcc-4_1_0-stm8 Build graph does not yet exist for configuration 'default'. Starting from scratch. Resolving project for configuration default Setting up build graph for configuration default Building for configuration default compiling main.c [stm8s103f3-redblink] compiling gpio.c [stm8s103f3-redblink] linking stm8s103f3-redblink.ihx [stm8s103f3-redblink] Build done for configuration default.
Проект должен собраться в директорию default:
$ tree ./default/ ./default/ ├── default.bg └── stm8s103f3-redblink.2e6c123b ├── 3a52ce780950d4d9 │ ├── gpio.c.adb │ ├── gpio.c.asm │ ├── gpio.c.lst │ ├── gpio.c.rel │ ├── gpio.c.rst │ ├── gpio.c.sym │ ├── main.c.adb │ ├── main.c.asm │ ├── main.c.lst │ ├── main.c.rel │ ├── main.c.rst │ └── main.c.sym ├── stm8s103f3-redblink.ihx ├── stm8s103f3-redblink.lk └── stm8s103f3-redblink.map 2 directories, 16 files
Прошиваем прошивку програматором:
$ stm8flash -c stlinkv2 -p stm8s103f3 -w ./default/stm8s103f3-redblink.2e6c123b/stm8s103f3-redblink.ihx
Determine FLASH area
STLink: v2, JTAG: v29, SWIM: v7, VID: 8304, PID: 4837
Due to its file extension (or lack thereof), "./default/stm8s103f3-redblink.2e6c123b/stm8s103f3-redblink.ihx" is considered as INTEL HEX format!
104 bytes at 0x8000... OK
Bytes written: 104
Замечательно, светодиод мигает!
Чтобы очистить проект, делаем так:
$ qbs clean
Restoring build graph from disk
Cleaning up for configuration default
Давайте посмотрим как обстоят дела с отладкой STM8. Для этого нам следует скомпилировать прошивку в ELF-формат. Вручную это достигается последовательным вводом следующих команд:
$ sdcc -mstm8 --debug --out-fmt-elf -I./ -D STM8S103 -c -o main.rel -c main.c $ sdcc -mstm8 --debug --out-fmt-elf -I./ -D STM8S103 -c -o gpio.rel -c gpio.c $ sdcc -o blink.elf -mstm8 --debug --out-fmt-elf gpio.rel main.rel
Подключаем ST-LinkV2 с микроконтроллером к компьютеру, и запускаем openocd:
$ /opt/bin/openocd-v11 -f /opt/share/openocd/scripts/interface/stlink-dap.cfg -f /opt/share/openocd/scripts/target/stm8s103.cfg -c "init" -c "reset halt"
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
srst_only separate srst_gates_jtag srst_open_drain connect_deassert_srst
Info : STLINK V2J29S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.278009
Info : clock speed 800 kHz
Info : starting gdb server for stm8s.cpu on 3333
Info : Listening on port 3333 for gdb connections
target halted due to debug-request, pc: 0x00008000
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Контакт есть. Запускаем GDB:
Кроме Си-кода можно отлаживать ассемблерный код:
Убедились, что отладка работает, завершаем сеанс. Пока отладка нам ненужна. Сейчас нас будет интересовать как скомпилировать прошивку посредством Qbs.
Чтобы изменить сборку прошивки с ihx-формата на elf-формат, нужно править Qbs-скрипт сборки. Давайте посмотри что у нас есть изначально. Файл "redblink.qbs":
import qbs CppApplication { condition: { if (!qbs.architecture.contains("stm8")) return false; return qbs.toolchain.contains("iar") || qbs.toolchain.contains("sdcc") } name: "stm8s103f3-redblink" cpp.positionIndependentCode: false // // IAR-specific properties and sources. // Properties { condition: qbs.toolchain.contains("iar") cpp.commonCompilerFlags: ["-e"] cpp.driverLinkerFlags: [ "--config_def", "_CSTACK_SIZE=0x100", "--config_def", "_HEAP_SIZE=0x100", ] } Group { condition: qbs.toolchain.contains("iar") name: "IAR" prefix: "iar/" Group { name: "Linker Script" prefix: cpp.toolchainInstallPath + "/../config/" fileTags: ["linkerscript"] files: ["lnkstm8s103f3.icf"] } } // // SDCC-specific properties and sources. // Properties { condition: qbs.toolchain.contains("sdcc") } // // Common code. // Group { name: "Gpio" files: ["gpio.c", "gpio.h"] } Group { name: "System" files: ["system.h"] } files: ["main.c"] }
Мы можем убрать код для IAR и тем самым существенно сократить скрипт:
import qbs CppApplication { Group { name: "Gpio" files: ["gpio.c", "gpio.h"] } Group { name: "System" files: ["system.h"] } files: ["main.c"] }
Однако будет лучше, если мы откажемся от использования данного скрипта, и вместо него начнем писать свой.
Для начала пусть будет так:
import qbs Product { type: ["application"] Depends { name: "cpp" } name: "firmware" files: [ "system.h", "gpio.h", "gpio.c", "main.c", ] }
Проверяем:
$ qbs clean; qbs build profile:sdcc-4_1_0-stm8 Restoring build graph from disk Cleaning up for configuration default Restoring build graph from disk Building for configuration default compiling gpio.c [firmware] compiling main.c [firmware] linking firmware.ihx [firmware] Build done for configuration default.
Лично мне не нравятся эти скупые строки "compiling gpio.c [firmware]", я хочу видеть с какими параметрами вызывается компилятор. Для этого мы можем добавить флаг "--command-echo-mode command-line":
Теперь сборка у нас будет выглядеть следующим образом:
$ qbs clean && qbs build profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Restoring build graph from disk Cleaning up for configuration default Restoring build graph from disk Building for configuration default /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug /usr/bin/sdcc -mstm8 -o /tmp/redblink/default/firmware.9bcf18e4/firmware.ihx /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel Build done for configuration default.
Кроме "command-line" допустимы так-же варианты: "silent", "summary" и "command-line-with-environment".
По логу сборки можно увидеть, что: во-первых, нам добавили в опции компиляции флаг "--debug".
Нашей задачей сейчас будет задать сборку в ELF-формате. Для того следует добавить флаги: "--debug","--out-fmt-elf" для компилятора и компоновщика:
import qbs
Product {
type: ["application"]
Depends { name: "cpp" }
name: "firmware"
cpp.driverFlags: ["--out-fmt-elf"]
cpp.driverLinkerFlags: ["--debug","--out-fmt-elf"]
cpp.executableSuffix: ".elf"
files: [
"system.h",
"gpio.h",
"gpio.c",
"main.c",
]
}
В данном случае я пропустил флаг "--debug" в опциях компиляции, т.к. Qbs добавляет его сама. Собираем:
$ qbs clean && qbs build profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Restoring build graph from disk Cleaning up for configuration default Restoring build graph from disk Resolving project for configuration default Building for configuration default /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug --out-fmt-elf /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug --out-fmt-elf /usr/bin/sdcc -mstm8 -o /tmp/redblink/default/firmware.9bcf18e4/firmware.elf /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/default/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel --debug --out-fmt-elf Build done for configuration default.
Проверяем ELF-файл:
$ file /tmp/redblink/default/firmware.9bcf18e4/firmware.elf
/tmp/redblink/default/firmware.9bcf18e4/firmware.elf: ELF 32-bit MSB executable, STMicroeletronics STM8 8-bit, version 1 (IRIX), statically linked, with debug_info, not stripped
Вроде бы всё Ок.
Теперь, когда у нас появилась возможность собирать прошивку как в elf-формат так и ihx, мы можем добавить раздельные опции компиляции для сборок "debug" и "release". Для этого следует все специфичные опции сборки собрать в своих "Properties":
import qbs Product { type: ["application"] Depends { name: "cpp" } name: "firmware" files: [ "system.h", "gpio.h", "gpio.c", "main.c", ] Properties { condition: qbs.buildVariant === "debug" cpp.driverFlags: ["--out-fmt-elf"] cpp.driverLinkerFlags: ["--debug","--out-fmt-elf"] cpp.executableSuffix: ".elf" } Properties { condition: qbs.buildVariant === "release" cpp.cFlags: ["--opt-code-size"] cpp.executableSuffix: ".ihx" } }
Чтобы указать используемый профиль qbs, к команде следует добавить: "config:имя_профиля".
Удаляем каталог с дефолтным профилем:
$ rm -r ./default
Производим сборку с профилем "release"
$ qbs build config:release profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Build graph does not yet exist for configuration 'release'. Starting from scratch. Resolving project for configuration release Setting up build graph for configuration release Building for configuration release /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size /usr/bin/sdcc -mstm8 -o /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel Build done for configuration release.
Проверяем:
$ find ./release -name *.ihx -exec file {} \;
./release/firmware.9bcf18e4/firmware.ihx: ASCII text
То же самое для профиля "debug":
$ qbs build config:debug profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Build graph does not yet exist for configuration 'debug'. Starting from scratch. Resolving project for configuration debug Setting up build graph for configuration debug Building for configuration debug /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug --out-fmt-elf /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug --out-fmt-elf /usr/bin/sdcc -mstm8 -o /tmp/redblink/debug/firmware.9bcf18e4/firmware.elf /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel --debug --out-fmt-elf Build done for configuration debug.
$ find ./debug -name *.elf -exec file {} \;
./debug/firmware.9bcf18e4/firmware.elf: ELF 32-bit MSB executable, STMicroeletronics STM8 8-bit, version 1 (IRIX), statically linked, with debug_info, not stripped
Как видно, все работает как надо.
При очистке сборки, также следует указывать профиль:
$ qbs clean config:release ; qbs clean config:debug
Restoring build graph from disk
Cleaning up for configuration release
Restoring build graph from disk
Cleaning up for configuration debug
Еще бы хотелось сделать печать размера скомпилированной прошивки. Для этого к конец файла "redblink.qbs" следует добавить правило (Rule):
Rule { inputs: ["application"] Artifact { filePath: product.name fileTags: "flash" } prepare: { var size_name=input.filePath var cmd=new Command("stm8-size",size_name) cmd.description = "print size of the firmware" return [cmd] } }
Также, в поле "Type"(сразу после Product) следует добавить "flash":
type: ["application","flash"]
Теперь сборка будет выглядеть так:
$ qbs build config:release profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Restoring build graph from disk Resolving project for configuration release Building for configuration release /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size /usr/bin/sdcc -mstm8 -o /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel /usr/local/bin/stm8-size /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx Build done for configuration release. /usr/local/bin/stm8-size /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx text data bss dec hex filename 0 104 0 104 68 /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx
Правило работает на оба профиля сборки. Сборка с профилем "debug", теперь будет выглядеть так:
$ qbs build config:debug profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Restoring build graph from disk Resolving project for configuration debug Building for configuration debug /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug --out-fmt-elf /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --debug --out-fmt-elf /usr/bin/sdcc -mstm8 -o /tmp/redblink/debug/firmware.9bcf18e4/firmware.elf /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/debug/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel --debug --out-fmt-elf /usr/local/bin/stm8-size /tmp/redblink/debug/firmware.9bcf18e4/firmware.elf /usr/local/bin/stm8-size /tmp/redblink/debug/firmware.9bcf18e4/firmware.elf text data bss dec hex filename 105 0 0 105 69 /tmp/redblink/debug/firmware.9bcf18e4/firmware.elf Build done for configuration debug.
Тут по выводу "stm8-size" можно увидеть, что в случае ihx файла данные из секции .text почему-то попали в секцию .data.
Так же можно сделать вывод, что опция компиляции "--opt-code-size" не особо влияет на размер прошивки ;)
Сейчас мы немного модифицируем проект, и в первую очередь, для примера, добавим простой ассемблерный файл с функцией задержки "utils.s":
.module UTILS .area CODE .globl _delay delay: ld a, #0x06 ldw y, #0x1a80 ; 0x61a80 = 400000 i.e. (2*10^6 MHz)/5cycles loop: subw y, #0x01 ; decrement with set carry sbc a,#0x0 ; decrement carry flag i.e. a = a - carry_flag jrne loop ret
Файл "main.c" приводим к следующиму типу:
#include "gpio.h" #include "system.h" extern void delay(); int main(void) { gpio_init_red_led(); while (1) { gpio_toggle_red_led(); delay(); } }
В файл проекта "redblink.qbs" потребуется добавить пару строк (выделено красным):
import qbs Product { type: ["application","flash"] Depends { name: "cpp" } name: "firmware" cpp.assemblerFlags: ["-plosgffwy"] files: [ "system.h", "gpio.h", "gpio.c", "main.c", "util.s", ] Properties { condition: qbs.buildVariant === "debug" cpp.cFlags: ["--debug","--out-fmt-elf"] cpp.linkerFlags: ["--debug","--out-fmt-elf"] cpp.executableSuffix: ".elf" } Properties { condition: qbs.buildVariant === "release" cpp.cFlags: ["--opt-code-size"] cpp.executableSuffix: ".ihx" } Rule { inputs: ["application"] Artifact { filePath: product.name fileTags: "flash" } prepare: { var size_name=input.filePath var cmd=new Command("stm8-size",size_name) cmd.description = "print size of the firmware" return [cmd] } } }
Собираем проект:
$ qbs clean config:release && qbs build profile:sdcc-4_1_0-stm8 config:release Restoring build graph from disk Cleaning up for configuration release Restoring build graph from disk Building for configuration release sdasstm8 -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -plosgffwy -ol /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/util.s.rel /mnt/tmp/mcu/qbs/redblink/util.s [firmware] sdcc /mnt/tmp/mcu/qbs/redblink/main.c -c -o /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size [firmware] sdcc /mnt/tmp/mcu/qbs/redblink/gpio.c -c -o /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size [firmware] sdcc -mstm8 -o /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/firmware.ihx /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/util.s.rel [firmware] print size of the firmware [firmware] /usr/local/bin/stm8-size /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/firmware.ihx text data bss dec hex filename 0 85 0 85 55 /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/firmware.ihx Build done for configuration release.
Прошиваем:
$ stm8flash -c stlinkv2 -p stm8s103f3 -w ./release/firmware.9bcf18e4/firmware.ihx
Determine FLASH area
STLink: v2, JTAG: v29, SWIM: v7, VID: 8304, PID: 4837
Due to its file extension (or lack thereof), "./release/firmware.9bcf18e4/firmware.ihx" is considered as INTEL HEX format!
85 bytes at 0x8000... OK
Bytes written: 85
Если светодиод мигает, тогда все отлично.
Не лишним будет собрать с "debug" профилем. Проверить отладку. Напоминаю, что, чтобы поставить точку останова на ассемблерной функции следует ее стравить на физический адрес:
Хотя на первый взгляд, кажется что все работает как надо, на самом деле это не так. Тут есть еще подводный камень, который сразу и не увидишь. Проблема проявиться если мы переименуем файл "until.s" в скажем "delay.s". В "redblnk.qbs" также следует сменить имя файла.
Тогда сборка будет выглядеть так:
$ qbs clean config:release && qbs build profile:sdcc-4_1_0-stm8 config:release Restoring build graph from disk Cleaning up for configuration release Restoring build graph from disk Resolving project for configuration release Building for configuration release sdasstm8 -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -plosgffwy -ol /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/delay.s.rel /mnt/tmp/mcu/qbs/redblink/delay.s [firmware] sdcc /mnt/tmp/mcu/qbs/redblink/gpio.c -c -o /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size [firmware] sdcc /mnt/tmp/mcu/qbs/redblink/main.c -c -o /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size [firmware] sdcc -mstm8 -o /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/firmware.ihx /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/delay.s.rel /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel [firmware] print size of the firmware [firmware] /usr/local/bin/stm8-size /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/firmware.ihx text data bss dec hex filename 0 85 0 85 55 /mnt/tmp/mcu/qbs/redblink/release/firmware.9bcf18e4/firmware.ihx Build done for configuration release.
Я выделил красным нашу проблему, которая заключатся в порядке следования "rel" файлов в параметрах компоновщика. Qbs их сортирует в алфавитном порядке и приводит это к тому, если мы сейчас попробуем залить прошивку, то получим ошибку:
$ stm8flash -c stlinkv2 -p stm8s103f3 -w ./release/firmware.9bcf18e4/firmware.ihx Determine FLASH area STLink: v2, JTAG: v29, SWIM: v7, VID: 8304, PID: 4837 Due to its file extension (or lack thereof), "./release/firmware.9bcf18e4/firmware.ihx" is considered as INTEL HEX format! Address 0000 is out of range at line 1
Флешер ругается на то, что адреса в прошивке начинаются с 0x0000. Причина этого кроется скорее всего в том, что компоновщику не хватет каких-то данных от наших ассемблерных исходников. И самым простым способом решить данную проблему будет такая сортировка rel-файлов, когда компоновщику сначала указываются rel-файлы от Си-модулей, а уже потом от ассемблерных.
Сделать это можно только путем правки скрипта "sdcc.js" который реализует поддержку SDCC в Qbs. К счастью, это не сложно. Нужный нам скрипт находится здесь:
/usr/share/qbs/modules/cpp/sdcc.js
Это JavaScript, он имеет 548 строк. Часть из них отвечает за сборку по архитектуру MSC051, часть за STM8, и какая-то часть общая. Я использую не самую последнюю версию Qbs версии 1.16, актуальную версию данного скрипта вы можете найти на github.com по адресу: https://github.com/qbs/qbs/blob/master/share/qbs/modules/cpp/sdcc.js
Итак, чтобы исправить нашу проблему, открываем файл "/usr/share/qbs/modules/cpp/sdcc.js", и в теле функции "linkerFlags() вносим следующие изменения:
// Linker scripts. var scripts = inputs.linkerscript ? inputs.linkerscript.map(function(scr) { return "-f" + scr.filePath; }) : []; if (scripts) Array.prototype.push.apply(escapableLinkerFlags, scripts); } else { // Output. args.push(outputs.application[0].filePath); ////////// НАЧАЛО ИЗМЕННИЙ ///////////////////////////// // Inputs. // if (inputs.obj) // args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); if (inputs.obj) { var asm_obj=[]; inputs.obj.forEach(function(scr) { var l=scr.filePath; if (l.match(".s.rel")) asm_obj.push(l); else args.push(l); }); args = args.concat(asm_obj); } //////// КОНЕЦ ИЗМЕНЕНИЙ ////////////////////////////// // Library paths. args = args.concat(allLibraryPaths.map(function(path) { return "-k" + path })); // Linker scripts. // Note: We need to split the '-f' and the file path to separate // lines; otherwise the linking fails. inputs.linkerscript.forEach(function(scr) { escapableLinkerFlags.push("-f", scr.filePath); }); }
Еще раз собираем:
$ qbs clean config:release && qbs build config:release profile:sdcc-4_1_0-stm8 --command-echo-mode command-line Restoring build graph from disk Cleaning up for configuration release Restoring build graph from disk Resolving project for configuration release Building for configuration release /usr/bin/sdasstm8 -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -plosgffwy -ol /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/delay.s.rel /tmp/redblink/delay.s /usr/bin/sdcc /tmp/redblink/main.c -c -o /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size /usr/bin/sdcc /tmp/redblink/gpio.c -c -o /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel -DNDEBUG -I/usr/share/sdcc/include/stm8 -I/usr/share/sdcc/include -mstm8 --opt-code-speed --opt-code-size /usr/bin/sdcc -mstm8 -o /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/gpio.c.rel /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.rel /tmp/redblink/release/firmware.9bcf18e4/3a52ce780950d4d9/delay.s.rel /usr/local/bin/stm8-size /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx /usr/local/bin/stm8-size /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx text data bss dec hex filename 0 85 0 85 55 /tmp/redblink/release/firmware.9bcf18e4/firmware.ihx Build done for configuration release.
Теперь все на своем месте. Прошиваем:
$ stm8flash -c stlinkv2 -p stm8s103f3 -w ./release/firmware.9bcf18e4/firmware.ihx
Determine FLASH area
STLink: v2, JTAG: v29, SWIM: v7, VID: 8304, PID: 4837
Due to its file extension (or lack thereof), "./release/firmware.9bcf18e4/firmware.ihx" is considered as INTEL HEX format!
85 bytes at 0x8000... OK
Bytes written: 85
На этот раз все правильно.
Как настраивать Qt Creator для работы с микроконтроллерами я уже рассказывал на примере STM32: "Qt Creator + CMake + STM32". В случае STM8 все делается аналогично, единственное, что еще потребуется в настройки Qbs заглянуть.
Во-первых, вы должны включить плагин baremetal, после чего у вас должен появиться SDCC в списке доступных компиляторов:
Добавьте отладчик:
Настройте параметры запуска OpenOCD:
Здесь поле "Configuration file" полностью не влазит в скрин, там должен быть полный путь до скрипта интерфейса(отладчика), без всяких "-f".
Затем создайте девайс:
И настройте Kit:
Также потребуется заглянуть в настройки Qbs:
Для примера работы с Qt Creator мы возьмем более сложный пример чем простая мигалка. Пусть это будет эхо программа для интерфеса UART1. Структура проекта будет выглядеть так:
$ tree . . ├── asm │ ├── delay.s │ └── stm8s103f.s ├── inc │ ├── stm8s103f.h │ ├── stm8s_clk.h │ ├── stm8s_tim4.h │ ├── stm8s_uart1.h │ └── uart1.h ├── main.c ├── s103uart.qbs └── src └── uart1.c
Исходники можно будет скачать из архива в конце статьи. В данном случае суть не в них. Сейчас нас будет интересовать только Qbs-файл, который будет выглядеть так:
import qbs Product { type: ["application","flash"] Depends { name: "cpp" } name: "firmware" cpp.assemblerFlags: ["-plosgffwy"] cpp.includePaths:["inc"] files: [ "main.c", "inc/uart1.h", "src/uart1.c", "asm/delay.s", ] Properties { condition: qbs.buildVariant === "debug" cpp.cFlags: [--out-fmt-elf"] cpp.driverLinkerFlags: ["--debug","--out-fmt-elf"] cpp.executableSuffix: ".elf" } Properties { condition: qbs.buildVariant === "release" cpp.cFlags: ["--opt-code-size"] cpp.executableSuffix: ".ihx" } Rule { inputs: ["application"] Artifact { filePath: product.name fileTags: "flash" } prepare: { var cmd=new Command("stm8-size",input.filePath) cmd.description = "print size of the firmware" return [cmd] } } }
Открываем этот проект в QtCreator и при конфигурации выбираем профиль "baremetal STM8":
Для печати строк компиляции проекта, в свойствах сборки проекта следует отметить галочку "Show command lines":
Попробуйте собрать проект:
Если посмотреть на лог сборки, то система нам в опции сборки добавила флаг "--opt-code-speed". А при сборке с профилем Debug, она добавляет флаг "--debug".
Для прошивки микроконтроллера из Release профиля(кликом на зеленый треугольник "Run"), в свойствах проекта добавим Custom Command со следующими параметрами:
Прошивка:
Из Debug-профиля должна быть доступна отладка. Для этого поставте точку остановки и кликните по зеленому треугольнику с жуком:
В режиме трассировки можно даже поставить точку остановки в на ассемблерный код:
Периферийных регистров нет, QtCreator принимает описания периферийных регистров в формате SVD. Это формат Keil для ARM микроконтроллеров. Для STM8 таких файлов нет, а табличку с регистрами в данный формат не преобразуешь. Он очень замороченный.
При отладке доступна консоль отладчика GDB, куда можно вводить любые команды отладчика или монитора. В частности меня смущала путаница с секциями .text и .data и ввел глобальную переменную с предустановленным значением чтобы понять, что инициализация глобальных переменных происходит корректно:
Ответ система выдает в такой замысловатой форме.
Я не нашел как сделать подсветку синтаксиса дизассемблерного кода в режиме отладки, но ассемблерный код проекта можно раскрасить на свой вкус:
Для этого в каталог "~/.config/QtProject/qtcreator/generic-highlighter/syntax/" помещается файл "stm8asm.xml" с XML-разметкой. Я на быструю руку создал универсальную разметку ассемблера для STM8 и ARM
показать файл разметки<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE language SYSTEM "language.dtd"> <language name="ARM Assembler" version="0.2" kateversion="5.0" section="Assembler" extensions="*.s;*.S;*.asm" mimetype="text/x-asmarm" author="flanker"> <highlighting> <list name="keywords"> <item> adcs </item> <item> push </item> <item> pop </item> <item> adds </item> <item> add </item> <item> adr </item> <item> ands </item> <item> asrs </item> <item> bics </item> <item> bkpt </item> <item> cmn </item> <item> cmp </item> <item> cpsid </item> <item> cpsie </item> <item> dmb </item> <item> dsb </item> <item> eors </item> <item> isb </item> <item> ldm </item> <item> ldr </item> <item> ldrb </item> <item> ldrh </item> <item> ldrsb </item> <item> ldrsh </item> <item> lsls </item> <item> lsrs </item> <item> mov </item> <item> mov.w </item> <item> movs </item> <item> mrs </item> <item> msr </item> <item> muls </item> <item> mvns </item> <item> snop </item> <item> orrs </item> <item> rev </item> <item> rev16 </item> <item> revsh </item> <item> rors </item> <item> rsbs </item> <item> sbcs </item> <item> sev </item> <item> stm </item> <item> str </item> <item> strb </item> <item> strh </item> <item> sub </item> <item> subs </item> <item> svc </item> <item> sxtb </item> <item> sxth </item> <item> tst </item> <item> uxtb </item> <item> uxth </item> <item> wfi </item> <item> wfe </item> <item> pushw </item> <item> popw </item> <item> ret </item> <item> call </item> <item> subw </item> <item> sbc </item> <item> ld </item> <item> dec </item> <item> inc </item> <item> ldw </item> <item> decw </item> <item> incw </item> <item> iret </item> <item> clr </item> <item> clrw </item> <item> bset </item> <item> bres </item> <item> bcpl </item> <item> cp </item> <item> cpw </item> <item> div </item> <item> divw </item> <item> tnzw </item> <item> tnz </item> </list> <list name="branch instructions"> <item> b </item> <item> bl </item> <item> blx </item> <item> bx </item> <item> beq </item> <item> bne </item> <item> bcs </item> <item> bcc </item> <item> bmi </item> <item> bmi.n </item> <item> bpl </item> <item> bvs </item> <item> bvc </item> <item> bhi </item> <item> bls </item> <item> bge </item> <item> ble </item> <item> blt </item> <item> bgt </item> <item> bal </item> <item> jreq </item> <item> jrnc </item> <item> jrne </item> <item> jra </item> <item> btjt </item> <item> btjf </item> </list> <list name="registers"> <item> r0 </item> <item> r1 </item> <item> r2 </item> <item> r3 </item> <item> r4 </item> <item> r5 </item> <item> r6 </item> <item> r7 </item> <item> r8 </item> <item> r8 </item> <item> r10 </item> <item> r11</item> <item> r12 </item> <item> r13 </item> <item> r14 </item> <item> r15 </item> <item> sp </item> <item> pc </item> <item> lr </item> <item> a </item> <item> x </item> <item> y </item> <item> yl </item> <item> xl </item> </list> <contexts> <context name="Base" attribute="Normal Text" lineEndContext="#stay"> <RegExpr String= "[{}\[\[()]" attribute = "Special Symbol" context="#stay"/> <RegExpr String= "^[a-zA-Z0-9_].*?:" attribute = "Label" context="#stay"/> <!--RegExpr String= "b[csxlmpne]*\ *[a-zA-Z0-9_]*" attribute = "Branch Instructions" context="#stay"/--> <RegExpr String="=0x[0-9a-fA-F]*" attribute="Number" context="#stay"/> <RegExpr String="=[0-9a-zA-Z_]*" attribute="Number" context="#stay"/> <RegExpr String="#0x[0-9a-fA-F]*" attribute="Number" context="#stay"/> <RegExpr String="#[0-9]*" attribute="Number" context="#stay"/> <RegExpr String="=[0-9]*" attribute="Number" context="#stay"/> <keyword attribute="Register" context="#stay" String="registers" /> <keyword attribute="Directive" context="#stay" String="directives" /> <keyword attribute="Keyword" context="#stay" String="keywords" /> <keyword attribute="Branch Instructions" context="#stay" String="branch instructions"/> <RegExpr String= "\.asciz" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.balign" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.bss" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.data" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.end" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.global" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.long" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.section" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.text" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.word" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.type" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.global" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.cpu" attribute = "Data Type" context="#stay"/> <RegExpr String= "cortex-m0" attribute = "Data Type" context="#stay"/> <RegExpr String= "cortex-m3" attribute = "Data Type" context="#stay"/> <RegExpr String= "cortex-m4" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.weak" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.thumb" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.thumb_set" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.fpu\ softvfp" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.size" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.syntax\ unified" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.area" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.globl" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.module" attribute = "Data Type" context="#stay"/> <RegExpr String= "\.include" attribute = "Data Type" context="#stay"/> <RegExpr String= ";.*" attribute = "Comment" context="#stay"/> <DetectChar attribute="String" context="string" char=""" /> <RegExpr String= "\@.*$" attribute = "Comment" context="#pop"/> <Detect2Chars attribute="Comment" context="Comment Type 2" char="/" char1="*" beginRegion="Comment"/> </context> <context attribute="Comment" lineEndContext="#stay" name="Comment Type 2"> <Detect2Chars attribute="Comment" context="#pop" char="*" char1="/" endRegion="Comment"/> </context> <context attribute="String" lineEndContext="#stay" name="string" > <DetectChar attribute="String" context="#pop" char=""" /> </context> </contexts> <itemDatas> <itemData name="Label" color="#e1cb3b" /> <itemData name="Normal Text" color="#333333" /> <itemData name="Keyword" color="#32cc32" /> <itemData name="Branch Instructions" color="#39f077" /> <itemData name="Register" defStyleNum="dsKeyword" /> <itemData name="Hex" color="#ff4433"/> <itemData name="Number" color="#ff5f1f"/> <itemData name="String" defStyleNum="dsString" /> <itemData name="Comment" defStyleNum="dsComment" /> <itemData name="Special Symbol" color="#ffffff" /> <!--itemData name="Data Type" defStyleNum="dsComment" /--> <itemData name="Data Type" color="#f500f1" /> </itemDatas> </highlighting> <general> </general> </language>
Мне не хочется усложнять статью, но не могу не сказать в заключение пару слов о качестве кода SDCC. Пару месяцев я писал проект драйвера FM-приемника RDA5807m пока у меня не сгорел ST-Link. Потом я переносил проект на ATmega328 и я могу сравнить работу нормального компилятора (avr-gcc) с SDCC-STM8.
Первое и самое главное, что хочется сказать - "It's work!". Это работает. Мой проект под финал весил под 16КБ, это несколько тысяч строк исходного кода на Си и Ассемблере, и SDCC этот код успешно компилировал в рабочую прошивку:
Т.е. пользоваться SDCC можно, хотя конечно он постоянно выкидывал фортели. Если быть справедливым, большую часть моих претензий относится скорее к компоновщику нежели компилятору. Причем компилятор STM8 в SDCC в настоящее время вполне приличный. Какие-то сложные алгоритмы приходилось переписывать на ассемблере, но компилятор много раз срывал мои овации. Всего один пример - сдвиг влево на 8 бит:
uint16_t tuner=(uint16_t)(msb<<8) + (uint16_t)lsb; 0009CA 7B 02 [ 1] 2756 ld a, (0x02, sp) 0009CC 95 [ 1] 2757 ld xh, a 0009CD 4F [ 1] 2758 clr a 0009CE 97 [ 1] 2759 ld xl, a 0009CF 0F 02 [ 1] 2760 clr (0x02, sp) 0009D1 72 FB 02 [ 2] 2761 addw x, (0x02, sp)
Здесь компилятор понял, что от него не требуется что-то сдвигать, а что нужно поменять местами младший и старший байт, при этом очистив младший.
Есть еще множество примеров с указателями, но как уже сказал, не хочу усложнять статью.
В тоже время компоновщик постоянно норовил что-нибудь выкинуть. Если ваша функция слишком длинная и локальная переменная перестает быть видимой. Это совершенно нормально. Поэтому длинный главный цикл не приветствуется ;). В одном модуле у меня "отвалилось" деление. Пришлось быстро писать функции деления на ассемблере и далее вызывать уже эти функции:
.globl _div_func _div_func: ldw x,(0x03,sp) ld a,(0x05,sp) div x,a ret .globl _divmod_func _divmod_func: ldw x,(0x03,sp) ld a,(0x05,sp) div x,a ret
Использование статических переменных запрещено:
Вот этот код SDCC мне так и не простил:
void (*command)(void)=task[state]; command();
постоянно жаловался на него, хотя и компилировал его абсолютно нормально:
warning 244: pointer types incompatible from type 'void generic* fixed' to type 'void function ( ) code* auto'
avr-gcc был не таким ворчливым ;)
Если говорить про эффективность оптимизации кода в SDCC, то могу примести следующие цифры. Первый вариант драйвера мню был написан в 2019-ом году на SDCC версии 3.9. Он весит:
stm8-size ./firmware.ihx text data bss dec hex filename 0 6422 0 6422 1916 ./firmware.ihx
Около полутора килобайт здесь строковые константы. Давайте пересоберем ее в SDCC v4.2:
text data bss dec hex filename 0 6039 0 6039 1797 firmware.ihx
Во-первых проект собрался. но я не уверен что он в рабочем состоянии, т.к. в SDCC-4.x поменяли формат вызова функций на совместимый с Cosmic. Тем не менее, прогресс есть. Прошивка стала весить меньше. А теперь давайте используем опцию оптимизации "--opt-code-size"
text data bss dec hex filename 0 6027 0 6027 178b firmware.ihx
Прошивка уменьшилась еще на целых ДВЕНДЦАТЬ байт?!
Делайте выводы сами.
Когда в проекте столкнулся с необходимостью выводить информацию на кириллице (подразумевалось работа с меню), встал вопрос с работой в кодировках отличных от юникода. И в QtCreator обнаружилась замечательная возможность использовать пользовательские кодировки для выбранных файлов:
На этом тему заканчиваю. Надеюсь данная информация будет кому-то полезной, или хотя бы развлечет)) Just for fun!
ZIP-архив с исходниками обоих используемых в статье проектов, а также итоговый "sdcc.js" можно скачать здесь.
Я не планировал писать про использование Qbs для STM32 проектов, т.к. использую CMake, но мне показалось, что без этого тема использования Qbs будет раскрыта не полностью.
С одной стороны составвление Qbs-скрипта для STM32 проще чем для STM8, т.к. не придется допиливать скрипты Qbs. С другой стороны, в сравнении с STM8, проекты для ARM сложнее по структуре, за счет широкого использования библиотек: CMSIS, HAL, SPL, USB FS Device Lib, RTOS, и пр.
В качестве примера возьмем проект мигалки на CMSIS для Bluepill. Его структура будет выглядеть так:
$ tree . . ├── CMSIS │ ├── core │ │ ├── core_cm3.c │ │ └── core_cm3.h │ └── device │ ├── stm32f10x.h │ └── system_stm32f10x.h ├── SPL │ └── inc │ ├── stm32f10x_gpio.h │ └── stm32f10x_rcc.h ├── asm │ └── init.s ├── bluepill.qbs ├── inc │ └── main.h ├── main.c ├── script.ld └── src ├── my_misc.c └── startup.c 8 directories, 13 files
Исходники можно скачать здесь, сейчас не в них суть.
Здесь у нас четыре наших рабочих файла + qbs-скрипт. Остальные файлы нас не интересуют вообще, т.к. мы с ними работать не будем. Это библиотека CMSIS, заголовочные файлы от SPL и скрипт компоновщика. Тем не менее, они должны быть прописаны в проекте сборки прошивки.
На финальном этапе наш рабочий проект должен выглядеть как-то так:
Вместо того чтобы выкладывать финальный Qbs-скрипт с каким-то пояснениям, я бы хотел сделать пошаговый мануал, т.к. от него будет больше пользы.
Поэтому опять на какое-то время перейдем в консоль.
Самый простой Qbs-скрипт для данного проекта, который я смог составить, будет выглядеть так:
import qbs Product { type: ["application"] Depends { name: "cpp" } name: "firmware" cpp.cLanguageVersion: "c99" cpp.positionIndependentCode: false cpp.executableSuffix: ".elf" cpp.includePaths:[ "CMSIS/device", "CMSIS/core", "SPL/inc", "inc", ] cpp.driverLinkerFlags: [ "-specs=nosys.specs", "-specs=nano.specs", ] cpp.linkerFlags: [ "--gc-sections", "-T" + path + "/script.ld", ] cpp.cFlags: [ "-mthumb", "-mcpu=cortex-m3", ] cpp.defines: [ "STM32F10X_MD", "SYSCLK_FREQ_72MHz", ] files: [ "asm/init.s", "src/startup.c", "src/my_misc.c", "main.c", ] }
Ищем ARM профиль компиляции:
$ qbs config --list|grep arm
profiles.arm-none-eabi-gcc-10_3.cpp.archiverPath: "/usr/local/bin/arm-none-eabi-ar"
profiles.arm-none-eabi-gcc-10_3.cpp.assemblerPath: "/usr/local/bin/arm-none-eabi-as"
profiles.arm-none-eabi-gcc-10_3.cpp.nmPath: "/usr/local/bin/arm-none-eabi-nm"
profiles.arm-none-eabi-gcc-10_3.cpp.objcopyPath: "/usr/local/bin/arm-none-eabi-objcopy"
profiles.arm-none-eabi-gcc-10_3.cpp.stripPath: "/usr/local/bin/arm-none-eabi-strip"
profiles.arm-none-eabi-gcc-10_3.cpp.toolchainInstallPath: "/usr/local/bin"
profiles.arm-none-eabi-gcc-10_3.cpp.toolchainPrefix: "arm-none-eabi-"
profiles.arm-none-eabi-gcc-10_3.qbs.toolchainType: "gcc"
Собираем проект:
$ qbs build profile:arm-none-eabi-gcc-10_3 Build graph does not yet exist for configuration 'default'. Starting from scratch. Resolving project for configuration default WARNING: Could not detect target platform ('linux' given) Setting up build graph for configuration default Building for configuration default assembling init.s [firmware] compiling my_misc.c [firmware] compiling startup.c [firmware] compiling main.c [firmware] linking firmware.elf [firmware] Build done for configuration default.
Чтобы увидеть опции компиляции добавим параметр "--command-echo-mode command-line":
$ qbs clean && qbs build profile:arm-none-eabi-gcc-10_3 --command-echo-mode command-line Restoring build graph from disk Cleaning up for configuration default Restoring build graph from disk Building for configuration default /usr/local/bin/arm-none-eabi-as -g -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/2445be8241aabd34/init.s.o /mnt/tmp/mcu/qbs/stm32f103c8/asm/init.s /usr/local/bin/arm-none-eabi-gcc -g -O0 -Wall -Wextra -specs=nosys.specs -specs=nano.specs -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/f27fede2220bcd32/startup.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/src/startup.c /usr/local/bin/arm-none-eabi-gcc -g -O0 -Wall -Wextra -specs=nosys.specs -specs=nano.specs -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/f27fede2220bcd32/my_misc.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/src/my_misc.c /usr/local/bin/arm-none-eabi-gcc -g -O0 -Wall -Wextra -specs=nosys.specs -specs=nano.specs -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/3a52ce780950d4d9/main.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/main.c /usr/local/bin/arm-none-eabi-gcc -Wl,--gc-sections,-T/mnt/tmp/mcu/qbs/stm32f103c8/script.ld -specs=nosys.specs -specs=nano.specs -o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/firmware.elf /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/2445be8241aabd34/init.s.o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/3a52ce780950d4d9/main.c.o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/f27fede2220bcd32/my_misc.c.o /mnt/tmp/mcu/qbs/stm32f103c8/default/firmware.9bcf18e4/f27fede2220bcd32/startup.c.o Build done for configuration default.
Нам осталось внести пару правок в Qbs-файл проекта, и его можно уже будет загружать в QtCreator.
Во-первых, надо добавить профили компиляции Debug и Release:
import qbs
Product {
type: ["application"]
Depends { name: "cpp" }
name: "firmware"
cpp.cLanguageVersion: "c99"
cpp.positionIndependentCode: false
cpp.executableSuffix: ".elf"
cpp.includePaths:[
"CMSIS/device",
"CMSIS/core",
"SPL/inc",
"inc",
]
cpp.driverLinkerFlags: [
"-specs=nosys.specs",
"-specs=nano.specs",
]
cpp.linkerFlags:
[
"--gc-sections",
"-T" + path + "/script.ld",
]
cpp.cFlags: [
"-mthumb",
"-mcpu=cortex-m3",
]
cpp.defines: [
"STM32F10X_MD",
"SYSCLK_FREQ_72MHz",
]
Properties
{
condition: qbs.buildVariant === "debug"
cpp.defines: outer.concat(["DEBUG=1"])
cpp.debugInformation: true
cpp.optimization: "none"
}
Properties
{
condition: qbs.buildVariant === "release"
cpp.debugInformation: false
cpp.optimization: "fast"
}
files: [
"asm/init.s",
"src/startup.c",
"src/my_misc.c",
"main.c",
]
}
Кроме режимов оптимизации "fast" (-O2) и "none" доступен также "small" (-0s).
Проверяем:
$ qbs build config:release profile:arm-none-eabi-gcc-10_3 --command-echo-mode command-line Build graph does not yet exist for configuration 'release'. Starting from scratch. Resolving project for configuration release WARNING: Could not detect target platform ('linux' given) Setting up build graph for configuration release Building for configuration release /usr/local/bin/arm-none-eabi-as -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/2445be8241aabd34/init.s.o /mnt/tmp/mcu/qbs/stm32f103c8/asm/init.s /usr/local/bin/arm-none-eabi-gcc -O2 -Wall -Wextra -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DNDEBUG -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/f27fede2220bcd32/my_misc.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/src/my_misc.c /usr/local/bin/arm-none-eabi-gcc -O2 -Wall -Wextra -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DNDEBUG -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/main.c /usr/local/bin/arm-none-eabi-gcc -O2 -Wall -Wextra -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DNDEBUG -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/f27fede2220bcd32/startup.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/src/startup.c /usr/local/bin/arm-none-eabi-gcc -Wl,--gc-sections,-T/mnt/tmp/mcu/qbs/stm32f103c8/script.ld -o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/firmware.elf /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/2445be8241aabd34/init.s.o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/3a52ce780950d4d9/main.c.o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/f27fede2220bcd32/my_misc.c.o /mnt/tmp/mcu/qbs/stm32f103c8/release/firmware.9bcf18e4/f27fede2220bcd32/startup.c.o -specs=nosys.specs -specs=nano.specs Build done for configuration release.
Последнее что нам нужно сделать: конвертирование прошивки в BIN формат и печать размера прошивки. Для этого добавляем следующее Rule(правило):
import qbs Product { type: ["application","flash"] Depends { name: "cpp" } name: "firmware" cpp.cLanguageVersion: "c99" cpp.positionIndependentCode: false cpp.executableSuffix: ".elf" cpp.includePaths:[ "CMSIS/device", "CMSIS/core", "SPL/inc", "inc", ] cpp.driverLinkerFlags: [ "-specs=nosys.specs", "-specs=nano.specs", ] cpp.linkerFlags: [ "--gc-sections", "-T" + path + "/script.ld", ] cpp.cFlags: [ "-mthumb", "-mcpu=cortex-m3", ] cpp.defines: [ "STM32F10X_MD", "SYSCLK_FREQ_72MHz", ] Properties { condition: qbs.buildVariant === "debug" cpp.defines: outer.concat(["DEBUG=1"]) cpp.debugInformation: true cpp.optimization: "none" } Properties { condition: qbs.buildVariant === "release" cpp.debugInformation: false cpp.optimization: "fast" } files: [ "asm/init.s", "src/startup.c", "src/my_misc.c", "main.c", ] Rule { inputs: ["application"] Artifact { filePath: product.name fileTags: "flash" } prepare: { var size_name=input.filePath var argsObjcopy = ["-O", "binary", input.filePath, output.filePath+".bin"] var cmd_bin=new Command("arm-none-eabi-objcopy",argsObjcopy) var cmd_size=new Command("arm-none-eabi-size",size_name) cmd_bin.description = "convert to binary format" cmd_size.description = "print size of the firmware" return [cmd_bin,cmd_size] } } }
Проверем:
$ qbs clean config:debug && qbs build config:debug profile:arm-none-eabi-gcc-10_3 --command-echo-mode command-line Restoring build graph from disk Cleaning up for configuration debug Restoring build graph from disk Building for configuration debug /usr/local/bin/arm-none-eabi-as -g -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/2445be8241aabd34/init.s.o /mnt/tmp/mcu/qbs/stm32f103c8/asm/init.s /usr/local/bin/arm-none-eabi-gcc -g -O0 -Wall -Wextra -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG=1 -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/f27fede2220bcd32/my_misc.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/src/my_misc.c /usr/local/bin/arm-none-eabi-gcc -g -O0 -Wall -Wextra -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG=1 -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/f27fede2220bcd32/startup.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/src/startup.c /usr/local/bin/arm-none-eabi-gcc -g -O0 -Wall -Wextra -pipe -fvisibility=default -mthumb -mcpu=cortex-m3 -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG=1 -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/device -I/mnt/tmp/mcu/qbs/stm32f103c8/CMSIS/core -I/mnt/tmp/mcu/qbs/stm32f103c8/SPL/inc -I/mnt/tmp/mcu/qbs/stm32f103c8/inc -std=c99 -o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/3a52ce780950d4d9/main.c.o -c /mnt/tmp/mcu/qbs/stm32f103c8/main.c /usr/local/bin/arm-none-eabi-gcc -Wl,--gc-sections,-T/mnt/tmp/mcu/qbs/stm32f103c8/script.ld -o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/firmware.elf /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/2445be8241aabd34/init.s.o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/3a52ce780950d4d9/main.c.o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/f27fede2220bcd32/my_misc.c.o /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/f27fede2220bcd32/startup.c.o -specs=nosys.specs -specs=nano.specs /usr/local/bin/arm-none-eabi-objcopy -O binary /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/firmware.elf /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/firmware.bin /usr/local/bin/arm-none-eabi-size /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/firmware.elf Build done for configuration debug. /usr/local/bin/arm-none-eabi-size /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/firmware.elf text data bss dec hex filename 1324 8 1056 2388 954 /mnt/tmp/mcu/qbs/stm32f103c8/debug/firmware.9bcf18e4/firmware.elf
Данный проект уже сейчас можно открывать в QtCreator, но я бы сделал еще кое-что. На данный момент, у нас все рабочие файл свалены в одну кучу. Это:
files: [ "asm/init.s", "src/startup.c", "src/my_misc.c", "main.c", ]
Я бы предложил вместо данного списка файлов ввести группы. Тогда Qbs-скрипт примет такой, окончательный вид:
import qbs Product { type: ["application","flash"] Depends { name: "cpp" } name: "firmware" property string Home: path property string Inc: Home + "/inc" property string Src: Home + "/src" property string Asm: Home + "/asm" cpp.cLanguageVersion: "c99" cpp.positionIndependentCode: false cpp.executableSuffix: ".elf" cpp.includePaths:[ "CMSIS/device", "CMSIS/core", "SPL/inc", "inc", ] cpp.driverLinkerFlags: [ "-specs=nosys.specs", "-specs=nano.specs", ] cpp.linkerFlags: [ "--gc-sections", "-T" + path + "/script.ld", ] cpp.cFlags: [ "-mthumb", "-mcpu=cortex-m3", ] cpp.defines: [ "STM32F10X_MD", "SYSCLK_FREQ_72MHz", ] Properties { condition: qbs.buildVariant === "debug" cpp.defines: outer.concat(["DEBUG=1"]) cpp.debugInformation: true cpp.optimization: "none" } Properties { condition: qbs.buildVariant === "release" cpp.debugInformation: false cpp.optimization: "fast" } Group { name: "Assembly" fileTags: ["asm"] files: [ Asm + "/*.s", ] } Group { name: "Inc" files: [ Inc + "/*.h", ] } Group { name: "Src" files: [ Src + "/*.c", ] } files: [ "main.c", ] Rule { inputs: ["application"] Artifact { filePath: product.name fileTags: "flash" } prepare: { var size_name=input.filePath var argsObjcopy = ["-O", "binary", input.filePath, output.filePath+".bin"] var cmd_bin=new Command("arm-none-eabi-objcopy",argsObjcopy) var cmd_size=new Command("arm-none-eabi-size",size_name) cmd_bin.description = "convert to binary format" cmd_size.description = "print size of the firmware" return [cmd_bin,cmd_size] } } }
В массивах вида: file [Prefix + "/*.h"] можно использовать как перечень файлов, так и маски имен. На ваш вкус.
Теперь, прежде чем загружать проект в QtCreator, следует создать BareMetal Kit для микроконтроллеров STM32F103xx
Он создается аналогично таковому для STM8, но с одним отличием. Нам нужно будет указать файл в формате SVD с описанием периферийных регистров ввода-вывода:
Он нужен для того, чтобы в режиме отладки мы могли бы просматривать значения этих регистров. Данный файл берется с сайта Keil: https://www.keil.com/dd2/pack/
Скачанный пак является ZIP-архивом в котором содержатся среди прочего SVD-файлы. В архиве, который можно скачать по ссылке: stm8_qbs.zip, уже содержится файл "STM32F103xx.svd".
После этого открываем проект в QtCreator. Отладка должна работать сразу, для прошивки из профиля release создаем "custom executable":
Если нужна печать флагов компиляции при сборке проекта, в свойствах проекта, во вкладке "Build Steps" отмечаем чекбокс "Show command lines". Это нужно делать для обоих профилей компиляции, для Release и Debug:
При отладке если выбрать вкладку с периферийными регистрами, то в ней будет поначалу пустое окно. Нужно щелкнуть по нему провой кнопкой миши и выбрать группу периферийных регистров:
Теперь при трассировке у вас будет отображаться значения выбранной группы:
К сожалению, одновременно выбирается только одна группа РВВ.
С отладкой STM8 к сожалению все не так просто как хотелось бы, и часто OpenOCD отказывается запускаться. Выглядит это так:
$ openocd -f interface/stlink-dap.cfg -f target/stm8s105.cfg -c "init" -c "reset halt" Open On-Chip Debugger 0.11.0 Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html srst_only separate srst_gates_jtag srst_open_drain connect_deassert_srst Info : STLINK V2J29S7 (API v2) VID:PID 0483:3748 Info : Target voltage: 3.274194 Error: stlink_swim_enter_failed (unable to connect to the target)
Какой-то определенной причины. когда возникает данный бег я выявить пока не смог. Поначалу думал, что отладка для STM8 еще "слишком сырая". Потом я думал, что OpenOCD не поддерживает чипы STM8S207, и т.д. Определенно могу сказать, что от чипа это не зависит, она возникала и на stm8s105 и на stm8s103.
Решается проблема достаточно просто, прошивкой какого-нибудь простейшего Blink, возможно поможет очистка флеш-памяти. После этого отладка снова начинает работать, до следующего подключения платы с stlink к компьютеру.
Архив с файлам к статье stm8_qbs.zip