【问题标题】:Is there a better way to define a preprocessor macro for doing bit manipulation?有没有更好的方法来定义一个预处理宏来进行位操作?
【发布时间】:2019-04-26 14:19:25
【问题描述】:

拍摄宏:

GPIOxMODE(gpio,mode,port)    ( GPIO##gpio->MODER   = ((GPIO##gpio->MODER &   ~((uint32_t)GPIO2BITMASK << (port*2))) | (mode << (port * 2))) )

假设寄存器的复位值为0xFFFF.FFFF,我想设置一个2位宽为任意值。这是为 STM32 编写的 每个端口有 15 个引脚的 MCU。 GPIO2BITMASK 定义为 0x3。有没有更好的方法来清除和设置随机 2 位在任何地方 32 位宽寄存器。

端口 0 - 15 的有效范围
模式 0 - 3 的有效范围

我想出的方法是移位掩码,反转它,将其与现有寄存器值进行逻辑与,将结果与移位的新值进行逻辑或。

我希望将掩码和新值结合起来,以减少逻辑操作移位操作的数量。目标也是保持流程足够通用,以便我可以用于 1、2、3 或 4 位宽度的位操作。

有没有更好的办法?

从长远来看,是否有更好的方法确实是一个悬而未决的问题。我正在寻找一种方法,可以减少逻辑运算和位移运算的数量,同时是一个简单的单行语句。

答案是否定的

必须进行重置/设置以确保您正在写入的位字段具有所需的值。

收到的答案可能会更好(根据意见/偏好/哲学/实践),因为它们不需要宏并且具有参数检查。在 cmets 和响应中也指出了这种风格的坑。

【问题讨论】:

  • 首先,我会在宏扩展中将modeport 括在括号中。
  • modeport 通常是常量吗?
  • 已经过度使用了括号,并且有额外的括号包围模式和端口实际上会降低可读性。
  • 模式和端口是常量。我目前正在使用枚举,但我不明白为什么如果你想做一个连续的端口范围就不能使用循环。即模式 2 的端口 9-13。
  • 稍微降低可读性(仅在宏定义中)比找出作为带有运算符的表达式的参数为什么会中断更好。

标签: c embedded


【解决方案1】:

出于多种原因,应避免将此类宏作为牌匾:

  1. 它们不可调试
  2. 很难发现它们容易出错

还有很多其他原因

您可以使用内联函数归档相同的结果。生成的代码将同样有效

static inline __attribute__((always_inline)) void GPIOMODE(GPIO_TypeDef *gpio, unsigned mode, unsigned pin)
{
    gpio -> MODER &= ~(GPIO_MODER_MODE0_Msk << (pin * 2));
    gpio -> MODER |= mode << (pin * 2);
}

如果你喜欢宏

#define GPIOxMODE(gpio,mode,port)    {volatile uint32_t *mdr = &GPIO##gpio->MODER; *mdr &= ~(GPIO_MODER_MODE0_Msk << (port*2)); *mdr |= mode << (port * 2);}

我希望将掩码和新值结合起来以减少 逻辑运算位移位运算。

你不能。您需要重置然后设置位。

【讨论】:

  • 我并不执着于宏解决方案。我希望提高可读性,但将代码的冗长程度降至最低。在让代码在硅片上运行之前,我希望预处理器完成所需的任何计算。如果我能保持宏足够简单,我会在那里节省几个周期。
  • 如果可以保证位域全为 1,那么您可以反转“模式”移位并将其与寄存器值进行异或。但如果位域不全为 1,则进程中断。
  • 如果你能保证,它不再是通用的了。顺便说一句,如果你重新配置你怎么能保证什么。这种“有保证”的假设会导致有保证的问题。
  • @MadVocoder42 你不会在这里保存任何时钟。代码将是相同的。
  • 不管怎样,你确实回答了我的问题.....“你不能”我一直在寻找一个有点合乎逻辑的答案,但是,在你和 John Bollinger 之间,我正在做几乎什么你们两个建议。除非有任何语言偏好和细微差别。
【解决方案2】:

我想出的方法是移位掩码,反转它, 与现有的寄存器值进行逻辑与,逻辑或 结果带有位移的新值。

那或等效的方法就是这样做。

我希望将掩码和新值结合起来以减少 逻辑运算 位移位运算。目标也是保持 处理足够通用,以便我可以用于 1,2,3 的位操作 或 4 位宽度。

有没有更好的办法?

您必须完成两个基本目标:

  1. 确保受影响范围内应关闭的位实际上已关闭,并且
  2. 确保受影响范围内应打开的位实际上已打开。

在一般情况下,它们需要两个单独的操作:按位与强制位关闭,按位或(或异或,如果位首先被清除)打开所需位。可能有一些方法可以简化原始值和目标值的特定情况,但是如果您想要通用的东西,正如您所说,那么您的选择是有限的。

不过,就个人而言,我认为我倾向于从多个部分构建它,将 GPIO 选择与实际计算分开。至少,您可以分离出一个通用宏来设置位范围:

#define SETBITS32(x,bits,offset,mask) ((((uint32_t)(x)) & ~(((uint32_t)(mask)) << (offset))) | (((uint32_t)(bits)) << (offset)))

#define GPIOxMODE(gpio,mode,port) (GPIO##gpio->MODER = SETBITS32(GPIO##gpio->MODER, mode, port * 2, GPIO2BITMASK)

但请注意,似乎没有什么好的方法可以避免这样的宏多次评估其某些参数。因此,将SETBITS32 写为函数可能更安全。在任何情况下,编译器都可能内联这样的函数,但您可以通过声明 staticinline 来最大化这种可能性:

static inline uint32_t SETBITS32(uint32_t x, uint32_t bits, unsigned offset, uint32_t mask) {
    return x & ~(mask << offset) | (bits << offset);
}

这也更容易阅读,尽管它与宏一样,确实假设 bits 在掩码区域之外没有设置位。

当然还有其他类似的配方。例如,如果您不需要支持不连续的位范围,则可以指定位计数而不是位掩码。这种替代方法可以防止用户提供超出指定范围的位,并且还具有一些参数验证:

static inline uint32_t set_bitrange_32(uint32_t x, uint32_t bits, unsigned width,
        unsigned offset) {
    if (width + offset > 32) {
        // error: invalid parameters
        return x;
    } else if (width == 0) {
        return x;
    }
    uint32_t mask = ~(uint32_t)0 >> (32 - width);
    return x & ~(mask << offset) | ((bits & mask) << offset);
}

【讨论】:

  • 谢谢。看起来我的宏中有最简单的重置/设置形式,而且我无法摆脱两个位移位和逻辑 AND/OR 操作。我认为参数验证会有所帮助,但会增加复杂性,我希望保持简单。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-01
  • 2013-11-09
  • 1970-01-01
  • 2011-08-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多