【发布时间】:2021-09-04 22:50:26
【问题描述】:
我正在研究一个表示微控制器 (STM32) 的一组硬件引脚的类。选定的引脚在端口上可能不连续,但假定它们是有序的。例如,如果创建此 PortSegment 对象来表示 PA2、PA3 和 PA6 引脚,我希望能够进行类似 segment = 0b101u 的分配,它设置 PA2 和 PA6 并重置 PA3。
目前我还没有为不连续的引脚实现 ctor。当前一个只允许表示像 PA2、P3 和 PA4 这样的连续引脚。但是,将压缩位(如上例中的0b101u)映射到实际硬件位的逻辑是针对不连续情况实现的。
我认为像segment = 0b101u 这样的赋值可以主要在编译时计算,并且只有在运行时加载实际的硬件寄存器(STM32 的BSRR,它处理硬件引脚的原子设置和重置),使用预先计算的值。不幸的是,这不是发生的情况,要加载到 BSRR 的值也是在运行时计算的。
这是我正在测试的代码的稍微简化和半生不熟的版本。端口选择(GPIOA、GPIOB 等)代码被省略。
#include <cstdint>
volatile uint32_t BSRR {0}; // Assume it's a HW register for atomic pin access.
class PortSegment {
public:
constexpr PortSegment(uint8_t start, uint8_t end)
: selection{calculateSelection(start, end)} {}
uint16_t operator=(uint16_t setVal) const;
// operator uint16_t() const; // to be implemented later
private:
static constexpr uint16_t calculateSelection(uint8_t start, uint8_t end);
static constexpr uint16_t mapBits(uint16_t val, uint16_t selection);
uint16_t selection; // Table of used bits in the port
};
// Used in ctor
constexpr uint16_t PortSegment::calculateSelection(uint8_t start, uint8_t end)
{
uint16_t result {0};
for (unsigned i = start; i <= end; ++i) result |= (1u << i);
return result;
}
// static function
constexpr uint16_t PortSegment::mapBits(uint16_t val, uint16_t selection)
{
uint16_t result {0};
for (unsigned i = 0; i < 16; ++i) {
if (selection & 1u) {
if (val & (1u << i)) {
result |= (1u << i);
}
}
else {
val <<= 1;
}
selection >>= 1;
}
return result;
}
inline uint16_t PortSegment::operator=(uint16_t setVal) const
{
uint32_t mapped {mapBits(setVal, selection)};
BSRR = ((~mapped << 16) | mapped)
& ((static_cast<uint32_t>(selection) << 16) | selection);
return setVal;
}
int main()
{
constexpr PortSegment segment {2,5}; // Use port pins 2,3,4,5
segment = 0b1010u;
}
selection 成员变量表示端口中使用的引脚。例如,0b111100 表示使用 PA2、PA3、PA4、PA5。问题是,mapBits() 函数在编译期间没有被评估。我也尝试使它成为非静态成员函数,但没有任何改变。按照我的逻辑,当PortSegment类的segment对象创建时,在编译时一切都是已知的,也可以知道要加载到BSRR中的值。但我似乎遗漏了什么。
我发现另一个奇怪的事情是,如果我将mapBits() 函数中的selection >>= 1; 更改为selection <<= 1;(这对算法没有意义),mapBits() 可以计算编译时间。
这里是code in Godbolt。
【问题讨论】:
-
您的赋值运算符是..
const? -
mapBits未在 constexpr 上下文中使用,因此不需要在编译时完成,直至优化器。 -
@Jarod42 赋值运算符实际上并没有改变对象的状态。它仅用于加载硬件
BSRR寄存器,它不是类的一部分。 -
离题:
operator=不分配任何东西也不返回*this(PortSegment &) 可以搞砸未来的维护者。现在这个赋值操作符是个谎言,这应该是一些有名字的函数来解释它的作用。 -
gcc将您的代码编译为单个movl指令,可以追溯到 6.1 版。
标签: c++ c++17 stm32 constexpr constexpr-function