[Home] [Donate!] [Контакты]

Минимальное C++ приложение для микроконтроллера ARM (Cortex-M3)

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

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

Оглавление
Минимальное C++ приложение для микроконтроллера ARM (Cortex-M3)
Введение
Процесс запуска программы
CMSIS
Создание нового проекта в IDE
Файловая структура проекта
Анализ минимального приложения
Отладка
Смотрите также
Начинаем работу с микроконтроллером
GPIO: порты ввода/вывода общего назначения: зажигаем светодиод
Исключения и прерывания

Введение

Для работы нам потребуется микроконтроллер и средства разработки. Начинать эксперименты с микроконтроллерами проще всего, используя какую-либо оценочную плату. Это избавляет от хлопот с пайкой; не нужно самостоятельно заботиться об обеспечении микроконтроллера правильным питанием, о кварцевой стабилизации. Оценочная плата уже содержит основные внешние цепи, необходимые для нормальной работы микроконтроллера - как раз то, что нужно, чтобы сразу приступить к экспериментам.

Какой именно выбран микроконтроллер - не столь важно, особенно если идёт речь о минимальном приложении. Пусть это будет устройство на основе ядра Cortex-M3, а именно STM32F100RBT6B, в составе оценочной платы STM32VLDISCOVERY с интегрированным отладчиком ST-Link на борту. Тактовая частота микроконтроллера - до 24 МГц; имеется встроенный RC-генератор с заводской подгонкой на 8 МГц, flash 128 Кб, RAM 8 Кб, 12-бит ADC, 12-бит двухканальный DAC, несколько многофункциональных таймеров, множество интерфейсов и многое другое. Может быть, по современным меркам не самый актуальный выбор, но на самом деле, этот микроконтроллер способен на гораздо большее, чем выполнение минимальное приложения.

В качестве программного средства разработки используется Atollic TrueSTUDIO (for STM32), бесплатная среда для STM32 разработчиков. Как базирующаяся на Eclipse, эта система является достаточно удобной и будет понятна всем, кто ранее имел дело с Eclipse.

Минимальная программа чрезвычайно проста, она состоит из единственного пользовательского файла, содержащего определение единственной функции main

/*  File: main.cpp
    Минимальная программа для микроконтроллера
    STM32F100RB
*/
#include "stm32f10x.h"
int main()
{
    while(true){}
}
//подробнее...

Тем не менее, эта программа достойна достаточно подробного теоретического обсуждения.

Процесс запуска программы

Как обычно, как это принято в C/C++, программа должна иметь функцию с именем main, которая является точкой входа в программу, с неё начинается выполнение пользовательского кода. Однако, вызову main предшествует много интересного.

Рассмотрим, каким образом происходит запуск программы в микроконтроллере, если рассматривать этот процесс на низком уровне.

В процессорах Cortex-M в памяти адресу 0x0 должна быть расположена таблица векторов прерываний. Каждый элемент таблицы является 32-битовым (4-байтовым) значением - адресом обработчика исключения. Самый первый элемент - начальное значение указателя стека (R13 или SP). Затем следует вектор сброса (по адресу 0x4), далее - векторы всех остальных исключений. Как минимум, таблица должна содержать 4 элемента: значение SP и векторы - сброса, исключения NMI, исключения HardFault. Остальные исключения не могут возникнуть, пока не будут явно разрешены и если работа с ними не требуется, создавать их обработчики и помещать их адреса в таблицу векторов прерываний необязательно.

Карта распределения памяти
..... Прочее
0x20001FFF
.....
0x20000000
RAM (в данном случае объём 8Кб = 0x2000 байт)
..... Зарезервировано
..... Свободная часть FLASH FLASH
..... Остальной код программы
HardFault_Handler: Код обработчика исключения
NMI_Handler: Код обработчика исключения
Reset_Handler: Стартовый код: выполнение программы начинается отсюда
..... Остальные векторы
0x0000000C Вектор для HardFault_Handler
0x00000008 Вектор для NMI_Handler
0x00000004 Вектор сброса (Reset_Handler)
0x00000000 Начальное значение SP=0x20002000 (располагаем стек в конце доступной области RAM).

