Начало работы с STM32 в Keil MDK-ARM
STM32 — это семейство 32-разрядных микроконтроллеров фирмы STMicroelectronics.
Микроконтроллеры содержат микропроцессорное ядро ARM, точнее ARM Cortex-M. Это ядро присуще не только микроконтроллерам STM32, оно существует само по себе, и на его основе выпускается множество микроконтроллеров от разных производителей.
Keil MDK-ARM (произносится «Кеил эм-ди-кей арм») — это среда разработки для микроконтроллеров с ядром ARM Cortex-M.
Среда продается за большие деньги, но существует бесплатная ограниченная версия, которую можно скачать с официального сайта.
Существует множество других сред разработки. Например, последнее время автор пользуется средой, которую описал в другой статье. Но в этой пойдет речь о Keil MDK-ARM, потому что она имеет большую популярность и достаточно проста.
Первым делом качаем последнюю версию Keil MDK-ARM с официального сайта. Она имеет ряд ограничений, которые не влияют на большинство задач. Самое существенное ограничение состоит в том, что среда не сгенерирует программу объемом больше 32 КБ, но такую программу надо постараться написать. Хотя микроконтроллеры STM32 часто имеют намного больший объем, вплоть до 2 МБ.
На момент написания статьи последняя версия Keil MDK-ARM — 5.18 (от 5 февраля 2016).
Чтобы скачать среду, надо заполнить форму на сайте Keil:

Следует ввести настоящий адрес электронной почты, иначе сайт будет ругаться. После заполнения и нажатия Submit, дается ссылка для скачивания:

Если вы хотите избежать заполнения формы, то воспользуйтесь страницей, которую сделал автор, чтобы облегчить скачивание.
Качаем и устанавливаем. Установленная среда занимает на диске около 1,3 ГБ. После установки автоматически запускается менеджер пакетов:

Он нужен, чтобы качать, устанавливать и обновлять различные дополнения (пакеты).
Менеджер пакетов появился в MDK-ARM версии 5, что уменьшило объем установочного файла почти в два раза (версия 4.71 была объемом 550 МВ, а версия 5.00 — 300 МБ).
В левой части менеджера мы выбираем устройства (микроконтроллеры), а в правой соответствующие им пакеты.
Допустим, нам надо вести разработку под микроконтроллер STM32F407VG, который установлен на отладочной плате STM32F4-Discovery.
Тогда находим этот микроконтроллер в списке слева и устанавливаем соответствующий пакет DFP:

Можно заметить, что среди установленных пакетов есть CMSIS. CMSIS — это библиотека для ядра Cortex-M, общая для всех микроконтроллеров. Библиотека разрабатывается фирмой ARM и доступна для скачивания с официального сайта после регистрации. Можно было бы не устанавливать этот пакет, а пользоваться официальным выпуском библиотеки, но это дополнительные сложности.
Закрываем менеджер пакетов и запускаем Keil uVision5 (произносится мю-вижен):

Keil uVision5 — это часть MDK-ARM, графический интерфейс среды, который включает редактор кода:

Я рекомендую произвести небольшую настройку редактора. Выбираем меню «Edit -> Configuration…», и производим следующие настройки:

- Кодировка UTF-8.
- Правая граница кода в 80 символов. по 4 пробела.
Эти настройки довольно спорные. У каждого разработчика свои предпочтения.
Теперь создаем проект. Для этого выбираем меню «Project -> New uVision Project…». В открывшемся окне выбираем расположение и имя проекта. Для проекта лучше создать отдельную папку и сохранить проект туда.
После сохранения появится окно выбора устройства. Выбираем нужный микроконтроллер и нажимаем «ОК». Если бы мы не установили нужный пакет, то микроконтроллера не было бы в списке:

В следующем окне предстоит выбрать компоненты, которые будут использоваться в проекте. Необходимо выбрать «CMSIS:CORE» и «Device:Startup»:

