对微控制器进行编程时,使用带有可变数量参数的C ++和模板

具有Cortex Mx内核的ARM(以STM32F10x为例)


KDPV 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 kB

而且,如果仅使用此文件中的定义,则必须这样编写(使用串行端口状态寄存器的示例):

 // ---------------------------------------------------------------------------- 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-扩展的通用串行异步收发器)编写此包装类,它具有先进的功能,具有串行异步收发器,并且能够将我们的小型微控制器连接到台式机系统或便携式计算机,因此非常吸引人。 Cortex的特点是开发了时钟系统,因此您必须先从它开始,然后配置相应的I / O引脚以与外设一起工作,因为在STM32F1xx系列中,如 足其它的ARM Cortex微控制器不能只配置端口引脚来输入或输出和工作在同一时间与周围。

好吧,让我们从打开计时开始。 时钟系统称为RCC寄存器,用于时钟控制,还表示一种数据结构,向该指针分配了特定的地址值。

 /* =========================================================================*/ 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 ++来说,看起来有些不寻常,或者有些不寻常。 最好像这样使用OOP进行不同的编写。

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

看起来更好,但是在二十一世纪,我们将走得更远,使用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>(); 

las,天真的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,我必须说,情况并非总是如此,只有在更复杂的情况下才可以实现I / O端口类。 好吧,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 

现在,您应该继续工作并编写输入/输出端口类。 I / O端口位的工作由于以下事实而变得很复杂:为一个端口支路的配置分配了四个位,因此在16位端口上需要64位的配置,该位又分为两个32位CRL和CRH寄存器。 另外,位掩码的宽度大于1。但是在这里,滚动浏览C ++ 17可以看到其功能。

图片

接下来,将编写TGPIO类,以及与其他外设,串行端口,I2C,SPI,DAP,计时器等相关的类,这些通常在ARM Cortex微控制器中存在,然后可以通过此类LED闪烁。

但有关更多内容,请参见下一个注释。 该项目的源代码在github上

用于撰写笔记的互联网文章


在C ++ 11中具有可变数量参数的模板
模板的创新
C ++语言创新17。 第1部分。卷积和推导
STM微控制器文档链接列表
可变参数宏

关于Khabr的文章促使我写了本笔记


Attiny13上的交通信号灯

朱利安·阿桑奇(Julian Assange)被英国警察逮捕
空间是模糊的记忆

书面04/12/2019-宇宙航行日快乐!

聚苯乙烯
Stm CubeMx中的STM32F103c8t6 图片来自CubeMX的STM32F103c8t6。

首先,使用由Eclips扩展程序创建的用于GNU MCU的文本,以使用Eclipse ARM EmbeddedSTM CubeMX微控制器 ,即存在标准C ++, _ start ()和_init()函数的文件,中断向量的定义来自Eclipse MCU ARM Embedded,Cortex M3核心寄存器和工作文件来自CubeMX所做的项目。


PPS
在KDPV上,表示使用STM32F103c8t6控制器进行的调试。 并非每个人都有这样的董事会,但购买起来并不难,但这超出了本文的范围。

Source: https://habr.com/ru/post/zh-CN448288/


All Articles