На прошлом занятии мы развернули рабочее пространство, а также собрали первый проект из готового исходника. Сейчас предлагается рассмотреть возможности по управлению портами и линиями ввода-вывода, предоставляемые библиотекой. Все классы для работы с GPIO доступны в заголовочных файлах <ioport.h> и <iopins.h>
Библиотека Zhele предоставляет два основных семейства классов для работы с портами и линиями ввода-вывода:
-
PortX, где X - буквенное обозначение порта. То есть в зависимости от целевого контроллера для программиста доступны классы Porta, Portb и так далее. Данные классы позволяют управлять портом ввода вывода:
- Включать и отключать тактирование порта.
- Читать и записывать значения.
- Задавать настройки порта (конфигурацию, режим, подтяжку, скорость) для конкретных линий (по маске).
-
PXY, где X - буквенное обозначение порта, Y - номер линии. Опять же в зависимости от выбранного контроллера пользователю доступны различные класса, например, Pa1, Pa2 и так далее. Данные классы содержат аналогичный набор методов, только применяются они для конкретной линии.
Также для каждого класса линии ввода-вывода есть соответствующий ему класс с суффиксом Inv, который представляет собой инвертированную версию. Это актуально, например, для платы Blue Pill, на линии C13 которого расположен светодиод, однако для его включений необходимо подать на соответствующий пин логический 0.
Заголовочный файл ioports предоставляет следующую иерархию классов:
Базовый класс NativePortBase
содержит перечисления для более строгого контроля
типов (в этом, кстати, заключется одно из преимуществ C++ - строгий контроль за типами):
/// Конфигурация порта
enum Configuration
{
Analog = 0, ///< Аналоговый режим
In = 0x04, ///< Вход
Out = 0x03, ///< Выход
AltFunc = 0x0B ///< Альтернативная функция
};
// Режим порта
enum DriverType
{
PushPull = 0, ///< push-pull
OpenDrain = 4 ///< open-drain
};
/// Подтяжка
enum PullMode
{
NoPull = 0, ///< Без подтяжки
PullUp = 0x08, ///< Подтяжка к "+" (pull-up)
PullDown = 0x18 ///< Подтяжка к "-" (pull-down)
};
/// Скорость
enum Speed
{
Slow = 2, ///< Медленная (< 2 MHz)
Medium = 1, ///< Средняя (< 10 MHz)
Fast = 3 ///< Быстрая (< 50MHz)
};
Базовый класс в свою очередь расширяют два:
NullPort
- класс-заглушка, все операции которого не производят реальных действий.- Шаблон
PortImplementation
- шаблон класса для конкретного порта ввода вывода, имеет три шаблонных параметра:_Regs
- обертка над регистрами порта (структуройGPIO_TypeDef
)._ClkEnReg
- обертка над регистром включения тактирования (об этом более подробно на следующих занятиях).ID
- буква порта, по сути, не используется, так что пояснения можно опустить.
Шаблон класса PortImplementation
инстанцирован значениями, соответствующими реальным портам контроллера, например:
using Porta = PortImplementation<GPIOA, Zhele::Clock::PortaClock, 'A'>;
, а программисту, соответственно, доступен новый тип Porta
со своими статическими методами.
PortImplementation
имеет интерфейс, представленный следующими статическими методами:
DataType Read()
- возвращает текущее значение регистраODR
.void Write(DataType value)
- записыват значение в регистрODR
.void ClearAndSet(DataType clearMask, DataType setMask)
- очищает и устанавливает линии, соответствующие маскам (через регистрBSRR
).void Set(DataType value)
- устанавливает значение линии по заданной маске (через регистрBSRR
).void Clear(DataType value)
- сбрасывает линии по заданной маске (через регистрBSRR
).void Toggle(DataType value)
- инвертирует выводе по заданной маске.DataType PinRead()
- читает порт в режиме входа.void SetConfiguration(Configuration configuration, DataType mask)
- устанавливает конфигурацию линий по заданной маске.void SetSpeed(Speed speed, DataType mask)
- устанавливает скорость линий по заданной маске.void SetDriverType(DriverType driver, DataType mask)
- устанавливает режим выхода по заданной маске.void SetPullMode(PullMode mode, DataType mask)
- устанавливает подтяжку по заданной маске.void AltFuncNumber(uint8_t number, DataType mask)
- устанавливает номер альтернативной функции по заданной маске (недоступно для контроллеров Stm32F1, поскольку у них модуль AFIO работает иначе).void Enable()
- включает тактирование порта.void Disable()
- выключает тактирование порта.
Для всех методов с параметрами библиотека предоставляет их шаблонный вариант, позволяющий сэкономить Flash-память за счет оптимизации, рекомендуется использовать именно шаблонный вариант, если настройки известны на этапе компиляции. Например, задать конфигурацию порта можно так: Porta::SetConfiguration(0xffff, Porta::Configuration::Out)
, а можно более оптимально: Porta::SetConfiguration<0xffff, Porta::Configuration::Out>()
.
Решим элементарную задачу - настроить линии 0, 1, 3 порта A
на выход (push-pull) и установить
на них высокий уровень.
Porta::Enable();
Porta::SetConfiguration<Porta::Configuration::Out, 0b1011>();
Porta::SetDriverType<Porta::DriverType::PushPull, 0b1011>();
Porta::Set(0b1011);
Полный проект этого примера доступен на Яндекс.Диске
Прошивка занимает 0 байтов RAM и 64 байтов Flash (вместе со стандартным startup получается 28 и 380 соответственно).
Запуск прошивки в Proteus демонстрирует, что программа работает правильно (высокий уровень на выходых PA0, PA1, PA3
).
Гораздо чаще на практике приходится взаимодействовать с отдельными линиями ввода-вывода. Для этих целей библиотека предоставляет набор классов, инстанцирующих шаблон класса TPin
, содержащий те же самые методы, что и рассмотренный ранее класс, реализующий порт ввода-вывода. Очевидно, что методы конфигурации и настройки одельной линии лишены первого параметра - маски. Решим ту же задачу используя три независимых типа: Pa0, Pa1, Pa3
:
Pa0::Port::Enable();
Pa0::SetConfiguration<Pa0::Configuration::Out>();
Pa0::SetDriverType<Pa0::DriverType::PushPull>();
Pa0::Set();
Pa1::Port::Enable(); // Это лишнее, порт итак тактируется
Pa1::SetConfiguration<Pa1::Configuration::Out>();
Pa1::SetDriverType<Pa1::DriverType::PushPull>();
Pa1::Set();
Pa3::Port::Enable(); // Это лишнее, порт итак тактируется
Pa3::SetConfiguration<Pa3::Configuration::Out>();
Pa3::SetDriverType<Pa3::DriverType::PushPull>();
Pa3::Set();
Полный проект этого примера доступен на Яндекс.Диске.
Прошивка занимает уже 0 байтов RAM и 144 байтов Flash (вместе со стандартным startup получается 28 и 380 соответственно).
В Proteus мы получим ту же самую картинку.
Можно заметить, что было бы крайне удобно иметь возможность скомпоновать собственный виртуальный порт из заданных линий и управлять им как целым. Такая возможность в библиотеке есть и будет рассмотрена на следующем занятии.
Статья в формате PDF доступна также на Яндекс.Диске.