ARM с ядром Cortex Mx (на примере STM32F10x)


КДПВ Микроконтроллер ARM Cortex M3 STM32F103c8t6 широко распространен как 32-х битный микроконтроллер для любительских проектов. Как для практически любого микроконтроллера, для него существует SDK, включающая, в том числе и заголовочные файлы C++ определения периферии контроллера.

И вот там последовательный порт, например, определен как структура данных, а экземпляр этой структуры находится в области адресов, отведенной под регистры и мы имеем доступ к этой области через указатель на конкретный адрес.

Для тех, кто не сталкивался с этим ранее, я немного опишу, как это определено, те же из читателей, которые знакомы с этим, могут пропустить это описание.

Эта структура и её экземпляр описаны вот так:

/* =========================================================================*/
typedef struct {
    __IO uint32_t CR1;  /*!< USART Control register 1, Address offset: 0x00 */ 
     .
     .
     .
    __IO uint32_t ISR;  /*!< USART Interrupt and status register, ... */ 
} USART_TypeDef; // USART_Type было бы достаточно.

/* =========================================================================*/
#define USART1_BASE    (APBPERIPH_BASE + 0x00013800)
     .
     .
     .
#define USART1         ((USART_TypeDef *) USART1_BASE)
#define USART1_BASE    0x400xx000U

Подробнее можно посмотреть здесь stm32f103xb.h ? 800 кБайт

И если пользоваться только только определениями в этом файле, приходится писать вот так (пример использования регистра состояний последовательного порта):

// ----------------------------------------------------------------------------
if (USART1->ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG))
{

}

А пользоваться приходится, потому что существующие фирменные решения, известные как CMSIS и HAL слишком сложны, чтобы использовать их в любительских проектах.

Но если писать на C++, то можно написать так:

// ----------------------------------------------------------------------------
USART_TypeDef & Usart1 = *USART1;

// ----------------------------------------------------------------------------
if (Usart1.ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG))
{

}

Изменяемая ссылка инициализируется указателем. Это небольшое облегчение, но приятное. Ещё лучше, конечно, написать небольшой класс-оберточку над этим, при этом такой прием всё равно пригодится.

Конечно, хотелось бы сразу написать этот класс-оберточку над последовательным портом (EUSART – extended universal serial asinhronous reseiver-transmitter), таким заманчивым, с расширенными возможностями, последовательным асинхронным приемопередатчиком и иметь возможность связать наш маленький микроконтроллер с настольной системой или ноутбуком, но микроконтроллеры Cortex отличаются развитой системой тактирования и начать придется с неё, а потом ещё сконфигурировать соответствующие выводы портов ввода-вывода для работы с периферией, потому что в серии STM32F1xx, как и во многих других микроконтроллерах ARM Cortex нельзя просто так сконфигурировать выводы порта на ввод или вывод и работать при этом с периферией.

Что же, начнем с включения тактирования. Система тактирования называется RCC регистры управления тактированием (registers for clock control) и тоже представляет из себя структуру данных, объявленному указателю на которую присвоено конкретное значение адреса.

/* =========================================================================*/
typedef struct
{
    .
    .
    .
} RCC_TypeDef;

Поля этой структуры, объявленные вот так, где __IO определяет volatile:

/* =========================================================================*/
__IO uint32_t CR;

соответствуют регистрам из RCC, а отдельные биты этих регистров включению или функции тактирования периферии микроконтроллера. Всё это хорошо описано в документации (pdf).

Указатель на структуру определен как

/* =========================================================================*/
#define RCC          ((RCC_TypeDef *)RCC_BASE)

Работа с битами регистров без использования SDK обычно выглядит таким образом:

Вот включение тактирования порта A.

// ----------------------------------------------------------------------------
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

Можно включить два и более бита сразу

// ----------------------------------------------------------------------------
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;

Выглядит для C++ немного, что ли, непривычно. Лучше было бы написать по другому, вот так, например, используя ООП.

// ----------------------------------------------------------------------------
Rcc.PortOn(Port::A);

Выглядит лучше, но в XXI веке мы пойдем немного дальше, воспользуемся C++ 17 и напишем с использованием шаблонов с переменным количеством параметров ещё красивее:

