From c18c4693e1d6144fb32a6f57f00ea28742499637 Mon Sep 17 00:00:00 2001 From: "Simon A. Eugster" Date: Sun, 22 Sep 2019 22:33:29 +0200 Subject: [PATCH] Split off header file and improve readme --- README.md | 12 +- SimpleNeopixelDemo/SimpleNeoPixel.h | 153 ++++++++++ SimpleNeopixelDemo/SimpleNeopixelDemo.ino | 330 ++++++---------------- 3 files changed, 250 insertions(+), 245 deletions(-) create mode 100644 SimpleNeopixelDemo/SimpleNeoPixel.h diff --git a/README.md b/README.md index 8076234..18155a9 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,15 @@ which allows to **control hundreds of pixels** with e.g. a small ATtiny85. The [Adafruit NeoPixel](https://github.com/adafruit/Adafruit_NeoPixel) library allocates 4 bytes per LED, and an ATtiny85 can only control a bit more than 50 pixels. -More info at: +So why use the Adafruit NeoPixel library at all? There are good reasons for both. + +* Use SimpleNeoPixel if you want to control loads of pixel and your code is *fast*, + and you are not afraid of manually adjusting some timings in the library, if required. +* Use Adafruit NeoPixel as an out-of-the-box solution to control as many pixels + as fit into the microcontroller’s dynamic memory, without worrying about low-level performance. + +*Fast* means less than 6 µs per pixel. This allows for some arithmetic operations, +but a sine is already far too slow. + +More in-depth info at: [NeoPixels Revealed: How to (not need to) generate precisely timed signals](http://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/) diff --git a/SimpleNeopixelDemo/SimpleNeoPixel.h b/SimpleNeopixelDemo/SimpleNeoPixel.h new file mode 100644 index 0000000..9875ed1 --- /dev/null +++ b/SimpleNeopixelDemo/SimpleNeoPixel.h @@ -0,0 +1,153 @@ + +/* + This is an example of how simple driving a Neopixel can be + This code is optimized for understandability and changability rather than raw speed + More info at http://wp.josh.com/2014/05/11/ws2812-neopixels-made-easy/ +*/ + +// Change this to be at least as long as your pixel string (too long will work fine, just be a little slower) + +#define PIXELS 96*11 // Number of pixels in the string + +// These values depend on which pin your string is connected to and what board you are using +// More info on how to find these at http://www.arduino.cc/en/Reference/PortManipulation + +// These values are for the pin that connects to the Data Input pin on the LED strip. They correspond to... + +// Arduino Yun: Digital Pin 8 +// DueMilinove/UNO: Digital Pin 12 +// Arduino MeagL PWM Pin 4 + +// You'll need to look up the port/bit combination for other boards. + +// Note that you could also include the DigitalWriteFast header file to not need to to this lookup. + +#define PIXEL_PORT PORTB // Port of the pin the pixels are connected to +#define PIXEL_DDR DDRB // Port of the pin the pixels are connected to +#define PIXEL_BIT 4 // Bit of the pin the pixels are connected to + +// These are the timing constraints taken mostly from the WS2812 datasheets +// These are chosen to be conservative and avoid problems rather than for maximum throughput + +#define T1H 900 // Width of a 1 bit in ns +#define T1L 600 // Width of a 1 bit in ns + +#define T0H 400 // Width of a 0 bit in ns +#define T0L 900 // Width of a 0 bit in ns + +// The reset gap can be 6000 ns, but depending on the LED strip it may have to be increased +// to values like 600000 ns. If it is too small, the pixels will show nothing most of the time. +#define RES 6000 // Width of the low gap between bits to cause a frame to latch + +// Here are some convience defines for using nanoseconds specs to generate actual CPU delays + +#define NS_PER_SEC (1000000000L) // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives + +#define CYCLES_PER_SEC (F_CPU) + +#define NS_PER_CYCLE ( NS_PER_SEC / CYCLES_PER_SEC ) + +#define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE ) + +// Actually 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 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] "I" (PIXEL_BIT), + [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] "I" (PIXEL_BIT), + [onCycles] "I" (NS_TO_CYCLES(T0H) - 2), + [offCycles] "I" (NS_TO_CYCLES(T0L) - 2) + + ); + + } + + // 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 + + +} + + +inline void sendByte( unsigned char byte ) { + + for( unsigned char bit = 0 ; bit < 8 ; bit++ ) { + + sendBit( bitRead( byte , 7 ) ); // Neopixel wants bit in highest-to-lowest order + // so send highest bit (bit #7 in an 8-bit byte since they start at 0) + byte <<= 1; // and then shift left so bit 6 moves into 7, 5 moves into 6, etc + + } +} + +/* + + The following three functions are the public API: + + ledSetup() - set up the pin that is connected to the string. Call once at the begining of the program. + sendPixel( r g , b ) - send a single pixel to the string. Call this once for each pixel in a frame. + show() - show the recently sent pixel on the LEDs . Call once per frame. + +*/ + + +// Set the specified pin up as digital out + +void ledsetup() { + + bitSet( PIXEL_DDR , PIXEL_BIT ); + +} + +inline void sendPixel( unsigned char r, unsigned char g , unsigned char b ) { + + sendByte(g); // Neopixel wants colors in green then red then blue order + sendByte(r); + sendByte(b); + +} + + +// Just wait long enough without sending any bots to cause the pixels to latch and display the last sent frame + +void show() { + _delay_us( (RES / 1000UL) + 1); // Round up since the delay must be _at_least_ this long (too short might not work, too long not a problem) +} diff --git a/SimpleNeopixelDemo/SimpleNeopixelDemo.ino b/SimpleNeopixelDemo/SimpleNeopixelDemo.ino index b437e9b..ffa7b19 100644 --- a/SimpleNeopixelDemo/SimpleNeopixelDemo.ino +++ b/SimpleNeopixelDemo/SimpleNeopixelDemo.ino @@ -1,205 +1,52 @@ - -/* - This is an example of how simple driving a Neopixel can be - This code is optimized for understandability and changability rather than raw speed - More info at http://wp.josh.com/2014/05/11/ws2812-neopixels-made-easy/ -*/ - -// Change this to be at least as long as your pixel string (too long will work fine, just be a little slower) - -#define PIXELS 96*11 // Number of pixels in the string - -// These values depend on which pin your string is connected to and what board you are using -// More info on how to find these at http://www.arduino.cc/en/Reference/PortManipulation - -// These values are for the pin that connects to the Data Input pin on the LED strip. They correspond to... - -// Arduino Yun: Digital Pin 8 -// DueMilinove/UNO: Digital Pin 12 -// Arduino MeagL PWM Pin 4 - -// You'll need to look up the port/bit combination for other boards. - -// Note that you could also include the DigitalWriteFast header file to not need to to this lookup. - -#define PIXEL_PORT PORTB // Port of the pin the pixels are connected to -#define PIXEL_DDR DDRB // Port of the pin the pixels are connected to -#define PIXEL_BIT 4 // Bit of the pin the pixels are connected to - -// These are the timing constraints taken mostly from the WS2812 datasheets -// These are chosen to be conservative and avoid problems rather than for maximum throughput - -#define T1H 900 // Width of a 1 bit in ns -#define T1L 600 // Width of a 1 bit in ns - -#define T0H 400 // Width of a 0 bit in ns -#define T0L 900 // Width of a 0 bit in ns - -// The reset gap can be 6000 ns, but depending on the LED strip it may have to be increased -// to values like 600000 ns. If it is too small, the pixels will show nothing most of the time. -#define RES 6000 // Width of the low gap between bits to cause a frame to latch - -// Here are some convience defines for using nanoseconds specs to generate actual CPU delays - -#define NS_PER_SEC (1000000000L) // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives - -#define CYCLES_PER_SEC (F_CPU) - -#define NS_PER_CYCLE ( NS_PER_SEC / CYCLES_PER_SEC ) - -#define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE ) - -// Actually 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 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] "I" (PIXEL_BIT), - [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] "I" (PIXEL_BIT), - [onCycles] "I" (NS_TO_CYCLES(T0H) - 2), - [offCycles] "I" (NS_TO_CYCLES(T0L) - 2) - - ); - - } - - // 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 - - -} - - -inline void sendByte( unsigned char byte ) { - - for( unsigned char bit = 0 ; bit < 8 ; bit++ ) { - - sendBit( bitRead( byte , 7 ) ); // Neopixel wants bit in highest-to-lowest order - // so send highest bit (bit #7 in an 8-bit byte since they start at 0) - byte <<= 1; // and then shift left so bit 6 moves into 7, 5 moves into 6, etc - - } -} - -/* - - The following three functions are the public API: - - ledSetup() - set up the pin that is connected to the string. Call once at the begining of the program. - sendPixel( r g , b ) - send a single pixel to the string. Call this once for each pixel in a frame. - show() - show the recently sent pixel on the LEDs . Call once per frame. - -*/ - - -// Set the specified pin up as digital out - -void ledsetup() { - - bitSet( PIXEL_DDR , PIXEL_BIT ); - -} - -inline void sendPixel( unsigned char r, unsigned char g , unsigned char b ) { - - sendByte(g); // Neopixel wants colors in green then red then blue order - sendByte(r); - sendByte(b); - -} - - -// Just wait long enough without sending any bots to cause the pixels to latch and display the last sent frame - -void show() { - _delay_us( (RES / 1000UL) + 1); // Round up since the delay must be _at_least_ this long (too short might not work, too long not a problem) -} - +#include "SimpleNeoPixel.h" /* That is the whole API. What follows are some demo functions rewriten from the AdaFruit strandtest code... - + https://github.com/adafruit/Adafruit_NeoPixel/blob/master/examples/strandtest/strandtest.ino - + Note that we always turn off interrupts while we are sending pixels becuase an interupt could happen just when we were in the middle of somehting time sensitive. - - If we wanted to minimize the time interrupts were off, we could instead - could get away with only turning off interrupts just for the very brief moment - when we are actually sending a 0 bit (~1us), as long as we were sure that the total time + + If we wanted to minimize the time interrupts were off, we could instead + could get away with only turning off interrupts just for the very brief moment + when we are actually sending a 0 bit (~1us), as long as we were sure that the total time taken by any interrupts + the time in our pixel generation code never exceeded the reset time (5us). - + */ // Display a single color on the whole string void showColor( unsigned char r , unsigned char g , unsigned char b ) { - - cli(); + + cli(); for( int p=0; pg->b where // hue of 0 = Full red // hue of 128 = 1/2 red and 1/2 green // hue of 256 = Full Green // hue of 384 = 1/2 green and 1/2 blue // ... - + unsigned int firstPixelHue = 0; // Color for the first pixel in the string - - for(unsigned int j=0; j=(3*256)) { // Normalize back down incase we incremented and overflowed currentPixelHue -= (3*256); } - + unsigned char phase = currentPixelHue >> 8; unsigned char step = currentPixelHue & 0xff; - + switch (phase) { - - case 0: + + case 0: sendPixel( ~step , step , 0 ); break; - - case 1: + + case 1: sendPixel( 0 , ~step , step ); break; - case 2: + case 2: sendPixel( step ,0 , ~step ); break; - + } - - currentPixelHue+=pixelAdvance; - - - } - + + currentPixelHue+=pixelAdvance; + + + } + sei(); - + show(); - + firstPixelHue += frameAdvance; - + } } - + // I added this one just to demonstrate how quickly you can flash the string. // Flashes get faster and faster until *boom* and fade to black. void detonate( unsigned char r , unsigned char g , unsigned char b , unsigned int startdelayms) { while (startdelayms) { - - showColor( r , g , b ); // Flash the color + + showColor( r , g , b ); // Flash the color showColor( 0 , 0 , 0 ); - - delay( startdelayms ); - + + delay( startdelayms ); + startdelayms = ( startdelayms * 4 ) / 5 ; // delay between flashes is halved each time until zero - + } - + // Then we fade to black.... - + for( int fade=256; fade>0; fade-- ) { - + showColor( (r * fade) / 256 ,(g*fade) /256 , (b*fade)/256 ); - + } - + showColor( 0 , 0 , 0 ); - - + + } void setup() { - - ledsetup(); - + + ledsetup(); + } @@ -352,20 +199,15 @@ void loop() { colorWipe(255, 0, 0, 0); // Red colorWipe(0, 255, 0, 0); // Green colorWipe(0, 0, 255, 0); // Blue - + // Send a theater pixel chase in... theaterChase(127, 127, 127, 0); // White theaterChase(127, 0, 0, 0); // Red theaterChase( 0, 0, 127, 0); // Blue - + rainbowCycle(1000 , 20 , 5 ); detonate( 255 , 255 , 255 , 1000); - - return; - -} - - - + return; +}