The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Electronics Workbench

Electrical & Electronic gadgets

  • Hard Drive Platter Mood Light: Improved Trigonometry

    The original Mood Light firmware used the current time in milliseconds as a factor in the sin() argument, assuming that the Arduino runtime would Do The Right Thing. Having been gently disabused of that notion, here’s another pass that resets the argument after every full cycle to keep the trig from going crazy. Thanks to all of you for helping out… [grin]

    The hardware still looks like this, though:

    Hard Drive Mood Light - high angle
    Hard Drive Mood Light – high angle

    Define a structure to hold everything needed to calculate each color, then make an array holding one structure per color:

    struct pixcolor_t {
    	byte Prime;
    	unsigned int NumSteps;
    	unsigned int Step;
    	float StepSize;
    	byte MaxPWM;
    	byte Value;
    };
    
    enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};
    
    #define RESOLUTION 1000
    
    struct pixcolor_t Pixels[PIXELSIZE];
    

    The general idea is to increment the integer Step from 0 through NumSteps - 1 to create the sine wave, with the total number of steps per cycle being Prime times the RESOLUTION.

    The angular argument is Step * StepSize, with the size of each step equal to 2π / NumSteps. Because Step gets reset to zero after reaching NumSteps - 1, the argument never exceeds 2π and the trig never falls off the rails.

    Soooo, calculating the PWM value for each color goes like this:

    byte StepColor(byte Color) {
    
        Pixels[Color].Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize));
    	
    	Pixels[Color].Step = (Pixels[Color].Step >= Pixels[Color].NumSteps) ? 0 : Pixels[Color].Step + 1;
    	
    	if (0 == Pixels[Color].Step) {
    		printf("Color %d cycle end at %d\r\n",Color,Pixels[Color].NumSteps);
    	}
    
        return Pixels[Color].Value;
    }
    

    The MaxPWM parameter limits the perceived brightness, although not the peak current. Each Neopixel dissipates 300-ish mW at full throttle, they’re mounted on a plastic structure, and there’s not a lot of air flowing between those platters; running at half power makes a lot of sense.

    Initializing the structure values happens in the setup() function, because it’s easier than filling in all the array structure entries by hand:

    	Pixels[RED].Prime = 5;
    	Pixels[GREEN].Prime = 7;
    	Pixels[BLUE].Prime = 11;
    	
    	for (byte c=0; c < PIXELSIZE; c++) {
    		Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime;
    		Pixels[c].Step = random(Pixels[c].NumSteps);
    		Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;
    		Pixels[c].MaxPWM = 128;
    		StepColor(c);
    	}
    

    The Phase value has Gone Away, because it really didn’t add anything to the proceedings. Instead, I randomize the starting Step, although there’s not a lot of randomness to be had early on in an Arduino program; that needs a bit more work. Adding a little PCB with a random noise source doesn’t seem cost-effective, although a photodetector peering out the side and adjusting the MaxPWM values might be a Good Thing.

    Come to think of it, limiting the sum of the PWM values might be more useful than limiting their individual maximum values. That’s a simple matter of software…

    The main() loop doesn’t have a lot to do. Every 25 ms it updates the three color PWM values, sets the new values into all 12 LED buffer locations, and sends the whole mess to the Neopixels. The RESOLUTION value acts as a gearshift between the 25 ms update rate and the speed at which complete cycles zip past. Absent the Prime factor, each cycle would require 25 ms * RESOLUTION ms to complete: call it 25 seconds.

    The Prime factors slow that down proportionally and push the repetition interval out to the product of all the factors. For the (5, 7, 11) factors shown below, that’s 5x7x11x253 s = 6×106 s = 70 days,

    Now it doesn’t matter how often the millis() value wraps. Every now & again, MillisThen will be just under 232 and MillisNow will be just over 0, but their (unsigned) difference will be some huge number, the conditional will trip, and nobody will notice the timing glitch…

    The Arduino source code:

    // Neopixel mood lighting for hard drive platter sculpture
    // Ed Nisley - KE4ANU - November 2015
    
    #include <Adafruit_NeoPixel.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_NEO = 6;				// DO - data out to first Neopixel
    
    const byte PIN_HEARTBEAT = 13;		// DO - Arduino LED
    
    //----------
    // Constants
    
    const unsigned long UpdateMS = 25ul - 4ul;		// update LEDs only this many ms apart minus loop() overhead
    
    //----------
    // Globals
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN_NEO, NEO_GRB + NEO_KHZ800);
    
    uint32_t FullWhite = strip.Color(255,255,255);
    uint32_t FullOff = strip.Color(0,0,0);
    
    struct pixcolor_t {
    	byte Prime;
    	unsigned int NumSteps;
    	unsigned int Step;
    	float StepSize;
    	byte MaxPWM;
    	byte Value;
    };
    
    enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};
    
    #define RESOLUTION 1000
    
    struct pixcolor_t Pixels[PIXELSIZE];								// everything that calculates the pixel colors
    
    byte Map[] = {0,5,6,11, 1,4,7,10, 2,3,8,9};							// pixel numbers around platter, bottom to top.
    
    //-- Figure PWM based on current state
    
    byte StepColor(byte Color) {
    
        Pixels[Color].Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize));
    	
    	Pixels[Color].Step = (Pixels[Color].Step >= Pixels[Color].NumSteps) ? 0 : Pixels[Color].Step + 1;
    	
    	if (0 == Pixels[Color].Step) {
    		printf("Color %d cycle end at %d\r\n",Color,Pixels[Color].NumSteps);
    	}
    	
    //	printf("Step: %d Color: %d Value: %d\r\n",Pixels[Color].Step,(word)Color,(word)Pixels[Color].Value);
    	
        return Pixels[Color].Value;
    }
    
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //------------------
    // Set the mood
    
    void setup() {
    	
    	pinMode(PIN_HEARTBEAT,OUTPUT);
    	digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
    	Serial.begin(57600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Mood Light with Neopixels\r\nEd Nisley - KE4ZNU - November 2015\r\n");
    	
    /// set up Neopixels
    	
    	strip.begin();
    	strip.show();
    	
    // lamp test: run a brilliant white dot along the length of the strip
    	
    	printf("Lamp test: walking white\r\n");
    	
    	strip.setPixelColor(0,FullWhite);
    	strip.show();
    	delay(500);
    	
    	for (int i=1; i<strip.numPixels(); i++) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		strip.setPixelColor(i-1,FullOff);
    		strip.setPixelColor(i,FullWhite);
    		strip.show();
    		digitalWrite(PIN_HEARTBEAT,LOW);
    		delay(500);
    	}
    	
    	strip.setPixelColor(strip.numPixels() - 1,FullOff);
    	strip.show();
    	delay(500);
    	
    // and around the disks
    	
    	printf(" ... using Map array\r\n");
    	
    	strip.setPixelColor(Map[0],FullWhite);
    	strip.show();
    	delay(250);
    	
    	for (int i=1; i<strip.numPixels(); i++) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		strip.setPixelColor(Map[i-1],FullOff);
    		strip.setPixelColor(Map[i],FullWhite);
    		strip.show();
    		digitalWrite(PIN_HEARTBEAT,LOW);
    		delay(250);
    	}
    	
    	strip.setPixelColor(Map[strip.numPixels() - 1],FullOff);
    	strip.show();
    	delay(250);
    	
    	MillisNow = MillisThen = millis();
    	randomSeed(MillisNow + analogRead(7));
    	printf("First random number: %ld\r\n",random(10));
    	
    	Pixels[RED].Prime = 5;
    	Pixels[GREEN].Prime = 7;
    	Pixels[BLUE].Prime = 11;
    	
    	for (byte c=0; c < PIXELSIZE; c++) {
    		Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime;
    		Pixels[c].Step = random(Pixels[c].NumSteps);
    		Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;
    		Pixels[c].MaxPWM = 128;
    		StepColor(c);
    	}
    	printf("Prime scales: (%d,%d,%d)\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime);
    	printf("Initial step: (%d,%d,%d)\r\n",Pixels[RED].Step,Pixels[GREEN].Step,Pixels[BLUE].Step);
    	printf("  ...  color: (%d,%d,%d)\r\n",Pixels[RED].Value,Pixels[GREEN].Value,Pixels[BLUE].Value);
    	
    	for (int i=0; i<strip.numPixels(); i++) { strip.setPixelColor(Map[i],strip.Color(Pixels[RED].Value,Pixels[GREEN].Value,Pixels[BLUE].Value)); } strip.show(); } //------------------ // Run the mood void loop() { // printf("Loop! %ld %ld\r\n",MillisNow,MillisThen); MillisNow = millis(); if ((MillisNow - MillisThen) > UpdateMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    
    		for (byte c=0; c < PIXELSIZE; c++) {
    			StepColor(c);
    		}
    		
    		for (int i=0; i < strip.numPixels(); i++) {
    			strip.setPixelColor(i,strip.Color(Pixels[RED].Value,Pixels[GREEN].Value,Pixels[BLUE].Value));
    		}
    		strip.show();
    
    		MillisThen = MillisNow;
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    	
    }
    
  • Sony HDR-AS30V Camera vs. STK NP-BX1 Batteries

    This sheaf of tests shows three of the four STK NP-BX1 batteries deliver about 4 W·h during a constant 500 mA discharge, with battery B trailing behind:

    Sony NP-BX1 - Wasabi FG - STK ABCD - Wh scale - 2015-11-03
    Sony NP-BX1 – Wasabi FG – STK ABCD – Wh scale – 2015-11-03

    After the three most recent bike rides, I popped the partially discharged battery into the tester and used the same test current:

    Sony NP-BX1 - STK ABD - charged vs used - Wh scale - 2015-11-22
    Sony NP-BX1 – STK ABD – charged vs used – Wh scale – 2015-11-22

    The longer curves come from the top chart (with different colors), the shorter ones from the partially discharged batteries. In an ideal world, the shorter curves should give the energy left in the battery after the ride, so subtracting that from the before-ride capacity gives the energy used during the ride.

    The results for battery A may not be typical, as the camera turned off before I rolled into the garage. The camera may run with a battery voltage below the 2.8 V cutoff in those tests, so it can extract more energy than the tests. The slope of the curve toward the end suggests it won’t get much, but that will still bias the results.

    In round numbers, the bike rides required:

    • A: 3.8 – 0.1 = 3.7 W·h
    • B: 3.6 – 1.4 = 2.2 W·h
    • D: 4.2 – 1.0 = 3.2 W·h

    I generally turn the camera off during the mid-ride pause (Protip: never wear a helmet camera into a Port-a-Loo), so at least two of the rides have discontinuous usage. I figured the total run time from the video file sizes at the rate of 22.75 min/4.0 GB, blithely ignoring issues like the battery recovering during the pauses, the effect of ambient temperature vs. camera heating on battery temperature, and so forth and so on.

    In an ideal world, dividing the total energy by the run time (converted from minutes to hours and not venturing into pirate·ninja territory) should produce a nearly constant value equal to the camera’s power dissipation:

    • A: 3.7 W·h / 1.25 h = 2.96 W
    • B: 2.2 W·h / 1.0 h = 2.1 W
    • D: 3.2 W·h / 1.4 h = 2.25

    Ignoring the suspiciously high result for battery A, it looks like the HDR-AS30V really does dissipate a bit over 2 W while recording 1920×1080@60fps video. That’s with GPS, WiFi, and NFC turned off, of course.

    Which turns out to be pretty close to the test conditions: 3.7 V x 500 mA = 1.85 W. I could goose the test current to 600 mA = 2.2 W/3.7 V for the next tests, but maybe long-term consistency is a virtue.

  • Hard Drive Platter Mood Light: First Light!

    Disassembling the (unglued!) platter stack simplified wiring the the Neopixels:

    Hard Drive Mood Light - test light
    Hard Drive Mood Light – test light

    Orienting the strips in alternate directions kept the white data connections between adjacent strips on the top and bottom level. If they sat in the same direction, the data wires would run from top to bottom.

    Each Neopixel draw 60 mA max, so each side of the pillar can draw 180 mA and lighting up all four sides in full-throttle white draws a bit over 720 mA. That’s more than those little Wire-Wrap wires should be forced to carry, but the tiny Neopixel solder pads aren’t good for much more than that. The revised column model has wiring channels behind both strip ends to provide access to the slightly larger pads on the rear surface; the fact that all the end pads get cut in half doesn’t help matters.

    The red and blue power wires connect adjacent strips, with two opposite strips wired in parallel at the bottom of the column. There’s a 100 µF cap across the incoming power leads: as much capacitor as would fit in the somewhat undersized base.

    A knockoff Arduino Pro Mini sits inline between a 5.2 VDC wall wart and the Mood Light with three connections: VCC, GND, and D6. It’s flapping around in mid-air with no protection whatsoever, so I’ll let your imagination draw that picture. I want to hide it in the base, along with a power jack, as part of the fine tuning.

    Anyhow, restacking the platters produced this pleasant effect:

    Hard Drive Mood Light - low angle
    Hard Drive Mood Light – low angle

    You’re seeing each LEDs both directly and through a reflection in the platter below it. Despite having handled the platters for a few days, the reflection’s clarity surprised me; the multiple reflections required to bounce the LED image to the edge of the platter work perfectly:

    Hard Drive Mood Light - high angle
    Hard Drive Mood Light – high angle

    Running the original firmware (which, as noted in the comments, will eventually fall off its rails), the colors change slowly enough to be always the same while you’re watching and always different after you look away:

    Hard Drive Mood Light - red
    Hard Drive Mood Light – red

    The platters stack sufficiently parallel to each other that the LED images still have the right spacing after multiple reflections. It’s not quite an infinite house of mirrors.

    With the LEDs running at half intensity (PWM limited to 128/255), the stack lights up a dark living room just fine. At full throttle, it’d probably be too bright…

    All in all, it looks suprisingly good!

  • Neopixel Current

    Adafruit’s Neopixels are RGB LEDs with a built-in current-limiting 400 Hz PWM controller and a serial data link. Successive Neopixels aren’t synchronized, so their PWM cycles can produce serious current spikes.

    Lighting up just the red LED in two Neopixels at PWM 16/255 produces this current waveform (at 10 mA/div):

    Neopixel current 10 mA - 16-0-0 0-1
    Neopixel current 10 mA – 16-0-0 0-1

    Each red LED draws about 20 mA, so when the two Neopixel PWM cycles coincide, you get a nasty 40 mA spike. When they don’t coincide, you get a pair of 20 mA pulses. Those pulses walk with respect to each other at a pretty good clip; the oscillators aren’t trimmed to precision.

    Lighting up three Neopixels with PWM 16/255 on the red does exactly what you’d expect. The horizontal scale  is now 100 µs/div, making the PWM pulses five times wider:

    Neopixel current 10 mA - 16-0-0 0-1-2
    Neopixel current 10 mA – 16-0-0 0-1-2

    The narrow spike comes from the brief shining instant when all three Neopixels were on at the same time. Now you have three PWM pulses, each with slightly different periods.

    Remember that these are PWM 16/255 pulses. When they’re at full brightness, PWM 255/255, there’s only a brief downtime between pulses that last nearly 2.5 ms and they’ll overlap like crazy.

    Obviously, the more Neopixels and the lower the average PWM setting, the more the average current will tend toward the, uh, average. However, it will have brutal spikes, so the correct way to size the power supply is to multiply the number of Neopixels in the string by the maximum possible 60 mA/Neopixel… which gets really big, really fast.

    A 1 meter strip of 144 knockoff Neopixels from the usual eBay supplier will draw 144 x 60 mA = 8.6 A when all the pulses coincide. Worse, the supply must be able to cope with full-scale transients and all the fractions in between. A husky filter cap would be your friend, but you need one with a low ESR and very high capacity to support the transients.

    No wonder people have trouble with their Neopixel strings; you really shouldn’t (try to) run more than one or two directly from an Arduino’s on-board regulator…

  • White LED Failures

    Well, that didn’t take long:

    Ring Light Mount - failed LEDs
    Ring Light Mount – failed LEDs

    The two dim LEDs to the left are actually very faintly lit, so I think the dark one has failed nearly open.

    When I installed those nine central LEDs, I didn’t notice that the bag (from the usual eBay source, IIRC) contained two different types of white LEDs. The difference shows up clearly under UV that lights up the yellow phosphor:

    Ring Light Mount - failed LEDs in UV
    Ring Light Mount – failed LEDs in UV

    By random chance, each of the three groups has one non-fluorescing LED. If I can extricate them from their epoxy tomb, maybe I can figure out which one failed.

    Rather than replace those, I’ll try a new-fangled chip-on-board light source, even though that might require a current limiter and maybe a heatsink. Obviously, this is getting out of hand, but maybe the same folks who can’t make a white LED can make a functional COB assembly for a buck… [sigh]

  • Hard Drive Platter Mood Light: Neopixel Firmware

    Having accumulated a pile of useless hard drives, it seemed reasonable to harvest the platters and turn them into techie mood lights (remember mood lights?). Some doodling showed that four of Adafruit’s high-density Neopixel strips could stand up inside the 25 mm central hole, completely eliminating the need to putz around with PWM drivers and RGB LEDs: one wire from an Arduino Pro Mini and you’re done:

    const byte PIN_NEO = 6;				// DO - data out to first Neopixel
    

    The firmware creates three sine waves with mutually prime periods, then updates the RGB channels with raised-sine values every 10 ms. The PdBase constant defines the common conversion from milliseconds to radians:

    const float PdBase = 0.05 * TWO_PI / 1000.0;	// scale time in ms to radians
    

    The leading 0.05 = 1/20 means the sine wave will repeat every 20 s = 20000 ms.

    Dividing that period by three small primes produces an RGB pattern that will repeat every 5x11x17 = 935 PdBase cycles = 18.7×103 s = 5.19 h:

    const float Period[] = {PdBase/5.0,PdBase/11.0,PdBase/17.0};		// mutually prime periods
    

    That’s languid enough for me, although I admit most of the colors look pretty much the same. Obviously, you can tune for best picture by dinking with a few constants.

    A Phase array sets the starting phase to 3π/2 = -90 degrees:

    float Phase[] = {3.0 * HALF_PI,3.0 * HALF_PI,3.0 * HALF_PI};		// sin(3π/2 ) = -1, so LEDs are off
    

    Jiggling those starting phases produces a randomized initial color that’s close to dark:

    	MillisNow = MillisThen = millis();
    	randomSeed(MillisNow + analogRead(6) + analogRead(7));
    	printf("Phases: ");
    	for (byte i=0; i<3; i++) {
    		Phase[i] += random(-1000,1000) * HALF_PI / 1000.0;
    		printf("%d ",(int)(Phase[i]*RAD_TO_DEG));
    	}
    	printf(" deg\r\n");
    

    With all that in hand, converting from time to color goes like this:

    uint32_t SineColor(unsigned long t) {
    byte rgb[3];
    
    	for (byte i=0; i<3; i++) {
    			rgb[i] = Intensity[i]/2.0 * (1 + sin(t * Period[i] + Phase[i]));
    	}
    	return strip.Color(rgb[0],rgb[1],rgb[2]);
    }
    

    The rest of the code scales neatly with the strip length defined in the magic instantiation:

    Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN_NEO, NEO_GRB + NEO_KHZ800);
    

    Although the colors change very slowly, shifting them all one chip toward the end of the 144 Neopixel strip at each update produces a noticeable difference that reassured me this whole mess was working:

    		for (int i=strip.numPixels()-1; i>0; i--) {
    			c = strip.getPixelColor(i-1);
    			strip.setPixelColor(i,c);
    		}
    
    		c = SineColor(MillisNow);
    		strip.setPixelColor(0,c);
    		strip.show();
    

    And with that in hand, It Just Worked…

    However, it’s worth noting that each Neopixel draws a bit over 60 mA at full white, which works out to a smidge under 9 A for a 144 LED strip. Because they’re PWM devices, the LEDs are either full-on or full-off, so the peak current can actually be 9 A, regardless of any reduced duty cycle to limit the intensity.

    The Adafruit driver includes an overall intensity control, but I added an Intensity array with separate values for each channel:

    float Intensity[] = {128.0,128.0,128.0};							// pseudo current limit - PWM is always full current
    

    That would allow throttling back the blue LEDs a bit to adjust the overall color temperature, but that’s definitely in the nature of fine tuning.

    The Adafruit Neopixel guide recommends a honkin’ big cap right at the strip, plus a 470 Ω decoupling resistor at the first chip’s data input. I think those attempt to tamp down the problems caused by underpowered supplies and crappy wiring; running it at half intensity produced a maximum average current just under the supply’s 3 A limit.

    The complete Arduino source code:

    // Neopixel mood lighting for hard drive platter sculpture
    // Ed Nisley - KE4ANU - November 2015
    
    #include <Adafruit_NeoPixel.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_NEO = 6;				// DO - data out to first Neopixel
    
    const byte PIN_HEARTBEAT = 13;		// DO - Arduino LED
    
    //----------
    // Constants
    
    const int UPDATEMS = 10ul - 4ul;		// update LEDs only this many ms apart minus loop() overhead
    
    const float PdBase = 0.05 * TWO_PI / 1000.0;	// scale time in ms to radians
    
    const float Period[] = {PdBase/5.0,PdBase/11.0,PdBase/17.0};		// mutually prime periods
    float Phase[] = {3.0 * HALF_PI,3.0 * HALF_PI,3.0 * HALF_PI};		// sin(3π/2 ) = -1, so LEDs are off
    float Intensity[] = {128.0,128.0,128.0};							// pseudo current limit - PWM is always full current
    
    //----------
    // Globals
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN_NEO, NEO_GRB + NEO_KHZ800);
    
    uint32_t FullWhite = strip.Color(255,255,255);
    uint32_t FullOff = strip.Color(0,0,0);
    
    //--- figure color from time in ms
    
    uint32_t SineColor(unsigned long t) {
    byte rgb[3];
    
    	for (byte i=0; i<3; i++) {
    			rgb[i] = Intensity[i]/2.0 * (1 + sin(t * Period[i] + Phase[i]));
    	}
    	return strip.Color(rgb[0],rgb[1],rgb[2]);
    }
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //------------------
    // Set the mood
    
    void setup() {
    	
    uint32_t c;
    
    	pinMode(PIN_HEARTBEAT,OUTPUT);
    	digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
    	Serial.begin(57600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Mood Light with Neopixels\r\nEd Nisley - KE4ZNU - November 2015\r\n");
    	
    /// set up Neopixels
    	
    	strip.begin();
    	strip.show();
    	
    // lamp test: run a brilliant white dot along the length of the strip
    	
    	printf("Lamp test: walking white\r\n");
    	
    	strip.setPixelColor(0,FullWhite);
    	strip.show();
    	delay(500);
    	
    	for (int i=1; i<strip.numPixels(); i++) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		strip.setPixelColor(i-1,FullOff);
    		strip.setPixelColor(i,FullWhite);
    		strip.show();
    		digitalWrite(PIN_HEARTBEAT,LOW);
    		delay(500);
    	}
    	
    	MillisNow = MillisThen = millis();
    	randomSeed(MillisNow + analogRead(6) + analogRead(7));
    	printf("Phases: ");
    	for (byte i=0; i<3; i++) {
    		Phase[i] += random(-1000,1000) * HALF_PI / 1000.0;
    		printf("%d ",(int)(Phase[i]*RAD_TO_DEG));
    	}
    	printf(" deg\r\n");
    	
    	c = SineColor(MillisNow);
    	printf("Initial time: %08lx -> color: %08lx\r\n",MillisNow,c);
    	
    	for (int i=0; i<strip.numPixels()-1; i++) {
    		strip.setPixelColor(i,c);
    	}
    	
    	strip.show();
    	
    }
    
    //------------------
    // Run the mood
    
    void loop() {
    	
    byte r,g,b;
    uint32_t c;
    
    	MillisNow = millis();
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		
    		for (int i=strip.numPixels()-1; i>0; i--) {
    			c = strip.getPixelColor(i-1);
    			strip.setPixelColor(i,c);
    		}
    
    		c = SineColor(MillisNow);
    		strip.setPixelColor(0,c);
    		strip.show();
    
    		MillisThen = MillisNow;
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    }
    
    
  • Sony NP-BX1 Batteries: Wasabi vs. SterlingTEK

    The combined results of the six most recent NP-BX1 batteries for my Sony HDR-AS30V helmet camera:

    Sony NP-BX1 - Wasabi FG - STK ABCD - Ah scale - 2015-11-03
    Sony NP-BX1 – Wasabi FG – STK ABCD – Ah scale – 2015-11-03

    One might reasonably conclude all six came from the same factory; the STK B battery looks like a dud. The two replacement batteries from STK performed slightly better than the first pair.

    The Wasabi and SterlingTEK batteries all carry a 1600 mA·h rating that’s far in excess of their actual 1000-ish mA·h performance. If they were advertised as 1.0 A·h batteries, they’d meet their specifications (for small values of “meet”), but nobody would buy a second-tier battery with less capacity than the Sony OEM battery’s 1.24 A·h.

    If you rummage around in previous posts, I did verify that battery capacity does increase with decreasing test current, but definitely not by the 60% needed to reach 1600 mA·h.

    Because most devices these days operate at constant power from a boost supply, presenting the results against a watt·hour scale would make sense:

    Sony NP-BX1 - Wasabi FG - STK ABCD - Wh scale - 2015-11-03
    Sony NP-BX1 – Wasabi FG – STK ABCD – Wh scale – 2015-11-03

    That doesn’t change the overall rankings, such as they are, but does include the effect of higher terminal voltage.

    The claimed specifications:

    • Sony OEM – 4.5 W·h
    • Wasabi – 5.7 W·h
    • STK – 5.9 W·h

    The Sony battery actually performed about as advertised, but the others fall short on this scale, too.

    They should survive for hour-long rides with the GPS tracker turned off, which is about as much as I want to ride at once. I’ll eventually autopsy the STK B battery, which won’t last all that long.

    Credit where credit is due: after I sent the first test results to STK, they sent a pair of replacement batteries and, based on the second test results, refunded the entire purchase price. I’m reluctant to give a five-star rating for customer service, because shipping mis-advertised products should carry a zero-star rating.