После сброса процессора (например, по включению питания), он загружает значение по адресу 0x0 в указатель стека. Ранняя инициализация указателя стека необходима для того, чтобы процессор мог обрабатывать исключения с самого начала работы.

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

Процессор Cortex-M3 допускает изменение положения таблицы векторов (например, стартовый код может сформировать таблицу в RAM и затем настроить процессор на новое расположение таблицы). Но для старта по-прежнему будет необходима хотя бы минимальная таблица из 4-х элементов по нулевому адресу.

Существенно меняет положение дел работа процессора под управлением отладчика. Здесь есть возможность полностью контролировать процесс старта. Допустим, можно остановить процессор, загрузить таблицу векторов прерываний и программу в RAM, скорректировать SP, скорректировать регистр SCB->VTOR, задающий положение таблицы с векторами прерываний и запустить программу с первой инструкции стартового кода. Таким образом, можно полностью отлаживать программу в RAM-памяти, не трогая содержимого FLASH.

У микроконтроллеров STM32F100xx основная Flash-память начинается с адреса 0x0800 0000, соответственно программы для запуска из Flash для них компонуются с расчётом на размещение по этому адресу. В то же время любой Cortex-M процессор после сброса ожидает обнаружить таблицу векторов прерывания по адресу 0x0. Каким образом тогда стартует STM32F100xx?

Загрузка микроконтроллера происходит с помощью механизма доступа к памяти по альтернативным адресам. В STM32F100xx подключением выводов BOOT1, BOOT0 к уровням логического 0 или 1, можно выбрать один из трёх режимов начальной загрузки.

Выводы выбора режима загрузки Режим загрузки Совмещение адресных пространств
BOOT1 BOOT0
x 0 Из основной Flash-памяти Основная Flash-память используется как загрузочная область
0 1 Из системной памяти Системная память используется как загрузочная область
1 1 Из SRAM Встроенная SRAM используется как загрузочная область

Если выбран режим загрузки из Flash, то основная Flash-память будет доступна как по адресам из адресного пространства, где она действительно находится (начиная с адреса 0x0800 0000), так и по адресам из адресного пространства загрузочной области (начиная с 0x0). Можно сказать, что происходит отображение области Flash на загрузочную область. То есть, обращаясь по адресу 0x0, процессор получит верное значение из Flash-памяти, физически расположенное по адресу 0x0800 0000, а, например, считывая значение вектора сброса по адресу 0x4, получит значение из Flash по адресу 0x0800 0004, где действительно будет помещен адрес точки входа в программу и т.д. со всеми векторами. Таким образом, микроконтроллеру обеспечивается возможность использовать таблицу векторов прерываний. В то же время, доступ по действительным адресам Flash сохраняется, так что на выполнение кода такое отображение не влияет.

Режим загрузки из системной памяти делает системную память по адресу 0x1FFF F000 доступной также по адресам загрузочной области (0x0). Содержимое системной памяти задано производителем, там содержится код для прошивки Flash-памяти через интерфейс USART1.

Трудно сказать, в каких случаях полезен режим отображения SRAM на загрузочную область. Но точно нет необходимости его использовать для отладки в RAM! Отладка в RAM производится при включённом обычном режиме загрузки из Flash. Как было сказано ранее, правильный старт микроконтроллера в случае отладки обеспечивается средствами отладчика.

CMSIS

Структура программы для Cortex-M3.
Рис. %img:ps

Проект программы для микроконтроллера включает в себя, как минимум, один файл с исходным кодом пользователя с реализацией функции int main(). Кроме того, требуется стартовый код, который отвечает за формирование таблицы векторов прерываний, обеспечивает начальную инициализацию микроконтроллера и инициализацию среды выполнения программы в соответствии с соглашениями языка C++. От пользователя не требуется самому разрабатывать стартовый код. Для того чтобы включить стартовый код в программу, следует использовать CMSIS.

CMSIS - the Cortex Microcontroller Software Interface Standard - стандарт программного интерфейса микроконтроллеров с ядром Cortex. Является уровнем абстрагирования от оборудования (HAL, Hardware Abstraction Layer) для процессоров из серии Cortex-M и обеспечивает изоляцию программного обеспечения от особенностей реализации микроконтроллера.