// ----------------------------------------------------------------------------
 Rcc.PortOn<Port::A, Port::B>();

Где Rcc определена вот таким образом:

// ----------------------------------------------------------------------------
TRcc & Rcc = *static_cast<TRcc *>RCC; 

От этого и начнем строить обертку над регистрами тактирования. Для начала определим класс и указатель (ссылку) на него.

Сначала хотелось написать в стандарте C++ 11/14 с использованием рекурсивной распаковки параметров шаблона функции. Хорошая статья об этом приведена в конце заметки, в разделе ссылки.

// ============================================================================
enum class GPort : uint32_t
{
    A = RCC_APB2ENR_IOPAEN,
    B = RCC_APB2ENR_IOPBEN,
    C = RCC_APB2ENR_IOPCEN,

};

// ----------------------------------------------------------------------------
class TRcc: public ::RCC_TypeDef
{
    private:
        TRcc() = delete;
        ~TRcc() = delete;


    // ========================================================================
    public:
        template<GPort... port>
        inline void PortOn(void) // Без явного разворачивания (inline)
        {                         // не развернется при -Og или -O0
            APB2ENR |= SetBits<(uint32_t)port...>();
        }

    // ------------------------------------------------------------------------
#define BITMASK 0x01    // Макроопределение здесь гарантирует нам, что константа
#define MASKWIDTH 1     // не будет перенесена компилятором в память. Брать от
                        // неё указатель мы не собираемся и у нас есть #undef.
    private:
        // Функциональное пролистывание (fold) пакета параметров рекурсией.
        template<uint8_t bitmask>
        inline constexpr uint32_t SetBits(void)
        {
            // Немного избыточная проверка, ведь GPort это enum
 	    //  (а, кстати, bitmask это и не бит).
            // static_assert(bitmask < 16, "Превышена разрядность.");
            return bitmask;
        }

        template<uint8_t bit1, uint8_t bit2, uint8_t... bit>
        inline constexpr uint32_t SetBits(void)
        {
            return SetBits<bit1>() | SetBits<bit2, bit...>();
        }

};

#undef BITMASK
#undef MASKWIDTH

    // ------------------------------------------------------------------------
    TRcc & Rcc = *static_cast<TRcc *>RCC;

Рассмотрим вызов функции включения тактирования порта:

    Rcc.PortOn<GPort::A>();

