jump
jump

Reputation: 67

How to access member variable with gcc inline assembler code

So I've recently stumbled across the blog post NeoPixels Revealed: How to (not need to) generate precisely timed signals and supporting github project and am now trying to encapsulate the heart of this code into a c++ class so that I can access various neopixel strings from multiple arduino uno digital pins.

To do this, I've created a protected member variable (m_PixelChannel) which stores the pin required to access the light string. However, I can't get the assembly code to recognize the member variable. Below is the code that I'm trying to make work (which is more-or-less a direct copy-paste of the original code from the github project with a classname added before it):

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

I'm convinced that it is that m_PixelChannel variable causing the problems; something to do with the constraints I suppose, becuase I can get it to work again by uncommenting the PIXEL_STRING0 line of code. Alternatively, I could pass the value as a parameter to the method and use the "n" constraint code to get it working (as I have successfully done) but I don't think I should have to pass a parameter to a method that has access to the value already...

I've tried the following constraint codes, with no luck: "n", "o", "I", "m", "+m", "r" and "g".

Obviously I am missing something. Can someone, please, point me in the right direction to make this work?

Upvotes: 0

Views: 628

Answers (2)

jump
jump

Reputation: 67

I decided that I really didn't like the template<> approach that I used in the comment to Chris Dodd's response. So after a number of iterations, I was able to figure out how to make it work...

  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)
      );
    }
  }  

Where m_PinMask = _BV(digital_pin);

Notice that the calls to sbi/cbi have been replaced by calls to st and the changes in the constraint-types.

Applying these changes has the code doing exactly what I'm wanting to do while staying within the timing requirements for the bit-bang process.

Thanks again to Chris for pointing me in the right direction!

Upvotes: 0

Chris Dodd
Chris Dodd

Reputation: 126243

The problem is that the operands to the SBI instruction must be constants (immediate values). So the only constraint that works is I and the value has to be a constant. There's no way to set a variable bit.

If you want to set a variable bit, you have to use something like a switch statement to select one of 8 different instructions.

Upvotes: 3

Related Questions