Shop OBEX P1 Docs P2 Docs Learn Events
Newbie question: Neopixels w/ Prop, don't understand how to pass hex-value variables — Parallax Forums

Newbie question: Neopixels w/ Prop, don't understand how to pass hex-value variables

I'm a neophyte Spinner.

I've got a Neopixel array hooked up to a prop and I can use Chip's neopixel driver code to test my hardware and things work, ie, I can change colors by manually changing this value in the code:

' $00_GG_RR_BB
' Fill the grid with a white
repeat i from 0 to numPixels-1
pixels := $00_FF_FF_FF

I manually change those hex values and voila, colors change appropriately. cool!

but now I want to change those values with a variable and I can't figure out how to replace those hard-coded hex values with variables. So, here's my incorrect syntax for what I want to do:

repeat j from 0 to pulselength
fadefactor := j/pulselength

repeat i from 0 to (numPixels-1)

red := 256*fadefactor
green := 256*fadefactor
blue := 256*fadefactor

redh := fdx.Hex(red, 2)
grnh := fdx.Hex (green, 2)
bluh := fdx.Hex (blue, 2)

pixels := $00_grnh_redh_bluh

I hope that makes sense... the use of the fdx object (FullDuplexSerial) was my attempt to make hex values... how do I use variables for that pixels := statement?

Here's Chip's Neopixel object on the Obex:

http://obex.parallax.com/object/774