После нажатия «OK» процесс создания проекта завершится.
В дальнейшем вы всегда сможете запустить окно выбора компонентов, чтобы добавить или удалить их. Для этого надо выбрать меню «Project -> Manage -> Run-Time Evironment…».
При выборе компонентов может оказаться, что какой-то компонент зависит от других компонентов, которые вы не выбрали. Об этом вы узнаете из сообщений в нижней части окна. Необходимо будет выбрать зависимые компоненты.

После создания проекта описанным способом, в окне справа вы увидите следующую структуру проекта:
Здесь мы видим название проекта «example», цель проекта «Target 1», пустую группу файлов «Source Group 1», компоненты CMSIS и Device.
Целей проекта может быть сколько угодно. Цель включает в себя важнейшие настройки проекта в том числе выбор микроконтроллера. Цели нужны, чтобы можно было собрать программу разными способами для одних и тех же файлов исходного кода. Например, вам может понадобиться, чтобы проект охватывал множество микроконтроллеров.
Группы файлов нужны, чтобы красиво группировать файлы исходного кода. Группы помогают легко ориентироваться в файлах в большом проекте. Например, у вас может быть группа файлов, отвечающих за светодиоды, и отдельная группа с файлами для взаимодействия с USB.
В структуре мы видим два файла. Один с расширением «s». Он содержит исходный код на языке ассемблера. Другой с расширением «с». Он содержит исходный код на языке Си.
Собрать проект и получить файл прошивки можно нажав клавишу F7. Но в таком виде проект не будет собран и вы получите ошибку, потому что отсутствует функция «main()».
Функция «main()» — это точка входа в вашу программу, то с чего начинается программа. Ее наличие обязательно если вы пишите программу на языке Си.
Давайте создадим эту функцию. Кликнем на группе «Source Group 1» правой кнопкой и выберем «Add New Item to ‘Source Group 1’…» (перевод: добавить новый элемент в группу ‘Source Group 1’). Создадим файл «main.c»:

