Макросы для читаемости

При программировании STM32 производитель контроллеров предлагает использовать библиотеку Standart Peripherial Libray, но мне не нравится то количество кода, которое приходится писать, чтобы инициализировать вывод для выполнения каких-либо функций. Так же код, на мой взгляд, получается не особо читаемым. При поиске способа исправить эти недочёты вспомнились «макросы Аскольда Волкова», названные так по имени автора.
Например, инициализация портов для USART3 STM32F407 выглядит примерно так:
#define EVAL_COM1                        USART3
#define EVAL_COM1_CLK                    RCC_APB1Periph_USART3

#define EVAL_COM1_TX_PIN                 GPIO_Pin_10
#define EVAL_COM1_TX_GPIO_PORT           GPIOC
#define EVAL_COM1_TX_GPIO_CLK            RCC_AHB1Periph_GPIOC
#define EVAL_COM1_TX_SOURCE              GPIO_PinSource10
#define EVAL_COM1_TX_AF                  GPIO_AF_USART3

#define EVAL_COM1_RX_PIN                 GPIO_Pin_11
#define EVAL_COM1_RX_GPIO_PORT           GPIOC
#define EVAL_COM1_RX_GPIO_CLK            RCC_AHB1Periph_GPIOC
#define EVAL_COM1_RX_SOURCE              GPIO_PinSource11
#define EVAL_COM1_RX_AF                  GPIO_AF_USART3

#define EVAL_COM1_IRQn                   USART3_IRQn
void STM_EVAL_COMInit(USART_TypeDef* USARTx /* COM_TypeDef COM*/, USART_InitTypeDef* USART_InitStruct)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  /* Enable GPIO clock */
  RCC_AHB1PeriphClockCmd(EVAL_COM1_TX_GPIO_CLK | EVAL_COM1_RX_GPIO_CLK, ENABLE);

  /* Enable UART clock */
  RCC_APB1PeriphClockCmd(EVAL_COM1_CLK, ENABLE);

  /* Connect PXx to USARTx_Tx*/
  GPIO_PinAFConfig(EVAL_COM1_TX_GPIO_PORT, EVAL_COM1_TX_SOURCE, EVAL_COM1_TX_AF);

  /* Connect PXx to USARTx_Rx*/
  GPIO_PinAFConfig(EVAL_COM1_RX_GPIO_PORT, EVAL_COM1_RX_SOURCE, EVAL_COM1_RX_AF);

  /* Configure USART Tx as alternate function  */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Pin = EVAL_COM1_TX_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(EVAL_COM1_TX_GPIO_PORT, &GPIO_InitStructure);

  /* Configure USART Rx as alternate function  */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = EVAL_COM1_RX_PIN;
  GPIO_Init(EVAL_COM1_RX_GPIO_PORT, &GPIO_InitStructure);

  /* USART configuration */
  USART_Init(EVAL_COM1, USART_InitStruct);
    
  /* Enable USART */
  USART_Cmd(EVAL_COM1, ENABLE);
}


Много? Да. Читаемо? Относительно. Понятно, что происходит, но количество действий и промежуточных операций замыливают смысл. Это как было. А стало так:
#include "avmacro.h"
  #define COM3_TX  C, 10, H, F, 0x07
  #define COM3_RX  C, 11, H, F, 0x07

void STM_EVAL_COMInit(USART_TypeDef* USARTx /* COM_TypeDef COM*/, USART_InitTypeDef* USART_InitStruct)
{
  clk_on(COM3_TX); // так же и на приём, порт один и тот же
  clk_on(COM3_RX);
  dir_af_pp(COM3_TX);
  dir_af_pp(COM3_RX);
  // Enable UART clock //
  RCC_APB1PeriphClockCmd(EVAL_COM1_CLK, ENABLE);
  // USART configuration //
  USART_Init(EVAL_COM1, USART_InitStruct);
    
  // Enable USART //
  USART_Cmd(EVAL_COM1, ENABLE);
}

Тонкость в том, что макросы в приведённом виде не производят проверку диапазонов, корректности значений, используется небезопасная операция склейки ## — учитывайте это (стандартные используют проверку и assert ).
Кому нравится — берите в архиве.
  • 0
  • 11 апреля 2017, 09:14
  • Hoksmur
  • 1
Файлы в топике: avmacro.zip

Комментарии (18)