CMSIS состоит из нескольких компонентов: CMSIS-CORE, CMSIS-Driver и др. Стартовый код, (а также доступ к ядру процессора и периферии) обеспечивается компонентом CMSIS-CORE.

Компонент CMSIS-CORE реализован как библиотека, состоящая из части, общей для всех устройств данной архитектуры (входит в состав пакета ARM::CMSIS Pack, доступного на сайте ARM) и процессоро-зависимой части (реализация файлов из этой части библиотеки зависит от особенностей процессора, файлы предоставляются производителем). Процессоро-зависимые файлы в своих именах содержат название устройства, для которого они предназначены. На схеме эта часть имени условно обозначена <device>. Например, для микроконтроллера STM32F100RB, файлы, обозначенные на схеме как startup_<device>.s, system_<device>.h, system_<device>.c, <device>.h названы разработчиком startup_stm32f10x_md_vl.s; system_stm32f10x.h; system_stm32f10x.c, stm32f10x.h. Хорошая среда разработки содержит в себе файлы CMSIS для большого количества устройств что, конечно, значительно упрощает работу.

В соответствии с CMSIS, проект должен содержать кроме файлов с кодом пользователя следующие файлы:

  1. startup_<device>.s - этот файл отвечает за формирование таблицы векторов прерываний в коде и загрузку правильного значения в указатель стека; обеспечивает для всех исключений обработчик по умолчанию (как правило, это бесконечный цикл) с использованием механизма "слабого" связывания: если на этапе компоновки обнаружится обработчик исключения, реализованный пользователем, в таблицу векторов будет помещён адрес этого обработчика, иначе будет использоваться обработчик по умолчанию; также здесь реализован стартовый код - обработчик сброса, который инициализирует секцию инициализированных данных в RAM, обнуляет секцию неинициализированных данных, вызывает функцию SystemInit() (функция определена в файле system_<device>.c) для конфигурирования микроконтроллера, вызывает функции для инициализации в соответствии с требованиями языка C++ (например, конструируются глобальные объекты) и в конечном итоге вызывает функцию main(), с которой начинается выполнение основного кода пользователя.
  2. system_<device>.c - содержит, как минимум, реализацию функций void SystemInit(), void SystemCoreClockUpdate(), определяет переменную uint32_t SystemCoreClock. Функция void SystemInit() вызывается из стартового кода (смотри файл startup_<device>.s) и конфигурирует микроконтроллер: настраивает систему тактирования (после сброса тактирование происходит от внутреннего RC-генератора, функция обеспечивает переключение на тактирование от генератора, стабилизированного внешним кварцем, настраивает PLL - систему фазовой автоподстройки частоты, позволяющей задать коэффициент умножения частоты, в т.ч. и нецелый); обновляет переменную SystemCoreClock (переменная содержит значение тактовой частоты ядра, в герцах); при наличии внешней шины для подключения внешней RAM, также конфигурируется эта шина.
  3. system_<device>.h - содержит прототипы функций, определённых в system_<device>.c.

В соответствии с требованиями CMSIS, указанные выше 3 файла должны быть именно скопированы в каталог проекта, так как стандарт CMSIS допускает редактирование их содержимого пользователем. Конечно, без особой необходимости их лучше не трогать - даже если их код не идеален, они имеют неоспоримое достоинство - они работают. Зато весьма познавательно будет ознакомиться с документацией производителя или просмотреть содержимое этих файлов - файлы самодокументированы с помощью комментариев. Особый интерес представляют system_<device>.c и <device>.h. Иногда разработчики предусматривают возможность конфигурирования кода с помощью средств препроцессора. Практически это осуществляется заданием на уровне проекта соответствующих символов препроцессора. В командной строке компилятора символы задаются с помощью ключа "-D": "-Dname[=value]"; если используется IDE, символы задаются в свойствах проекта в разделе Compiler/Symbols.

