Newbie question: Neopixels w/ Prop, don't understand how to pass hex-value variables
chris_bfs
Posts: 38
in Propeller 1
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!
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
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.
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. │
└─────────────────────────────────────────────────────────────────────────────────────────┘
}}
{
' Fill the grid with blue
repeat i from 0 to numPixels-1
pixels := $00_00_00_FF
}
the lights do come on blue.
Ah! of course. thank you.
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
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.
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.
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.
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.
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.
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.
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!
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:
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
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.
The usual culprit is is timing. In my demo, I am calling the WS2812b start method from my driver: 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/
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
You could give this a try. In my demo, go into the setup method and change the pixel driver start method to this:
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:
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.
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:
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.
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.
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.
Have fun -- and make cool stuff!