Данный сайт является зеркалом сайта www.count-zero.ru

Настройка: Eclipse, SW4STM32, STM32CubeIDE и Qt Creator, для отладки проекта на STM32F103C8 (BluePill)

разделы: STM32 , среда разработки , дата: 16 ноября 2019г.

Данную статью по содержанию можно разделить на две или три части. С одной стороны я хотел рассказать в ней о конфигурации ряда IDE основанных на кодовой базе Eclipse, т.к. когда я впервые настраивал подключение отладчика к Eclipse и SW4STM32, мне не показался этот процесс простым и "интуитивно понятным".

Вторая часть статьи в каком-то роде продолжает прошлогоднюю статью по STM32, Т.к. в качестве демонстрационного примера берётся проект из той статьи. Однако, одним заимствованием ограничиться не получилось, и для развития идеи, я описал недостатки данного проекта, и предложил более совершенный вариант Makefile'а.

Получившийся Makefile дал возможность перенести проект на систему сборки CMake. Это в свою очередь дало возможность продемонстрировать использование Qt Creator для программирования и отладки микроконтроллеров STM32.

В техническом плане, проект используемый в качестве примера, очень простой. Это обычный Blink, состоящий из Си-кода и ассемблере ARM. Из ресурсов микроконтроллера используется лишь порт ввода-вывода GPIO_C и системный таймер SysTick. В проекте не затрагиваются DMA, USB, FSMC и прочие интерфейсы. Также в стороне осталась библиотека newlib, поддержка языка программирования C++, и работа с проектами STM32CubeMX. Т.е. несмотря на доработку проекта в статье, он все ещё годится лишь для несложных задач, и более соответствует уровню микроконтроллеров с архитектурой Cortex-M0/M0+.

Используемые в статье Hardware и Software. В качестве операционной системы использовалась Slackware GNU/Linux (русские физики рекомендуют), в качестве целевого микроконтроллера - STM32F103C8T6 (Blue Pill). В качестве программатора и отладчика использовался китайский клон: "ST-Link v2", а также JTAG отладчик на чипе FT232H. Из софта, в качестве gdb сервера в статье используется: "OpenOCD", а в качестве флешера: "st-flash". Используемый туллчейн arm-none-eabi-gcc имеет версию - 8.3.1, релиз от 20190703. Версия CMake - 3.15.5. Qt Creator используемый при написании статьи был версий 4.9.2 и 4.10.2.

Должен предупредить, что свой ST-Linkv2 я покупал достаточно давно, и он на чипе STM32. Сейчас на али продаются программаторы ST-Linkv2 на чипе CKS32F103C8T6, и с ними могут быть нюансы.

Полезные материалы по теме статьи:

Содержание:

I. Отладка в Eclipse, SW4STM32 и STM32CubeIDE:

  1. Общая настройка
  2. Создание базового проекта
  3. Настройка параметров отладки (Debug)
  4. Настройка конфигурации Release
  5. Управление проектом в Eclipse
  6. Настройка SW4STM32
  7. Настройка STM32CubeIDE

II. "Допиливание" Makefile и создание на его основе CMake проекта:

  1. Использование Eclipse совместно со своим Makefile
  2. Ограничения используемого Makefile
  3. Устранение ограничений используемого Makefile
  4. Использование динамической памяти
  5. Eclipse + CMake

III. Использование Qt Creator для программирования и отладки микроконтроллеров STM32:

  1. Qt Creator + CMake + STM32

1) Eclipse IDE for C/C++ Developers - общая настройка

Актуальная на данный момент версия среды разработки Eclipse - это 2019-09 R (4.13.0). О ней далее и пойдёт речь.

Скачав инсталлятор с официального сайта, устанавливаем версию для разработки на языках программирования Си и Си++

После установки и запуска Eclipse нам откроется стандартное окно среды разработки:

Здесь посередине расположен редактор кода. Слева, справа и снизу - панели со стыкуемыми окнами. Их можно закреплять в любом порядке, на любой из трёх панелей. Перечень доступных окон можно найти в меню: Window->Show View. Сверху расположена панель инструментов - Toolbar.

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

Через маркетплейс устанавливаем ARM плагин (сейчас он переименован в MCU плагин):

Если вас интересует тёмная тема оформления - устанавливаем Darkest Dark Theme:

После установки последнего плагина, Eclipse будет выглядеть так:

И здесь видны главные "косяки" тёмной темы. Линейка меню осталась светлой, контекстное меню тоже светлое, выпадающие списки выглядят вот так:

Откроем через меню: Window->Preferences окно настроек, там в DevStyle можно настроить тему оформления:

Здесь же можно переключить обратно на светлую тему, если ваши глаза начали болеть от тёмной темы:

Мне самому более привычна светлая тема оформления, поэтому, в дальнейшем я буду использовать именно её.

В Code Style можно настроить автоформатирование текста под себя. По-умолчанию, предлагается четыре базовых темы автоформатирования на основе которых можно создать свою тему:

Здесь же можно настроить ширину отступа:

Чтобы настроить значки на тулбаре, следует зайти через меню: Window->Perspective->Customize Perspective и снять галочки с тех значков которые вам не нужны:

Чтобы убрать окошко быстрого поиска из тулбара, следует щёлкнуть по нему правой кнопкой мыши, и в контекстном меню выбрать: "Hide".

2) Создание базового проекта

Теперь нам нужно создать базовый проект который мы возьмём за основу. Для этого через меню: File->New->C/C++ Project открываем новый Си-проект:

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

Теоретически мы могли бы выбрать вариант "Hello World ARM Project", но там начинать проект пришлось бы с написания скрипта компоновщика под свой микроконтроллер. Кроме этого, пришлось бы делать ещё много черновой работы.

Следующее окно настройки шаблона проекта, в котором мы указываем семейство нашего микроконтроллера, объем оперативной и флеш-памяти, тип шаблона: мигалка или пустой шаблон (следует выбрать Empty, т.е. пустой шаблон), а также транспорт для вывода отладочных сообщений (Trace output):

На выводе отладочных сообщений следует остановиться по-подробнее. В качестве транспорта мы можем выбрать semihosting или использовать SWO пин, выхода на который нет в китайских программаторах ST-Link V2. И хотя в сети есть проекты по доработке программатора, мы этим заниматься не будем. Вместо этого мы будем использовать штатный semihosting. Из двух предлагаемых вариантов semihosting'а, я интуитивно выбрал, что-то обозначенное как DEBUG:

Ещё один момент - способ реализации вывода отладочных сообщений. По моим наблюдениями, Freestanding занимает меньше места флеше чем остальные:

Далее создание проекта сводится к нажиманию кнопки Next и только в последнем диалоговом окне надо будет ввести путь до ARM тулчейна. После создания проекта нужно будет собрать прошивку. Это можно будет сделать щёлкнув мышкой по молотку в тулбаре или же правой кнопкой мыши по проекту в Project Explorer слева, и в появившимся контекстном меню выбрать Build Project:

Если сборка прошла успешно, можно начинать писать тестовый проект мигалки. Для этого содержимое main.c приведём с следующему виду:

#include <stdio.h>
#include <stdlib.h>
#include "diag/Trace.h"
#include "stm32f10x_gpio.h"

void dummy_loop(uint32_t count);

// ----- main() ---------------------------------------------------------------

// Sample pragmas to cope with warnings. Please note the related line at
// the end of this function, used to pop the compiler diagnostics status.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#pragma GCC diagnostic ignored "-Wreturn-type"

int main(int argc, char* argv[]) {

#ifdef DEBUG
    // Show the program parameters (passed via semihosting).
    // Output is via the semihosting output channel.
    trace_dump_args(argc, argv);

    // Send a greeting to the trace device (skipped on Release).
    trace_puts("Hello ARM World!");

    // Send a message with use implementation of printf
    trace_printf("Arguments count: %d\n", argc);
#endif

    // enable GPIOC port
    RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
    // --- GPIO setup ----
    GPIOC->CRH &= ~(uint32_t)(0xf<<20);
    GPIOC->CRH |=  (uint32_t)(0x2<<20);

    while(1){
        GPIOC->BSRR=GPIO_Pin_13;
        dummy_loop(2600000);
        GPIOC->BRR=GPIO_Pin_13;
        dummy_loop(2600000);
    }
}

void dummy_loop(uint32_t count){
    while(--count);
}

#pragma GCC diagnostic pop

// ----------------------------------------------------------------------------

Это адаптированный пример из прошлогодней статьи, там он подробно разбирался. В данном случае в макроблок DEBUG были добавлены вызовы следующих функций: trace_dump_args(), trace_puts(), trace_printf(). Они отвечают за вывод отладочных сообщений через semihosting, их реализация находится в файле Trace.c, если кому интересно.

3) Настройка параметров отладки (Debug)

Теперь, когда у нас есть готовая прошивка, необходимо ее как-то загрузить на микроконтроллер. В Eclipse доступно два типа сборки прошивки: Debug и Release. Выбрать тип сборки можно кликнув правой кнопкой мыши по проекту в окне Project Explorer, и в появившимся контекстном меню последовательно выбираем:
Build Configuration->Set Active->тип сборки

Сборка Debug подразумевает работу с отладчиком. Соответственно, прошивка собирается с отладочной информацией и отладочными функциями для последующей отладки. В случае сборки Release прошивка компилируется без всякой отладочной информации, и в микроконтроллер она загружается минуя процесс отладки.

Для отладки STM32 в Eclipse есть поддержка OpenOCD. Страница с описанием настройки находится здесь: https://gnu-mcu-eclipse.github.io/debug/openocd/. В соответствие с этой инструкцией, делаем следующее:

В меню: Window->Preferences->MCU->Global OpenOCD Path указываем путь к OpenOCD:

Далее, правой кнопкой мыши щёлкаем по проекту в окне Project Explorer, и в появившимся контекстном меню последовательно выбираем: "Debug As ->Debug Configuration":

В окне конфигурации находим пункт "GDB OpenOCD Debugging", и щёлкаем по иконке сверху "New launch configuration":

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

Во вкладке Debugger нужно будет указать параметры запуска OpenOCD (Config options):

-f interface/stlink-v2.cfg -f target/stm32f1x_stlink.cfg

Также вы можете поменять название создаваемой конфигурации, если название задаваемое по-умолчанию вас чем-то не устраивает. После этого можно нажать Debug чтобы приступить к отладке. Естественно, что при этом ST-Link V2 c микроконтроллером должны быть уже подключены в компьютеру. После этого последует окно предупреждения перехода в отладочный режим (Debug perspective):

После этого произойдёт переключение в режим отладки:

Здесь хочу обратить внимание на окна: Memory, Disassembly и Registers. Окошко Memory у меня не работало. Поэтому если мне нужно было получить дамп памяти, я в Debugger Console вбивал что-то вроде: x/16xb адрес. В окне Disassembly вызовите контекстное меню. Через него можно добавить поля с адресами и оп-кодами. В окне Registers, если вам нужно отслеживать значения нескольких регистров, то выделите из одновременно.

Отладка производится обычным способом. Вывод отладочных сообщений посредством semihostinig'а будет производится в окно отладки - Console обведено синим). Т.е. если нам в процессе отладки нужно будет распечатать значения каких-то переменных, мы сделаем это через semihosting. Это снимает с нас необходимость возиться с UART'ом или "поднимать" через USB последовательный порт (Virtual Com Port). Для завершения отладки кликайте по красному квадратику, и не забудьте перейти в режим C++ на панели perspective (подчёркнуто синим). Снова войти в отладочный режим можно щёлкнув по зелёному жуку (подчёркнуто зелёным). Соответственно, в туллбаре должен быть выбран профиль DEBUG, и имя вашей отладочной конфигурации.