Пример: для устройства STM32F100RB файлы system_<device>.c и <device>.h имеют имена system_stm32f10x.c, stm32f10x.h.
Предупреждение: детали реализации файлов и способы конфигурирования зависят от версий файлов!

Файлы хорошо комментированы. Изучая stm32f10x.h узнаём, что для компиляции требуется задать символ препроцессора, определяющий тип используемого микроконтроллера (так как файл является универсальным для группы устройств). Для STM32F100RB это будет STM32F10X_MD_VL.

Стартовый код написан из предположения, что к микроконтроллеру подключён кварцевый резонатор на частоту 8 МГц, при этом в процессе инициализации система тактирования микроконтроллера настраивается на частоту 24 МГц (более высокая частота по сравнению с частотой генератора, стабилизированного кварцем, получается за счёт использования PLL). Если это не то, что требуется, значит это тот случай, когда файл из CMSIS нужно исправить.

Если планируется запуск программы из RAM, то требуется задать символ VECT_TAB_SRAM, иначе будет использоваться таблица векторов прерываний из FLASH-памяти, соответственно обработка исключений не будет работать правильно, возникновение исключений приведёт к непредсказуемым результатам.

И, наконец, последнее что потребуется для компиляции проекта - файл вида <device>.h и файлы core_xxx.h из каталога CMSIS\Include (для Cortex-M3 это core_cm3.h, core_cmFunc.h, core_cmInstr.h). Эти файлы можно не копировать в проект, они могут храниться и использоваться централизованно (пользователь не должен их редактировать); достаточно добавить каталоги, в которых файлы находятся в качестве путей поиска заголовочных файлов для компилятора. Однако, среды разработки, как правило, копируют и эти файлы в каталог проекта. Такой подход имеет свои достоинства: упрощается распространение проекта в исходных кодах и устраняется риск сделать свои старые проекты неработоспособными после очередного обновления CMSIS.

Файл <device>.h - процессоро-зависимый файл, часть CMSIS, содержит определения для доступа к периферийным устройствам процессора; содержит определения номеров всех прерываний для всех исключений и прерываний процессора. Например, для микроконтроллера STM32F100RB файл будет иметь имя stm32f10x.h. Даже если определения из файла не применяются в коде пользователя, этот файл необходим для компиляции, так как он требуется для компиляции system_<device>.c. Впрочем, трудно представить себе реальную программу, написанную без использования средств доступа к периферии из <device>.h. Простой пример. Фрагмент программы, написанный без использования средств CMSIS:

*(uint32_t*)(0x40011010) = 0x00000200;

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

К счастью, CMSIS даёт возможность писать более наглядный код и избавляет от необходимости помнить адреса регистров всех периферийных устройств. В CMSIS устройство описывается как структура, регистры - члены этой структуры. Доступ к регистрам осуществляется как доступ к членам структуры по указателю на неё. Например, для устройства - порта ввода-вывода GPIOC, файл stm32f10x.h содержит примерно следующий код (это не фрагмент файла, а эквивалентный код, иллюстрирующий концепцию):

// Регистры описываются как volatile,
// для предотвращения нежелательной оптимизации.
#define __IO volatile

// Тип GPIO_TypeDef описывает тип и
// относительное расположение регистров
// устройства GPIO.
typedef struct
{
    __IO uint32_t CRL;
    __IO uint32_t CRH;
    __IO uint32_t IDR;
    __IO uint32_t ODR;
    __IO uint32_t BSRR;
    __IO uint32_t BRR;
    __IO uint32_t LCKR;
} GPIO_TypeDef;

// Адрес области, отведённой под периферию.
#define PERIPH_BASE           ((uint32_t)0x40000000)
// Адрес области памяти для периферийных
// устройств, подключённых к шине APB2.
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
// Адрес области памяти, выделенной для устройства GPIOC.
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
// Указатель на структуру для доступа к устройству GPIOC.
#define GPIOC                 ((GPIO_TypeDef *) GPIOC_BASE)

Такой же подход используется для определения всех периферийных устройств микроконтроллера. Кроме того, файл содержит определения констант. Например, константа для установки бита 9 с использованием регистра BSRR определена как

