📜 ⬆️ ⬇️

Using C ++ and templates with a variable number of arguments when programming microcontrollers

ARM with Cortex Mx core (for example STM32F10x)


KDPV The ARM Cortex M3 STM32F103c8t6 microcontroller is widely distributed as a 32-bit microcontroller for amateur projects. As for almost any microcontroller, there is an SDK for it, including, among other things, the C ++ header files for determining the periphery of the controller.

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 allocated 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, as defined, those readers who are familiar with this may skip this description.
')
This structure and its copy 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 

More details can be found here stm32f103xb.h ≈ 800 KB

And if you use only the definitions in this file, you have to write like this (an example of using the serial port state register):

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

And you have to use it, because existing proprietary solutions known as CMSIS and HAL are too complex to use in amateur projects.

But if you write in C ++, you can write this:

 // ---------------------------------------------------------------------------- USART_TypeDef & Usart1 = *USART1; // ---------------------------------------------------------------------------- if (Usart1.ISR & (ONE_ISR_FLAG & OTHER_ISR_FLAG)) { } 

A variable reference is initialized by a pointer. This is a slight relief, but a pleasant one. It is even better, of course, to write a small wrapper class over it, while such an approach would still be useful.

Of course, I would like to immediately write this class-wrapper 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 with a desktop or laptop, but microcontrollers Cortex differs in the developed clocking system and you will have to start with it, and then still configure the corresponding I / O port pins to work with the 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 with the inclusion of clocking. The clocking system is called the RCC registers for the control of clocking (registers for clock control) and also represents a data structure, the declared pointer to which is assigned a specific value of the address.

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

Fields of this structure declared like this, where __IO defines volatile:

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

correspond to the registers from the RCC, and the individual bits of these registers enable or the clocking function of the microcontroller's periphery. All this is well described in the documentation (pdf) .

The pointer to the 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 clocking port A.

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

You can turn on two or more bits at once.

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

It looks for C ++ a bit, perhaps, 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 with the use of templates with a variable number of parameters even more beautiful:

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

Where Rcc is defined like this:

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

From this we will start building a wrapper over the clocking registers. To begin with we will define a class and the pointer (link) to it.

At first I wanted to write in the C ++ 11/14 standard using recursive decompressing of the parameters of the function template. A good article about this is given at the end of the note, 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<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; 

Consider calling the port clock enable function:

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

GCC will deploy it to this command set:

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

Happened? Check further

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

Alas, not quite, the naive GCC unfolded the recursion closing call separately:

  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] 

In defense of GCC, it must be said that this is not always the case, but only in more complex cases, as will be seen when implementing the I / O port class. Well, here is C ++ 17 to the rescue. Let's rewrite the TRCC class using the built-in scrolling capabilities.

 // ---------------------------------------------------------------------------- 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 

Now it happened:

 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] 

And the class code has become easier.

Conclusion: C ++ 17 allows us using the templates with a variable number of parameters to get the same minimum set of instructions (even when optimization is turned off), which is obtained by using the classic 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 code classes, and so on.

Something like that written in C ++

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

And the classic text on the registers:

 RCC->APB2 |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; 

unfolds into an optimal set of instructions. Here is the code generated by GCC (optimization turned off by -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 

Now you should continue to work and write a class port I / O. Working with the I / O port bits is complicated by the fact that four bits of the port are allocated to the configuration of one port leg, and thus the 16-bit port requires 64 bits of configuration, which are divided into two 32-bit CRL and CRH registers. Plus, the width of the bit mask becomes larger than 1. But even then scrolling through C ++ 17 shows its capabilities.

image

Next, the TGPIO class will be written, as well as classes for working with other peripherals, serial port, I2C, SPI, PDP, timers, and much more, which is usually present in ARM Cortex microcontrollers and then it will be possible to flash with such LEDs.

But more about that in the next article. Sources of the project on githaba .

Online articles used in writing notes


Templates with a variable number of arguments in C ++ 11 .
Innovations in the templates .
C ++ language innovations 17. Part 1. Convolution and excretion .
List of links to documentation for STM microcontrollers .
Macros with a variable number of parameters

The articles on Khabré that prompted me to write this note


Traffic light at Attiny13 .

Julian Assange arrested by British police
Cosmos as a vague memory

Written 04/12/2019 - Happy Cosmonautics Day!

PS
STM32F103c8t6 to Stm CubeMx Picture STM32F103c8t6 of CubeMX.

As a starting point, the text created by the Eclips extension for working with the GNU MCU Eclipse ARM Embedded Microcontroller and STM CubeMX microcontrollers is used as a starting point. There are files of the standard C ++ functions, _start () and _init (), the interrupt vector definitions are taken from the Eclipse MCU ARM Embedded, and the Register Definition and Cortex M3 core files are from a project made by CubeMX.


Pps
On is shown debugging with the controller STM32F103c8t6. Not everyone has such a fee, but it is easy to get it, although it is beyond the scope of this note.

Source: https://habr.com/ru/post/448288/


All Articles