【问题标题】:How to access member variable with gcc inline assembler code如何使用 gcc 内联汇编代码访问成员变量
【发布时间】:2019-08-29 03:25:01
【问题描述】:

所以我最近偶然发现了这篇博文 NeoPixels Revealed: How to (not need to) generate precisely timed signals 并支持 github project,现在我正在尝试将此代码的核心封装到 C++ 类中,以便我可以从多个 arduino uno 数字引脚访问各种新像素字符串。

为此,我创建了一个受保护的成员变量 (m_PixelChannel),它存储访问灯串所需的引脚。但是,我无法让汇编代码识别成员变量。下面是我正在尝试制作的代码(这或多或少是来自 github 项目的原始代码的直接复制粘贴,并在其前添加了一个类名):

// send a bit to the string. We must to drop to asm to enusre that the complier does
// not reorder things and make it so the delay happens in the wrong place.
inline void fastNeoPixels::sendBit(bool bitVal) {

  if (bitVal) { // 0 bit
    asm volatile(
        "sbi %[port], %[bit] \n\t" // Set the output bit
        ".rept %[onCycles] \n\t"   // Execute NOPs to delay exactly the specified number of cycles
        "nop \n\t"
        ".endr \n\t"
        "cbi %[port], %[bit] \n\t" // Clear the output bit
        ".rept %[offCycles] \n\t"  // Execute NOPs to delay exactly the specified number of cycles
        "nop \n\t"
        ".endr \n\t" ::
        [port] "I"(_SFR_IO_ADDR(PIXEL_PORT)),
        [bit] "r"(m_PixelChannel),
        // [bit] "I" (PIXEL_STRING0),
        [onCycles] "I"(NS_TO_CYCLES(T1H) - 2), // 1-bit width less overhead  for the actual bit setting, note that this delay could be longer and everything would still work
        [offCycles] "I"(NS_TO_CYCLES(T1L) - 2) // Minimum interbit delay. Note that we probably don't need this at all since the loop overhead will be enough, but here for correctness
    );
  } else { // 1 bit
    // **************************************************************************
    // This line is really the only tight goldilocks timing in the whole program!
    // **************************************************************************
    asm volatile(
        "sbi %[port], %[bit] \n\t" // Set the output bit
        ".rept %[onCycles] \n\t"   // Now timing actually matters. The 0-bit must be long enough to be detected but not too long or it will be a 1-bit
        "nop \n\t"                 // Execute NOPs to delay exactly the specified number of cycles
        ".endr \n\t"
        "cbi %[port], %[bit] \n\t" // Clear the output bit
        ".rept %[offCycles] \n\t"  // Execute NOPs to delay exactly the specified number of cycles
        "nop \n\t"
        ".endr \n\t" ::
        [port] "I"(_SFR_IO_ADDR(PIXEL_PORT)),
        [bit] "r" (m_PixelChannel),
        // [bit] "I" (PIXEL_STRING0),
        [onCycles] "I"(NS_TO_CYCLES(T0H) - 2),
        [offCycles] "I"(NS_TO_CYCLES(T0L) - 2)
    );
  }  // if (bitVal)...

  // Note that the inter-bit gap can be as long as you want as long as it doesn't exceed the 5us reset timeout (which is A long time)
  // Here I have been generous and not tried to squeeze the gap tight but instead erred on the side of lots of extra time.
  // This has thenice side effect of avoid glitches on very long strings becuase
}

我确信是 m_PixelChannel 变量导致了问题;与我想的约束有关,因为我可以通过取消注释 PIXEL_STRING0 代码行来让它再次工作。或者,我可以将值作为参数传递给方法并使用“n”约束代码使其工作(正如我已经成功完成的那样),但我认为我不应该将参数传递给具有已经访问了该值...

我尝试了以下约束代码,但没有成功:“n”、“o”、“I”、“m”、“+m”、“r”和“g”。

显然我错过了一些东西。有人可以为我指出正确的方向来完成这项工作吗?