#define GPIO_BSRR_BS9    ((uint32_t)0x00000200)

Тогда предложенную ранее строку кода можно записать в виде

GPIOC->BSRR = GPIO_BSRR_BS9;

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

Файл core_cm3.h (включает файлы core_cmFunc.h, core_cmInstr.h; в зависимости от используемого ядра будет иметь имя core_cm0.h для Cortex-M0, core_cm0plus.h для Cortex-M0+ и т.д.) - не зависимая от производителя часть CMSIS, этот заголовочный файл обеспечивает доступ к ядру процессора; определяет встраиваемые функции для доступа к инструкциям процессора из C/C++ кода. Даже если непосредственно эти возможности в коде пользователя не применяются, файл необходим для компиляции, так как он является включаемым для файла <device>.h, а файл <device>.h является включаемым для файла system_<device>.c, а этот файл определяет функцию void SystemInit(), которая вызывается из файла startup_<device>.s, который необходим, так как содержит стартовый код.

Создание нового проекта в IDE

Рассмотрен процесс создания проекта для Atollic TrueSTUDIO for ARM v.5.4.0, может отличаться для других версий.

Из меню вызываем мастера для создания нового C++ проекта:
File>New>C++ Project

1. Страница мастера "C++ Project"

Project name - имя проекта.
Location - расположение создаваемого проекта в файловой системе.
Эти два поля определяют имя каталога <Location>\<Project name>, куда будет помещено всё содержимое проекта. Например, если: Project name = minimal, Location = x:\prj\arm, тогда для проекта будет создан каталог x:\prj\arm\minimal.

Выбираем тип проекта (Project type). Можно выбрать Empty Project или Embedded C++ Project. Если выбрать создание пустого проекта, то потребуется довольно много ручных действий по настройке. Во втором случае получим правильно настроенный проект, который включает в себя содержимое по умолчанию и файлы CMSIS, причём для нашего минимального проекта там будет много лишнего. Но поскольку удалять лишнее проще, чем добавлять недостающее, выбираем второй вариант - Embedded C++ Project.

В поле Toolchains выбираем Atollic ARM Tools

[Next>] - переходим на следующую страницу мастера.

2. Страница мастера "TrueSTUDIO Hardware configuration"

Здесь указывается конфигурация оборудования.

Vendor (производитель), в нашем случае STMicroelectronics.
Evaluation board (оценочная плата) - можно указать None, или тип используемой оценочной платы: STM32VL_Discovery (тогда большинство остальных полей заполнятся автоматически и в проект будут добавлены файлы поддержки платы).
Microcontroller family (семейство микроконтроллеров) - наш STM32F100RB по классификации STM принадлежит к семейству STM32 Medium density Value Line devices.
Microcontroller - STM32F100RB.
Floating point - Software implementation, без вариантов, так как Cortex-M3 процессоры не имеют FPU, возможна только программная поддержка чисел с плавающей точкой.
Floating point unit (блок арифметики с плавающей запятой) - в нашем случае поле недоступно по указанной выше причине.
Code location (местоположение кода). Процессоры Cortex-M3 поддерживают выполнение программы как из FLASH памяти, так и из RAM памяти. Естественно, чтобы программу можно было выполнить из RAM, она туда должна быть загружена, это выполняет отладчик. Кроме того программа должна быть скомпонована с учётом места расположения. Благодаря этой опции правильно формируются скрипты для отладчика и компоновщика. Выбираем RAM, так как программа не имеет большой ценности, чтобы прошивать её во flash-память.
Instruction set (набор инструкций) - Thumb2, без вариантов, Cortex-M не поддерживает других наборов инструкций.
Endianess (порядок байтов) - Little endian, без вариантов, Big endian не поддерживается.
[Next>]

3. Страница мастера "TrueSTUDIO Software configuration"

Здесь выбирается и настраивается Runtime library (библиотека времени выполнения), настраивается оптимизация по размеру кода. Для минимального проекта можно оставить все настройки по умолчанию, кроме опции Use tiny printf..., которую лучше отключить.

