
Using C ++ and templates with a variable number of arguments when programming microcontrollers
ARM with Cortex Mx core (using STM32F10x as an example)

And there, the serial port, for example, is defined as a data structure, and an instance of this structure is located in the address area reserved for registers and we have access to this area through a pointer to a specific address.
For those who have not come across this before, I will describe a little how it is defined, the same readers who are familiar with this may skip this description.
This structure and its instance are described like this:
/* =========================================================================*/
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
You can see more details here stm32f103xb.h ≈ 800 kB.
And if you use only the definitions in this file, you have to write like this (example of using the serial port status register):
// ----------------------------------------------------------------------------
if (USART1->ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG))
{
}
And you have to use it because the existing proprietary solutions known as CMSIS and HAL are too complex to use in amateur projects.
But if you write in C ++, then you can write like this:
// ----------------------------------------------------------------------------
USART_TypeDef & Usart1 = *USART1;
// ----------------------------------------------------------------------------
if (Usart1.ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG))
{
}
A mutable reference is initialized with a pointer. This is a little relief, but pleasant. Better yet, of course, to write a small wrapper class over this, while this technique is still useful.
Of course, I would like to immediately write this wrapper class over the serial port (EUSART - extended universal serial asinhronous reseiver-transmitter), so attractive, with advanced features, a serial asynchronous transceiver and be able to connect our small microcontroller to a desktop system or laptop, but microcontrollers Cortex are distinguished by an advanced clocking system and you will have to start from it, and then configure the corresponding I / O pins to work with peripherals, because in the STM32F1xx series, as in legged other ARM Cortex microcontrollers can not just configure the port pins to input or output and work at the same time with the periphery.
Well, let's start by turning on timing. The clock system is called RCC registers for clock control and also represents a data structure, a pointer to which is assigned a specific address value.
/* =========================================================================*/
typedef struct
{
.
.
.
} RCC_TypeDef;
Fields of this structure declared like this, where __IO defines volatile:
/* =========================================================================*/
__IO uint32_t CR;
correspond to the registers from RCC, and the individual bits of these registers are turned on or the clock functions of the periphery of the microcontroller. All this is well described in the documentation (pdf) .
A pointer to a structure is defined as
/* =========================================================================*/
#define RCC ((RCC_TypeDef *)RCC_BASE)
Working with register bits without using the SDK usually looks like this:
Here is the inclusion of port A. clocking.
// ----------------------------------------------------------------------------
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
You can enable two or more bits at once
// ----------------------------------------------------------------------------
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
It looks a bit unusual for C ++, or something unusual. It would be better to write differently, like this, for example, using OOP.
// ----------------------------------------------------------------------------
Rcc.PortOn(Port::A);
It looks better, but in the XXI century we will go a little further, use C ++ 17 and write using templates with a variable number of parameters even more beautiful:
// ----------------------------------------------------------------------------
Rcc.PortOn();
Where Rcc is defined like this:
// ----------------------------------------------------------------------------
TRcc & Rcc = *static_castRCC;
From this, we will begin to build a wrapper over the clock registers. First, we define a class and a pointer (link) to it.
At first, I wanted to write in the C ++ 11/14 standard using recursively unpacking the parameters of a function template. A good article about this is provided at the end of the article, in the link section.
// ============================================================================
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
inline void PortOn(void) // Без явного разворачивания (inline)
{ // не развернется при -Og или -O0
APB2ENR |= SetBits<(uint32_t)port...>();
}
// ------------------------------------------------------------------------
#define BITMASK 0x01 // Макроопределение здесь гарантирует нам, что константа
#define MASKWIDTH 1 // не будет перенесена компилятором в память. Брать от
// неё указатель мы не собираемся и у нас есть #undef.
private:
// Функциональное пролистывание (fold) пакета параметров рекурсией.
template
inline constexpr uint32_t SetBits(void)
{
// Немного избыточная проверка, ведь GPort это enum
// (а, кстати, bitmask это и не бит).
// static_assert(bitmask < 16, "Превышена разрядность.");
return bitmask;
}
template
inline constexpr uint32_t SetBits(void)
{
return SetBits() | SetBits();
}
};
#undef BITMASK
#undef MASKWIDTH
// ------------------------------------------------------------------------
TRcc & Rcc = *static_castRCC;
Consider the call to the port clock enable function:
Rcc.PortOn();
GCC will deploy it into such a set of commands:
ldr r3, [pc, #376] ; (0x8000608 )
ldr r0, [r3, #24]
orr.w r0, r0, #4
str r0, [r3, #24]
Happened? Check next
Rcc.PortOn();
Alas, the not-so-naive GCC deployed the trailing recursion call separately:
ldr r3, [pc, #380] ; (0x8000614 )
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]
In defense of GCC, I must say that this is not always the case, but only in more complex cases, which will be seen when implementing the I / O port class. Well, C ++ 17 is in a hurry to help. Rewrite the TRCC class using the built-in scrolling capabilities.
// ----------------------------------------------------------------------------
class TRcc: public ::RCC_TypeDef
{
private:
TRcc() = delete; // Мы не создаем экземпляр класса, а
~TRcc() = delete; // используем для инициализации указатель.
// ========================================================================
public:
template
inline void PortOn(void) // Без явного разворачивания (inline)
{ // не развернется при -Og или -O0
APB2ENR |= SetBits17<(uint32_t)port...>();
}
// ------------------------------------------------------------------------
#define BITMASK 0x01 // Макроопределение здесь гарантирует нам, что константа
#define MASKWIDTH 1 // не будет перенесена компилятором в память. Брать от
// неё указатель мы не собираемся и у нас есть #undef.
private:
// Функциональное пролистывание (fold) пакета параметров рекурсией. С++ 17.
template
inline constexpr uint32_t SetBits17(void)
{
return (bitmask | ...); // Можно и справа налево ... | bit
}
};
#undef BITMASK
#undef MASKWIDTH
Now it turned out:
ldr r2, [pc, #372] ; (0x800060c )
ldr r0, [r2, #24]
orr.w r0, r0, #28 ; APB2ENR |= Gport::A | Gport::B | GPort::C
str r0, [r3, #24]
And the class code has become simpler.
Conclusion: C ++ 17 allows us to use the templates with a variable number of parameters to get the same minimum set of instructions (even when optimization is turned off) that is obtained when using the classical work with the microcontroller through register definitions, but at the same time we get all the advantages of strong C ++ typing, checks during compilation, reused through the structure of the base classes of the code, and so on.
Here is something like this written in C ++
Rcc.PortOn();
And the classic text on registers:
RCC->APB2 |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
unfold in an optimal set of instructions. Here is the code generated by GCC (optimization off -Og):
ldr r2, [pc, #372] ; (0x800060c ) [Адрес структуры RCC]
ldr r0, [r2, #0] ; r0 = RCC->APB2 // [Адрес регистра APB2]
orr.w r0, r0, #160 ; r0 |= 0x10100000
str r0, [r2, #0] ; RCC->APB2 = r0
Now you should continue working and write the class of the input-output port. Working with I / O port bits is complicated by the fact that four bits are allocated for the configuration of one port leg, and thus 64 bits of configuration are required on a 16-bit port, which are divided into two 32-bit CRL and CRH registers. Plus, the width of the bitmask becomes more than 1. But here, scrolling through C ++ 17 shows its capabilities.

Next, the TGPIO class will be written, as well as classes for working with other peripherals, a serial port, I2C, SPI, DAP, timers and much more, which is usually present in ARM Cortex microcontrollers and then it will be possible to blink with such LEDs.
But more about that in the next note. Sources of the project on github .
Internet articles used to write notes
Templates with a variable number of arguments in C ++ 11 .
Innovations in the templates .
C ++ Language Innovation 17. Part 1. Convolution and derivation .
List of links to documentation for STM microcontrollers .
Variable Parameter Macros
Articles on Khabr that prompted me to write this note
Traffic light on Attiny13 .
Julian Assange arrested by UK police
Space as a vague memory
Posted on 04/12/2019 - Happy Cosmonautics Day!
PS
Picture STM32F103c8t6 from CubeMX.
As a starting point, the text created by the Eclips extension for working with the GNU MCU Eclipse ARM Embedded and STM CubeMX microcontrollers is used , i.e. there are files of standard C ++, _start () and _init () functions, definitions of interrupt vectors are taken from Eclipse MCU ARM Embedded, and the Cortex M3 core register and work files are from a project made by CubeMX.

As a starting point, the text created by the Eclips extension for working with the GNU MCU Eclipse ARM Embedded and STM CubeMX microcontrollers is used , i.e. there are files of standard C ++, _start () and _init () functions, definitions of interrupt vectors are taken from Eclipse MCU ARM Embedded, and the Cortex M3 core register and work files are from a project made by CubeMX.
PPS
On KDPV debugging with the STM32F103c8t6 controller is represented. Not everyone has such a board, but it is not difficult to purchase it, however, this is beyond the scope of this article.