Reputation: 67
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
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
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