Library - позволяет выбрать между Reduce и Standard вариантами C/C++ библиотек.
Use tiny printf/sprintf/fprintf (small code size) - включить в проект файл с реализацией функций printf и т.д., уменьшающей размер кода; в данном случае эти функции не используются и опцию надо отключить, чтобы не загромождать проект лишними файлами.
Generate system calls file (enable I/O redirection and OS integration) - опция позволяет включить в проект ещё один файл (системных вызовов).
Далее идёт группа опций "Optimization" (по умолчанию - все включены для уменьшения размера генерируемого кода).
Remove unused code (dead code removal) - удалять неиспользуемый код.
Remove unused data (dead data removal) - удалять неиспользуемые данные.
Disable C++ runtime type information (RTTI) - отключить C++ RTTI (информацию о типах времени выполнения).
Disable C++ exception handling - отключить обработку C++ исключений (в простейшем приложении обработка C++ исключений не используется, но в серьёзных может и потребоваться).
[Next>]

4. Страница мастера "TrueSTUDIO Debugger configuration"

Debug probe - аппаратный отладчик, для платы STM32VLDISCOVERY - это ST-LINK.
[Next>]

5. Последняя страница мастера "Select configurations"

Выбор создаваемых конфигураций, по умолчанию предлагается создать две: Debug и Release.
[Finish]

Файловая структура проекта

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

Для отладки программы в RAM в случае микроконтроллера STM32F100RB, необходимо в настройках проекта добавить символ препроцессора VECT_TAB_SRAM для компилятора C. Это требование обусловлено особенностями кода инициализации в файле system_stm32f10x.c.

В каталоге проекта будут размещены следующие объекты:

folder .settings Служебный каталог с файлами, содержащими настройки для проекта.
folder Debug Каталоги содержат объектные файлы - результаты компиляции и результат компоновки. Имеется по каталогу на каждую конфигурацию построения (по умолчанию создаётся 2 конфигурации: Debug, Release, но каталог создаётся только после первого построения программы).
folder Release
source folder Libraries В этот каталог скопированы файлы библиотеки CMSIS. Как правило, даже последние версии любого пакета разработки содержат не последние версии CMSIS.
source folder src В этом каталоге находятся файлы пользователя с исходным кодом, сюда скопированы файлы библиотеки CMSIS вида startup_<device>.s и system_<device>.c (в соответствии с требованиями CMSIS).
source folder Utilities* Этот каталог создаётся, если в мастере создания проекта выбран тип оценочной платы; сюда копируются файлы для поддержки этой платы.
file .cproject Служебные файлы проекта с настройками.
file .project
file stm32_ram.ld* Скрипт для компоновщика. * Для компоновки программы, которая будет запускаться из flash-памяти и программы для запуска из RAM требуются разные скрипты. Соответственно, скрипт для компоновки запускаемого из flash кода будет иметь имя вида xxx_flash.ld.

В каталоге src с файлами, содержащими исходный код программы, будет примерно следующее содержимое:

C++ file main.cpp Основной файл проекта, содержит функцию int main(), которая вызывается после старта программы.
asm-file startup_stm32f10x_md_vl.s Файл из библиотеки CMSIS. Отвечает за формирование области с векторами прерываний; содержит код для начальной инициализации микроконтроллера после сброса; после инициализации в конечном итоге осуществляет вызов функции int main().
h-file stm32f10x_conf.h Заголовочный файл, который в свою очередь включает в себя все заголовочные файлы для данного микроконтроллера с описаниями CMSIS-Driver API. Требуются для обеспечения высокоуровневого, независимого от аппаратуры доступа к периферийным устройствам микроконтроллера.
C file stm32f1xx_it.c Здесь определены функции для нескольких обработчиков исключений микроконтроллера. Даны в качестве образца.
h-file stm32f1xx_it.h Заголовочный файл с описаниями функций - обработчиков исключений из stm32f1xx_it.c.
C file system_stm32f10x.c Файл из библиотеки CMSIS, содержит функции для базового конфигурирования микроконтроллера, в первую очередь - конфигурирование системы тактирования.

Анализ минимального приложения