Если вы внимательно посмотрите на окошко дизассемблера, то увидите, что оптимизатор GCC выбросил из главного цикла вызов функции dummy_loop(). Если вам не по душе такие фокусы вы можете отключить оптимизатор в свойствах проекта, сменив оптимизацию -Og на -O0:

После этого следует очистить проект и пересобрать его заново. Запустив новую отладочную сессию, можно будет увидеть, что на этот раз, всё на своём месте:

Кроме ST-Link V2 для отладки можно использовать JTAG отладчик на чипе FT232H, о котором я рассказывал в прошлогодней статье: Отладка с помощью JTAG адаптера на чипе FT232H. Отличий в работе ST-Link V2 и FT232H я не заметил, semihosting одинаково работает там и там. Для добавления нового отладчика, открываем Debug Configuration, и дублируем конфигурацию отладчика ST-Link V2.:

Во вкладке Debugger, в Config options поменяйте строку с параметрами на:

-f /usr/local/share/openocd/scripts/ft232h.cfg -f target/stm32f1x.cfg

где ft232h.cfg - файл следующего содержания:

interface ftdi
ftdi_vid_pid 0x0403 0x6014
ftdi_layout_init 0x0c08 0x0f1b

После этого можно сохранять новую конфигурацию и запускать отладку.

4) Настройка конфигурации Release

Теперь следует настроить конфигурацию Release, при которой собирается финальная прошивка, и которая прошивается в микроконтроллер в Arduino-стиле, т.е. минуя стадию отладки.

Для этого в свойствах проекта, для сборки Release, нужно указать формат итоговой прошивки RAW, он же Binary:

Кроме этого, нам понадобится вспомогательный скрипт для прошивки микроконтроллера:

#!/usr/bin/bash
[ -f "$1" ] || { echo "Error: Firmware $1 not fount!"; exit 1; }
/usr/local/bin/st-flash  write "$1" 0x08000000

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

Поместим скрипт в файл с названием, скажем: "/usr/local/bin/stm32upload.sh". Так же ему потребуется дать права на выполнение:

chmod +x  /usr/local/bin/stm32upload.sh

Далее, возвращаемся к Eclipse, кликаем правой кнопкой мыши по проекту в окне Project Explorer, и в появившимся контекстном меню выбираем: Run as ->Run Configurations.... После чего откроется уже знакомое окно. В данном случае нас будет интересовать пункт: C/C++ Application. Щёлкнув по этому пункту нам следует создать новую конфигурацию. Для этого щёлкаем по значку "New launch configuration" сверху:

Для новой конфигурации придумаем название, а во вкладке Main, нужно найди поле C/C++ Application и вписать туда полный путь до ранее созданного скрипта:

Во вкладке Arguments указываем полное имя файла с прошивкой через переменные Eclipse: ${project_loc}/Release/${project_name}.bin. Переменные среды ${project_loc} и ${project_name} должны быть доступны через список вызываемый кнопкой Variables:

После этого нам надо будет отключить оптимизацию для типа сборки Release, чтобы GCC не выкидывал из кода функцию dummy_loop():

Затем, кликнув правой кнопкой мыши по проекту в окне Project Explorer, в контекстном меню меняем тип сборки с Debug на Release:

Теперь пересобираем проект. В данном случае прошивка будет весить ~1.5K вместо пяти килобайт с хвостиком при сборке с профилем Debug.

На туллбаре меняем профиль с Debug на Run. Если у вас единственный профиль Release, то он выберется автоматически. Если не совсем понятно, что речь идёт об поле с 00_blink Release на скриншоте:

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

5) Управление проектом в Eclipse

Попробуем немного развить проект, чтобы на нём показать основные приемы для работы с проектами в Eclipse.

Кликаем правой кнопкой мыши по предыдущему проекту 00_blink в окне Project Explorer, и в появившимся контекстном меню выбираем: Close Project. Этот проект мы больше трогать не будем.

Снова кликаем правой кнопкой мыши по проекту 00_blink в окне Project Explorer, и в появившимся контекстном меню выбираем: Copy. Далее кликнув по пустому полю в окне Project Explorer нажимаем Ctrl+V, после чего, в появившимся диалоговом окне задаём имя нового проекта, и жмём Copy:

В итоге получаем дубликат проекта, в котором сохраняются все файлы предыдущего проекта а также настройки проекта (Preferences), но вот конфигурации Debug и Release придётся создать заново. Хотя, конфигурацию Release можно будет заново не создавать, она универсальная, и будет доступна через выпадающий список на туллбаре. Так же при удалении проекта, вы предварительно должны удалять конфигурации Debug и Release проекта, что бы они не захламляли потом выпадающий список конфигураций на туллбаре.

Теперь попробуем добавить ассемблерный файл в проект с функцией задержки на одну миллисекунду. В этот файл впоследствии можно будет складывать обработчики прерываний написанные на ассемблере.

В окне Project Explorer щёлкаем правой кнопкой мыши по каталогу проекта src и в контекстном меню последовательно выбираем: New->Source File. В появившимся диалогом окне в качестве имени нового файла указываем asm.S:

Имя файла может быть любым на ваш вкус, а расширение должно быть обязательно заглавная S или asm:

Список расширений может редактироваться, но по умолчанию это asm или S.

Содержимое ассемблерного файла пусть пока будет таким:

.syntax unified
.cpu cortex-m3
.thumb

.global delay_ms
delay_ms:
    push {r1}
l1: mov.w r1,#10285     @ 72000/7
lp: subs r1,#1
    bne lp
    subs r0,#1
    bne l1
    pop {r1}
    bx lr

Из файла main.c убираем функцию dummy_loop(). В главном цикле вызов функции dummy_loop(2600000) заменяем на delay_ms(1000). В начале файла размещаем объявление функции: extern void delay_ms(uint32_t ms);. После этого очищаем проект и собираем заново. Должно работать. Теперь в опциях оптимизации проекта можно ставить флаги -Os или -Og, GCC не будет выбрасывать вызов функции delay_ms().

После компиляции и загрузки в плату "Blue Pill", светодиод должен начать мигать с полупериодом в одну секунду.

Аналогичным образом в проект добавляются исходные файлы на Си/Си++ или заголовочные файлы.

Теперь добавим в проект функцию задержки на таймере SysTick. Для этого скопируем в проект код из прошлогодней статьи: Функция задержки на прерывании таймера SysTick>. Т.о., содержимое файла main.c будет выглядеть так:

#include <stdio.h>
#include <stdlib.h>
#include "diag/Trace.h"
#include "stm32f10x_gpio.h"

extern void delay_ms(uint32_t ms);

// ----- main() ---------------------------------------------------------------

// Sample pragmas to cope with warnings. Please note the related line at
// the end of this function, used to pop the compiler diagnostics status.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#pragma GCC diagnostic ignored "-Wreturn-type"

static __IO uint32_t s_timer;

void SysTick_Handler(void)
{
        if (s_timer)
            s_timer--;
}

void systick_delay(__IO uint32_t val) {
    // ------- SysTick CONFIG --------------
    if (SysTick_Config(72000)) // set 1ms
    {
        while(1); // error
    }

    s_timer=val;

    while(s_timer) {
        asm("wfi");
    };

    SysTick->LOAD &= ~(SysTick_CTRL_ENABLE_Msk);    // disable SysTick
}

int main(int argc, char* argv[]) {

#ifdef DEBUG
    // Show the program parameters (passed via semihosting).
    // Output is via the semihosting output channel.
    trace_dump_args(argc, argv);

    // Send a greeting to the trace device (skipped on Release).
    trace_puts("Hello ARM World!");

    // Send a message with use implementation of printf
    trace_printf("Arguments count: %d\n", argc);
#endif

    // enable GPIOC port
    RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
    // --- GPIO setup ----
    GPIOC->CRH &= ~(uint32_t)(0xf<<20);
    GPIOC->CRH |=  (uint32_t)(0x2<<20);
    // Let's go..
    __enable_irq();

    while(1){
        GPIOC->BSRR=GPIO_Pin_13;
        systick_delay(1000);
        GPIOC->BRR=GPIO_Pin_13;
        systick_delay(1000);
    }
}

#pragma GCC diagnostic pop

// ----------------------------------------------------------------------------

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

6) Настройка SW4STM32

SW4STM32 базируется все на том-же Eclipse, но в отличии от него может импортировать проекты из STM32CubeMX. Когда-то это было здорово, но не так давно ST купила среду разработки TrueStudio и на основе его теперь выпускает STM32CubeIDE. Это тоже сделано на Eclipse, тоже бесплатно и тоже кроссплатформенно. Поэтому если вам требуется работать с STM32CubeMX, то лучше будет взять STM32CubeIDE. Однако, в SW4STM32 имеется хороший минималистический шаблонный проект, который на мой взгляд заслуживает внимания.

Для создания проекта, переходим по меню: "File->New->C Project" и запускаем мастер создания нового проекта. В диалоговом окне создания проекта нам нужно будет задать имя проекта и выбрать тулчейн:

Далее выбираем свой микроконтроллер:

В завершении следует выбрать фреймворк: SPL, HAL или без них. Если мастер проекта запускается впервые, то фреймворк придётся ещё скачать:

Что бы настроить работу с отладчиком, в Debug Configuration создаём новую конфигурацию выбрав для этого пункт: "Ac6 STM32 Debugging" и щёлкнув по иконке "New launch configuration":

После этого задаём имя конфигурации, а во вкладке "Main", в поле C/C++ Application нужно будет вписать:
Debug/имя_проекта.elf

Во вкладке Debugger нужно будет вписать пути до arm-none-eabi-grb, openocd, а также до скриптов stm32f1x.cfg и stlink-v2.cfg:

Для проверки в main.c напишем код мигалки:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"

// ----- main() ---------------------------------------------------------------

// Sample pragmas to cope with warnings. Please note the related line at
// the end of this function, used to pop the compiler diagnostics status.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#pragma GCC diagnostic ignored "-Wreturn-type"

static __IO uint32_t s_timer;

void SysTick_Handler(void)
{
    if (s_timer)
        s_timer--;
}

void systick_delay(__IO uint32_t val) {
    // ------- SysTick CONFIG --------------
    if (SysTick_Config(72000)) // set 1ms
    {
        while(1); // error
    }

    s_timer=val;

    while(s_timer) {
        asm("wfi");
    };

    SysTick->LOAD &= ~(SysTick_CTRL_ENABLE_Msk);    // disable SysTick
}

int main() {
    // enable GPIOC port
    RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
    // --- GPIO setup ----
    GPIOC->CRH &= ~(uint32_t)(0xf<<20);
    GPIOC->CRH |=  (uint32_t)(0x2<<20);
    // Let's go..
    __enable_irq();

    while(1){
        GPIOC->BSRR=GPIO_Pin_13;
        systick_delay(1000);
        GPIOC->BRR=GPIO_Pin_13;
        systick_delay(1000);
    }
}

#pragma GCC diagnostic pop

После чего можно будет щёлкать на зелёного жука на тулбаре. Мы должны перейти в окно отладки:

Здесь мне понравилось окно I/O registers с периферийными регистрами. В Eclipse такого окна нет.

Создание профиля запуска Release в SW4STM32 аналогично созданию такового в Eclipse, за тем исключением, что в SW4STM32 прошивка по умолчанию формируется в BIN формате, а не в iHEX как в Eclipse. Т.о. менять формат прошивки не требуется, все и так замечательно:

В целом, как я уже говорил, шаблонный проект SW4STM32 мне понравился своей компактностью. Скрипт компоновщика здесь занимает всего один файл вместо трёх в Eclipse, а системные вызовы реализованы в одном файле syscalls.c, в то время как в Eclipse для этого используется библиотека newlib-nano. Скрипт компоновщика после некоторой модификации я использовал в прошлогодней статье, где речь шла о создании минимальных по объёму прошивок и о программировании STM32 на регистрах. Конечно, здесь нет semihosting'а и реализации функции printf(), но в определённых случаях это можно считать и за плюс.