В созданный файл добавим код:
В конец файла стоит добавить пустую строку, иначе при сборке вы получите предупреждение «warning: #1-D: last line of file ends without a newline».
Теперь проект можно собрать клавишей F7. В результате вы получите файл «Objects\example.axf» (по умолчанию имя файла совпадает с именем проекта). Файл располагается в папке с проектом.
Обычно разработчику требуется файл прошивки в формате Intel HEX. Чтобы получить его, надо произвести настройку цели. Чтобы увидеть настройки цели нажмите Alt-F7, перейдите на вкладку «Output» и выберите «Create HEX File».
После очередной сборки вы получите файл «Objects\example.hex».
Сейчас программа не делает ничего, и прошивать ее бессмысленно. Давайте напишем программу, которая управляет состоянием ножки микроконтроллера.
Запустим выбор компонентов с помощью меню «Project -> Manage -> Run-Time Evironment…» и выберем компонент «Device:STM32Cube Hal:GPIO».
В нижней части окна мы увидим неудовлетворенную зависимость «Device:STM32Cube Hal:Common». Выберем этот компонент и увидим еще больший список зависимостей. Необходимо выбрать все требуемые зависимости:
- Device:STM32Cube Hal:Common
- Device:STM32Cube Hal:RCC
- Device:STM32Cube Hal:PWR
- Device:STM32Cube Hal:Cortex
- Device:STM32Cube Framework:Classic
STM32Cube — это библиотека, которую предоставляет STMicroelectronics.
При выборе компонентов мы выбираем какие возможности этой библиотеки использовать.
Микроконтроллер, кроме ядра, содержит большое количество периферийных устройств: АЦП, ЦАП, таймеры, различные интерфейсы и многое другое. Каждое периферийное устройство имеет свое название. Например, устройство для работы с портами микроконтроллера называется GPIO, об этом можно узнать из документации на микроконтроллер.
Библиотека STM32Cube многоуровневая, то есть включает в себя множество промежуточных библиотек. Одна из промежуточных библиотек называется STM32Cube HAL, или просто HAL. Она поделена на модули и каждый модуль соответствует какому-нибудь периферийному устройству. Название модуля совпадает с названием устройства, например, имеется модуль GPIO.
Существует большое количество документации по STM32Cube. Но основное описание по работе с периферийными устройствами содержится в руководстве по HAL. Это руководство разработчик использует большую часть времени. Обратимся к нему, чтобы заставить шевелиться ножки микроконтроллера.
Для начала подключим HAL в нашей программе, добавив строчку перед определением функции «main()»:
В самом начале функции «main()» вызовем функцию «HAL_Init()», которая инициализирует библиотеку.
Таким образом мы получим следующий код в файле «main.c»:
Продолжение следует…
На этом я вынужден прервать свою статью, так как в данный момент мне не на чем отлаживать программу, то есть нет под рукой отладочной платы.
Я написал программу, которая собирается и теоретически должна работать, но я не хочу вводить в заблуждение читателя. Выше изложенный материал считаю полезным и без конечного результата.
В статье я хотел еще привести конечный код и объяснить, как он работает. Могу лишь поделиться непроверенной программой:
Creating a Project in Keil uVision
When we open Keil uVision For first time we will be able to see ide as shown in figure below.
To create a new 8051 project using Keil IDE, Click on the ‘ Project ‘ item on the IDE Menu bar and select ‘ New uVision Project… ‘ as shown in the above image.
Now create a Folder to store your project and give a name to your Project files (*.uvproj), for eg exp1 (exp1.uvproj) , then save it.
Selecting 8051 MicroController
- After saving the project , a dialog box will appear to select a Device
- Then type “at89c51” in search box
- Click on AT89C51 and click on OK
- You will get another dialog as shown below. Asking to copy STARTUP.A51, Click on No
Writing Code
Now inorder to write code we need a blank file , Click on File >> New. or can use keyboard shorcut Ctrl + N
Write a code as shown in the figure below
Now “Save As” the file , Click on File >> Save As.
Give a file name to your code with extenstion *.asm, for eg code.asm , then save it.
In left corner you can see a Project Box , in that Project Box expand Target1 , then Right click on Source Group 1 , Then click on Add Existing Files to Group 'Source Group 1'.
Select the Files of type : Asm Source file (*.s*;*.src*;*.a*) , Select the file with extenstion *.asm , here in my case it is "code1.asm" , Click on Add and click on Close
Project Build
To Bulid Project , Click on Project >> Build Target or press on F7 key
If the Build is successful there will not be any error or any warning as shown in the figure below , else verify the code and rebuild it.
Debug and Run Code
Next step is to debug the code , For that click on Debug >> Start/StopDebugSession or press on Ctrl + F5
After Entering to debug , you will have a similar screen as shown in the figure below
Next step is to Run the code , For that click on Debug >> Run or press on F5 or click "Run" button highlighted in green color
After a few seconds we need to Stop running the code , For that click on Debug >> Stop or click "Stop" button highlighted in red color
Before Closing the Keil uVision We need to stop Debugging , For that click on Debug >> Start/Stop Debug Session or press on Ctrl + F5 , Then close it
For programs of 8051 Microcontroller , you can visit my github link given below
Первый проект на STM32F407VE Keil
В этой статье мы рассмотрим пример создания проекта с нуля для платы STM32F4VE . Мы создадим проект, подключим к нему необходимые библиотеки, создадим отдельные файлы в которых будем хранить настройки портов микроконтроллера и будем к ним обращаться из основной программы. Итоговая программа будет исполнять следующее задание — при нажатии на кнопку установленную на плате будет загораться светодиод. Одна кнопка один светодиод. Эта статья не будет объяснять как настраивать порты и писать правильно функции и обращения — статья для тех у кого возникают проблемы в самом начале работы с редактором Keil uVision5 и для тех кто не решается на него перейти .. пока не решается ..
Создадим папку в которой будет находиться наш проект — в моем случае это 407 (от названия микроконтроллера).