RSS свернуть / развернуть
ST рекомендует использовать STM32 CubeMX и HAL вместо STL.
Если куб и хал не нравится, а на регистрах писать грустно, есть хорошая библиотека libopencm3:
github.com/libopencm3/libopencm3-examples/blob/master/examples/stm32/f4/stm32f4-discovery/usart/usart.c
Код получается понятный и без макросовой магии.
0
Для читаемости тогда уж надо и о скорости в имени как-то сказать — dir_af_pp не раскрывает. И Волкова по тексту поправьте.
0
Поправил, спасибо.
0
Это приведённая каша из кода в разном стиле и магических макросов лучше читается?

#define COM3_TX  C, 10, H, F, 0x07
#define COM3_RX  C, 11, H, F, 0x07


И include внутри функции, мягко говоря, не рекомендуется.
0
Не по феншую написано, зачем спорить? Из тестового проекта скопировал. Да и дефайны за функцию лучше бы вынести. Поправить?
0
Мозги автору очередного велосипеда не поправишь. Может сам одумается.
0
Я всё так же голосую за простые абстрактные функции, которым передаётся указатель на описание пина (порт, номер вывода):
static const TPin Led = {PC, 8};
gp_Output(&Led), gp_Input(&Led), gp_High(&Led), gp_Low(&Led), gp_Set(&Led, true), gp_Get(&Led), gp_PullUp(&Led), gp_PullDown(&Led) и т.д.

Из плюсов: функции просты и легко портируются на почти любой другой контроллер (хоть AVR, хоть LPC23xx, хоть ещё кто); назначение очевидно, описание вывода требует лишь название порта и номера вывода в нём, а не десять строк разных магических констант, взятых неизвестно откуда, как в SPL; нет магии макросов, которая при отладке не видна — сразу по коду видно, какие действия и в каком порядке совершаются над выводом и каким именно; нет привязки к какой-либо аппаратно-зависимой библиотеке типа HAL или SPL; т.к. описание вывода — структура, оно легко интегрируется в другие структуры и собирается в массивы; можно хранить указатель на вывод в переменной.

Бывают, конечно, специфические параметры: альтернативная функция gp_AlternateFunction или настройка скорости gp_FastSpeed, но обычно тот код, где они нужны, и так будет переписываться при переносе на другой контроллер (ибо завязан на аппаратные модули).
Отдельность функций не является проблемой, обычно вывод настраивается за одно-три действия.

Из минусов: небольшой оверхед на вызов функции, но обычно он слишком незначителен, чтобы обращать на него внимание (оптимизация — второй этап, да и то, только при необходимости); как уже сказал, есть отличия в возможностях модулей ввода-вывода — для разных платформ набор функций будет немного отличаться (например, в AVR не будет gp_PullDown), но тут ничего не поделаешь.

* Названия функций условны
0
Если есть возможность эти функции сделать static или inline, то оверхеда на вызов функции не будет

godbolt.org/g/psmNAq
0
рано ответило, а если ещё параметр указателя структуры порта — константа
godbolt.org/g/CcYbIa
то нет оверхеда на хранение структуры (код идентичен прямой записи в регистр gpio)
0
вернее так: godbolt.org/g/mLPiur
0
Если компилятор поддерживает, то можно соптимизировать и так :)

Хранение: 2 байта на один вывод, вот и всё.
0
Стандартные Библиотеки СТМ индусы писали. Они местами ну очень кривы во всяком случае STM8).
Лично я считаю что макросов лучше — меньше, потому что они при отладке не раскрываются на шаги. Но из меня плохой программист.
0
Если хочется одновременно хорошо читаемого кода и оптимального выхлопа компилятора (на уровне прямой записи в регистры) можно еще покурить шаблоны С++. Правда, платить за это придется мозговыносящестью самой библиотеки. Для GPIO тут уже есть пример от Neiver'а.
0
  • avatar
  • Vga
  • 11 апреля 2017, 20:46
хочется чего то интерактивного — пример инициализации последовательного порта… не идеал

в принципе некоторые подобные операторы позволяют работать не с полным списком, а только с нужными для конкретного применения набором параметров
0
CubeMX попробуй.
0
этот оператор позволяет менять настройки порта в процессе работы — то есть это функциональность уже среды программирования, а не только предварительная настройка как в Кубе.
Аналогичная КУБовая интерактивная настройка операторов может повысить эффективность и безглючность программирования за счёт исключения коллизий.
0
Может случиться эбонит на какой-нибудь силовой плате, если мышкой промахнёшься.
0
Насколько знаю, в кейле есть возможность оформить код так, чтобы потом мышкой менять какие-то параметры, выбирая их из списка. Не знаю только, работает это только для асма или можно и для си. Но особой нужды в таком не испытывал. Всё так, конечно не настроищь, но что-то можно.

Настройка параметров в Кейле
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.