GCC развернет его вот в такой набор команд:

   ldr     r3, [pc, #376]  ; (0x8000608 <main()+392>)
   ldr     r0, [r3, #24]
   orr.w   r0, r0, #4
   str     r0, [r3, #24]

Получилось? Проверим дальше

    Rcc.PortOn<GPort::A, GPort::B, GPort::C>();

Увы, не совсем, наивный GCC развернул замыкающий вызов рекурсии отдельно:

    ldr     r3, [pc, #380]  ; (0x8000614 <main()+404>) 
    ldr     r0, [r3, #24]
    orr.w   r0, r0, #4      ; APB2ENR |= GPort::A
    str     r0, [r3, #24]
    ldr     r0, [r3, #24]
    orr.w   r0, r0, #28     ; APB2ENR |= Gport::B | GPort::C
    str     r0, [r3, #24] #24]

В защиту GCC нужно сказать, что вот так разворачивается не всегда, а только в более сложных случаях, что будет видно при реализации класса порта ввода-вывода. Что же, тут на помощь спешит C++ 17. Перепишем класс TRCC, используя возможности встроенного пролистывания.

// ----------------------------------------------------------------------------
class TRcc: public ::RCC_TypeDef
{
    private:
        TRcc() = delete;      // Мы не создаем экземпляр класса, а
        ~TRcc() = delete;    // используем для инициализации указатель.


    // ========================================================================
    public:
        template<GPort... port>
        inline void PortOn(void) // Без явного разворачивания (inline)
        {                         // не развернется при -Og или -O0
            APB2ENR |= SetBits17<(uint32_t)port...>();
        }

    // ------------------------------------------------------------------------
#define BITMASK 0x01    // Макроопределение здесь гарантирует нам, что константа
#define MASKWIDTH 1     // не будет перенесена компилятором в память. Брать от
                        // неё указатель мы не собираемся и у нас есть #undef.
    private:
        // Функциональное пролистывание (fold) пакета параметров рекурсией. С++ 17.
        template<uint8_t... bitmask>
        inline constexpr uint32_t SetBits17(void)
        {
            return (bitmask | ...); // Можно и справа налево ... | bit
        }
};

#undef BITMASK
#undef MASKWIDTH

Вот теперь получилось:

ldr     r2, [pc, #372]  ; (0x800060c <main()+396>)
ldr     r0, [r2, #24]
orr.w   r0, r0, #28     ; APB2ENR |= Gport::A | Gport::B | GPort::C
str     r0, [r3, #24]

И код класса стал проще.

Вывод: C++ 17 позволяет нам с помощью шаблонов с переменным числом параметров получить такой же минимальный набор инструкций (даже при выключенной оптимизации), какой получается при использовании классической работы с микроконтроллером через определения регистров, но при этом мы получаем все преимущества сильной типизации C++, проверок во время компиляции, переиспользуемого через структуру базовых классов кода и так далее.

Вот как-то так записанное на C++

Rcc.PortOn<Port::A, Port::B, Port::C>();

И классический текст на регистрах:

RCC->APB2 |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;

разворачиваются в оптимальный набор инструкций. Вот код, сгенерированный GCC (оптимизация выключена -Og):

    ldr     r2, [pc, #372]  ; (0x800060c <main()+396>) [Адрес структуры RCC]
    ldr     r0, [r2, #0]    ; r0 = RCC->APB2 // [Адрес регистра APB2]
    orr.w   r0, r0, #160    ; r0 |= 0x10100000
    str     r0, [r2, #0]    ; RCC->APB2 = r0

Теперь следует продолжить работу и написать класс порта ввода-вывода. Работа с битами портов ввода-вывода осложняется тем, что на конфигурацию одной ножки порта отводится четыре бита и, таким образом, на 16-ти битный порт требуется 64 бита конфигурации, которые разделены на два 32-х битные регистра CRL и CRH. Плюс ещё ширина битовой маски становится больше 1. Но и тут пролистывание C++ 17 показывает свои возможности.

image

Далее будет написан класс TGPIO, а также классы для работы с другой периферией, последовательного порта, I2C, SPI, ПДП, таймеров и многого другого, что обычно присутствует в микроконтроллерах ARM Cortex и тогда можно будет помигать вот такими светодиодиками.

Но об этом в следующей заметке. Исходники проекта на гитхабе.

Интернет статьи, использованные при написании заметки


Шаблоны с переменным количеством аргументов в C++11.
Нововведения в шаблонах.
Языковые новшества C++17. Часть 1. Свёртка и выведение.
Список ссылок на документацию по микроконтроллерам STM.
Макросы с переменным числом параметров

Статьи на Xабре, побудившие меня всё-таки написать эту заметку


Светофорчик на Attiny13.

Джулиан Ассанж арестован полицией Великобритании
Космос как смутное воспоминание

Написано 12.04.2019 – С Днем Космонавтики!

P. S.
STM32F103c8t6 в Stm CubeMx Картинка STM32F103c8t6 из CubeMX.

В качестве отправной точки использован текст, созданный расширением Eclips'а для работы с микроконтроллерами GNU MCU Eclipse ARM Embedded и STM-ского CubeMX, т. есть файлы стандартных функций C++, _start() и _init(), определения векторов прерываний взяты из MCU Eclipse ARM Embedded, а файлы определения регистров и работы с ядром Cortex M3 – из проекта, сделанного CubeMX.


P. P. S.
На КДПВ изображена отладка с контроллером STM32F103c8t6. Далеко не у всех есть такая плата, но приобрести её несложно, правда, это выходит за рамки данной заметки.

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


  1. 8street
    16.04.2019 11:58
    +3

    А пользоваться приходится, потому что существующие фирменные решения, известные как CMSIS и HAL слишком сложны, чтобы использовать их в любительских проектах.
    Извините, но мне кажется сложным ваш пример. По сути, вы сейчас начали писать альтернативу вышеназванным библиотекам, но только на C++. К тому же, в статье не хватает объяснения для закоренелых сишников, как включить, например, C++ в своем проекте.


    1. Goron_Dekar
      16.04.2019 12:32

      Сложные не по освоению, а по поведению.
      Описанный тут вариант использует всю мощь компил-тайма, и после компиляции не вызывает неконтролируемого программистом поведения.
      А закоренелым Сшникам ничего не объяснить, увы. Для них есть статьи как перейти на плюсы.


    1. lamerok
      16.04.2019 16:08

      По, моему, Пример хороший, пользоваться им очень просто…
      Да и класс получился простым...


    1. NordicEnergy
      16.04.2019 16:51

      фирменные решения, известные как CMSIS и HAL
      При чем никто не поправил, что CMSIS это как раз то, что в начале статьи автор использовал. И вообще это стандартная либа на все cortex, а не поделка ST. Вероятно имелось ввиду «SPL и HAL».


  1. andreili
    16.04.2019 12:38

    Вот что я для себя запилил на С++ для STM32: github.com/andreili/STM32FX_FM
    Уже есть очень многое, включая полную поддержку USB HOST (MSC + HID), scmRTOS, FATfs.
    Проект активно развивается, есть поддержка и STM32F103, но частичная — я в основном сосредоточился на STM32F407, для которого и начинал писать данный проект.
    Но подход с шаблонами возьму на заметку — может и обновлю свои классы в репе ;)


    1. Goron_Dekar
      16.04.2019 12:49

      Для USB не хватает генерации дескрипторов в компил-тайме.
      Очень много препроцессора.
      Нет поддержки векторов прерываний в памяти с динамической сменой вектора.


      1. andreili
        16.04.2019 13:20

        1) У меня нет поддержки USB Device как бы ;) Я пока что только Host делал, поскольку он был мне нужен.
        2) Постепенно ухожу от него;
        3) Есть, ещё как есть:

        #ifdef USE_MEMORY_ISR
        __attribute__((section(".isr_vector"))) const ISR::ShortVectors interruptsVectorTable =
        #else
        volatile __attribute__((section(".isr_vector"))) const ISR::Vectors interruptsVectorTable =
        #endif
        {
        #ifdef USE_MEMORY_ISR
           (uint32_t)&_estack,
           ISR::Reset
        };

        Ну и кусок из стандартного init'а:
            /* Configure the Vector Table location add offset address ------------------*/
            #ifdef VECT_TAB_SRAM
            SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
            #else
            SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
            #endif

        Опять-таки — данным функционалом не пользовался, потому не проверял пока…


  1. KonstantinSpb
    16.04.2019 12:45

    Да, надо популяризировать С++ среди С программистов, некоторые думают, что С++ генерит более тяжелый код


    1. Goron_Dekar
      16.04.2019 12:52

      для этого надо написать опус про С++ без стандартной библиотеки.


      1. TheDaemon
        16.04.2019 16:40

        А еще без исключений, без RTTI (dynamic_cast) и т.д.
        На stack overflow множество раз обсуждали почему плохо писать на C++ ядерные драйвера, это же относится и к МК. Вывод во всех эти обсуждениях простой: писать можно, но приходится быть очень осторожным и надо неплохо понимать, что же генерирует компилятор при использовании тех или иных конструкций C++.


        1. NordicEnergy
          16.04.2019 16:54
          +1

          А при написание проекта на С ничего понимать не надо?


          1. TheDaemon
            16.04.2019 17:24
            +2

            Конечно надо. Просто количество конструкций языка C кардинально меньше :) Поэтому требования к уровню компетентности программиста тоже будут ниже


            1. lamerok
              16.04.2019 19:30
              +1

              Зато код на С++ понятнее на бизнес логике. Конечно внутренности страшные, но пользоваться и понимать, как в целом работает программа на много проще.


              1. TheDaemon
                16.04.2019 20:51
                +3

                А еще код на С++ типобезопасный, более реюзабельный, проще масштабируется… да и просто писать его приятней. Просто надо понимать цену :)


            1. NordicEnergy
              16.04.2019 19:53

              Доля истины в ваших словах конечно есть, но никто не заставляет от плюсов использовать все синтаксические прелести. В МК реально нужно 20...30% от возможностей С++, остальное просто не использовать и тогда уже сверхквалификации не нужна, как мне кажется.


              1. lamerok
                16.04.2019 20:57

                Дак на оборот, я бы сказал, приходится изголятся, и юзать практически все возможности языка, чтобы было экономнее...


        1. Goron_Dekar
          16.04.2019 17:12

          Исключения как раз редко меняют в новых стандартах, как и RTTI. А вот описание изменений в новых стандартах почти с первого предложения тонет в std::, а в пространстве ядра нету этой вашей std. И в результате у пользователей возникает ощущение, что С++ это про контейнеры, итераторы и многопоточность, а std является неотъемлемой частью языка. Что не верно.


          1. TheDaemon
            16.04.2019 17:33

            Тут скорее проблема в том, что включенные исключения, даже если их никто не кидает, порождают лэндинг пады, т.е. избыточный код. RTTI порождает кучу дополнительной информации. Все это кардинально усложняет linker-скрипты и т.д.
            Я видал несколько реализаций std под МК. Конечно, они не 100% соответствуют стандарту.


          1. 0xd34df00d
            16.04.2019 19:49
            +1

            std действительно является неотъемлемой частью языка (она описана в стандарте языка).

            Но вот std::tuple, std::invoke, std::integer_sequence, std::strong_order и прочие подобные вещи — это совсем не про контейнеры, итераторы и многопоточность (или динамическую память).


          1. lamerok
            18.04.2019 15:41

            Std это неотъемлемая часть стандарта и поставщик компилятора, должен ее поставлять… Например IAR поставляет свою реализацию, соответствующую стандарту, там есть вещи не реализованные до конца, например thread, или atomic, потому что их реализации зависят от того какую РТОС, например вы используете, но есть заготовку пустые, для того, чтобы могли реализовать это… И пользоваться как обычным std..


    1. NordicEnergy
      16.04.2019 16:53

      Так думают только деды по моему, которым лень переучиваться :)) На хабре вроде даже было тестовое сравнение размеров финальных бинарников на С и С++, там даже плюсы были чуть впереди в ряде задач.


  1. mkulesh
    16.04.2019 12:49

    Спасибо за статью, с интересом посмотрел Ваш код. Но все-таки согласен с предыдущим комментарием. HAL, быть может, не так эстетичен, в нем есть баги, но я бы тоже не стал говорить о его сложности. В Вашем варианте тоже есть неприятная особенность — у Вас используемые порты и аппаратно-зависимые константы «размазаны» по всему коду как и в НAL. На малых проектах не проблема. По вот на больших… Основная фишка STM32 — фактическая многозадачность на операциях ввода-вывода за счёт DMA. Вот и представьте читабельность Вашего кода на проекте, где 4-5 разных интерфейсов (типа SDIO для карты памяти, I2S для звука, SPI для дисплея, USB и USART для чего-нибудь еще) работают асинхронно с задействованием 10 или более прерываний. В этом случае имеет смысл полностью отделять слой аппаратной конфигурации (порты, устройства ввода-вывода, прерывания, регистры) от бизнес-логики. И вот здесь С++ поверх HAL или CMSIS, а не вместо их — очень мощный инструмент. Посмотрите, если интересно, наш проект github.com/den-makarov/stm32async.
    В нем мы пытаемся на С++ разработать слой аппаратного описания ресурсов контроллера и слой асинхронного управления этими ресурсами.


    1. Koyanisqatsi
      16.04.2019 13:28

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


    1. lamerok
      16.04.2019 21:14

      Охохо… Посмотрел немного, как это будет работать с РТОС? SPI если без DMA брать, я бы сделал через интерфейс, сделал бы пару виртуальных функций, типа ReadAndWrite, открыть сессию, чтобы ресурс захватить, закрыть сессию. И работал бы так, например драйвер лсд открывает сессию, конфигурит AЦП под себя, работает с SPI, пока он работает, другой потребитель, например драйвер АЦП курит, как только сессия закрылась, АЦП открывает сессию, конфигурит этот же SPI под себя, работает с ним, закрывает сессию.
      Для UART бы подписчиков сделал на обработку прерываний, так как через UART в основном только один потребитель работает, например Modbus, подписался бы на обработку по приему и передаче…… В общем непонятно, зачем глючный HAL оборачивать и ограничивать себя в дизайне, можно изначально продумать все хорошо, отвязать абстрактную аппаратную часть от логики по другому.


  1. lamerok
    16.04.2019 16:09
    +1

    Вместо #define MASK 0xF, используйте static constexpr uint32_t Mask = 0xF;


    1. kovserg
      16.04.2019 22:05

      чем static constexprt uint32_t Mask = 0xF; лучше enum { Mask=0xF };?


      1. 0xd34df00d
        17.04.2019 02:28
        +1

        На уровне языка — тип разный. У Mask в первом варианте тип — uint32_t, во втором — некоторый безымянный анонимный енам. Может неправильно себя вести с шаблонами, ожидающими прямо uint32_t, а не некий конвертируемый в него тип, может просто приводить к некоему раздуванию кода (особенно когда таких енамов у вас будет несколько).


        На уровне семантики кода — более ясно передаётся намерение. У вас нет перечисления с единственным элементом. У вас просто есть известная на этапе компиляции целочисленная константа.


        1. kovserg
          17.04.2019 08:48

          К раздуванию из-за типа целочисленной константы? Пример можно.

          Чем длиньше выражение тем более ясно намерение? ideone.com/4UCivk

          static constexpr int fn(int a) { while(a) a=(a*5)&255; return a; }
          static constexpr int zero=fn(0);
          static constexpr int twix=fn(1);
          


          1. 0xd34df00d
            17.04.2019 21:38

            К раздуванию из-за типа целочисленной константы? Пример можно.

            Из-за лишних инстанциирований шаблона.


            Если у вас есть


            template<typename T> void foo(T) {}

            то для


            enum { Mask1 = 0xff };
            enum { Mask2 = 0xcc };
            
            foo(Mask1);
            foo(Mask2);

            у вас будет два инстанциирования шаблона, тогда как для варианта с constexpr он будет один.


            1. kovserg
              17.04.2019 22:07

              Это только если не использовать оптимизацию.


              1. 0xd34df00d
                17.04.2019 22:15

                Какая оптимизация заставит линкер слить две функции с разными именами, пусть даже и одинаковым телом?


                Да и в рамках одного TU она тоже не поможет:


                template<typename T>
                __attribute__((noinline)) int foo(T t)
                {
                    return static_cast<int>(t);
                }
                
                int main()
                {
                    enum { Mask1 = 0xff };
                    enum { Mask2 = 0xcc };
                
                    return foo(Mask1) + foo(Mask2);
                }

                gcc 8.3, -O3:


                _Z3fooIZ4mainEUt_EiT_.constprop.0:
                        movl    $255, %eax
                        ret
                _Z3fooIZ4mainEUt0_EiT_.constprop.1:
                        movl    $204, %eax
                        ret
                main:
                        call    _Z3fooIZ4mainEUt_EiT_.constprop.0
                        movl    %eax, %edx
                        call    _Z3fooIZ4mainEUt0_EiT_.constprop.1
                        addl    %edx, %eax
                        ret


                1. TheDaemon
                  17.04.2019 22:55

                  LTO?


                  1. 0xd34df00d
                    17.04.2019 22:58

                    Это уже тогда не совсем линкер в классическом понимании, но да, засчитывается.


                    Впрочем, следующий за этим пример показывает, что LTO не поможет: даже в рамках одного TU там компилятор ничего не объединяет.


                    1. TheDaemon
                      17.04.2019 23:25

                      Я уже почти поверил, но потом вспомнил, что у меня есть WSL…

                      мой gcc функции объединяет


                      1. 0xd34df00d
                        17.04.2019 23:41

                        Хм, интересно. Я не ожидал, что LTO повлияет на поведение даже в рамках одного TU, но вообще это имеет смысл, да.

                        Спасибо, что попробовали.


                        1. TheDaemon
                          17.04.2019 23:43

                          Вам спасибо, интересная дискуссия :)


  1. Temtaime
    16.04.2019 18:18
    +3

    Автор использует фишки C++11 и при этом пишет void для функций без аргументов — это какой-то современный стиль?


    1. technic93
      17.04.2019 11:17
      +1

      Ещё автору следует static_cast использовать.


      1. lamerok
        17.04.2019 11:52

        Да, в общем случае это так, но для enum вполне достаточно С++ приведения типов uint32_t(..). — static_cast тут будет загромождать полезную информацию.


        1. andreili
          17.04.2019 15:11

          Ныне GCC ругается на такое старое приведение типов, требуя как раз таки cast'оов ;)


          1. lamerok
            17.04.2019 15:14

            GCC ругается на Си приведение, а на С++ не может ругаться…
            Может вы путаете (uint8_t)7 и uint8_t(7). Первое Сишное, второе С++


            1. andreili
              17.04.2019 17:18

              Хм, не знал. Спасибо за подсказку ;)


            1. 0xd34df00d
              17.04.2019 21:39

              Лучше uint8_t { 7 }, на narrowing ругнется.


  1. lamerok
    16.04.2019 19:20

    Но, непонятно зачем вообще делать наследование от структуры RCC, да еще и публичное? В итоге, у наследника, кроме методов PortOn еще и куча ненужных регистров, и вообще наследование такой стурктуры тут не к месту. А потом объявлять глобальный указатель на структуру.
    Ну и void в параметре метода надо конечно убирать. В С++ это никакого смысла не имеет.
    Общее правило для дизайна, наследовать только интерфейс, т.е. виртуальные функции, остальное можно сделать агрегацией или композицией.
    Наверное лучше бы сделать так?

    class Rcc
    {
    
    public:
      template<GPort... port>
      inline static void PortOn() 
      {                         
        rcc.AHB1ENR |= SetBits17<(std::uint32_t)port...>();
      }
    
    private:
      static constexpr RCC_TypeDef & rcc = *RCC ;
      template<std::uint8_t... bitmask>
      inline static constexpr std::uint32_t SetBits17()
      {
        return (bitmask | ...); 
       }
    };
    
    int main()
    {
      Rcc::PortOn<GPort::A, GPort::B, GPort::C>();
      return 0;
    }
    


  1. r44083
    16.04.2019 23:42

    Прошу обратить внимание на мою библиотечку C++ для абстракции от железа:
    https://github.com/r44083/omef


    Может пригодится (см. https://github.com/r44083/omef/tree/master/src/hal)
    Комментарии приветствуются.


    1. lamerok
      17.04.2019 04:47

      Можно, конструкторы и методы делать constexpr, также, скажем для порта методы должны быть константными.
      Это позволит создаваемые объекты располагать в ПЗУ, а не в ОЗУ. А это влияет на надежность.
      Скажем, создал я объект порта при запуске, он в ОЗУ, и работает устройство 10 лет без передыха, За 10 лет с ОЗУ, что то да случится, и никто об этом не узнает. А поведение устройства может стать непредсказуемым. А вот если все будет в ПЗУ, то во первых там надежнее, а во вторых легко проверяется, считая контрольную сумму программы. По крайней мере можно просто обнаружить сбой.


      1. r44083
        17.04.2019 09:37

        То есть такие объекты не могут содержать данные? Иначе как их модифицировать?


        1. lamerok
          17.04.2019 10:18

          Да, можно держать constexpr указатели или ссылки, как в примере выше,

          static constexpr RCC_TypeDef & rcc = *RCC

          Вообще в АРМ положить объект в ПЗУ затруднительно, const этого не гарантирует, может лежать как в ОЗУ так и в ПЗУ. Если вы, например забудете в классе сказать хотя бы одному методу, что он константный, хотя реально он не будет менять данные класса, то константный объект на 90% будет в ОЗУ. В АВР было слово _flash и все ложилось в ПЗУ, в АРМ другая архитектура и const не гарантирует что объект будет в ПЗУ, только constexpr. Но с ним ограничения есть, например нельзя делать constexpr конструктор классу, который наследует виртуальный класс.


  1. Ryppka
    17.04.2019 08:47

    А чем, например, github.com/kvasir-io/Kvasir не угодил? NIH-синдром?


  1. GarryC
    17.04.2019 15:16
    +1

    Мне все таки кажется, что надо подниматься выше. Пользователю (программисту в нашем случае) не нужно ставить биты а регистрах, ему нужно включить функцию, а как именно это делается, от него надо прятать, а у Вас совсем иной подход.