Копируем в созданную папку библиотеки. Я стараюсь всегда все сохранить в каждом проекте т.к. через определенное время очень сложно бываем найти нужное. Название папок соответствует названиям папок которые были в архиве при скачивании их с сайта ST.сом. Это библиотеки для четвертой версии микроконтроллеров. Подробно я рассказывал как их найти в примере для платы STM32F103C8, но можно скачать у меня CMSIS и STM32F4xx_StdPeriph_Driver

Также в одну из папок копируем два основных файла это stm32f4xx.h и stm32f4xx_conf.h

Также для основных файлов проекта я создаю отдельную папку Main. И все создаваемые файлы буду копировать в нее.

Запускаем Keil uVision5


Выбираем ранее созданную папку и в ней создаем файл проекта

Сохраняем название проекта и автоматически попадаем в окно выбора микроконтроллера.

Выбираем тот что установлен на плате

Ок и переходим в следующее окно. В нем Keil uVision5 предлагаем установить необходимые библиотеки автоматически.

Выбираем для проекта.

Нажимаем OK и переходим в основное окно

Меняем название на User и добавляем в проект

Основной файл main.c

Но прежде чем его добавить кнопкой Add — сохраняем его в папку Main которую создали ранее.
Выбираем Location — Обзор папок

Переходим в директорию ищем папку Main или создаем ее .



Когда все выбрано и создано нажимаем Add. Возвращаемся в основное окно выбираем Project — Options for .

И переходим в настройки проекта. Меняем частоту на 8 000 000 Гц.

Переходим на вкладку C/C++

И заполняем ее начиная с Include Paths

Добавляем все пути к папкам и библиотекам

В итоге должно быть так

Нажимаем OK и в основном окне добавляем в строку Define следующую строку
USE_STDPERIPH_DRIVER,STM32F4XX

Нажимаем Ok и возвращаемся в основное окно. Добавляем в main.c следующие строки
#include «main.h»
#include «stm32f4xx.h»
#include «stm32f4xx_conf.h»
#include «stm32f4xx_rcc.h»
#include «stm32f4xx_gpio.h»

Нажимаем F7 и собираем проект. После того как проект собрался без ошибок мы можем удалить файл main.h т.к. он скопировался в +main.c.


Находим скопированный main.h и открываем его. Будем использовать его для определение и всего того, что мешает в основном main.c

Начинаем его заполнять его содержанием..
#ifndef MAIN_H
#define MAIN_H
#endif
При этом не забывая про последнюю пустую строку.

После того как мы записали в main.h начальные строчки. Создадим еще два файла в которых будем хранить настройки портов ввода и вывода. Назовем этот файл как port_ini.c и соответственно port_ini.h
Начнем с создания port_ini.c (по аналогии с main.c и в туже папку)

После создания файла открываем его и вносим в него следующие строчки
#include «port_ini.h»

Сохраняем файл и переходим к созданию port_ini.h

После создания файла открываем его и вносим следующие строки
#ifndef PORT_INI_H
#define PORT_INI_H
#include «stm32f4xx.h»
void GPIO_init(void);
void button_ini(void);
Не забываем про пустую строку ..

Нажимаем F7 и как в прошлый раз мы можем удалить port_ini.h т.к. его копия переместилась в + port_ini.c