【问题讨论】:

    标签: c++ gcc arduino avr inline-assembly


    【解决方案1】:

    问题在于 SBI 指令的操作数必须是常量(立即值)。所以唯一有效的约束是I,并且值必须是一个常数。无法设置变量位。

    如果你想设置一个变量位,你必须使用类似 switch 语句来选择 8 个不同指令中的一个。

    【讨论】:

    • 虽然不幸的是,这是真的,但我不能让自己用一堆 switch() 语句使代码过于复杂(该函数需要四个 swiitch() - 每个都有8 例)。因此,我将类设为模板 类,如下所示: template class...{};并用 [bit] "I" (PORT_PIN) 替换了 [bit] 特定的输入约束,这在编译时是已知的。感谢您为我指明正确的方向;这实际上是我所要求的。 :)
    • @jump:是的,即使禁用优化,使用模板来确保编译时常量的值正是这个答案前半部分建议的方法。您可以使用 constexpr arg 编写一个包装函数,将其 arg 用作模板参数(或 CPP 宏),让您从用例中隐藏模板语法。这应该全部内联到启用优化的sbi
    【解决方案2】:

    我决定我真的不喜欢我在评论 Chris Dodd 的回复时使用的模板 方法。因此,经过多次迭代,我能够弄清楚如何使其工作......

      void sendBit( bool bitVal ) {
        volatile uint8_t _hi   = *m_PixelPORT |  m_PinMask;
        volatile uint8_t _lo   = *m_PixelPORT & ~m_PinMask;
        if ( bitVal ) {
          asm volatile (
            "st %a[port], %[hi]"  "\n\t"        // Set the output bit
            ".rept %[onCycles]"  "\n\t"   // Execute NOPs to delay exactly the specified number of cycles
            "nop"                "\n\t"   
            ".endr"              "\n\t"  
            "st %a[port], %[lo]"  "\n\t"   // Clear the output bit
            ".rept %[offCycles]" "\n\t"   // Execute NOPs to delay exactly the specified number of cycles
            "nop"                "\n\t"
            ".endr"              "\n\t"
            : [port] "+e" (m_PixelPORT)
            : [hi] "r" (_hi),
              [lo] "r" (_lo),
              [onCycles] "I" (NS_TO_CYCLES(T1H) - 2), // 1-bit width less overhead  for the actual bit setting, note that this delay could be longer and everything would still work
              [offCycles] "I" (NS_TO_CYCLES(T1L) - 2) // Minimum interbit delay. Note that we probably don't need this at all since the loop overhead will be enough, but here for correctness
          );
        } else {
          asm volatile (
            "st %a[port], %[hi]"  "\n\t"        // Set the output bit
            ".rept %[onCycles]"  "\n\t"   // Execute NOPs to delay exactly the specified number of cycles
            "nop"                "\n\t"   
            ".endr"              "\n\t"  
            "st %a[port], %[lo]"  "\n\t"   // Clear the output bit
            ".rept %[offCycles]" "\n\t"   // Execute NOPs to delay exactly the specified number of cycles
            "nop"                "\n\t"
            ".endr"              "\n\t"
            : [port] "+e" (m_PixelPORT)
            : [hi] "r" (_hi),
              [lo] "r" (_lo),
              [onCycles] "I" (NS_TO_CYCLES(T0H) - 2),
              [offCycles]   "I" (NS_TO_CYCLES(T0L) - 2)
          );
        }
      }  
    

    其中m_PinMask = _BV(digital_pin);

    请注意,对 sbi/cbi 的调用已替换为对 st 的调用,并且约束类型。

    应用这些更改后,代码可以完全按照我想要的方式执行,同时保持在 bit-bang 过程的时序要求内。

    再次感谢 Chris 为我指明了正确的方向!

    【讨论】:

    • 为什么是_hi_lo volatile?除非 asm 语句本身应该修改这些局部变量,否则强制编译器溢出它们并在每次读取时重新加载是没有意义的。但看起来情况并非如此。
    • 对于bitval 可以匹配"I" 约束的情况,您可以使用if (__builtin_constant_p(bitval)) 来使用更优化的asm。 (适用于 gcc,但不适用于 clang。Clang 在内联之前评估 __builtin_constant_p,因此对于这样的包装器函数,它总是错误的。
    • @PeterCordes 这两个 cmets 似乎都是经验丰富的 AVR 程序员会关心的事情。不幸的是,那不是我。我仍在学习 Arduino 本身的所有细节。幸运的是,我有多年的 C++ 经验,这很有帮助;但是 AVR 组装?没那么多。我只是认为 volatile 只是将值强制到寄存器中,而不需要先进行某种 MOV 操作。如果您说我不需要它并且将其设置为 volatile 是有害的,那么我将更改代码以查看它是否仍然有效。谢谢。 - 跳跃
    • volatile 强制变量进入内存,而不是寄存器。您不需要 volatile 在您的本地人上,只需要 asm volatile 语句。他们可以读取普通的非易失性局部变量(这是正常的用例),如果编译器将这些局部变量保存在寄存器中或它想要做的任何事情中,那就完全没问题了。您可以查看编译器的 asm 输出以了解它是如何填充模板的,以及编译器选择在您的 asm 语句之前/之后放置哪些指令。 (启用优化)。
    • 用 asm 术语来说,一个普通的本地变量是一个寄存器。您想将其用于volatile uint8_t *m_PixelPORT MMIO 指针或其他东西的结果。您需要的是指向易失性的指针,而不是将结果存储在 volatile 本地。 godbolt.org/z/N01TVo (AVR G++ 5.4) 显示如果您取消注释 volatile(我在那里评论),这个函数的总 asm 会膨胀多少。
    猜你喜欢
    • 2012-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-11
    • 2019-12-31
    • 1970-01-01
    • 1970-01-01
    • 2021-01-19
    相关资源
    最近更新 更多