thank you very much in advance!

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2017-10-12 19:25
    Use: pixels := ( green << 16 ) | ( red << 8 ) | blue

    green, red, and blue are each values from 0 to 255 representing the brightness of the corresponding LED.

    Note: fadefactor won't work the way you want since fadefactor is an integer variable and can't hold a fraction. You can scale the calculation to keep things integer:

    fadefactor := 256*j/pulselength
    red := fadefactor
    green := fadefactor
    blue := fadefactor

    This may not quite be what you want, but I hope it points you in the right direction.
  • thanks very much for the quick help!

    unfortunately, still not working... could be something I'm doing with fdx.Hex command. also, I defined redh/grnh/bluh as "long" variables, then try to assign them hex values, does that work?

    Here's the entire program:

    CON
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000

    {{

    I used the following 8x8 NeoPixel grid from AdaFruit:
    https://www.adafruit.com/products/1487

    Propeller Demo Board

    7404
    P0 ────────────────── NeoPixel DIN
    +5 In ──────────┐
    GND ───────┐ │
    │ │
    │ │
    2A Wall Wart │ │
    │ │
    +5 ────────┼──┻─────────┳─ NeoPixel +5
    │ 1000µF 
    GND ────────┻────────────┻─ NeoPixel GND

    In my case the NeoPixels are powered by 5V. They require the
    Data in (DIN) signal to be close to 5V. The propeller GPIO
    output is only 3.3V. I used a 7404 Hex Inverter chip to boost
    the output up to 5V. I used two of the inverters on the the
    chip back to back to re-invert the signal to match the propeller
    output value. If you only want to use one of the inverters then
    you can invert the propeller output in the code.

    }}

    OBJ
    NEO : "NeoPixel"
    fdx : "FullDuplexSerial"

    VAR
    long command ' Command trigger -- write non-zero value
    long buffer ' Pointer to the pixel data buffer
    long numPixels ' Number of pixels to write
    long pin ' The pin number to send data over

    ' Pixel buffer
    long pixels[64]

    long redh
    long bluh
    long grnh
    long red
    long green
    long blue
    long pulselength
    long j
    long k
    long fadefactor

    PUB Main : i


    pin := 18

    dira[pin] := 1
    outa[pin] := 0
    numPixels := 24

    NEO.start(@command)


    PauseMSec(200)

    repeat j from 0 to pulselength
    fadefactor := j/pulselength

    repeat i from 0 to (numPixels-1)

    red := 256*fadefactor
    green := 256*fadefactor
    blue := 256*fadefactor

    redh := fdx.Hex(red, 2)
    grnh := fdx.Hex(green, 2)
    bluh := fdx.Hex(blue, 2)

    pixels := (grnh<<16)|(redh<<8)|bluh

    ' $00_GG_RR_BB
    {
    ' Fill the grid with blue
    repeat i from 0 to numPixels-1
    pixels := $00_00_00_FF
    }
    {
    ' Add some diagonals
    repeat i from 0 to 7
    pixels[i*8 + i] := $00_00_FF_00 ' Red diagonal upper-left to bottom-right
    pixels[i*8 +7 -i] := $00_00_00_FF ' Blue diagonal upper-right to bottom-left
    }

    buffer := @pixels
    command := 1


    repeat

    PRI PauseMSec(Duration)
    waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)

    CON
    {{
    ┌─────────────────────────────────────────────────────────────────────────────────────────┐
    │ TERMS OF USE: MIT License │
    ├─────────────────────────────────────────────────────────────────────────────────────────┤
    │Permission is hereby granted, free of charge, to any person obtaining a copy of this │
    │software and associated documentation files (the "Software"), to deal in the Software │
    │without restriction, including without limitation the rights to use, copy, modify, merge,│
    │publish, distribute, sublicense, and/or sell copies of the Software, and to permit │
    │persons to whom the Software is furnished to do so, subject to the following conditions: │
    │ │
    │The above copyright notice and this permission notice shall be included in all copies or │
    │substantial portions of the Software. │
    │ │
    │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, │
    │INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR │
    │PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE│
    │FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR │
    │OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER │
    │DEALINGS IN THE SOFTWARE. │
    └─────────────────────────────────────────────────────────────────────────────────────────┘
    }}
  • I have 3 little neopixel boards daisy-chained together, so 24 pixels total, so ignore the notes at the top.
  • and, if I comment out my little "color fade" routine attempt, and un-comment this:

    {
    ' Fill the grid with blue
    repeat i from 0 to numPixels-1
    pixels := $00_00_00_FF
    }

    the lights do come on blue.
  • Mike Green wrote: »
    Use: pixels := ( green << 16 ) | ( red << 8 ) | blue

    green, red, and blue are each values from 0 to 255 representing the brightness of the corresponding LED.

    Note: fadefactor won't work the way you want since fadefactor is an integer variable and can't hold a fraction. You can scale the calculation to keep things integer:

    fadefactor := 256*j/pulselength
    red := fadefactor
    green := fadefactor
    blue := fadefactor

    This may not quite be what you want, but I hope it points you in the right direction.

    Ah! of course. thank you.
  • well, I already found a lot wrong in what I posted... here's my latest attempt to get things working:

    CON
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000


    OBJ
    NEO : "NeoPixel"
    fdx : "FullDuplexSerial"

    VAR
    long command ' Command trigger -- write non-zero value
    long buffer ' Pointer to the pixel data buffer
    long numPixels ' Number of pixels to write
    long pin ' The pin number to send data over

    ' Pixel buffer
    long pixels[64]

    long redh
    long bluh
    long grnh
    long red
    long green
    long blue
    long pulselength
    long j
    long k
    long fadefactor

    PUB Main : i


    pin := 18

    dira[pin] := 1
    outa[pin] := 0
    numPixels := 24

    NEO.start(@command)


    PauseMSec(200)

    pulselength := 10000

    repeat j from 0 to pulselength
    red := 255*j/pulselength
    green := 255*j/pulselength
    blue := 255*j/pulselength
    redh := fdx.Hex(red, 2)
    grnh := fdx.Hex(green, 2)
    bluh := fdx.Hex(blue, 2)
    repeat i from 0 to (numPixels-1)
    ' pixels := $00_00_00_FF
    pixels := (grnh<<16)|(redh<<8)|bluh




    ' $00_GG_RR_BB
    {
    ' Fill the grid with blue
    repeat i from 0 to numPixels-1
    pixels := $00_00_00_FF
    }


    buffer := @pixels
    command := 1


    repeat

    PRI PauseMSec(Duration)
    waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)

    CON
  • Mike GreenMike Green Posts: 23,101
    edited 2017-10-12 20:41
    Don't use the fdx.Hex calls. They're for converting an integer value into a displayable hex value. Use the "red","green","blue" variables directly in the pixels assignment.

    JonnyMac has a Spin object in the Object Exchange (OBEX) for use with WS2812 NeoPixels. It works and would save you some effort. The name starts with "jm_".

    In cutting and pasteing your code, you've used square brackets ([ ]) for subscripts as you should. The forum software interprets some of these and strips them out. Better to use [ code ] and [ /code] to surround your code (without the extra spaces). You probably have [ i ] in places that's gone.
  • tomcrawfordtomcrawford Posts: 1,126
    edited 2017-10-12 21:23
    JonnyMac removed all his stuff from OBEX some time ago. Here is a copy of what I believe was his latest. The archive date and time are wrong because I had to re-archive it because somehow my PC lost part of the filename.
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-10-13 01:45
    I did pull everything down for a while because I am doing maintenance on my entire object library (about 200 objects) -- I want to make sure all my objects are consistent with each other.

    In actual fact, my latest driver handles WS28xx and SK68xx and will work with 24- and 32-bit (RGBW) pixels (this was never posted in ObEx). The data format for this driver is $RR_GG_BB_WW (the WW byte is not used in the 24-bit types). I've built this driver around requirements from my friends in Hollywood (Rick Galinson used it in props for the "Destiny 2" commercial), my own work, and from others involved in professional lighting. Hopefully, the attached demo will get you going.

    Like the driver you're trying to use, my code will let you change buffers and the output pin. My driver also auto-updates. Most of my projects involve manipulating data in a single pixel buffer; I don't want to have to tell the pixel driver to refresh the pixels -- it just happens.
  • Thank you, Jon.
  • thanks to everyone!

    Mike, thanks - I was wondering why some of posted text was italicized. okay, will do the code header/footer thing.

    thanks very much to everyone who responded and I'll check out the code.
  • Hi Jon - I'm wondering if you might help me understand a couple of things about the code you posted above.

    Quick background, I'm using the Adafruit Neopixel strips found here:

    https://www.adafruit.com/product/1426

    and I have 3 of them daisy-chained to make 24 pixels total. as far as I can understand, it takes 16 bits for each of the colors, per pixel, and they're just RGB (no W), with this format:

    $00_GG_RR_BB

    so, my questions:

    1) I'm wondering how I can make your code work with this format, versus the $RR_GG_BB_WW you described above.
    2) I noticed that you have code (or at least variables, I haven't looked around much) to use the eeproms on the I2C bus in some way. I don't know what the eeproms are being used for, but I noticed that the i2C pin numbers were assigned in (at least) 3 of your files: jm_rgbx_pixel, jm_fullduplexserial and the pixel demo top program. Since I have a custom board and my pin assignments are not the same as a standard Parallax protoboard, I changed those in all 3 files to match my board. What is the eeprom used for and have I done the sufficient thing to change them in those 3 files (only)?

    Sorry for what are probably some pretty lame questions; lots of new stuff here for me, trying to wrap my head around it all.


  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-10-13 11:27
    The demo that I uploaded was tested on a 24-pixel Neopixel ring from Adafruit. If you just change the output pin to match your connection, it should work.

    1) My driver does the red and green swap so you don't have to. I like to code in an obvious fashion. We're used to getting color values in red, green, blue order, so that's what my driver uses. Note, though, that my RGB values are left aligned in the long. For standard Neopixels, the low byte of that long doesn't matter because it is never used.

    2) This program is not using I2C. That said, the Propeller does have an I2C bus and I spell that out in every single program. I'm a bit of a nut when it comes to neat formatting -- more bugs and errors are created from sloppy formatting than logic problems in my experience. At the top of every one of my programs is a list of the IO. For example, I am in Dallas this week with my laser-tag customer. The player code is 6000 lines of Spin/PASM. Here are the pin definitions for the board I designed.
    con { io pins }
    
      RX1      = 31  { i }                                          ' programming / terminal
      TX1      = 30  { o }
    
      SDA1     = 29  { io }                                         ' eeprom / i2c
      SCL1     = 28  { io }
    
      XB_RX    = 27  { i }                                          ' xbee
      XB_TX    = 26  { o }
    
      IR_IN    = 25  { i }                                          ' ir input
    
      LCD_D7   = 24  { io }                                         ' not used as button
      LCD_D6   = 23  { io }                                         ' mode button
      LCD_D5   = 22  { io }                                         ' reload button
      LCD_D4   = 21  { io }                                         ' trigger button
      LCD_E    = 20  { o }
      LCD_RS   = 19  { o }
      LCD_BL   = 16  { o }                                          ' backlight control
    
      BTN_M    = LCD_D6
      BTN_R    = LCD_D5
      BTN_T    = LCD_D4
    
      BT_TX    = 18  { o }
      BT_RX    = 17  { i }
    
      MUZL_B   = 15  { o }                                          ' muzzle flash color control
      MUZL_R   = 14  { o }
      MUZL_G   = 13  { o }
    
      A_SYNC   = 12  { o }                                          ' dac output
      A_SCLK   = 11  { o }
      A_DATA   = 10  { o }
    
      IR1_OUT  =  9  { o }                                          ' muzzle ir output
      IRX_MODE =  8  { o }                                          ' ir output power control
    
      SD_CS    =  7  { o }                                          ' microSD connections
      SD_DI    =  6  { o }
      SD_CLK   =  5  { o }
      SD_DO    =  4  { i }
    
      IR2_OUT  =  3  { o }                                          ' hit sensor ir output
    
      HIT_G    =  2  { o }                                          ' hit sensor color control
      HIT_R    =  1  { o }
      HIT_B    =  0  { o }
    
    Note that the programming/debug and I2C/EEPROM pins are spelled out. In this particular device there is an INA219 battery monitor connected to the EEPROM pins. When the program is running, this lets me measure battery voltage so I can warn the player (the code includes my WAV audio player) that it's time for a re-charge.

    In the demo I uploaded, the only thing you should have changed is the output to the LEDs. You should definitely NOT make any changes to any of the child objects -- they are designed to work in any program. If you modify an object for a single application, it's no longer very useful because you'll have to keep a separate copy for each app (assuming different connections). For my objects the deal with IO, I put reminders in the code that RX1/TX1 and SDA1/SCL1 are pre-defined and probably shouldn't be used. There are exceptions, of course. My I2C objects allow you to share the bus used by the Propeller EEPROM.

    I would suggest that you delete my demo files that you modified, download the demo again, and in the application change the output pin to match your board (it should look like the code snippet below). Then run the program again.
    con { io pins }
    
      RX1  = 31                                                     ' programming / terminal
      TX1  = 30
      
      SDA1 = 29                                                     ' eeprom / i2c
      SCL1 = 28
    
      LEDS = 18                                                     ' LED signal pin
    
    The reason I used RX1/TX1 and SDA1/SCL1 is that these are the primary serial and I2C pins used in a Propeller project. Yes, you may in fact have secondary serial (note the XB_RX/XB_TX pins for an XBee module in the laser-tag project) and I2C ports, but that doesn't remove the first -- leave those definitions alone.

    You don't know me, but I'm asking you to trust me on these things:
    -- I've designed dozens of custom Propeller boards for EFX-TEK, our clients, and my personal clients -- I won't steer you wrong
    -- I program in Spin and PASM, and after 10+ years am pretty good with it
    -- You should never have to modify a child object; it's poorly designed (IMO) if you have to modify it for your app
    -- For my demos, you only need update the functional IO beyond RX1/TX1 and SDA1/SCL1 to get things to work (Neopixel output pin in this case)

    Okay, then, have fun with my pixel code -- and make something cool!
  • Hi Jon - I sure appreciate you taking the time to write with all the details. I like to learn as I go, so getting some background and supplementary info is always great. thanks!

    Well, I did as you said, created a new folder with your files, changed the pin to mine and I get the same as before: all pixels are white and I think full-on.

    Some background:

    using this code (cleaned up copy of what I pasted previously), I can switch colors, including combos of the RGB channels, so I know the hardware is working and I have the right pin:
    
    CON
      _clkmode        = xtal1 + pll16x
      _xinfreq        = 5_000_000
    
    
    OBJ    
        NEO    : "NeoPixel"
    '    fdx    : "FullDuplexSerial"
    
    VAR
        long   command    ' Command trigger -- write non-zero value
        long   buffer     ' Pointer to the pixel data buffer
        long   numPixels  ' Number of pixels to write
        long   pin        ' The pin number to send data over
    
        ' Pixel buffer
        long   pixels[64]
        
        long redh
        long bluh
        long grnh
        long red
        long green
        long blue
        long pulselength
        long j
        long k
        long fadefactor
        
    PUB Main : i
    
    
      pin := 18
    
      dira[pin] := 1
      outa[pin] := 0
      numPixels  := 24
     
      NEO.start(@command)
      ' $00_GG_RR_BB    
    
      PauseMSec(200)
      
      pulselength := 10000
      repeat
    '    command := 0
        repeat j from 0 to pulselength
          red := 255*j/pulselength
          green := 255*j/pulselength
          blue := 255*j/pulselength
    '      redh := fdx.Hex(red, 2)
    '      grnh := fdx.Hex(green, 2)
    '      bluh := fdx.Hex(blue, 2)  
          repeat i from 0 to (numPixels-1)
            pixels[i] := $00_FF_00_FF   
    '         pixels[i] := (grnh<<16)|(redh<<8)| bluh    
    '         pixels[i] := (green<<16)|(red<<8)|blue 
    
    
        buffer     := @pixels
        command    := 1
    
    
    
    PRI PauseMSec(Duration)
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
    
    

    So, the above routine does in fact give me purple, full bright (as far as I can tell) and across all pixels. One thing I haven't tried is to address fewer than the full set of 24 pixels, which I'll try next.

    thanks in advance for any comments - Chris
  • woops, I misspoke, the above gives me Aqua, not purple! forgot I changed that when experimenting... so, those FFs are turning on blue and green, not blue and red.

    Also, I changed # of pixels in my routine pasted just above to 16 and the first 16 of the chain of 24 turned from white (having just been running your code) to aqua. the last 8 remain white.
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-10-13 11:50
    Just so you know, I don't post code that I haven't tested. Since my name was mentioned in the tread (due to the work I've done with LED pixels and a driver that is used by a lot of pros) -- the code ran fine on my board. I am sitting in a hotel at the moment else I would post a picture (the hardware is at the office).

    The usual culprit is is timing. In my demo, I am calling the WS2812b start method from my driver:
    pub start_2812b(p_buf, pixels, pin, holdoff)
    
    '' Start pixel driver for WS2812b LEDs  
    '' -- p_buf is pointer to [long] array holding pixel data
    '' -- count is # of pixels supported by array at p_buf
    '' -- pin is serial output to WS2812b string
    '' -- holdoff is the delay between data bursts
    ''    * units are 100us (0.1ms) 10 units = 1ms
    
      return startx(p_buf, pixels, pin, holdoff, true, 24, 400, 850, 800, 450)
    
    As you can see, this calls the explicit (X) start method and the timing used matches the timing documented in the driver you're running.

    When I get to the office I will plug your code into my board.

    Question: How are you driving the output? Is it direct from the Propeller, or through something else? The board I'm using has 4427s (MOSFET driver) on it to lift the 3.3v output from the Propeller to a very stiff 5v. I really like this chip for driving pixels and servos -- especially if the connection is more than a few inches.

    FWIW, this is the board I'm using (yes, I designed it): http://www.efx-tek.com/topics/hc-8.html

    It's our most popular Propeller-based product. The original design was based on input from tech crew at Disneyland, and the original prototypes ran in the Haunted Mansion (they may still be there). Over the years we've made improvements, like changing the output buffers to TT4427s. This helped Legoland who do a lot of servo-based shows.

    I use the HC-8+ in my work with Alliance Studio, a Hollywood props & creature shop run by Steve Wang (he created the Predator among a lot of cool movie monsters). Alliance does a lot of work building displays for the gaming industry. Having the power of the Propeller really makes a difference -- especially with last-moment client changes. You might find some of the pictures on this page interesting, especially if you're a gamer (I'm not!).

    -- https://www.pinterest.com/jonmcphalen/techno-art/
  • Fantastic, thanks Jon. I'm using this chip as the 3.3v-5v level-shifter:

    http://www.ti.com/lit/ds/symlink/sn74ahct125.pdf

    so, Prop --> one channel of the SN74 --> input to pixel array with <2" links between them. However, the wires are in close proximity to other switchers I have on my board.

    I'll look at the 4427s.

    My neopixel-dedicated 5V power supply is this:

    http://www.mouser.com/ds/2/281/oki-78sr-56393.pdf

    which gets either 12V during charging or ~7.4V when running off the batteries. I haven't scoped it to check how clean it is.


    I wonder if I hooked things up to a Prop Proto-Board or something, whether it work off of that; that is, I'm just wondering is some portion of your code assumes certain pinouts for different things based on the standard proto-board arrangement, such that it might hang up or something with my custom pinout? maybe you can shoot that theory down for me...

    Very cool board, that HC-8.. if I could carve out enough time to dabble in some fun halloween animatronics, seems like it would be the go-to board..

    thanks again - Chris

  • Pixel timing can be very sensitive. I am in the office now with my hardware (which is part of a project I'm working on for my Dallas customer). I ran my original demo and notice a couple glitches that didn't show up yesterday. I changed from WS2812B to standard WS2812 timing and it works without glitches

    You could give this a try. In my demo, go into the setup method and change the pixel driver start method to this:
      strip.start_2812(@pixbuf1, STRIP_LEN, LEDS, 1_0)              ' use standard WS2812 pixel timing
    
    See if that makes a difference.

    I looked at the code you posted. It seems to want to fade everything from off to full white, but you're not updating the LEDs at each step. Because I'm an anal-retentive nut when it comes to code formatting, I took what I think you want to do and stuck into my style. It's running now.

    Warning about the driver you're using:
    -- the start() method is not standard -- should return 1..8 (not false) if the driver started, or 0 (false) if you are out of cogs
    -- if you call start() more than once, you'll have multiple cogs running -- this may not be what you want.
    -- there is no stop() method which is a standard feature of child objects that spawn a cog

    You could update that driver like this:
    var
    
      long cog
      
    
    pub start(paramBlock)
    
    '' Start the NeoPixel driver cog
    
      cog := cognew(@NeoCOG, paramBlock) + 1
    
      return cog
    
    
    pub stop
    
    '' Stop the NeoPixel driver cog if running
    
      if (cog)
        cogstop(cog - 1)
        cog := 0
    
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-10-13 15:51
    Whoops, I forgot to attach my demo using the driver you're experimenting with.
  • chris_bfschris_bfs Posts: 38
    edited 2017-10-16 01:50
    Thanks again Jon.

    New development: Friday, I hooked up another test-bed, this time with 2 of the neopixel 16-pixel rings, daisy-chained together, with that same level shifter and 5V supply, but this time to a Prop Proto-Board I had around.

    I loaded the new stuff you sent, changed the timings to the 2812 and voila! things are working on the new platform. Strangely, things didn't work on my original hardware.

    I was wondering why that might be, so I took a look at the timings. I may not understand what's going on in your timing routines (jm_time_80), so I could be off here, but it looks like the 2812 and 2812b timings were close to the min allowable timing. This might be causing some issues with timing variations from hardware to hardware or those glitches you mention above.

    To keep the 1.25us total pulse width shown in the spec sheet, I changed the timings to what you see below and your demo code now works for both hardware test-beds.
    
    pub start_2812(p_buf, count, pin, holdoff)
    
    '' Start pixel driver for WS2812 LEDs 
    '' -- p_buf is pointer to [long] array holding pixel data
    '' -- count is # of pixels supported by array at p_buf
    '' -- pin is serial output to WS2812 string
    '' -- holdoff is the delay between data bursts
    ''    * units are 100us (0.1ms) 10 units = 1ms
    
      return startx(p_buf, count, pin, holdoff, true, 24, 350, 800, 700, 600)
      
    pub start_2812CC(p_buf, count, pin, holdoff)
    
    '' Start pixel driver for WS2812 LEDs 
    '' -- p_buf is pointer to [long] array holding pixel data
    '' -- count is # of pixels supported by array at p_buf
    '' -- pin is serial output to WS2812 string
    '' -- holdoff is the delay between data bursts
    ''    * units are 100us (0.1ms) 10 units = 1ms
    
      return startx(p_buf, count, pin, holdoff, true, 24, 300, 950, 950, 300)
    
    

    Your original (unless I accidentally changed something) is top, and I created a "CC" version, with the new timing, below. I'm not sure how important it is that the sum of the nominal timing values = 1.25us, given the +/-600nm variation also cited.

    Anyhow, I bring this up because it's possible that for some hardware with very low latency level-shifters, the original timing settings work while, for other arrangements, keeping the pulse transitions very far to either side of the "ambiguous zone" in the middle of that pulse definition seems to be required, at least in my case. Who knows if this new timing would be any good for longer pixel chains.



    Here's code, copied from you originally, then modified by me, that's working for me (along with new timing above). This is included for the records and/or to clarify for someone puzzling through this issue in future:
    
    con { timing }
    
      _clkmode = xtal1 + pll16x                                     
      _xinfreq = 5_000_000                                          ' use 5MHz crystal
    
      CLK_FREQ = (_clkmode >> 6) * _xinfreq                         ' system freq as a constant
      MS_001   = CLK_FREQ / 1_000                                   ' ticks in 1ms
      US_001   = CLK_FREQ / 1_000_000                               ' ticks in 1us
      
    
      TRM_BAUD = 115_200                                            ' for terminal
    
      
    con { io pins }
    
      RX1  = 31                                                     ' programming / terminal
      TX1  = 30
      
      SDA1 = 29                                                     ' eeprom / i2c
      SCL1 = 28
    
      LEDS = 18                                                     ' LED signal pin
    
    
    con
    
      STRIP_LEN = 32                                                ' Adafruit 24-pixel ring
      PIX_BITS  = 24                                                ' RGB pixels
      
    
    obj
    
    ' main                                                          ' * master Spin cog
      time  : "jm_time_80"                                          '   timing and delays
      io    : "jm_io_basic"                                         '   essential io
      strip : "jm_rgbx_pixel"                                       ' * Smart pixel driver
      term  : "jm_fullduplexserial"                                 ' * serial IO for terminal
                                                                 
    ' * uses cog when loaded                                         
                                                                     
    
    var
    
      long  pixbuf1[STRIP_LEN]                                      ' pixel buffers
      long  pixbuf2[STRIP_LEN]
      long  pixbuf3[STRIP_LEN]
    
    
    dat
    
      Chakras       long    strip#RED, strip#ORANGE, strip#YELLOW 
                    long    strip#GREEN, strip#BLUE, strip#INDIGO
    
    
    pub main | p_pixels, pos, ch  
    
      
    
      longfill(@pixbuf1, $10_00_00_00, STRIP_LEN)                   ' prefill buffers
      longfill(@pixbuf2, $00_10_00_00, STRIP_LEN)
      longfill(@pixbuf3, $00_00_10_00, STRIP_LEN)
    
      repeat 3                                                      ' demonstrate buffer switching
        strip.use(@pixbuf1, STRIP_LEN, LEDS, PIX_BITS)
        repeat until strip.connected
        time.pause(500)
        strip.use(@pixbuf2, STRIP_LEN, LEDS, PIX_BITS) 
        time.pause(500)    
        strip.use(@pixbuf3, STRIP_LEN, LEDS, PIX_BITS) 
        time.pause(500)
                           
      repeat 3
        p_pixels := @pixbuf1                                        ' use pixbuf1
        repeat (STRIP_LEN * 2) + 1                                  ' change pointer to scroll next array
          strip.use(p_pixels, STRIP_LEN, LEDS, PIX_BITS)
          time.pause(50)
          p_pixels += 4 
    
      repeat 
        repeat 3
          color_wipe($10_00_00_00, 500/STRIP_LEN)
          color_wipe($00_10_00_00, 500/STRIP_LEN)      
          color_wipe($00_00_10_00, 500/STRIP_LEN)     
         
        repeat 3
          repeat pos from 0 to 255
            strip.set_all(strip.wheelx(pos, $10))
            time.pause(20)
    
        repeat 3
          repeat pos from 0 to 255
            repeat ch from 0 to STRIP_LEN-1
              strip.set(ch, strip.wheelx(256 / STRIP_LEN * ch + pos, $10))   
            time.pause(4)
    
        strip.clear
        strip.fill( 0,  7, $10_00_00_00)
        strip.fill( 8, 15, $00_10_00_00)
        strip.fill(16, 23, $00_00_10_00)
        time.pause(2000)
        strip.clear
        time.pause(250)
    
    
    pub color_chase(p_colors, len, ms) | base, idx, ch
    
    '' Performs color chase 
    
      repeat base from 0 to len-1                                   ' do all colors in table
        idx := base                                                 ' start at base
        repeat ch from 0 to strip.num_pixels-1                      ' loop through connected leds
          strip.set(ch, long[p_colors][idx])                        ' update channel color 
          if (++idx == len)                                         ' past end of list?
            idx := 0                                                ' yes, reset
       
        time.pause(ms)                                              ' set movement speed
    
    
    pub setup                                                        
                                                                     
    '' Setup IO and objects for application                          
                                                                     
      time.start                                                    ' setup timing & delays
                                                                     
      io.start(0, 0)                                                ' clear all pins (master cog)
    
      strip.start_2812(@pixbuf1, STRIP_LEN, LEDS, 1_0)             ' use WS2812 pixel timing
                                                               
      term.start(RX1, TX1, %0000, TRM_BAUD)                         ' start serial for terminal
    
      
    con
    
      ' Routines ported from C code by Phil Burgess (www.paintyourdragon.com)
    
    
    pub color_wipe(rgb, ms) | ch
    
    '' Sequentially fills strip with color rgb
    '' -- ms is delay between pixels, in milliseconds
    
      repeat ch from 0 to strip.num_pixels-1 
        strip.set(ch, rgb)
        time.pause(ms)
    
    
    pub rainbow(ms) | pos, ch
    
      repeat pos from 0 to 255
        repeat ch from 0 to strip.num_pixels-1
          strip.set(ch, strip.wheel((pos + ch) & $FF))
        time.pause(ms)
        
    
    pub rainbow_cycle(cycles, ms) | pos, ch 
    
      repeat pos from 0 to 255
        repeat ch from 0 to strip.num_pixels-1
          strip.set(ch, strip.wheel(((ch * 256 / strip.num_pixels) + pos) & $FF))
        time.pause(ms)
        
    
    dat { license }
    
    {{
    
      Terms of Use: MIT License
    
      Permission is hereby granted, free of charge, to any person obtaining a copy of this
      software and associated documentation files (the "Software"), to deal in the Software
      without restriction, including without limitation the rights to use, copy, modify,
      merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
      permit persons to whom the Software is furnished to do so, subject to the following
      conditions:
    
      The above copyright notice and this permission notice shall be included in all copies
      or substantial portions of the Software.
    
      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
      INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
      PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
      HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
      CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
      OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    
    }}
    
    
  • One other question: I've been able to get everything to work except for "color_chase".. it creates a lot of seemingly random colors and flashing all around. Is this method working for you and what should it do? thanks a ton!
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-10-15 20:51
    Deleted by JonnyMac. I really wish we could attach files to posts when editing.
  • JonnyMacJonnyMac Posts: 9,104
    edited 2017-10-15 22:08
    FTR... I get a little sensitive/testy when somebody modifies my code and leaves my name on it. I will be responsible for my code, but not yours. In the future, please don't modify my code and post it (even just segments) unless you want to take my name off and be responsible for the whole works. I have been criticized for code I didn't write more times than I can remember. Okay, enough on that.

    All that said, the problems you'e experienced drove me to evaluate the code and see something I had long overlooked: the PASM section isn't accounting for loop overhead. The saving grace of the driver -- even as it stands -- is that it exposes the explicit start method which allows you to use any timing values you want.

    The attached demo leaves the current driver intact and uses the startx() method with your suggested timing values. I've also added comments to color_chase() and included two variations of its use in the demo loop. It should make sense now.

    Finally, I've also attached my preferred driver schematic.

    I have to rewrite the shift_out loop to deal with the instruction overhead; that will take a bit of time, especially as I'm living in a hotel for two weeks and didn't think to pack a USB oscilloscope.


  • Hi Jon - thanks for taking the time to do that. I edited my "code" section to remove your name from the header and will keep what you said in-mind in future.

  • As I stated before, this week I'm working on a new product for my Dallas client, and we are using a NeoPixel ring in it. Updating my driver is more than a matter of pride, I'm deploying it in commercial products and want to make it as good as I can.

    After pacing around the room a lot, and three different experiments, I think I have found a solution that keeps the interface simple and the pulse timing accurate.

    The issue with the original driver is that I wasn't accounting for PASM loop overhead. It seemed simple to just subtract the overhead cycles from the waitcnt value for the pulse, but the way I had structured the code didn't allow for that -- the final waitcnt value was too small.

    I restructured the pulse output section of the shift_out loop to be more verbose, not interleaved like before. Since I don't have a 'scope with me I wrote a little test program that uses this pulse generation method and measures it at the same time with one of the counters (set to POS DETECT mode). This allowed me to empirically derive the adjustment for the new waitcnt value. Note that I've only tested at 80MHz but I'm pretty confident it will be fine at frequencies above that, too. I tested down to a 0.3us pulse as this is the spec for the SK6812.

    The interface to startx() has changed a bit -- you no longer have to specify the low-side timing for the bits. A secondary timer handles this keeping the bit frame to 1.25us. Note that there is going to be a little extra time time between pixels (to read color from hub and swap red & green bytes), but as long as this is under 9us there is no problem.

    If you would, please give this version a try. Thanks.

  • Will do! thanks, I'll report my findings today sometime.
  • Apologies for the delay. I'm happy to report that your newest works great on my hardware.

    To sweeten the news, I have since modified my hardware to include 2 new neopixel rings, making the total chain 56 pixels now. I have qty 2 of the 16 pixel rings and qty 2 of the 12 pixel rings. All I did was modify your STRIP_LEN variable to 56 and everything worked like a charm.

    thank you for all your help!

    As I build my own "demo" based on your code, I'll share my results, any issues I have.
  • I was pretty certain that would be the case, but I appreciate the feedback nonetheless. You won't have to worry about the number of pixels; I did a project with 380 pixels, and my friend Rick has used many more in his movie stuff. A previous driver update allows the user to set the reset timing; for long strings you need more than the standard 80us. I tend to use 0.5 to 1ms.

    Have fun -- and make cool stuff!
Sign In or Register to comment.