Отлично ! Подготовительную чать создали. Перейдем к программе управления светодиодами и кнопками — и заодно убедимся, что модульная система (тогда когда в основном файле main.c) только основная программа.
Выбираем вкладку port_ini.c и вносим следующие данные для инициализации портов микроконтроллера
#include «port_ini.h»
// Конфигурируем порты для управления светодиодами
void GPIO_init(void)
<
GPIO_InitTypeDef GPIO_InitStructure; // создаем структуру
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); // включаем тактирования порта В
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // включаем тактирование порта А
// Светодиод на плате подключенный к порту РА 6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // Пин 6
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // 2 Mhz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // настроен на выход
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // Подтяжка
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIOA->ODR ^= GPIO_Pin_6; // отключаем сразу светодиод
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Светодиод на плате подключенный к порту РА 7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIOA->ODR ^= GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
void button_ini(void)
<
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE); // включаем тактирование порта А
GPIO_InitTypeDef init_button; // Определяем структуру
// Кнопка на плате подключенная к порту РЕ 3
init_button.GPIO_Pin = GPIO_Pin_3;
init_button.GPIO_Mode = GPIO_Mode_IN;
init_button.GPIO_Speed = GPIO_Low_Speed ;
init_button.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOE, &init_button);
// Кнопка на плате подключенная к порту РЕ 4
init_button.GPIO_Pin = GPIO_Pin_4;
init_button.GPIO_Mode = GPIO_Mode_IN;
init_button.GPIO_Speed = GPIO_Low_Speed ;
init_button.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOE, &init_button);
>
Не забываем про пустую строчку .


Все сохраняем и жмем F7 и получаем ошибку .
Error: L6218E: Undefined symbol GPIO_Init (referred from port_ini.o).

Чтобы исправить ошибку необходимо сделать следующее. Пропишем основные файлы которые хоть и указаны в путях к проекту, но не определяются корректно ..
Итак выбираем User жмем правую клавишу мыши и выбираем следующий пункт меню Manage Project Items.

В появившемся окне видны подключенные к проекту. Правое окно. Добавим в него нам необходимые файлы stm32f4xx_rcc.с и stm32f4xx_gpio.с.

Нажимаем Add Files ... и добавляем эти файлы





В итоге должно получиться ..


Жмем F7 и видим что ошибка пропала и мы можем писать программу дальше.

Определяем в файле port_ini.h следующие данные
#ifndef PORT_INI_H
#define PORT_INI_H
#include «stm32f4xx.h»
void GPIO_init(void);
void button_ini(void);

Дополняем main.c основной программой
#include «main.h»
#include «stm32f4xx.h»
#include «stm32f4xx_conf.h»
#include «stm32f4xx_rcc.h»
#include «stm32f4xx_gpio.h»
int main(void)
<
// Инициализируем порты
GPIO_init();
// Инициализируем кнопки
button_ini();
while (1)
<
if (BUTTON_k1 == 0) // Смотрим схему на плату
<
GPIO_ResetBits(GPIOA, GPIO_Pin_6); //Подаем «0» на PA6 светодиод светиться
>
else
<
GPIO_SetBits(GPIOA, GPIO_Pin_6); //Подаем «1» на PA6 иначе не светиться
>
if (BUTTON_k0 == 0)
<
GPIO_ResetBits(GPIOA, GPIO_Pin_7); //Подаем «0» на PA7
>
else
<
GPIO_SetBits(GPIOA, GPIO_Pin_7); //Подаем «1» на PA6