7) Настройка STM32CubeIDE

В начале 2018 года компания ST купила среду разработки Atollic TrueStudio за 7 млн. долларов. Почти сразу после покупки, ST выложила в свободный доступ (требовалось лишь регистрация на сайте фирмы) версию TrueStudio 9.0.1, которая: а) стала бесплатной; б) была выпилена поддержка сторонних микроконтроллеров (т.е. не STM32). На этом история развития TrueStudio собственно и закончилась, релиз 9.0.1 был объявлен финальным, а сам продукт белее не поддерживаемым компанией. TrueStudio переименовали в STM32CubeIDE, интегрировав в него мастер конфигурации аппаратной части микроконтроллера - STM32CubeMX.

Т.о. STM32CubeIDE - это фирменная бесплатная среда разработки с интегрированным STM32CubeMX и основанная на Eclipse. Последняя актуальная версия на сегодня - это 1.1.0. IDE базируется на Eclipse версии 4.13.0.v20190916-1323.

Окно STM32CubeMX реализовано в интерфейсе в виде ещё одного "Perspective" и доступно в правом углу туллбара наряду с окном редактора кода и окном отладочного режима:

Для создания проекта переходим последовательно по меню: "File->New->STM32 Project". Если это делается в первый раз после установки STM32CubeIDE, то среда какое-то время будет скачивать нужные файлы из сети, после чего откроется окно "куба", где нам нужно будет указать свой микроконтроллер:

В следующем диалоговом окне задаём имя проекта:

Потом последует окно в котором будет скачиваться HAL, если он ещё не скачан, после чего останется нажать кнопку Finish. В результате откроется окно проекта.

Для тестовой мигалки: a) в RCC нужно будет выставить кварц в качестве источника тактового сигнала; б) в SYS->Debug выбрать Serial Wire в качестве протокола отладки; в) ножку PC13 перевести в режим Output:

Для ножки PC_13 зададим метку "LED":

Далее настраиваем тактирование:

Чтобы сгенерировать проект, следует перейти в режим Perspective: C++ (правый угол на туллбаре), открыть какой-нибудь файл с исходным кодом, и когда станет активным "молоточек" на туллбаре - щёлкнуть по нему. Появится такое окошко:

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

Теперь в главный цикл помещаем код мигалки:

      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
      HAL_Delay(500);
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
      HAL_Delay(500);

После чего компилируем проект, и если всё обошлось без ошибок, настраиваем конфигурацию отладки.

Во вкладке Main все оставляем как есть:

Вкладка Debugger походит на аналогичную вкладку в SW4STM32:

И если все было настроено правильно, мы попадаем в окно отладки:

Создание профиля запуска Release в STM32CubeIDE аналогично созданию такового в Eclipse или SW4STM32, за небольшим исключением. В свойствах проекта нужно будет самому вписать команду для создания бинарного образа прошивки:

arm-none-eabi-objcopy -O binary "${BuildArtifactFileBaseName}.elf" "${BuildArtifactFileBaseName}.bin"

Хотя, технически, эту команду можно было бы перенести и в сам скрипт, разницы бы не было.

8) Eclipse + Makefile

К сожалению, Eclipse не даёт полного доступа к скриптам генерации проекта. К счастью, мы можем отказаться от использования в Eclipse системы сборки CDT (C/C++ Development Tools), и использовать Makefile. При этом я думаю, что должно быть понятно, что Мakefile и скрипты компоновщика придётся писать самим.

Для примера, перенесём в Eclipse проект из прошлогодней статьи: "5. Функция задержки на ассемблерных инструкциях". Исходные файлы проекта можно скачать с ресурса GitLab по адресу: https://gitlab.com/flank1er/stm32_bare_metal/tree/master/01_system_init. Для того что бы импортировать проект в Eclipse придётся немого изменить Makefile, но обо всем по порядку.

Через меню: "File ->New ->C/C++ Project" создаём новый проект. В качестве системы сборки указываем Makefile:

В следующем диалогом окне задаём имя проекта. Галочку напротив пункта: "Generate Source and Makefile" можете ставить или убирать по своему вкусу. Исходники как и Makefile у нас в любом случае будут свои:

После этого проще всего будет скопировать исходные файлы и скрипт компоновщика из https://gitlab.com/flank1er/stm32_bare_metal/tree/master/01_system_init в каталог проекта, после чего в Project Explorer нажмите F5, или в Project Explorer щёлкните правой кнопкой мыши по проекту, и затем в контекстном меню выберете: Refresh. После этого структура проект должна выглядеть так:

В директории build должны быть две директории для сборки с профилем run (директория default) и debug (директория make.debug.linux.x86_64). Последняя директория у вас может быть другой, это надо учитывать в Makefile, и приводить переменную RMPATH в соответствующий вид. Сам Makefile пусть будет таким:

PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
MCU=cortex-m3
CC=arm-none-eabi-gcc
LD=arm-none-eabi-ld
OBJCOPY=arm-none-eabi-objcopy
SIZE=arm-none-eabi-size
INC  = -I$(PROJECT_ROOT)CMSIS/device
INC += -I$(PROJECT_ROOT)CMSIS/core
INC += -I$(PROJECT_ROOT)SPL/inc
DEF = -DSTM32F10X_MD
DEF +=-DSYSCLK_FREQ_72MHz
CFLAGS=-mthumb -mcpu=$(MCU) -Werror $(DEF) $(INC)
ASFLAGS=-mthumb -mcpu=$(MCU) -Werror
LDFLAGS=-T$(PROJECT_ROOT)script.ld
TARGET=02_blink
OBJ = main.o init.o startup.o

ifeq ($(BUILD_MODE),debug)
	CFLAGS += -g
	DEF += -DDEBUG
	ASFLAGS += -g
	RMPATH = build/make.debug.linux.x86_64/
else ifeq ($(BUILD_MODE),run)
	CFLAGS += -O2
	ASFLAGS += -O2
	RMPATH = build/default/
else
	$(error Build mode $(BUILD_MODE) not supported by this Makefile)
endif

all:	$(OBJ)
	$(LD) $(LDFLAGS) -g  -o $(TARGET).elf  $(OBJ)
	$(OBJCOPY) -O binary $(TARGET).elf $(TARGET).bin
	$(SIZE)  $(TARGET).elf

%.o:	$(PROJECT_ROOT)%.c
	$(CC) -c -o $@ $< $(CFLAGS)
%.o:	$(PROJECT_ROOT)src/%.c
	$(CC) -c -o $@ $< $(CFLAGS)
%.o:	$(PROJECT_ROOT)asm/%.s
	$(CC) -c -o $@ $< $(ASFLAGS)

install:
	st-flash  write $(TARGET).bin 0x08000000
	
clean:
	rm -fr $(TARGET).elf $(TARGET).bin $(PROJECT_ROOT)$(RMPATH)$(OBJ)

Теперь, если вы будете переключать на туллбаре "Launch Mode" в Run или Debug, то проект будет собираться с тем или другим профилем:

С сожалению, не обходится без багов. Выражается это в том, что переключение между Run и Debug на туллбаре перестаёт работать. И проект каждый раз собирается в конфигурации Debug в директорию: build/make.debug.linux.x86_64. Частично проблему можно решить в начале Makefile прописав: BUILD_MODE=run. Это работает, но все-равно каталог сборки остаётся неизменным: build/make.debug.linux.x86_64. Поэтому когда меняется профиль сборки, следует очищать проект, чтобы не линковались объектные файлы собранные с иными опциями.

С учётом всего сказанного, профиль отладчика будет выглядеть так:

На вкладке Main нужно будет прописать относительный путь до файла прошивки, а вкладка Debugger заполняется аналогично вышеописанному:

Профиль для прошивки также заполняется аналогично типовому на Eclipse:

На вкладке аргументов главное правильно прописать каталоги. Если что-то идёт не так, добавьте в скрипт stm32upload.sh команду: echo "$1", чтобы видеть параметр который передаёт Eclipse скрипту. Меня это много раз выручало во время траблов.

Ещё хочу предупредить об одном глюке, который иногда проявляется при создании проектов на Makefile. Выглядит он так:

Синтаксический анализатор начинает тупить и подчёркивает все неизвестные ему типы, переменные, заголовочные файлы. Я не знаю отчего возникает такая проблема, частично ее можно решить, если открыть настройки анализатора через "Меню->Prefences->C/C++ ->Code Analisis" и снять галочки с чекбоксов: "Symbol is not resolved" и "Type cannot be resolved":

Данный глюк проступает когда создаётся дубликат проекта через: копировать - вставить. Поэтому если требуется создать дубликат проекта, то лучше сначала открыть пустой проект, а уже потом скопировать в него все необходимые файлы и каталоги, после чего обновить проект в Project Explorer командой Refresh (F5).

9) Ограничения используемого Makefile

На этом этапе следует обсудить вопрос: "Что не так с используемым нами Makefile и скриптом компоновщика из прошлогодней статьи?". Я нигде об этом упоминал, но составлял я их с целью получить минимальную по размеру прошивку. А согласно первому началу термодинамики известно, что если где-то прибыло, то значит, что и где-то убыло. Т.е. получив на выходе минимальную по размеру прошивку, мы сознательно отказались от некоторых возможностей языка Си. Иначе говоря, эти скрипты работают только при определённых условиях.

Что это за условия? Во-первых: не будет работать инициализация глобальных переменных. Она не будет работать по той простой причине, что в код прошивки не была добавлена функция копирования таких переменных из флеш-памяти (секция .data) в RAM.

Что бы наглядно убедиться в этом, добавим в программу проверочную функцию:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

extern void delay(uint32_t ms);
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int main()
{
    // enable GPIOC port
    RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
    // --- GPIO setup ----
    GPIOC->CRH &= ~(uint32_t)(0xf<<20);
    GPIOC->CRH |=  (uint32_t)(0x2<<20);
    //----------------------------
    for(int i=0;i<10;i++) {
        static int j=0;
        j += a[i];
        a[i]=j;
    }
    for(;;){
        GPIOC->BSRR=GPIO_Pin_13;
        delay(500);
        GPIOC->BSRR=GPIO_Pin_13;
        delay(500);
    }

}

Здесь проверочная функция выделена красным. Когда запускаем отладчик, становится очевидным, что наш массив int a[10] остался НЕинициализированным, и в переменную j записываются случайные данные. Так же НЕинициализируется сама переменная j, т.к. статические переменные фактически являются глобальными. Если бы мы перед началом цикла for() объявили j как локальную переменную: int j=0; то ее инициализация произошла бы успешно.

Может быть, вы можете предположить, что массив int a[10] не попал в ELF-файл? Давайте посмотрим:

$ arm-none-eabi-readelf -x .data ./02_blink.elf 

Hex dump of section '.data':
  0x20000000 00000000 01000000 02000000 03000000 ................
  0x20000010 04000000 05000000 06000000 07000000 ................
  0x20000020 08000000 09000000                   ........

Теперь через отладчик смотрим содержимое памяти в микроконтроллере:

И видим, что нашего массива там нет. Можно также разобрать алгоритм строки j+=a[i], и на пошаговой трассировке убедиться в том, что массив не инициализирован. Если скомпилировать программу без оптимизации, т.е. с ключом -O0, то алгоритм на ассемблере будет выполняться следующим образом:

Вторая проблема связанная с ограничением существующего Makefile заключается в том, что мы лишены возможности использования чисел с плавающей запятой. И если мы поменяем тип переменных массива a[10] и j на вещественный, то при компиляции получим банальную ошибку:

И хотя, без операций с плавающей точкой и инициализации глобальных переменных вполне можно прожить скажем на Cortex M0/M0+, для микроконтроллеров Cortex M3 и выше хотелось бы иметь нормально работающий туллчейн.

10) Устранение ограничений используемого Makefile

Для того, чтобы исправить ограничения принесённые в угоду минимализму, следует вернуть файлы взятые мною из стартового проекта SW4STM32 в их первоначальный вид. Первый - это скрипт компоновщика, а второй - ассемблерный файл startup_stm32.s

/**
  ******************************************************************************
  * @file      startup_stm32.s dedicated to STM32F103C8Tx device
  * @author    Ac6
  * @version   V1.0.0
  * @date      2019-10-15
  ******************************************************************************
  */


.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb

.global g_pfnVectors
.global Default_Handler

/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss

/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called.
 * @param  None
 * @retval : None
*/

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:
  ldr   r0, =_estack
  mov   sp, r0          /* set stack pointer */

/* Copy the data segment initializers from flash to SRAM */
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit

CopyDataInit:
  ldr r4, [r2, r3]
  str r4, [r0, r3]
  adds r3, r3, #4

LoopCopyDataInit:
  adds r4, r0, r3
  cmp r4, r1
  bcc CopyDataInit

/* Zero fill the bss segment. */
  ldr r2, =_sbss
  ldr r4, =_ebss
  movs r3, #0
  b LoopFillZerobss

FillZerobss:
  str  r3, [r2]
  adds r2, r2, #4

LoopFillZerobss:
  cmp r2, r4
  bcc FillZerobss

/* Call the clock system intitialization function.*/
  bl  SystemInit
/* Call static constructors */
  bl __libc_init_array
/* Call the application's entry point.*/
  bl main

LoopForever:
    b LoopForever


.size Reset_Handler, .-Reset_Handler

/**
 * @brief  This is the code that gets called when the processor receives an
 *         unexpected interrupt.  This simply enters an infinite loop, preserving
 *         the system state for examination by a debugger.
 *
 * @param  None
 * @retval : None
*/
    .section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
  b Infinite_Loop
  .size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The STM32F103C8Tx vector table.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
   .section .isr_vector,"a",%progbits
  .type g_pfnVectors, %object
  .size g_pfnVectors, .-g_pfnVectors


g_pfnVectors:
  .word _estack
  .word Reset_Handler
  .word NMI_Handler
  .word HardFault_Handler
  .word MemManage_Handler
  .word BusFault_Handler
  .word UsageFault_Handler
  .word 0
  .word 0
  .word 0
  .word 0
  .word SVC_Handler
  .word DebugMon_Handler
  .word 0
  .word PendSV_Handler
  .word SysTick_Handler
  .word WWDG_IRQHandler                     /* Window Watchdog interrupt                        */
  .word PVD_IRQHandler                      /* PVD through EXTI line detection interrupt        */
  .word TAMPER_IRQHandler                   /* Tamper interrupt                                 */
  .word RTC_IRQHandler                      /* RTC global interrupt                             */
  .word FLASH_IRQHandler                    /* Flash global interrupt                           */
  .word RCC_IRQHandler                      /* RCC global interrupt                             */
  .word EXTI0_IRQHandler                    /* EXTI Line0 interrupt                             */
  .word EXTI1_IRQHandler                    /* EXTI Line1 interrupt                             */
  .word EXTI2_IRQHandler                    /* EXTI Line2 interrupt                             */
  .word EXTI3_IRQHandler                    /* EXTI Line3 interrupt                             */
  .word EXTI4_IRQHandler                    /* EXTI Line4 interrupt                             */
  .word DMA1_Channel1_IRQHandler            /* DMA1 Channel1 global interrupt                   */
  .word DMA1_Channel2_IRQHandler            /* DMA1 Channel2 global interrupt                   */
  .word DMA1_Channel3_IRQHandler            /* DMA1 Channel3 global interrupt                   */
  .word DMA1_Channel4_IRQHandler            /* DMA1 Channel4 global interrupt                   */
  .word DMA1_Channel5_IRQHandler            /* DMA1 Channel5 global interrupt                   */
  .word DMA1_Channel6_IRQHandler            /* DMA1 Channel6 global interrupt                   */
  .word DMA1_Channel7_IRQHandler            /* DMA1 Channel7 global interrupt                   */
  .word ADC1_2_IRQHandler                   /* ADC1 and ADC2 global interrupt                   */
  .word USB_HP_CAN_TX_IRQHandler            /* USB High Priority or CAN TX interrupts           */
  .word USB_LP_CAN_RX0_IRQHandler           /* USB Low Priority or CAN RX0 interrupts           */
  .word CAN_RX1_IRQHandler                  /* CAN RX1 interrupt                                */
  .word CAN_SCE_IRQHandler                  /* CAN SCE interrupt                                */
  .word EXTI9_5_IRQHandler                  /* EXTI Line[9:5] interrupts                        */
  .word TIM1_BRK_IRQHandler                 /* TIM1 Break interrupt                             */
  .word TIM1_UP_IRQHandler                  /* TIM1 Update interrupt                            */
  .word TIM1_TRG_COM_IRQHandler             /* TIM1 Trigger and Commutation interrupts          */
  .word TIM1_CC_IRQHandler                  /* TIM1 Capture Compare interrupt                   */
  .word TIM2_IRQHandler                     /* TIM2 global interrupt                            */
  .word TIM3_IRQHandler                     /* TIM3 global interrupt                            */
  .word TIM4_IRQHandler                     /* TIM4 global interrupt                            */
  .word I2C1_EV_IRQHandler                  /* I2C1 event interrupt                             */
  .word I2C1_ER_IRQHandler                  /* I2C1 error interrupt                             */
  .word I2C2_EV_IRQHandler                  /* I2C2 event interrupt                             */
  .word I2C2_ER_IRQHandler                  /* I2C2 error interrupt                             */
  .word SPI1_IRQHandler                     /* SPI1 global interrupt                            */
  .word SPI2_IRQHandler                     /* SPI2 global interrupt                            */
  .word USART1_IRQHandler                   /* USART1 global interrupt                          */
  .word USART2_IRQHandler                   /* USART2 global interrupt                          */
  .word USART3_IRQHandler                   /* USART3 global interrupt                          */
  .word EXTI15_10_IRQHandler                /* EXTI Line[15:10] interrupts                      */
  .word RTCAlarm_IRQHandler                 /* RTC Alarms through EXTI line interrupt           */
  .word 0                                   /* Reserved                                         */
  .word TIM8_BRK_IRQHandler                 /* TIM8 Break interrupt                             */
  .word TIM8_UP_IRQHandler                  /* TIM8 Update interrupt                            */
  .word TIM8_TRG_COM_IRQHandler             /* TIM8 Trigger and Commutation interrupts          */
  .word TIM8_CC_IRQHandler                  /* TIM8 Capture Compare interrupt                   */
  .word ADC3_IRQHandler                     /* ADC3 global interrupt                            */
  .word FSMC_IRQHandler                     /* FSMC global interrupt                            */
  .word SDIO_IRQHandler                     /* SDIO global interrupt                            */
  .word TIM5_IRQHandler                     /* TIM5 global interrupt                            */
  .word SPI3_IRQHandler                     /* SPI3 global interrupt                            */
  .word UART4_IRQHandler                    /* UART4 global interrupt                           */
  .word UART5_IRQHandler                    /* UART5 global interrupt                           */
  .word TIM6_IRQHandler                     /* TIM6 global interrupt                            */
  .word TIM7_IRQHandler                     /* TIM7 global interrupt                            */
  .word DMA2_Channel1_IRQHandler            /* DMA2 Channel1 global interrupt                   */
  .word DMA2_Channel2_IRQHandler            /* DMA2 Channel2 global interrupt                   */
  .word DMA2_Channel3_IRQHandler            /* DMA2 Channel3 global interrupt                   */
  .word DMA2_Channel4_5_IRQHandler          /* DMA2 Channel4 and DMA2 Channel5 global interrupt */