Всего за несколько шагов, требующих считанные секунды, мы получаем готовый проект минимального приложения. Правда, несколько тяжеловесный. Чтобы его облегчить, можно удалить файл stm32f10x_conf.h и весь каталог Libraries\STM32F10x_StdPeriph_Driver, так как пока драйверы для доступа к периферии не используются. В свойствах проекта в этом случае удаляем символ препроцессора USE_STDPERIPH_DRIVER (по умолчанию он определён как для C, так и для C++ компилятора). Исключаем соответствующий каталог из списка каталогов для поиска включаемых файлов (..\Libraries\STM32F10x_StdPeriph_Driver\inc) - это необязательно, но иначе будем получать предупреждение компилятора. Можно удалить файлы stm32f1xx_it.h, stm32f1xx_it.c. Конечно, выделить обработку исключений в отдельный файл - хороший стиль, но в данном случае мы вообще ограничимся обработчиками по умолчанию из startup_stm32f10x_md_vl.s.

Теперь можно сосредоточить всё внимание на коде пользователя. Создаваемый по умолчанию файл main.cpp, несмотря на свою многословность (более двух сотен строк), по сути, сводится к следующему:

/*
 * File: main.cpp
 * Для отладки в RAM не забывайте
 * определять символ препроцессора
 * VECT_TAB_SRAM
 */
 
#include "stm32f10x.h"

int main(void)
{
    int i = 0;

    while (true)
    {
        i++;
    }
}

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

/*
 * File: main.cpp
 * (!!!) VECT_TAB_SRAM
 */
 
#include "stm32f10x.h"

int main(void)
{
    while (true) {}
}

Первый вариант программы веселее отлаживать (в программе хоть что-то происходит), а второй вариант в большей степени является минимальным.

По правде говоря, строку

#include "stm32f10x.h"

можно тоже исключить, так как никакие описания из stm32f10x.h не используются в файле main.cpp. Но всё же не будем этого делать, так как обычно в любой программе, более сложной, чем минимальная, требуется доступ к периферии, стало быть, подключение файла вида <device>.h необходимо.

В соответствии со стандартом C++, программа должна содержать глобальную функцию с именем main, с которой начинается выполнение пользовательского кода. Функция main должна иметь тип возвращаемого значения int:

int main() {/* ... */}

Однако, в программе для микроконтроллера не должно происходить возврата из main. Разумеется, если используется операционная система, то программа должна писаться с учётом требований этой операционной системы и там завершение программы возможно. Но на "чистом" микроконтроллере именно код пользователя полностью отвечает за все действия устройства до тех пор, пока не будет отключено питание. Обычно внутри функции main осуществляется конфигурирование используемых периферийных устройств, разрешаются обрабатываемые прерывания. После этого main ничего не делает, а всё функционирование программы сводится к обработке поступающих прерываний.

Самый простой способ предотвратить завершение main - поместить бесконечный цикл в конце функции:

    while (true) {}

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

Так как нет возврата из main, то отсутствует и оператор return.

Если всё же сделать возврат из main, то, естественно мы вернёмся в startup-код в файле startup_<device>.s, из которого функция main и была вызвана. Дальнейшие действия зависят от реализации. В моём случае, как показала отладка, это оказался опять просто пустой бесконечный цикл. Но лучше не полагаться на особенности реализации стороннего кода и не позволять своей программе терять управление.

Отладка

Отладка микроконтроллера является удалённой отладкой - код отладчика выполняется на одном процессоре, а отлаживаемый код - на другом. Выполняется отладка в Atollic TrueSTUDIO по стандартной схеме:

Компьютер USB   SWD  
IDE<==> GDB<==> GDB server<==> Аппаратный отладчик<==> MCU

Для отладки требуется подключить оценочную плату к USB-порту компьютера. Питание платы и аппаратного отладчика на плате осуществляется от USB.

Atollic TrueSTUDIO содержит все необходимые инструменты для отладки, в том числе и драйвер для USB. Поддерживается отладка в RAM. В зависимости от выбранной опции Code location: RAM/FLASH, будут использоваться соответствующие скрипты для старта отладки.

author: hamper; date: 2015-12-21; modified: 2016-02-26
  Рейтинг@Mail.ru