Программа простая — при нажатии на кнопку К- загорается светодиод.
Ниже привожу листинг файлов
1. main.с
#include «main.h»
#include «stm32f4xx.h»
#include «stm32f4xx_conf.h»
#include «stm32f4xx_rcc.h»
#include «stm32f4xx_gpio.h»
int main(void)
<
// Инициализируем порты
GPIO_init();
// Инициализируем кнопки
button_ini();
while (1)
<
if (BUTTON_k1 == 0)
<
GPIO_ResetBits(GPIOA, GPIO_Pin_6); //Подаем «0» на PA6
>
else
<
GPIO_SetBits(GPIOA, GPIO_Pin_6); //Подаем «1» на PA6
>
if (BUTTON_k0 == 0)
<
GPIO_ResetBits(GPIOA, GPIO_Pin_7); //Подаем «0» на PA7
>
else
<
GPIO_SetBits(GPIOA, GPIO_Pin_7); //Подаем «1» на PA6
main.h
#ifndef MAIN_H
#define MAIN_H
// Переопределим на более удобные названия кнопки
#define BUTTON_k1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define BUTTON_k0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
Port_ini.c
// Конфигурируем порты для управления светодиодами
void GPIO_init(void)
<
GPIO_InitTypeDef GPIO_InitStructure; // создаем структуру
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); // включаем тактирования порта В
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // включаем тактирование порта А
// Настройка порта под светодиод -> PB0 — этого светодиода нет на плате
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// Светодиод на плате подключенный к порту РА 6 —
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIOA->ODR ^= GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Светодиод на плате подключенный к порту РА 7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIOA->ODR ^= GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
void button_ini(void)
<
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE); // включаем тактирование порта А
GPIO_InitTypeDef init_button; // создаем структуру
// Кнопка на плате подключенная к порту РЕ 3
init_button.GPIO_Pin = GPIO_Pin_3;
init_button.GPIO_Mode = GPIO_Mode_IN;
init_button.GPIO_Speed = GPIO_Low_Speed ;
init_button.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOE, &init_button);
// Кнопка на плате подключенная к порту РЕ 4
init_button.GPIO_Pin = GPIO_Pin_4;
init_button.GPIO_Mode = GPIO_Mode_IN;
init_button.GPIO_Speed = GPIO_Low_Speed ;
init_button.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOE, &init_button);
>
Port_ini.h
#ifndef PORT_INI_H
#define PORT_INI_H
#include «stm32f4xx.h»
void GPIO_init(void);
void button_ini(void);
Ура. Светодиоды должны включаться и выключаться при нажатии на кнопки K1 и K0 на плате.
На этом примере мы научились с нуля создавать проект который имеет в своей структуре несколько файлов в которых удобно хранить и настраивать от дельно от основного файла.
Программирование ARM-контроллеров STM32 на ядре Cortex-M3. Часть 1. Установка MDK, создание проекта, основы Keil uVision
Сегодня мы начнём учиться программировать ARM-контроллеры STM32 на ядре Cortex-M3. Эти камни уже гораздо серьёзнее, чем восьмибитные пики и атмелы, поэтому для полного использования всех их возможностей без языка высокого уровня нам при программировании не обойтись (если мы конечно не мазохисты), но для лучшего понимания происходящего, начнём мы всё же с ассемблера, а потом уже подмешаем Си (тем более, что до полного использования возможностей этих камней нам пока как до Луны пешком).
Сред разработки под ARM-ы в настоящее время существует достаточно много, мы будем пользоваться одной из таких сред (кстати, одной из самых популярных), которая называется Keil uVision.
Первое, что нужно сделать — это скачать с официального сайта Keil и установить себе на компьютер пакет MDK-ARM. В настоящее время в его состав входит Keil uVision версии 5.11. Бесплатная версия, естественно, урезана и позволяет скомпилить максимум 32 Кбайта кода, но нам, простым радиохламерам, для любительских нужд этого более чем достаточно (ну, а если недостаточно, то, я думаю, все знают что делать).
Помимо пакета MDK-ARM нужно скачать и установить пакет для работы с контроллерами STM32, который называется Keil.STM32F1xx_DFP.1.0.5.pack. После установки этого пакета в среде uVision появится база данных контроллеров STM.
Всё, после выполнения этих двух действий можно приступать к разработке программ.
Итак, запускаем Keil uVision и выбираем пункт меню «New uVision Project…»
В появившемся окне проводника выбираем папку, в которой проект будет расположен, придумываем ему какое-нибудь имя и жмём кнопку «Сохранить».
После этого появится база различных контроллеров STM32, в которой нужно будет выбрать свой камень (в моём случае это stm32F103C8) и нажать «Ok».
Дальше нам предложат подключить к нашему проекту различные готовые куски кода и библиотеки драйверов. Достаточно поставить напротив соответствующего компонента галочку и он будет включен в наш проект. Это очень сильно облегчает жизнь программиста, но мы пока не будем этим пользоваться. Пока мы ставим перед собой цель разобраться как это всё работает и нас интересует проект на асме с чистого листа, поэтому не ставим никакие галочки, а просто жмём кнопку «OK».
Проект создан и окно uVision теперь выглядит вот так:
Слева расположен многофункциональный менеджер проекта, в котором в зависимости от выбранной внизу вкладки открывается либо структура проекта (вкладка Project), либо библиотека документации (вкладка Books), либо список используемых в проекте функций (вкладка Functions), либо шаблоны для часто используемых структур (вкладка Templates). Пользоваться менеджером просто и удобно, — когда мы выбираем камень, в библиотеку документации автоматически добавляются нужные книжки, список используемых в проекте функций также составляется автоматически. В области справа показывается содержимое открытых файлов проекта.
Добавим в наш проект asm-файл, в котором мы будем писать код. Щёлкаем по Source Group 1 правой кнопкой мыши и выбираем пункт Add New Item to Group ‘Source Group 1’…
После этого появится окошко, в котором нам предложат выбрать тип добавляемого файла и попросят этот файл как-нибудь назвать. Выбираем тип файла Asm File, назовём его, скажем, Proga1 и жмём кнопку Add. В менеджере проекта появляется наш файл. Если по нему два раза щёлкнуть мышкой — он откроется в окошке справа.
Пока этот файл пустой, но прежде чем писать в него какой-либо код, давайте вернёмся ещё раз к менеджеру проекта. Если щёлкнуть правой кнопкой мыши на Target1 и выбрать пункт меню Options for Target ‘Target1’, то откроется окошко с настройками нашего проекта. Кое-какие из этих настроек нам придётся подправить, а кое-какие просто интересно посмотреть.
На вкладке Device можно поменять модель контроллера.
На вкладке Target указаны начальные адреса и размеры используемых областей памяти. В случае с нашим STM32F103C8 мы имеем внутри кристалла 64 кБ (0x10000) flash-памяти (IROM1 — internal ROM), которая расположена по адресам, начиная с 0x08000000, а также 20 кБ (0x5000) ОЗУ (IRAM — internal RAM), которая расположена по адресам, начиная с 0x20000000. Эти данные устанавливаются автоматически при выборе контроллера, но если вдруг они случайно испортились, то их всегда можно подглядеть в доке на камень, открыв карту памяти (memory map).
На вкладке Output нужно не забыть поставить галочку напротив пункта Create HEX File. Здесь же можно выбрать отдельную папку для сохранения всяких вспомогательных файлов, создающихся при компиляции проекта. (кнопка Select Folder for Objects…)
На вкладке Listing настраиваются параметры создаваемых файлов листинга, которые также можно направить в отдельную папку, чтобы не засорять папку проекта.
На вкладке User можно настроить запуск различных пользовательских программ и скриптов в процессе построения проекта
Вкладки С/С++ и Asm предназначены для настройки процедур препроцессинга и ассемблирования и пока нам не интересны.
На вкладке Linker настраивается линковщик. Здесь возможны три варианта:
1) Ничего не менять. В этом случае стартовые адреса областей памяти (flash и RAM) будут взяты те, что прописаны на вкладке Linker напротив пунктов R/O Base, R/W Base. Кроме того, линковщик захочет чтобы точка входа была обозначена меткой Reset_Handler, а начало таблицы прерываний — меткой __Vectors. Это можно увидеть в командной строке линковщика (Linker Control String). Запись —entry Reset_Handler говорит о том, что метку Reset_Handler нужно считать точкой входа, а запись —first __Vectors — о том, что секцию с именем __Vectors нужно расположить в памяти первой. Соответственно, если наши названия не будут совпадать с теми, которые ожидает линковщик, — мы при компиляции получим ошибки и ворнинги. Кроме того, мы должны экспортировать эти метки, директивой EXPORT.
2) Поставить галочку напротив Use Memory Layout from Target Dialog. Если посмотреть в командную строку линковщика, то можно увидеть, что теперь набор команд выглядит по другому. В таком варианте линковщик будет использовать адреса областей памяти, указанные на вкладке Target. Причём он сначала на основе этих данных автоматически создаёт так-называемый scatter-файл (файл с расширением .sct), а потом компилит проект на основе scatter-файла. Если мы откроем scatter-файл в блокноте, то увидим, что теперь компилер хочет, чтобы секция, помещаемая в самое начало flash-памяти (там должна размещаться таблица векторов прерываний, но об этом позже) называлась RESET. Но в этом случае линковщику уже не нужно чтобы мы что-то куда-то экспортировали и не обязательно, чтобы метка, обозначающая точку входа, называлась именно Reset_Handle. Зато теперь нам нужно либо в программе обозначить точку входа директивой ENTRY, либо на вкладке Linker в окошке Misc controls добавить директиву —first, указав после неё имя нашей метки, отмечающей точку входа. Кроме того, теперь линковщик выдаёт при компиляции ворнинг из-за того, что хочет найти секции, обозначенные как InRoot$$Sections. Но у нас таких секций и нет, поэтому на этот ворнинг можно смело забить (а чтоб не надоедал — можно выключить его, добавив в строку Disable Warnings на вкладке Linker).
3) Ну и наконец, последний вариант — можно самому создать этот самый scatter-файл, который расскажет линковщику где какие области располагать. Для этого галочка Use Memory Layout from Target Dialog должна быть снята, а в папке с проектом нужно создать файл с раширением sct. Подробно о том, чем этот файл заполнять и как использовать описано в пятой главе мануала RealView Compilation Tools, а нам для простейшей программы хватит и вот такого:
Здесь ROM_Start_Address — адрес начала flash-памяти (в нашем случае 0x08000000), ROM_Size — размер flash-памяти (в нашем случае 0x10000), RAM_Start_Address — адрес начала оперативы нашего камня (в нашем случае 0x20000000), RAM_Size — размер оперативы (в нашем случае 0x10000).
Директива *.o (First_Section_Name, +First) в секции ER_IROM1 означает, что надо во всех объектных файлах поискать область с именем First_Section_Name и разместить её самой первой в секции ER_IROM1. Для контроллеров STM32 первой во flash-памяти должна быть область с адресом вершины стека и таблицей адресов прерываний.
Директива .ANY (+RO) означает, что далее в этой секции будут размещены все остальные области, помеченные в исходнике как READONLY, причём без разницы в каком порядке.
Директива .ANY (+RW +ZI) в секции RW_IRAM1 означает, что в этой секции будут размещены все области, помеченные в исходнике как READWRITE, причём без разницы в каком порядке.
После того, как файл создан и заполнен нужно на вкладке Linker, в строке Scatter File указать путь к этому файлу. Для удобства, справа есть кнопочка «…» при нажатии на которую откроется проводник и вам останется только выбрать этот файл в проводнике.
Обратите внимание, что после выбора scatter-файла набор команд в строке Linker control string изменится. Оттуда пропадут команды, размечающие области памяти и распределяющие различные секции по этим областям, поскольку теперь предполагается, что это сделано в scatter-файле.
На вкладке Debug нужно выбрать как вы будете отлаживать свой проект. Если у вас есть отладочная плата, то нужно поставить флажок, напротив пункта Use: и далее выбрать из выпадающего списка свою отладочную плату. Если отладочной платы у вас нет, то нужно поставить флажок напротив пункта Use Simulator. Это даст возможность пользоваться для отладки встроенным в IDE симулятором.
Последняя вкладка, Utilites, предназначена для настройки программирования чипа из IDE, и, соответственно, при отсутствии одного из поддерживаемых этой IDE программаторов также не сильно нам интересна.
Полную информацию по настройкам компилятора, ассемблера и прочим можно узнать, почитав литературу из раздела Tools Users Guide на вкладке Books в менеджере проекта, а с минимальными настройками мы только что разобрались. На этом пока всё, а в следующий раз давайте попробуем написать в созданном ранее asm-файле какую-нибудь маленькую программулинку.