/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/

    .weak   NMI_Handler
    .thumb_set NMI_Handler,Default_Handler

    .weak   HardFault_Handler
    .thumb_set HardFault_Handler,Default_Handler

    .weak   MemManage_Handler
    .thumb_set MemManage_Handler,Default_Handler

    .weak   BusFault_Handler
    .thumb_set BusFault_Handler,Default_Handler

    .weak   UsageFault_Handler
    .thumb_set UsageFault_Handler,Default_Handler

    .weak   SVC_Handler
    .thumb_set SVC_Handler,Default_Handler

    .weak   DebugMon_Handler
    .thumb_set DebugMon_Handler,Default_Handler

    .weak   PendSV_Handler
    .thumb_set PendSV_Handler,Default_Handler

    .weak   SysTick_Handler
    .thumb_set SysTick_Handler,Default_Handler

    .weak   WWDG_IRQHandler
    .thumb_set WWDG_IRQHandler,Default_Handler

    .weak   PVD_IRQHandler
    .thumb_set PVD_IRQHandler,Default_Handler

    .weak   TAMPER_IRQHandler
    .thumb_set TAMPER_IRQHandler,Default_Handler

    .weak   RTC_IRQHandler
    .thumb_set RTC_IRQHandler,Default_Handler

    .weak   FLASH_IRQHandler
    .thumb_set FLASH_IRQHandler,Default_Handler

    .weak   RCC_IRQHandler
    .thumb_set RCC_IRQHandler,Default_Handler

    .weak   EXTI0_IRQHandler
    .thumb_set EXTI0_IRQHandler,Default_Handler

    .weak   EXTI1_IRQHandler
    .thumb_set EXTI1_IRQHandler,Default_Handler

    .weak   EXTI2_IRQHandler
    .thumb_set EXTI2_IRQHandler,Default_Handler

    .weak   EXTI3_IRQHandler
    .thumb_set EXTI3_IRQHandler,Default_Handler

    .weak   EXTI4_IRQHandler
    .thumb_set EXTI4_IRQHandler,Default_Handler

    .weak   DMA1_Channel1_IRQHandler
    .thumb_set DMA1_Channel1_IRQHandler,Default_Handler

    .weak   DMA1_Channel2_IRQHandler
    .thumb_set DMA1_Channel2_IRQHandler,Default_Handler

    .weak   DMA1_Channel3_IRQHandler
    .thumb_set DMA1_Channel3_IRQHandler,Default_Handler

    .weak   DMA1_Channel4_IRQHandler
    .thumb_set DMA1_Channel4_IRQHandler,Default_Handler

    .weak   DMA1_Channel5_IRQHandler
    .thumb_set DMA1_Channel5_IRQHandler,Default_Handler

    .weak   DMA1_Channel6_IRQHandler
    .thumb_set DMA1_Channel6_IRQHandler,Default_Handler

    .weak   DMA1_Channel7_IRQHandler
    .thumb_set DMA1_Channel7_IRQHandler,Default_Handler

    .weak   ADC1_2_IRQHandler
    .thumb_set ADC1_2_IRQHandler,Default_Handler

    .weak   USB_HP_CAN_TX_IRQHandler
    .thumb_set USB_HP_CAN_TX_IRQHandler,Default_Handler

    .weak   USB_LP_CAN_RX0_IRQHandler
    .thumb_set USB_LP_CAN_RX0_IRQHandler,Default_Handler

    .weak   CAN_RX1_IRQHandler
    .thumb_set CAN_RX1_IRQHandler,Default_Handler

    .weak   CAN_SCE_IRQHandler
    .thumb_set CAN_SCE_IRQHandler,Default_Handler

    .weak   EXTI9_5_IRQHandler
    .thumb_set EXTI9_5_IRQHandler,Default_Handler

    .weak   TIM1_BRK_IRQHandler
    .thumb_set TIM1_BRK_IRQHandler,Default_Handler

    .weak   TIM1_UP_IRQHandler
    .thumb_set TIM1_UP_IRQHandler,Default_Handler

    .weak   TIM1_TRG_COM_IRQHandler
    .thumb_set TIM1_TRG_COM_IRQHandler,Default_Handler

    .weak   TIM1_CC_IRQHandler
    .thumb_set TIM1_CC_IRQHandler,Default_Handler

    .weak   TIM2_IRQHandler
    .thumb_set TIM2_IRQHandler,Default_Handler

    .weak   TIM3_IRQHandler
    .thumb_set TIM3_IRQHandler,Default_Handler

    .weak   TIM4_IRQHandler
    .thumb_set TIM4_IRQHandler,Default_Handler

    .weak   I2C1_EV_IRQHandler
    .thumb_set I2C1_EV_IRQHandler,Default_Handler

    .weak   I2C1_ER_IRQHandler
    .thumb_set I2C1_ER_IRQHandler,Default_Handler

    .weak   I2C2_EV_IRQHandler
    .thumb_set I2C2_EV_IRQHandler,Default_Handler

    .weak   I2C2_ER_IRQHandler
    .thumb_set I2C2_ER_IRQHandler,Default_Handler

    .weak   SPI1_IRQHandler
    .thumb_set SPI1_IRQHandler,Default_Handler

    .weak   SPI2_IRQHandler
    .thumb_set SPI2_IRQHandler,Default_Handler

    .weak   USART1_IRQHandler
    .thumb_set USART1_IRQHandler,Default_Handler

    .weak   USART2_IRQHandler
    .thumb_set USART2_IRQHandler,Default_Handler

    .weak   USART3_IRQHandler
    .thumb_set USART3_IRQHandler,Default_Handler

    .weak   EXTI15_10_IRQHandler
    .thumb_set EXTI15_10_IRQHandler,Default_Handler

    .weak   RTCAlarm_IRQHandler
    .thumb_set RTCAlarm_IRQHandler,Default_Handler

    .weak   TIM8_BRK_IRQHandler
    .thumb_set TIM8_BRK_IRQHandler,Default_Handler

    .weak   TIM8_UP_IRQHandler
    .thumb_set TIM8_UP_IRQHandler,Default_Handler

    .weak   TIM8_TRG_COM_IRQHandler
    .thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler

    .weak   TIM8_CC_IRQHandler
    .thumb_set TIM8_CC_IRQHandler,Default_Handler

    .weak   ADC3_IRQHandler
    .thumb_set ADC3_IRQHandler,Default_Handler

    .weak   FSMC_IRQHandler
    .thumb_set FSMC_IRQHandler,Default_Handler

    .weak   SDIO_IRQHandler
    .thumb_set SDIO_IRQHandler,Default_Handler

    .weak   TIM5_IRQHandler
    .thumb_set TIM5_IRQHandler,Default_Handler

    .weak   SPI3_IRQHandler
    .thumb_set SPI3_IRQHandler,Default_Handler

    .weak   UART4_IRQHandler
    .thumb_set UART4_IRQHandler,Default_Handler

    .weak   UART5_IRQHandler
    .thumb_set UART5_IRQHandler,Default_Handler

    .weak   TIM6_IRQHandler
    .thumb_set TIM6_IRQHandler,Default_Handler

    .weak   TIM7_IRQHandler
    .thumb_set TIM7_IRQHandler,Default_Handler

    .weak   DMA2_Channel1_IRQHandler
    .thumb_set DMA2_Channel1_IRQHandler,Default_Handler

    .weak   DMA2_Channel2_IRQHandler
    .thumb_set DMA2_Channel2_IRQHandler,Default_Handler

    .weak   DMA2_Channel3_IRQHandler
    .thumb_set DMA2_Channel3_IRQHandler,Default_Handler

    .weak   DMA2_Channel4_5_IRQHandler
    .thumb_set DMA2_Channel4_5_IRQHandler,Default_Handler

    .weak   SystemInit

/************************ (C) COPYRIGHT Ac6 *****END OF FILE****/

/*
******************************************************************************
**
**  File        : LinkerScript.ld
**
**  Author      : Auto-generated by Ac6 System Workbench
**
**  Abstract    : Linker script for STM32F103C8Tx Device from STM32F1 series
**                20Kbytes RAM
**                64Kbytes ROM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.
**
**  Target      : STMicroelectronics STM32
**
**  Distribution: The file is distributed as is, without any warranty
**                of any kind.
**
*****************************************************************************
** @attention
**
** <h2><center>&copy; COPYRIGHT(c) 2018 Ac6</center></h2>
**
** Redistribution and use in source and binary forms, with or without modification,
** are permitted provided that the following conditions are met:
**   1. Redistributions of source code must retain the above copyright notice,
**      this list of conditions and the following disclaimer.
**   2. Redistributions in binary form must reproduce the above copyright notice,
**      this list of conditions and the following disclaimer in the documentation
**      and/or other materials provided with the distribution.
**   3. Neither the name of Ac6 nor the names of its contributors
**      may be used to endorse or promote products derived from this software
**      without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*****************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x20005000;    /* end of RAM */

_Min_Heap_Size = 0;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Memories definition */
MEMORY
{
  RAM (xrw)     : ORIGIN = 0x20000000, LENGTH = 20K
  ROM (rx)      : ORIGIN = 0x8000000, LENGTH = 64K
}

