Работа непосредственно с 32-битными регистрами микроконтроллеров является не легкой задачей. Хотя можно написать приложение с использованием имен реестров, однако внесение изменений в существующую программу даже через короткий промежуток времени сопряжено с большими трудностями.
По этой причине разработчики все чаще используют предопределенные функции, которые выполняют операции над командами из многих регистров. Производитель предоставляет полные библиотеки в соответствии со стандартом CMSIS (Cortex Microcontroller Software Interface Standard — стандарт программного обеспечения для микроконтроллеров Cortex) для микроконтроллеров STM32 и других, оснащенных ядром Cortex-M3.
Они позволяют полностью контролировать микроконтроллеры из семейства STM32 без необходимости подробно изучать секреты встроенной периферии, что, конечно, не исключает возможности прямой работы с регистрами.
Структура интерфейса CMSIS и его место в приложении показано на рис. 1.
Рис. 1. Конструкция интерфейса CMSIS и его место в приложении
Стандарт CMSIS разделен на два основных уровня: базовый уровень периферийного доступа и промежуточный уровень доступа. Первый уровень содержит определения имен и обеспечивает доступ к базовым регистрам и периферийным устройствам, а второй уровень обеспечивает механизмы взаимодействия с интерфейсами связи.
Кроме того, вышеупомянутые уровни были расширены производителями микроконтроллеров, которые участвовали в создании CMSIS двумя «уровнями»: уровень доступа к периферийным устройствам и функции доступа для периферийных устройств.
CMSIS для STM32
Библиотека стандартных периферийных устройств STM32F10x v3.1.0 (StdPeriph_Lib) была написана в соответствии с форматом Doxygen, что значительно упрощает процесс создания документации и ее использования. Вся документация для этой библиотеки была включена в файл справки, а не в том виде, в котором она ранее применялась STMicroelectronics в отдельном PDF файле. Новый тип документации облегчил просмотр ее содержимого и поиск информации.
Вместе с архивом библиотеки StdPeriph_Lib мы получаем шаблоны проектов для трех самых популярных компиляторов: IAR, Keil и ARM-GCC.
Структура файлов проекта, например, с использованием аналого-цифрового преобразователя, построенного на основе шаблона проекта для среды uVision, показана на рис. 2:
Рис. 2. Файловая структура среды uVision для проекта с использованием аналого-цифрового преобразователя
Если шаблон проекта копируется в текущий каталог, то компилятор должен быть проинформирован о том, где искать соответствующие файлы, и используемые источники должны быть добавлены в проект. Мы делаем это в среде uVision, щелкая правой кнопкой мыши на имени проекта и выбирая опцию Manage Component из контекстного меню (рис. 2). Затем мы находим нужные файлы на диске и добавляем их.
Как упоминалось выше, для правильной компиляции проекта необходимо обновить места (пути), где компилятор будет искать заголовочные файлы * .h. Для этого выберите меню Project / Options for Target …, после чего откроется окно, в котором на вкладке C / C ++ редактируем путь для поиска файлов заголовков.
Структура библиотеки
Файлы в библиотеке разделены на два блока (модули), расположенных в каталогах \ STM32F10x_StdPeriph_Driver и \ CMSIS. Дерево каталогов модуля CMSIS Стандартной периферийной библиотеки показаны на рис. 3, а дерево STM32F10x_StdPeriph_Driver на рис. 4.
Рис. 3. Структура модуля CMSIS
Рис. 4. Структура модуля STM32F10x_StdPefriph_Driver
Отдельные файлы запуска подготовлены для каждого из подсемейств микроконтроллеров STM32 . В табл. 1 показано, какой файл запуска использовать с каким подсемейством STM32. Мы получаем три набора файлов запуска вместе с библиотекой API — для трех самых популярных компиляторов: Keil (ARM), IAR и GCC.
Таблица 1. Стартовые файлы для подсемейства STM32
Файл system_stm32f10x.c содержит определения и функции, которые можно использовать для настройки тактового сигнала микроконтроллера. При использовании функций, содержащихся в файле, сначала выберите, используя комментарии, как часто должен работать MCU.
Фрагмент, который необходимо отредактировать для этой цели, показан в примере 1. В коде приложения достаточно вызвать функцию SystemInit () и настроит все тактовые сигналы (системные часы, HCLK, PCLK2, PCLK1).
Пример 1. Фрагмент файла system_stm32f10x.c -f определения тактовой частоты системы, используемые функцией SystemInit ()
/ * #define SYSCLK_FREQ_HSE HSE_Value * / / * #define SYSCLK_FREQ_24MHz 24000000 * / / * #define SYSCLK_FREQ_36MHz 36000000 * / / * #define SYSCLK_FREQ_48MHz 48000000 * / / * #define SYSCLK_FREQ_56MHz 56000000 * / #define SYSCLK_FREQ_72MHz 72000000
Модуль STM32F10x_StdPeriph_Driver включает в себя файлы, которые, согласно их названию, содержат функции API, связанные с отдельными периферийными устройствами — см. Рис. 4. Эти библиотечные файлы редактировать не следует.
К каждому проекту прикреплены два важных файла: stm32f10x_conf.h и stm32f10x_it.c. В первом заголовочном файле мы включаем или отключаем (посредством комментариев) присоединение заголовочных файлов из модуля STM32F10x_StdPeriph_Driver -f к проекту, см. Пример 2.
Пример 2. Область файла stm32f10x_conf.h, где комментарии включают или отключают вложение заголовочных файлов.
/ * #include "stm32f10x_adc.h" * / / * #include "stm32f10x_bkp.h" * / / * #include "stm32f10x_can.h" * / / * #include "stm32f10x_crc.h" * / / * #include "stm32f10x_dac.h" * / / * #include "stm32f10x_dbgmcu.h" * / #include "stm32f10x_dma.h" / * #include "stm32f10x_exti.h" * / / * #include "stm32f10x_flash.h" * / / * #include "stm32f10x_fsmc.h" * / #include "stm32f10x_gpio.h" / * #include "stm32f10x_i2c.h" * / / * #include "stm32f10x_iwdg.h" * / / * #include "stm32f10x_pwr.h" * / #include "stm32f10x_rcc.h" / * #include "stm32f10x_rtc.h" * / / * #include "stm32f10x_sdio.h" * / #include "stm32f10x_spi.h" / * #include "stm32f10x_tim.h" * / / * #include "stm32f10x_usart.h" * / / * #include "stm32f10x_wwdg.h" * / / * # Включите "misc.h" * /
С точки зрения программиста, одним из наиболее важных файлов является stm32f10x_it.c, в который помещены все функции обработки прерываний. Его слегка модифицированный фрагмент с пустыми функциями показан, например, 3.
Как видите, это набор пустых функций, которые вызываются при возникновении соответствующего прерывания. По сравнению с версией этого файла, предоставленной STMicroelectronics, комментарии, которые были показаны здесь как избыточные, были удалены. Название сервисной функции прерывания уже само по себе имеет смысл. В результате был получен более лаконичный код без потери читабельности.
Задача программиста состоит в том, чтобы поместить код, который должен быть выполнен после наступления определенного события, в соответствующую функцию. Этот подход сделал проект прозрачным, и все исключения находятся в одном файле, и нет необходимости определять функции, вызываемые, когда система обнаруживает прерывание.
Пример 3. Фрагмент файла stm32f10x_it.c
void UsageFault_Handler (void) { while (1) {}; }; void SVC_Handler (void) { }; void DebugMon_Handler (void) { }; void PendSV_Handler (void) { }; void SysTick_Handler (void) { };
Конфигурация периферийных устройств
Отдельные типы данных создаются для каждого устройства независимо от того, является ли оно GPIO, контроллером прерываний или любым другим элементом системы. Для портов ввода / вывода они называются: GPIO_TypeDef и тип GPIO_InitTypeDef, используемый для инициализации. Для программиста начальный тип является наиболее важным, потому что это переменная этого типа, которую мы явно создаем в написанном коде.
Тип GPIO_TypeDef обеспечивает доступ к отдельным регистрам микроконтроллера и используется главным образом функциями API, в то время как переменная типа GPIO_InitTypeDef должна существовать в каждом приложении, которое использует порты ввода / вывода, поскольку оно используется для инициализации и настройки портов. В примере 4 указана ключевая часть кода, отвечающего за GPIOB порта конфигурации.
Пример 4. Использование API — настройка порта GPIOB
// PINs PB8, PB9 как двухтактные выходы GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init (GPIOB, & GPIO_InitStructure);
Переменная GPIO_InitStructure, созданная в начале, является структурой. Инициализация выводов или, в особом случае, всего порта выполняется таким образом, что программист заполняет поля структуры, а затем передает адрес переменной, подготовленный таким образом, в функцию инициализации.
В представленной ситуации нас интересуют три области структуры GPIO_InitStructure. Сначала мы определяем, какие из выводов будут настроены, затем выбираем нужный режим работы — в этом случае это будет двухтактный выход.
Затем мы устанавливаем максимальную скорость, с которой выводы смогут работать. Подготовленная таким образом переменная должна быть передана путем ввода ее адреса в аргументе в инициирующую функцию GPIO_Init (). Вторым аргументом, передаваемым функции, является имя порта, к которому должны применяться выбранные параметры.