Стандартная библиотека периферии
Библиотека CMSIS абстрагирует программиста от карты памяти микроконтроллера. Код получается эффективным, так как программист просит компилятор сделать только нужные вещи (записать какое-нибудь значение в нужное место). Проблема такого подхода в том, что нужно заглядывать в документацию, чтобы определить, в какой регистр и что нужно записать. У одного и того же производителя регистры на разных МК могут отличаться, как названием, так и количеством. Это неудобно.
Абстракция — мощный инструмент, которую легко реализовать. Вместо обращения к регистрам можно просто вызывать функции. И в CMSIS такая абстракция уже присутствует (совсем чуть-чуть).
NVIC_Enable(ADC1_2_IRQn);
// Вместо
NVIC->ISER[((uint32_t)(18) >> 5)] = (1 << ((uint32_t)(18) & 0x1F));
Если модуль несложный (те же порты ввода-вывода), то больших усилий для его инициализации прикладывать не нужно. Но вот если нужно настроить какой-нибудь таймер в нестандартный режим работы, то рутина по выставлению нужных битов в памяти принимает устрашающий характер. Стандартная библиотека периферии помогает заглядывать в документацию реже. Всё, что должен сделать программист (в общем случае) — это заполнить структуру с читаемыми параметрами и выполнить функцию.
Никто не защищен от ошибок. В библиотеке могут быть ошибки, и когда что-то не работает, возможно, причина в этом.
Стандартный проект будет включать в себя библиотеку CMSIS (она используется внутри StdPeriph), пользовательские файлы и файлы самой библиотеки.
Архив с библиотекой и примерами ее использования можно найти на странице целевого МК в разделе Design Resources. Каждому модулю периферии соответствует два файла: заголовочный (stm32f10x_ppp.h
) и исходного кода (stm32f10x_ppp.c
). Здесь ppp
— название периферии. К примеру, для работы с аналого-цифровым преобразователем нужны файлы stm32f10x_adc.h
и stm32f10x_adc.c
. Файлы misc.h
и misc.c
реализуют работу с контроллером прерываний NVIC и системным таймером SysTick (эти функции есть в CMSIS).
Чтобы подключить стандартную библиотеку, нужно в файле stm32f10x.h
определить макрос USE_STDPERIPH_DRIVER
.
Менять содержимое библиотеки — плохая практика. В интегрированных средах разработки обычно можно определять константы и вводить маркеры для компилятора. Это делается в настройках.
// stm32f10x.h
#ifdef USE_STDPERIPH_DRIVER
#include "stm32f10x_conf.h"
#endif
Заголовочный файл stm32f10x_conf.h
не является частью библиотеки, он пользовательский. С его помощью можно подключать или отключать части библиотеки.
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H
/* Includes ------------------------------------------*/
#include "stm32f10x_adc.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_can.h"
// ...
Оставшиеся два файла (stm32f10x_it.h
и stm32f10x_it.c
) выделены для реализации обработчиков прерываний, именно туда следует помещать данные функции.
В стандартной библиотеке периферии есть соглашение о наименовании функций и обозначений.
PPP
— акроним для периферии, например,ADC
.- Системные, заголовочные файлы и файлы исходного кода начинаются с префикса
stm32f10x_
. - Константы, используемые в одном файле, определены в этом файле.
- Константы, используемые в более чем одном файле, определены в заголовочных файлах. Все константы в библиотеке периферии чаще всего написаны в ВЕРХНЕМ регистре.
- Регистры рассматриваются как константы и именуются также БОЛЬШИМИ буквами.
- Имена функций, относящихся к определенной периферии, имеют префикс с ее названием, например,
USART_SendData()
. - Для настройки каждого периферийного устройства используется структура
PPP_InitTypeDef
, которая передается в функциюPPP_Init()
. - Для деинициализации (установки значения по умолчанию) можно использовать функцию
PPP_DeInit()
. - Функция, позволяющая включить или отключить периферию, именуется
PPP_Cmd()
. - Функция включения/отключения прерывания именуется
PPP_ITConfig
.
С полным списком вы можете ознакомиться в файле поддержки библиотеки.
Перепишем инициализацию порта ввода-вывода из предыдущего раздела. Во-первых, нужно включить тактирование модуля (подать питание) — делается это через функцию, объявленную в stm32f10x_rcc.h
:
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
Возможные варианты первого аргумента можно найти в комментарии к функции или в заголовочном файле. Так как мы работаем с портом A
, нам нужен RCC_APB2Periph_GPIOA
. Перечисление FunctionalState
определено в stm32f10x.h
:
typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;
Далее нужно обратиться к структуре порта из stm32f10x_gpio.h
:
typedef struct {
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
} GPIO_InitTypeDef;
Параметры структуры можно найти заголовочном файле.
// clocking
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// create structure and fill it
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_0;
gpio.GPIO_Speed = GPIO_Speed_2MHz;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
// initialization
GPIO_Init(GPIOA, &gpio);
Главное — запомнить порядок инициализации: включаем тактирование периферии, объявляем структуру, заполняем структуру, вызываем функцию инициализации. Другие периферийные устройства обычно настраиваются по подобной схеме.