/* Sections */
SECTIONS
{
  /* The startup code into ROM memory */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >ROM

  /* The program code and other data into ROM memory */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >ROM

  /* Constant data into ROM memory*/
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >ROM

  .ARM.extab   : {
    . = ALIGN(4);
    *(.ARM.extab* .gnu.linkonce.armextab.*)
    . = ALIGN(4);
  } >ROM

  .ARM : {
    . = ALIGN(4);
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
    . = ALIGN(4);
  } >ROM

  .preinit_array     :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
    . = ALIGN(4);
  } >ROM

  .init_array :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
    . = ALIGN(4);
  } >ROM

  .fini_array :
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
    . = ALIGN(4);
  } >ROM

  /* Used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections into RAM memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> ROM


  /* Uninitialized data section into RAM memory */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM



  /* Remove information from the compiler libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

Если посмотреть на код обработчика прерывания Reset, то по комментарим типа "Copy the data segment initializers from flash to SRAM" и "Zero fill the bss segment", можно предположить, что они производят инициализации секций .data и .bss.

Таким образом, у нас в проекте будет новый скрипт компоновщика, назовем его linker.ld. Вместо init.s у нас будет файл startup_stm32.s, кроме этого у нас появится ещё один ассемблерный файл, куда мы будем сбрасывать обработчики прерываний и функции на ассемблере. Назовем его my_asm.s.

Теперь задача - собрать наш проект. Это будет сделать не так просто, зато после модификации Makefile мы получим несколько дополнительных фишек.

Итак, пусть для начала наш Makefile не сильно отличается от вышеприведённого, изменён только перечень модулей и скрипт компоновщика:

PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
MCU=cortex-m3
CC=arm-none-eabi-gcc
LD=arm-none-eabi-ld
OBJCOPY=arm-none-eabi-objcopy
SIZE=arm-none-eabi-size
INC  = -I$(PROJECT_ROOT)CMSIS/device
INC += -I$(PROJECT_ROOT)CMSIS/core
INC += -I$(PROJECT_ROOT)SPL/inc
DEF = -DSTM32F10X_MD
DEF +=-DSYSCLK_FREQ_72MHz
CFLAGS=-mthumb -mcpu=$(MCU) -Werror $(DEF) $(INC)
ASFLAGS=-mthumb -mcpu=$(MCU) -Werror
LDFLAGS=-T$(PROJECT_ROOT)linker.ld
TARGET=03_blink
OBJ = main.o startup.o startup_stm32.o my_asm.o
RMPATH = build/make.debug.linux.x86_64/
ifeq ($(BUILD_MODE),debug)
	CFLAGS += -g -O0
	DEF += -DDEBUG
	ASFLAGS += -g
else ifeq ($(BUILD_MODE),run)
	CFLAGS += -O2
	ASFLAGS += -O2
else
	$(error Build mode $(BUILD_MODE) not supported by this Makefile)
endif

all:	$(OBJ)
	$(LD) $(LDFLAGS) -g  -o $(TARGET).elf  $(OBJ) 
	$(OBJCOPY) -O binary $(TARGET).elf $(TARGET).bin
	$(SIZE)  $(TARGET).elf

%.o:	$(PROJECT_ROOT)%.c
	$(CC) -c -o $@ $< $(CFLAGS)
%.o:	$(PROJECT_ROOT)src/%.c
	$(CC) -c -o $@ $< $(CFLAGS)
%.o:	$(PROJECT_ROOT)asm/%.s
	$(CC) -c -o $@ $< $(ASFLAGS)

install:
	st-flash  write $(TARGET).bin 0x08000000
	
clean:
	rm -fr $(TARGET).elf $(TARGET).bin $(PROJECT_ROOT)$(RMPATH)$(OBJ)

Однако при попытке сборки проекта с новым скриптом компоновщика, мы получим ошибку:

arm-none-eabi-ld: cannot find libc.a

Требуется стандартная библиотека языка Си (libc), которой раньше не требовалось. Можно попытаться добавить линковщику опцию --gc-sections, которая бы заставила его выкидывать неиспользуемый код, но эффекта это не принесёт. Решением будет использование GCC вместо LD для линковки проекта, а также указание опции -Wl,--gc-sections. Т.е. вместо:

	$(LD) $(LDFLAGS) -g  -o $(TARGET).elf  $(OBJ)

пишем:

	$(CC) $(LDFLAGS) -g  -o $(TARGET).elf $(OBJ) -Wl,--gc-sections

Замена линковщика на GCC, в дальнейшем позволит перейти на систему сборки CMake, т.к. CMake использует именно GCC для линковки проекта, и заставить его использовать LD, мне никак не удавалось.

Компилируем:

arm-none-eabi-gcc -c -o main.o /home/flanker/eclipse-workspace/03_blink/main.c -mthumb -mcpu=cortex-m3 -Werror -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG -I/home/flanker/eclipse-workspace/03_blink/CMSIS/device -I/home/flanker/eclipse-workspace/03_blink/CMSIS/core -I/home/flanker/eclipse-workspace/03_blink/SPL/inc -g -O0
arm-none-eabi-gcc -c -o startup.o /home/flanker/eclipse-workspace/03_blink/src/startup.c -mthumb -mcpu=cortex-m3 -Werror -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG -I/home/flanker/eclipse-workspace/03_blink/CMSIS/device -I/home/flanker/eclipse-workspace/03_blink/CMSIS/core -I/home/flanker/eclipse-workspace/03_blink/SPL/inc -g -O0
arm-none-eabi-gcc -c -o startup_stm32.o /home/flanker/eclipse-workspace/03_blink/asm/startup_stm32.s -mthumb -mcpu=cortex-m3 -Werror -g
arm-none-eabi-gcc -c -o my_asm.o /home/flanker/eclipse-workspace/03_blink/asm/my_asm.s -mthumb -mcpu=cortex-m3 -Werror -g
arm-none-eabi-gcc -T/home/flanker/eclipse-workspace/03_blink/linker.ld -g  -o 03_blink.elf main.o startup.o startup_stm32.o my_asm.o -Wl,--gc-sections
arm-none-eabi-objcopy -O binary 03_blink.elf 03_blink.bin
arm-none-eabi-size  03_blink.elf
   text	   data	    bss	    dec	    hex	filename
   1476	   1120	   1092	   3688	    e68	03_blink.elf
Build complete (0 errors, 0 warnings): /home/flanker/eclipse-workspace/03_blink/build/make.debug.linux.x86_64

И видим, что размер прошивки с 726 байт увеличился до почти полутора килобайт. Дополнительный код образовался не только за счёт содержимого модуля startup_stm32.s, но и за счёт библиотечных функций стандартной библиотеки. Их можно подавить используя ключи -nostartfiles и -nostdlib, но ничего хорошего из этого не выйдет, проверено. В данном случае выдаст ошибку: "init.c:(.text.__libc_init_array+0x44): undefined reference to `_init'"

Однако, и тут не все так гладко. Несмотря, на то, что программа успешно скомпилировалась, работать она не будет. Программа "зависает" в цикле "infinity loop" файла startup_stm32.s. Я не буду ходить вокруг да около, разглагольствуя о том, почему так происходит. Я лишь хотел показать перечень проблем при написании Makefile, а также показать почему один Makefile лучше другого, и наоборот. Поэтому я приведу готовой Makefile:

PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
MCU=cortex-m3
CC=arm-none-eabi-gcc
OBJCOPY=arm-none-eabi-objcopy
SIZE=arm-none-eabi-size
INC  = -I$(PROJECT_ROOT)CMSIS/device
INC += -I$(PROJECT_ROOT)CMSIS/core
INC += -I$(PROJECT_ROOT)SPL/inc
DEF = -DSTM32F10X_MD
DEF +=-DSYSCLK_FREQ_72MHz
CFLAGS=-mthumb -mfloat-abi=soft -mcpu=$(MCU) -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding  -Wall $(DEF) $(INC)
LDFLAGS=-mthumb -mfloat-abi=soft -mcpu=$(MCU) -T$(PROJECT_ROOT)linker.ld -Wl,-Map=output.map -Wl,--gc-sections
TARGET=03_blink
OBJ = main.o startup_stm32.o startup.o my_asm.o
RMPATH = build/make.debug.linux.x86_64/
ifeq ($(BUILD_MODE),debug)
	CFLAGS += -g3 -O0
	DEF += -DDEBUG
else ifeq ($(BUILD_MODE),run)
	CFLAGS += -O2
	RMPATH = build/default/
else
	$(error Build mode $(BUILD_MODE) not supported by this Makefile)
endif

all:	$(OBJ)
	$(CC) -g -O0 $(LDFLAGS)  -o $(TARGET).elf  $(OBJ) -lm
	$(OBJCOPY) -O binary $(TARGET).elf $(TARGET).bin
	$(SIZE)  $(TARGET).elf

%.o:	$(PROJECT_ROOT)%.c
	$(CC) -c -o $@ $< $(CFLAGS)
%.o:	$(PROJECT_ROOT)src/%.c
	$(CC) -c -o $@ $< $(CFLAGS)
%.o:	$(PROJECT_ROOT)asm/%.s
	$(CC) -c -o $@ $< $(ASFLAGS)

install:
	st-flash  write $(TARGET).bin 0x08000000

clean:
	rm -fr $(TARGET).elf $(TARGET).bin $(PROJECT_ROOT)$(RMPATH)$(OBJ)

Данный Makefile я составил по логу компиляции шаблонных проектов в Eclipse и SW4STM32.

В качестве проверочной программы пусть будет такой код:

#include <math.h>
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

extern void delay(uint32_t ms);
#ifdef DEBUG
float a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
#endif

int main()
{
    // enable GPIOC port
    RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
    // --- GPIO setup ----
    GPIOC->CRH &= ~(uint32_t)(0xf<<20);
    GPIOC->CRH |=  (uint32_t)(0x2<<20);
    //----------------------------
#ifdef DEBUG
    for(int i=0;i<10;i++) {
        static float j=0.f;
        j += a[i];
        j=sin(j*1.2f);
        a[i]=j;
    }
#endif
    for(;;){
        GPIOC->BSRR=GPIO_Pin_13;
        delay(500);
        GPIOC->BRR=GPIO_Pin_13;
        delay(500);
    }

}

Компилируем:

arm-none-eabi-gcc -c -o main.o /home/flanker/eclipse-workspace/03_blink/main.c -mthumb -mfloat-abi=soft -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding  -Wall -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG -I/home/flanker/eclipse-workspace/03_blink/CMSIS/device -I/home/flanker/eclipse-workspace/03_blink/CMSIS/core -I/home/flanker/eclipse-workspace/03_blink/SPL/inc -g3 -O0
arm-none-eabi-gcc -c -o startup_stm32.o /home/flanker/eclipse-workspace/03_blink/asm/startup_stm32.s 
arm-none-eabi-gcc -c -o startup.o /home/flanker/eclipse-workspace/03_blink/src/startup.c -mthumb -mfloat-abi=soft -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding  -Wall -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -DDEBUG -I/home/flanker/eclipse-workspace/03_blink/CMSIS/device -I/home/flanker/eclipse-workspace/03_blink/CMSIS/core -I/home/flanker/eclipse-workspace/03_blink/SPL/inc -g3 -O0
arm-none-eabi-gcc -c -o my_asm.o /home/flanker/eclipse-workspace/03_blink/asm/my_asm.s 
arm-none-eabi-gcc -g -O0 -mthumb -mfloat-abi=soft -mcpu=cortex-m3 -T/home/flanker/eclipse-workspace/03_blink/linker.ld -Wl,-Map=output.map -Wl,--gc-sections  -o 03_blink.elf  main.o startup_stm32.o startup.o my_asm.o -lm
arm-none-eabi-objcopy -O binary 03_blink.elf 03_blink.bin
arm-none-eabi-size  03_blink.elf
   text	   data	    bss	    dec	    hex	filename
  10240	   1120	   1092	  12452	   30a4	03_blink.elf
Build complete (0 errors, 0 warnings): /home/flanker/eclipse-workspace/03_blink/build/make.debug.linux.x86_64

И в этот раз все должно работать как надо (проверьте!):

11) Использование динамической памяти

Теперь нам нужно разобраться с такой штукой как динамическая память. Когда-то считалось, что это то, от чего должен должен бежать как черт от ладана любой мало-мальски квалифицированный программист микроконтроллеров. Однако времена меняются, и в микроконтроллерах становиться все больше памяти. К ним можно подключать все больше периферии, которая как известно любит эту память "кушать", и несмотря на то, что памяти становится больше, встаёт вопрос об эффективном управлении памятью. Динамическая память - это механизм который позволяет по ходу выполнения программы управлять памятью. А именно: выделять ее для чего-то и освобождать ее в последствии для чего-то другого.

Что не так с динамической памятью? Во-первых, она сложна в отладке. Ошибки при работе с динамической памятью не поддаются статическому анализу. Во-вторых, сам механизм управления будет занимать место в прошивке. Третья, самая большая проблема - это вероятность получить "неопределённое поведение". Происходит это так. В микроконтроллере есть три вида памяти: а) статическая память - это память различных переменных и массивов; б) динамическая память - о который собственно и речь; в) стековая память. Стековая память также меняется динамически по ходу выполнения программы. В результате всегда есть вероятность, что одна память перекроет другую. Особенно если вспомнить, что в ARM разрешено использовать регистр SP в качестве индексного для расположения локальных переменных "под" стеком.

Итак, если после всего прочтённого вам все ещё требуется динамическая память, то при попытке использовать malloc(), вы получите ошибку на этапе линковки:

sbrkr.c:(.text._sbrk_r+0xc): undefined reference to `_sbrk'

Чтобы ее исправить, в проект придётся перетащить файл syscalls.с из шаблонного проекта SW4STM32 (данный файл можно также взять из архива, доступном по ссылке в конце статьи). Он содержит реализацию системных вызовов, в том числе и sbrk.

Для проверки можно использовать такой исходник:

#include <math.h>
#include <stdlib.h>
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

#define SIZE 128

extern void delay(uint32_t ms);
#ifdef DEBUG
float a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
#endif

int main()
{
    // enable GPIOC port
    RCC->APB2ENR |= RCC_APB2Periph_GPIOC;
    // --- GPIO setup ----
    GPIOC->CRH &= ~(uint32_t)(0xf<<20);
    GPIOC->CRH |=  (uint32_t)(0x2<<20);
    //----------------------------
#ifdef DEBUG
    for(int i=0;i<10;i++) {
        static float j=0.f;
        j += a[i];
        j=sin(j*1.2f);
        a[i]=j;
    }
 // MALLOC testing
    char * ptr = NULL;
    ptr = (char*) malloc(SIZE);
    if (ptr != NULL) {
        // filling the array
        for(int i=0;i<SIZE;i++) {
            ptr[i]=(char)i;
        }
        // array check
        for(int i=0;i<SIZE;i++) {
            char j;
            j=ptr[i];
        }
        free(ptr);
    }
#endif

    for(;;){
        GPIOC->BSRR=GPIO_Pin_13;
        delay(500);
        GPIOC->BRR=GPIO_Pin_13;
        delay(500);
    }

}

После этого, в принципе все должно работать:

12) Eclipse + CMake

Теперь, когда мы разобрались с поддержкой стандартных возможностей языка Си в нашем проекте, можно переходить к системе сборке CMake. Для чего это надо? Во-первых, проекты CMake поддерживают многие редакторы кода, даже в MS Visual Studio начиная с Community 2017 есть поддержка CMake. Во-вторых, проект CMake можно конвертировать в проекты других IDE, например Sublime Text или CodeBlocks.

Сначала создадим консольный проект, который затем откроем в Eclipse. Структура проекта пусть будет аналогичной предыдущему проекту на Makefile. В корневой директории проекта разместим файл CMakeLists.txt следующего содержания:

cmake_minimum_required(VERSION 3.2)
project(05_cmake)
include_directories(${CMAKE_CURRENT_LIST_DIR}/inc)
include_directories(${CMAKE_CURRENT_LIST_DIR}/CMSIS/device)
include_directories(${CMAKE_CURRENT_LIST_DIR}/CMSIS/core)
include_directories(${CMAKE_CURRENT_LIST_DIR}/SPL/inc)
FILE(GLOB_RECURSE SPLFiles "SPL/inc/*.h")
FILE(GLOB_RECURSE CMSISFiles "CMSIS/*.h")


set(can_use_assembler TRUE)
enable_language(C ASM)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(POJECT_NAME 06_blink)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(SIZE arm-none-eabi-size)
set(CMAKE_STFLASH st-flash)
set(CWARN "-fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall")
set(CMCU "-mthumb -mfloat-abi=soft  -mcpu=cortex-m3")
set(CMAKE_C_FLAGS_DEBUG "-g3 -O0")
set(CMAKE_C_FLAGS "${CMCU} ${CWARN}")
set(LINKER_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/linker.ld")
set(CMAKE_EXE_LINKER_FLAGS "-T ${LINKER_SCRIPT} -Wl,-Map=output.map -Wl,--gc-sections")

add_definitions("-DSTM32F10X_MD -DSYSCLK_FREQ_72MHz")

if (CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_definitions("DEBUG")
endif()


set(SOURCE_EXE main.c asm/startup_stm32.s asm/my_asm.s src/startup.c src/syscalls.c)
add_executable(${PROJECT_NAME}.elf ${SOURCE_EXE})

add_custom_target(size COMMAND ${SIZE} ${PROJECT_NAME}.elf)

add_custom_target(upload
        COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
        COMMAND ${CMAKE_STFLASH} write ./${PROJECT_NAME}.bin  0x08000000
        )

Для проверки создадим директорию mybuild и из нее запускаем cmake:

$ cmake -DCMAKE_BUILD_TYPE=Debug ..

В результате должен появиться Makefile , и запустив команду make получим такой лог:

/usr/bin/cmake -S/home/flanker/mydev/tmp/06_cmake -B/home/flanker/mydev/tmp/06_cmake/mybuild --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles/progress.marks
make -f CMakeFiles/Makefile2 all
make[1]: вход в каталог «/home/flanker/mydev/tmp/06_cmake/mybuild»
make -f CMakeFiles/05_cmake.elf.dir/build.make CMakeFiles/05_cmake.elf.dir/depend
make[2]: вход в каталог «/home/flanker/mydev/tmp/06_cmake/mybuild»
cd /home/flanker/mydev/tmp/06_cmake/mybuild && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/flanker/mydev/tmp/06_cmake /home/flanker/mydev/tmp/06_cmake /home/flanker/mydev/tmp/06_cmake/mybuild /home/flanker/mydev/tmp/06_cmake/mybuild /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles/05_cmake.elf.dir/DependInfo.cmake --color=
Scanning dependencies of target 05_cmake.elf
make[2]: выход из каталога «/home/flanker/mydev/tmp/06_cmake/mybuild»
make -f CMakeFiles/05_cmake.elf.dir/build.make CMakeFiles/05_cmake.elf.dir/build
make[2]: вход в каталог «/home/flanker/mydev/tmp/06_cmake/mybuild»
[ 16%] Building C object CMakeFiles/05_cmake.elf.dir/main.c.o
arm-none-eabi-gcc -DDEBUG -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -g3 -O0   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/main.c.o   -c /home/flanker/mydev/tmp/06_cmake/main.c
/home/flanker/mydev/tmp/06_cmake/main.c: In function 'main':
/home/flanker/mydev/tmp/06_cmake/main.c:40:12: warning: variable 'j' set but not used [-Wunused-but-set-variable]
       char j;
            ^
[ 33%] Building ASM object CMakeFiles/05_cmake.elf.dir/asm/startup_stm32.s.o
arm-none-eabi-gcc -DDEBUG -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -g   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/asm/startup_stm32.s.o -c /home/flanker/mydev/tmp/06_cmake/asm/startup_stm32.s
[ 50%] Building ASM object CMakeFiles/05_cmake.elf.dir/asm/my_asm.s.o
arm-none-eabi-gcc -DDEBUG -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -g   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/asm/my_asm.s.o -c /home/flanker/mydev/tmp/06_cmake/asm/my_asm.s
[ 66%] Building C object CMakeFiles/05_cmake.elf.dir/src/startup.c.o
arm-none-eabi-gcc -DDEBUG -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -g3 -O0   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/src/startup.c.o   -c /home/flanker/mydev/tmp/06_cmake/src/startup.c
[ 83%] Building C object CMakeFiles/05_cmake.elf.dir/src/syscalls.c.o
arm-none-eabi-gcc -DDEBUG -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -g3 -O0   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/src/syscalls.c.o   -c /home/flanker/mydev/tmp/06_cmake/src/syscalls.c
[100%] Linking C executable 05_cmake.elf
/usr/bin/cmake -E cmake_link_script CMakeFiles/05_cmake.elf.dir/link.txt --verbose=1
arm-none-eabi-gcc -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -g3 -O0  -T /home/flanker/mydev/tmp/06_cmake/linker.ld -Wl,-Map=output.map -Wl,--gc-sections CMakeFiles/05_cmake.elf.dir/main.c.o CMakeFiles/05_cmake.elf.dir/asm/startup_stm32.s.o CMakeFiles/05_cmake.elf.dir/asm/my_asm.s.o CMakeFiles/05_cmake.elf.dir/src/startup.c.o CMakeFiles/05_cmake.elf.dir/src/syscalls.c.o  -o 05_cmake.elf 
make[2]: выход из каталога «/home/flanker/mydev/tmp/06_cmake/mybuild»
[100%] Built target 05_cmake.elf
make[1]: выход из каталога «/home/flanker/mydev/tmp/06_cmake/mybuild»
/usr/bin/cmake -E cmake_progress_start /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles 0

Здесь требуются некоторые пояснения. Флаг сборки -g, CMake вставляет самостоятельно, т.е. в конфиге CMakeLists.txt он нигде не прописан.

Далее запускаем команду: make help и видим такой вывод:

The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... edit_cache
... size
... rebuild_cache
... 05_cmake.elf
... upload
... asm/my_asm.o
... asm/startup_stm32.o
... main.o
... main.i
... main.s
... src/startup.o
... src/startup.i
... src/startup.s
... src/syscalls.o
... src/syscalls.i
... src/syscalls.s

Соответственно, что бы очистить проект нам нужно ввести make clean, чтобы узнать размер прошивки - make size, чтобы загрузить прошивку в микроконтроллер - make upload. Здесь думаю все понятно.

Теперь очистим директорию mybuild и заново запустим cmake с профилем Release:

$ cmake -DCMAKE_BUILD_TYPE=Release ..

После запуска make получим такой лог:

/usr/bin/cmake -S/home/flanker/mydev/tmp/06_cmake -B/home/flanker/mydev/tmp/06_cmake/mybuild --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles/progress.marks
make -f CMakeFiles/Makefile2 all
make[1]: вход в каталог «/home/flanker/mydev/tmp/06_cmake/mybuild»
make -f CMakeFiles/05_cmake.elf.dir/build.make CMakeFiles/05_cmake.elf.dir/depend
make[2]: вход в каталог «/home/flanker/mydev/tmp/06_cmake/mybuild»
cd /home/flanker/mydev/tmp/06_cmake/mybuild && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/flanker/mydev/tmp/06_cmake /home/flanker/mydev/tmp/06_cmake /home/flanker/mydev/tmp/06_cmake/mybuild /home/flanker/mydev/tmp/06_cmake/mybuild /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles/05_cmake.elf.dir/DependInfo.cmake --color=
Scanning dependencies of target 05_cmake.elf
make[2]: выход из каталога «/home/flanker/mydev/tmp/06_cmake/mybuild»
make -f CMakeFiles/05_cmake.elf.dir/build.make CMakeFiles/05_cmake.elf.dir/build
make[2]: вход в каталог «/home/flanker/mydev/tmp/06_cmake/mybuild»
[ 16%] Building C object CMakeFiles/05_cmake.elf.dir/main.c.o
arm-none-eabi-gcc  -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -O3 -DNDEBUG   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/main.c.o   -c /home/flanker/mydev/tmp/06_cmake/main.c
[ 33%] Building ASM object CMakeFiles/05_cmake.elf.dir/asm/startup_stm32.s.o
arm-none-eabi-gcc  -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -O3 -DNDEBUG   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/asm/startup_stm32.s.o -c /home/flanker/mydev/tmp/06_cmake/asm/startup_stm32.s
[ 50%] Building ASM object CMakeFiles/05_cmake.elf.dir/asm/my_asm.s.o
arm-none-eabi-gcc  -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -O3 -DNDEBUG   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/asm/my_asm.s.o -c /home/flanker/mydev/tmp/06_cmake/asm/my_asm.s
[ 66%] Building C object CMakeFiles/05_cmake.elf.dir/src/startup.c.o
arm-none-eabi-gcc  -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -O3 -DNDEBUG   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/src/startup.c.o   -c /home/flanker/mydev/tmp/06_cmake/src/startup.c
[ 83%] Building C object CMakeFiles/05_cmake.elf.dir/src/syscalls.c.o
arm-none-eabi-gcc  -I/home/flanker/mydev/tmp/06_cmake/inc -I/home/flanker/mydev/tmp/06_cmake/CMSIS/device -I/home/flanker/mydev/tmp/06_cmake/CMSIS/core -I/home/flanker/mydev/tmp/06_cmake/SPL/inc  -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -O3 -DNDEBUG   -DSTM32F10X_MD -DSYSCLK_FREQ_72MHz -o CMakeFiles/05_cmake.elf.dir/src/syscalls.c.o   -c /home/flanker/mydev/tmp/06_cmake/src/syscalls.c
[100%] Linking C executable 05_cmake.elf
/usr/bin/cmake -E cmake_link_script CMakeFiles/05_cmake.elf.dir/link.txt --verbose=1
arm-none-eabi-gcc -mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -Wall -O3 -DNDEBUG  -T /home/flanker/mydev/tmp/06_cmake/linker.ld -Wl,-Map=output.map -Wl,--gc-sections CMakeFiles/05_cmake.elf.dir/main.c.o CMakeFiles/05_cmake.elf.dir/asm/startup_stm32.s.o CMakeFiles/05_cmake.elf.dir/asm/my_asm.s.o CMakeFiles/05_cmake.elf.dir/src/startup.c.o CMakeFiles/05_cmake.elf.dir/src/syscalls.c.o  -o 05_cmake.elf 
make[2]: выход из каталога «/home/flanker/mydev/tmp/06_cmake/mybuild»
[100%] Built target 05_cmake.elf
make[1]: выход из каталога «/home/flanker/mydev/tmp/06_cmake/mybuild»
/usr/bin/cmake -E cmake_progress_start /home/flanker/mydev/tmp/06_cmake/mybuild/CMakeFiles 0

Здесь -DNDEBUG и ключ оптимизации -O3, cmake также вставляет самостоятельно. В зависимости от версии Cmake его поведение может отличаться, имейте это в виду. Я использовал версию 3.15.5.

Чтобы открыть проект в Eclipse есть два пути. Первый - это создание нового проекта из существующего CMake проекта. Второй - это преобразование Cmake проекта в Eclipse проект, с последующим импортом этого проекта в Eclipse.

В первом случае следует удалить директорию mybuild и скопировать проект в директорию проектов Eclipse. После чего, через меню File->New->Project->C/C++ Project в диалоговом окне выбирается создание CMake проекта из существующего:

Далее вводим директорию проекта, после чего жмём Finish:

После этого проект появится в Project Explorer, его можно будет сразу же собрать:

Создание профиля отладки и сама отладка не отличается от таковой в обычном проекте Eclipse. Управление проектом, осуществляется через редактирование CMakeLists.txt файла.

13) Qt Creator + CMake + STM32

Qt Creator IDE - это достойная альтернатива Eclispe при программирования микроконтроллеров STM32. В отличии от Eclipse, это нативное приложение и никакой Java для работы ему не нужно. В результате этого, Qt Creator по скорости отклика, на мой взгляд, в несколько раз превосходит Eclipse. Особенно это ощущается при работе с большими текстами программами, в несколько тысяч строк кода. Кроме того, Qt Creator визуально выглядит приятнее чем Eclipse, что тоже немаловажно.

Версия моего Qt Creator - 4.9.2:

Начиная с версии 4.0 в Qt Creator появилась тёмная тема оформления, и на мой взгляд вполне приличная, поэтому в скриншотах я буду использовать именно ее.

Для отладки с помощью Qt Creator нам понадобится отладчик arm-none-eabi-gdb c поддержкой python. Проверить, поддерживает ли ваш отладчик python, можно зайдя в него, и далее нужно ввести команду:

(gdb) python print("Hello, world!")
Hello, world!
(gdb)

В туллчене, который распространяется на сайте arm.com, имеется arm-none-eabi-gdb-py, но в моей системе этот отладчик не работал, возможно вам повезёт больше. Я же собрал нужный мне GDB из исходников, впрочем как и сам Qt Creator.

Настройка Qt Creator для работы с микроконтроллерами описана в следующем руководстве: Connecting Bare Metal Devices. Следуя этому руководству, первым делом следует включить плагин "Bare Metal". Плагины доступны через меню: Help->About Plugins...

Плагин "Bare Metal" необходим для осуществления отладки непосредственно в среде Qt Creator. Писать код и загружать прошивку в микроконтроллер можно и без него.

После активации плагина "Bare Metal", Qt Creator нужно будет перезапустить. Затем этот плагин следует настроить. Для этого через меню: Tools->Options->Devices заходим во вкладку: "Bare Metal". Внизу будет выпадающий список "Add", через который можно будет добавить OpenOCD качестве сервера GDB:

Дальнейшую настройку можно выполнить двумя способами. В первом, самом простом случае, в "Startup mode" выбираем "No Startup". Это будет означать, что запускать OpenOCD придется перед отладкой самому в консоли:

В случае, если указать "Startup in TCP/IP mode", то OpenOCD запускаться будет автоматически. Что бы это работало, нужно будет заполнить поля конфигурации примерно таким образом:

В качестве "Configuration file" следует вписать "/usr/share/openocd/scripts/interface/stlink-v2.cfg".

Теперь в разделе "Kits", во вкладке "Debuggers" следует указать отладчик. Как я уже говорил, он обязательно должен быть с поддержкой Python:

Во вкладке "CMake" должен быть указан путь до утилиты CMake:

И в последней вкладке "Kits" нужно будет создать профиль со следующими параметрами:

Обратите, внимание, что в качестве Cи и Си++ компиляторов следует указать штатные компиляторы системы, а не кросс-компиляторы комплекта arm-none-eabi-ххх которые используются при компиляции прошивки. Иначе при разборе CMakeLists.txt файла выдаст ошибку:

Параметр "CMake Configuration" я не трогал, оставил установленным по умолчанию. Я приведу его значение на всякий случай, т.к. системы у всех разные, а из-за неверной настройки могут быть проблемы с импортом CMake проекта:

CMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx}
CMAKE_C_COMPILER:STRING=%{Compiler:Executable:C}
CMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX}
QT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable}

На этом с конфигурацией Qt Creator закончено, далее нам следует импортировать наш CMake проект. Для этого, через меню: "File->Open File or Project" открываем диалоговое окно, находим каталог проекта, и открываем CMakeLists.txt проекта:

Далее выбираем созданный нами профиль "Bare Metal", после чего жмём на кнопку "Configure Project":

В случае успеха, CMake выдаст соответствующее сообщение:

А в окне проекта появится полная структура нашего проекта:

Огромным преимуществом Qt Creator перед Eclipse является то, что Qt Creator парсит CMakeLists.txt файл. Например, в файле CMakeLists.txt поменяем строку:

project(05_cmake)

на:

project(05_baremetal)

сохраняем файл и в окне проекта, и видим, что имя проекта изменилось:

Так же все происходит с опциями компиляции, списками файлов и т.д. Qt Creator сразу подхватывает изменения CMakeLists.txt. После Eclipse это впечатляет, правда. Теперь посмотрим на файл main.c. Выглядеть он будет так:

Здесь Qt Creator серым выделил блоки DEBUG и это значит, что профиль компиляции у нас не работает. Чтобы исправить ситуацию, сменим профиль "Default" на "Debug":

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

Ещё одним преимуществом Qt Creator является интеграция с различными системами контроля версий:

Я предлагаю воспользоваться Git, и открыть репозиторий проекта. Для этого проходим через меню: "Tools->Git->Create Repository" и выбираем каталог репозитория. В данном случае я выбрал каталог проекта:

Чтобы оставить первый коммит, выбираем через меню: "Tools->Git->Local Repository->Commit". Далее в диалоговом окне заполняем параметры коммита:

Теперь, вместо того чтобы множить сущности и "клонировать" проекты с теми или иными "фичами", можно будет использовать бранчи Git.

Следующим преимуществом Qt Creator является интеграция со средствами статического анализа кода. Для этого доступны Clang, Сppheck, и Valgrind:

Если судить по количеству warrning'ов: cppheck- более простой, а Clang имеет более мощный анализатор кода:

Здесь синее предупреждение - это Cppheck, а жёлтые предупреждения - это Сlang. Valgrind - это инструмент для поиска ошибок при работе с динамической памятью, но я не уверен что он умеет работать с embedded проектами. Да и статическим анализатором он не является.

Обилие предупреждений по скриншоте выше вызвано тем, что у нас в опциях компиляции не задан явно используемый стандарт языка Си. Используется версия компилятора по умолчанию, и соответственно в другом компиляторе, проект может не собраться. Для этого достаточно будет воспользоваться с компилятором без поддержки стандартов C99 и выше. Что бы задать используемый стандарт языка Си, в CMakeLists.txt заменим строку:

set(CMCU "-mthumb -mfloat-abi=soft  -mcpu=cortex-m3")

на:

set(CMCU "-mthumb -mfloat-abi=soft  -mcpu=cortex-m3 -std=gnu11")

После сохранения изменений в CMakeLists.txt файле, и последующей автоматической переконфигурации проекта, код станет почище:

Чтобы убрать верхнее жёлтое предупреждение, достаточно будет поставить служебное слово static перед объявлением массива. Чтобы исправить строку с синусом, нужно будет вспомнить про приведение типов, и переписать ее например так:

        j=(float)sin((double)(j*1.2f));

Синее предупреждение о неиспользуемой переменной, убирать не надо, т.к. это не баг, а фича.

Для демонстрации, я сымитировал пару ошибок, при которых проект благополучно собирается, но анализаторы тем не менее видят их:

В первом случае я сымитировал выход за границы массива. Сначала я сделал это через доступ к массиву (квадратные скобки), и эту ошибку заметили. В следующей строке я воспроизвёл её через указатели. И в этом случае статические анализаторы оказались бессильны. Вторая ошибка заключается в повторном вызове функции free(). Она была успешно обнаружена. Все ошибки были обнаружены с помощью Cppcheck.

Теперь, чтобы нам запустить процесс отладки, следует поставить точку остановки в начале программы, подключить программатор с микроконтроллером к компьютеру, и затем щёлкнуть по кнопке с жуком:

После этого Qt Creator перейдёт в режим отладки:

В этом режиме станет доступна стандартная панель трассировки программы, она подчёркнута красным, а вспомогательные окна можно будет вызвать через меню: "Window->Views":

В принципе мы получили стандартный отладочный интерфейс, но на мой взгляд более удобный, чем в Eclipse. Из минусов я бы назвал отсутствие доступа к консоли GDB, но если вспомнить, что консоль GDB использовалась в Eclipse для просмотра дампов памяти, то в Qt Creator такой проблемы нет.

Чтобы переключить на режим дизассемблера или обратно на исходный код, нужно нажать соответствующую кнопку на панели отладки:

К сожалению, я не нашёл способа как можно "прикрутить" подсветку ассемблерного листинга. В настройках имеется автоматическая подгрузка файлов подсветки синтаксиса, но она как водится не работает ;)

Что бы посмотреть дамп памяти, нужно щёлкнуть правой кнопкой мыши по регистру с нужным адресом и в открывшимся контекстном меню выбрать просмотр или редактирование ячеек памяти:

В случае если вы выбрали просмотр дампа памяти, то откроется такое окошко:

При наведении курсора мыши всплывает подсказка с номером ячейки памяти и ее значением.

Последнее, с чем осталось разобраться - это использование профилей компиляции: Debug, Release, и прошивка микроконтроллера минуя процесс отладки.

В Qt Creator простая и при этом мощная система работы с профилями компиляции, и в отличии от Eclipse, здесь ничего не глючит. Я уже показывал где переключаются профили компиляции, на левой панели снизу:

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

После парсинга файла CMakeLists.txt, Qt Creator составляет табличку с профилем проекта, куда заносятся директивы которые он нашёл в процессе парсинга CMakeLists.txt. Если поставить галочку "Advanced", то можно будет посмотреть эти опции, и при желании - скорректировать.

Через опции: Build Steps, Clean Steps, Build Environment можно управлять процессом сборки проекта. Например, мы хотим чтобы при сборке проекта показывало размер полученной прошивки. В CMakeLists.txt файле печать размера прошивки доступна через команду make size:

add_custom_target(size COMMAND ${SIZE} ${PROJECT_NAME}.elf)

Чтобы добавить эту команду к процессу компиляции, в разделе Build Steps нужно щёлкнуть по Add Build Step и в выпадающем списке выбрать - Build:

Это будет означать что мы хотим добавить одну из команд Makefile'а. Если же выбрать Custom Process Step, то можно будет добавить вообще запуск любой программы.

Но вернёмся к печати размера прошивки. Далее в появившемся списке следует отметить чекбокс size. После этого можно свернуть список щёлкнув по Detals:

Никаких кнопок "OK" искать не надо, изменения вступают в силу "на лету".

Теперь после компиляции мы видим размер прошивки:

Тоже самое можно сделать и для профиля компиляции Release:

Для профиля Release можно было бы в процесс сборки добавить и "make upload" для прошивки микроконтроллера . Но это было бы не совсем корректно. Более правильно будет "посадить" прошивку микроконтроллера на кнопку запуска, зеленый треугольник. Сделать это можно как минимум двумя способами.

Если мы зайдём в конфигурацию "Run Settings", то увидим примерно такую картинку:

Первый способ состоит в использовании Deployment Method. Второй способ заключается в задании Custom Run Configuration. В последнем случае мы просто-напросто запускаем какую-либо программу для прошивки нашего микроконтроллера. И тут можно вспомнить про скрипт stm32upload.sh, который использовали в Eclipse. В качестве параметра он принимает абсолютный путь файла прошивки, поэтому его следует скопировать (на скриншоте подчёркнуто красным).

Скрипт stm32upload.sh приведём к такому виду:

#!/usr/bin/bash
echo "$1"
[ -e "$1" ] || { echo "Error: Firmware $1 not fount!"; exit 1; }
/usr/local/bin/arm-none-eabi-objcopy -O binary "$1" /tmp/firmware.bin && /usr/local/bin/st-flash  write /tmp/firmware.bin 0x08000000

В принципе, процесс создания BIN'арного файла прошивки можно было бы поставить в этап компиляции, но и так тоже ничего.

Теперь, через кнопку Add нужно добавить Custom Executable, у которого в опциях вписываем файл с прошивкой и указываем скрипт в качестве выполняемой команды:

Запускать скрипт в терминале или нет, решайте сами.

После этого выбираем профили: а) для компиляции - Release; б) для запуска - Custom Executable;

После этого жмём зеленый треугольник или на клавиатуре "Ctrl+R", и перед нами появится лог успешной прошивки:

Второй способ заключается в использовании команды make upload. Чтобы "привязать" ее к кнопке запуска, можно воспользоваться настройкой Deployment method. К сожаления, для профиля на основе плагина "Bare Metal" он не доступен, но мы можем воспользоваться обычным десктопным профилем (профиль "Bare Metal" нужен только для осуществления отладки). Все опции сборки проекта у нас в хранятся в файле СMakeLists.txt.

Итак, открываем "Run Settings" десктопного Run профиля и добавляем Deplay Configuration. Работать она будет так. Сначала запускается make, после чего вызывается заданная нами программа. Т.к. в нашем случае будет достаточно вызова make, и т.к. указание программы обязательно, то в качестве программы зададим печать в консоль слова "Success":

После этого добавим Build Deploy Step и в выпадающем списке отметим чекбокс upload:

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

Теперь при щелчке по зелёному треугольнику, будет запускаться прошивка микроконтроллера:


На этом мне хотелось бы закончить свою затянувшуюся статью. Остались не рассмотренными: Sublime Text, VSCode и Segger Embedded Studio. По Sublime_Text есть статья на Хабре: Sublime Text как IDE для ARM на примере STM32, я не хотел бы ее повторять. По VSCode у меня ещё не сложилось какого-либо мнения, а Segger Embedded Studio мне не понравился чисто визуально. В принципе, мне нравится Qt Creator, и на этом я бы хотел остановиться.

Скачать workspace для Eclipse с исходниками используемыми в статье можно здесь: скачать