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

  • Got My Picture On The Cover Of Circuit Cellar

    Well, a picture of my circuitry, anyhow, there in the upper-right corner:

    Circuit Cellar 306 - January 2016 - Cover
    Circuit Cellar 306 – January 2016 – Cover

    It’s the avalanche noise random number generator:

    Reverse-bias noise amplifier - detail
    Reverse-bias noise amplifier – detail

    I’m pretty sure they color-negatived it to suppress that retina-burn magenta PETG holder.

    It wasn’t for my column, but the Tinny cover painting for Circuit Cellar Issue 29 (“Measurement and Control”) hangs near my desk:

    Robert Tinney - Cover for Circuit Cellar Issue 29
    Robert Tinney – Cover for Circuit Cellar Issue 29

    Back then, I was writing the Firmware Furnace column. Ah, those were the days…

     

  • Bike Helmet Earbud Replacement

    A bag arrived from halfway around the planet, bearing five sets of cheap earbuds. There was no way to tell from the eBay description, but they’re vented on the side:

    Cheap earbud - side vent detail
    Cheap earbud – side vent detail

    And also to the rear, down inside those deep slots below the chromed plastic cover:

    Cheap earbud - back openings
    Cheap earbud – back openings

    The raised lettering is a nice touch; the other earbud has a script L.

    The PET braid over the fragile wire should withstand a bit more abuse than usual. The strain relief isn’t anything to cheer, though, consisting of that rectangular channel with the wire loose inside. I figured I’d start minimal and fix whatever crops up; I have nine more earbuds to go.

    The motivation for all this was having the Gorilla Tape peel off the helmet, leaving a hardened mass of glue behind, then snagging the earbud wires. This is the new, somewhat better protected, wiring:

    Bell Helmet - mic-earbud wire - hardened tape adhesive
    Bell Helmet – mic-earbud wire – hardened tape adhesive

    In a triumph of hope over experience, I applied more Gorilla Tape:

    Bell Helmet - re-taped mic-earbud wiring
    Bell Helmet – re-taped mic-earbud wiring

    The helmet may need replacing after another iteration or two.

    My solid modeling hand has become stronger these days, so I should gimmick up a flat-ish wart anchoring the mic boom and all the wiring to the helmet shell.

  • Logitech M305 Mouse Switch Cleaning

    While installing Mint on the Lenovo Q150, I discovered that the right button on the (long disused) Logitech M305 wireless mouse wasn’t working. After replacing the batteries (always check the batteries), it still didn’t work, so I peeled the four slippery feet off the bottom, removed the screws, and confronted the interior:

    Logitech M305 mouse - interior
    Logitech M305 mouse – interior

    Much to my surprise, the button switches had removable covers:

    Logitech M305 mouse - switch disassembly
    Logitech M305 mouse – switch disassembly

    I put a minute drop of DeoxIT Red on a slip of paper, ran it between both pairs of contacts, removed a considerable amount of tarnish, reassembled in reverse order, and it’s all good again.

    The glue on the back of the slippery feet didn’t like being peeled off, so I expect they’ll fall off at some point.

    It’s much easier to drive a GUI with three functional buttons…

    [Update: Long-time commenter Raj notes:

    I always had problem with the middle button. I have replaced them a few times and learnt that they come with different operating pressures. The soft ones are hard to come by. I found an alternate in the PTT switches on Yaesu handies in my junk.

    That’s the blocky switch to the left of the shapely wheel cutout.]

  • Sony HDR-AS30V vs. STK NP-BX1: Power Estimate, Redux

    After 95 minutes on a pleasant ride with temperature around 55 °F, the STK C battery had 0.59 W·h remaining (dark green trace):

    Sony NP-BX1 - STK used - Wh scale - 2015-12-12
    Sony NP-BX1 – STK used – Wh scale – 2015-12-12

    The last time around, it had 1.85 W·h after 61 minutes. Subtracting the two (and ignoring that it may have started with slightly different charges and behave differently at different temperatures) says the camera used 1.26 W·h = 76 W·min in 34 minutes, which averages out to 2.2 W.

    That’s close enough to the “a bit over 2 W” figured from those partial-to-empty measurements for me.

    The discharge tests from early November:

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

    The best STK battery (D) holds just under 4.2 A·h, so its absolute longest run time could be 110-ish minutes. That graph shows the A cell was just about done after 75 minutes, so changing the battery after an hour still makes sense; you never know what will happen during the last few minutes of a ride…

  • Hard Drive Platter Mood Light: Correct Phase Timing

    As noted earlier, the timing for a π/16 phase delay works out to

    218 steps = (π/16) * (1 cycle/2π) * (7 * 1000 step/cycle)

    which amounts to a delay of 5.45 s = 218 step * 25 ms/step. That means a color should appear on the top platter 11 s after it appears on the bottom platter:

    Mood Light - pi over 16 phase - composite
    Mood Light – pi over 16 phase – composite

    But when I actually got out a stopwatch and timed the colors, the bottom-to-top delay worked out to a mere 3.5 s…

    After establishing that the steps ticked along at the expected 25 ms pace, the phase-to-step calculation produced the right answer, the increments were working as expected, I finally slept on the problem (a few times, alas) and realized that the increment happened in the wrong place:

    for (int i=0; i < LEDSTRINGCOUNT; i++) { // for each layer byte Value[PIXELSIZE]; for (byte c=0; c > PIXELSIZE; c++) { // figure the new PWM values if (++Pixels[c].Step >= Pixels[c].NumSteps) {   //  ... from incremented step
                Pixels[c].Step = 0;
            }
            Value[c] = StepColor(c,-i*Pixels[c].PlatterPhase);
        }
        uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE]);
     
        for (int j=0; j < LEDSTRIPCOUNT; j++) {              // fill layer with color
            strip.setPixelColor(Map[i][j],UniColor);
        }
    }
    

    The outer loop runs “for each layer”, so the increment happens three times on each step, making the colors shift three times faster than they should.

    Promoting the increments to their own loop solved the problem:

    	MillisNow = millis();
    	if ((MillisNow - MillisThen) > UpdateMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		
    		for (byte c=0; c < PIXELSIZE; c++) { // step to next increment in each color if (++Pixels[c].Step >= Pixels[c].NumSteps) {
    				Pixels[c].Step = 0;
    				printf("Cycle %d steps %d at %8ld delta %ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow - MillisThen));
    			}
    		}
    
    		for (int i=0; i < LEDSTRINGCOUNT; i++) {				// for each layer
    			byte Value[PIXELSIZE];
    			for (byte c=0; c < PIXELSIZE; c++) {				//  ... for each color
    				Value[c] = StepColor(c,-i*Pixels[c].PlatterPhase);		// figure new PWM value
    //				Value[c] = (c == RED && Value[c] == 0) ? Pixels[c].MaxPWM : Value[c];	// flash highlight for tracking
    			}
    			uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE]);
    			if (false && (i == 0))
    				printf("L: %d C: %08lx\r\n",i,UniColor);
    			for (int j=0; j < LEDSTRIPCOUNT; j++) {				// fill layer with color
    				strip.setPixelColor(Map[i][j],UniColor);
    			}
    		}
    		strip.show();
    
    		MillisThen = MillisNow;
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    

    And then It Just Worked.

    Verily, it is written: One careful measurement trumps a thousand expert opinions.

    Sheesh

    (The WordPress editor wrecked these code snippets. I’m leaving them broken so WP can maybe fix the problem.) The problem isn’t fixed, but these are OK now… as long as I don’t unleash the “improved” editor on the post, anyway.

  • Squidwrench Vending Machine: OEM Wiring Diagram

    An old vending machine in need of rebooting may provide fodder for some electronics tutorials at Squidwrench. To that end, here’s the OEM wiring diagram pasted inside the door:

    SqWr Vending Machine - OEM Wiring Diagram
    SqWr Vending Machine – OEM Wiring Diagram

    That’s endured a perspective transformation and a bit of contrast stretching; it looks awful, but being able to view it without squatting inside the machine makes it much easier to read…

    Each selector and motor cycle switch pair interact with the motor thusly:

    Vending Machine - Switches and Motor Doodle
    Vending Machine – Switches and Motor Doodle

    All of the motors have one side connected directly to the 24 VAC power transformer. The wiring diagram shows a pair of transformers in parallel, which seems odd.

    The Selector switches (an array of 30 on the front panel, with one broken that will surely call for some 3D printing) are in series, so the lowest-numbered one wins; the NO terminal of each Selector switch  goes directly to the control box. Pressing a switch connects the Red·Orange wire on the C terminal of the first switch to the control box on the same wire as the corresponding motor lead.

    Assuming the Motor Cycle switch parks in the NC position, it will disconnect the Orange wire from the Orange·Black wire and connect it to the lower motor lead and the Select switch (which may or may not be pressed by then), although we don’t know the timing. There’s surely a cam on the motor shaft.

    Some possibly relevant patents, found after a brief search with the obvious keywords:

  • Hard Drive Platter Mood Light: Color Gradations

    Now that the trig argument runs from 0 through 2π and resets for each complete cycle, it’s practical to add a phase that changes the colors on a per-layer basis.

    The first trick, filling each layer with a single color, requires a two-dimensional Map array that lists the pixels in the proper order:

    // number of LED strips around hub
    #define LEDSTRIPCOUNT 4
    
    // number of LEDs per strip
    #define LEDSTRINGCOUNT 3
    
    byte Map[LEDSTRINGCOUNT][LEDSTRIPCOUNT] = {{0,5,6,11}, {1,4,7,10}, {2,3,8,9}};	// pixel IDs around platter, bottom to top.
    

    Instantiate the Adafruit library buffer, as before, but now compute the proper number of pixels from the fundamental constants:

    Adafruit_NeoPixel strip = Adafruit_NeoPixel(LEDSTRIPCOUNT * LEDSTRINGCOUNT, PIN_NEO, NEO_GRB + NEO_KHZ800);
    

    You can still access the pixel buffer using a linear index, which the first part of the lamp test uses to walk a single white pixel through the string in the natural wiring order:

    	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);
    	}
    

    Then fill them with white, layer by layer from the bottom up, using the Map array:

    	for (int i=0; i < LEDSTRINGCOUNT; i++) {				// for each layer
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		for (int j=0; j < LEDSTRIPCOUNT; j++) {				// spread color around the layer
    			strip.setPixelColor(Map[i][j],FullWhite);
    			strip.show();
    			delay(250);
    		}
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    

    With that in hand, it took me a disturbing amount of time to figure out that the angular phase should apply to the slowest sine wave, with the two other phase angles being calculated from the corresponding number of time steps. That way, the phases correspond to the same fixed time delay in each sinusoid: the phases produce colors that have occurred (or will occur) at a specific time relative to “now”, with the sine function handling argument wrapping without forcing me to recalculate all those pesky indexes.

    The PlatterSteps variable holds the number of steps in the BASEPHASE angle in the slowest wave:

    	Pixels[RED].Prime = 3;
    	Pixels[GREEN].Prime = 5;
    	Pixels[BLUE].Prime = 7;
    	
    	PlatterSteps = (unsigned int) ((BASEPHASE / TWO_PI) *
    				RESOLUTION * (unsigned int) max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime));
    

    En passant, I set the PWM limits that keep the LED temperature under control, then compute the per-color values:

    	Pixels[RED].MaxPWM = 64;
    	Pixels[GREEN].MaxPWM = 64;
    	Pixels[BLUE].MaxPWM = 64;
    	
    	for (byte c=0; c < PIXELSIZE; c++) {
    		Pixels[c].NumSteps = RESOLUTION * (unsigned int) Pixels[c].Prime;
    		Pixels[c].Step = (false) ? random(Pixels[c].NumSteps) : Pixels[c].NumSteps - 1;
    		Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;				// in radians per step
    		Pixels[c].PlatterPhase = PlatterSteps * Pixels[c].StepSize;		// radians per platter
    	}
    

    Most of the type promotions / conversions / coercions among bytes / integers / floats happen without much attention, but every now & again I faceplanted one.

    Whenever it’s time for an update (every 25 ms seems OK), this code computes the new color for each layer and spreads it around:

    		for (int i=0; i < LEDSTRINGCOUNT; i++) {				// for each layer
    			byte Value[PIXELSIZE];
    			for (byte c=0; c > PIXELSIZE; c++) { // figure the new PWM values if (++Pixels[c].Step >= Pixels[c].NumSteps) {	//  ... from incremented step
    					Pixels[c].Step = 0;
    				}
    				Value[c] = StepColor(c,-i*Pixels[c].PlatterPhase);
    			}
    			uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE]);
    
    			for (int j=0; j < LEDSTRIPCOUNT; j++) {				// fill layer with color
    				strip.setPixelColor(Map[i][j],UniColor);
    			}
    		}
    

    The -i*Pixels[c].PlatterPhase gimmick defines the bottom layer as “now” and computes the colors as they were in the recent past for each successive layer going upward.

    With the phase difference boosted to π/4 to make the differences more visible:

    Mood Light - pi over 4 phase
    Mood Light – pi over 4 phase

    You’re seeing three LEDs reflected in the platters, of course.

    A phase difference of π/16 seems barely visible in this composite image,but it’s pleasant in person:

    Mood Light - pi over 16 phase - composite
    Mood Light – pi over 16 phase – composite

    The greenish ones come from a slightly different perspective. The purple ones show the progression over the course of a few seconds.

    A π/16 = 11.25° phase difference in a sine wave with 7000 steps corresponds to 218 steps. At 25 ms/step, that’s a 5.5 s delay and the top layer duplicates the bottom layer after 11 s.

    It’s surprisingly relaxing…

    The complete Arduino source code:

    // Neopixel mood lighting for hard drive platter sculpture
    // Ed Nisley - KE4ANU - December 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
    
    // number of steps per cycle, before applying prime factors
    #define RESOLUTION 1000
    
    float PlatterPhase = -TWO_PI/12.0;				// phase difference between platters
    
    // number of LED strips around hub
    #define LEDSTRIPCOUNT 4
    
    // number of LEDs per strip
    #define LEDSTRINGCOUNT 3
    
    //----------
    // Globals
    
    // instantiate the Neopixel buffer array
    
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(LEDSTRIPCOUNT * LEDSTRINGCOUNT, 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;
    };
    
    // colors in each LED
    enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};
    
    struct pixcolor_t Pixels[PIXELSIZE];								// all the data for each pixel color intensity
    
    byte Map[LEDSTRINGCOUNT][LEDSTRIPCOUNT] = {{0,5,6,11}, {1,4,7,10}, {2,3,8,9}};	// pixel IDs around platter, bottom to top.
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Figure PWM based on current state
    
    byte StepColor(byte Color, float Phi) {
    
    byte Value;
    
        Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi));
        return 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("Hard Drive Platter Mood Light with Neopixels\r\nEd Nisley - KE4ZNU - December 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);
    	
    // fill the layers
    	
    	printf(" ... fill using Map array\r\n");
    	
    	for (int i=0; i < LEDSTRINGCOUNT; i++) {				// for each layer
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		for (int j=0; j < LEDSTRIPCOUNT; j++) {				// spread color around the layer
    			strip.setPixelColor(Map[i][j],FullWhite);
    			strip.show();
    			delay(250);
    		}
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    	
    // clear to black
    	
    	printf(" ... clear\r\n");
    	
    	for (int i=0; i < LEDSTRINGCOUNT; i++) {				// for each layer
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    		for (int j=0; j < LEDSTRIPCOUNT; j++) {				// spread color around the layer
    			strip.setPixelColor(Map[i][j],FullOff);
    			strip.show();
    			delay(250);
    		}
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    	
    	delay(1000);
    	
    // set up the color generators
    
    	MillisNow = MillisThen = millis();
    	randomSeed(MillisNow + analogRead(7));
    	printf("First random number: %ld\r\n",random(10));
    
    	
    	Pixels[RED].Prime = 7;
    	Pixels[GREEN].Prime = 11;
    	Pixels[BLUE].Prime = 5;
    	
    	Pixels[RED].MaxPWM = 64;
    	Pixels[GREEN].MaxPWM = 64;
    	Pixels[BLUE].MaxPWM = 64;
    	
    	for (byte c=0; c < PIXELSIZE; c++) {
    		Pixels[c].NumSteps = RESOLUTION * (unsigned int) Pixels[c].Prime;
    		Pixels[c].Step = (true) ? random(Pixels[c].NumSteps) : Pixels[c].NumSteps - 1;
    		Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;
    	}
    	
    	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("Max PWM: (%d,%d,%d)\r\n",Pixels[RED].MaxPWM,Pixels[GREEN].MaxPWM,Pixels[BLUE].MaxPWM);
    	printf("Platter phase: %d deg\r\n",(int)(360.0*PlatterPhase/TWO_PI));
    }
    
    //------------------
    // Run the mood
    
    void loop() {
    	
    	MillisNow = millis();
    	if ((MillisNow - MillisThen) > UpdateMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    
    		for (int i=0; i < LEDSTRINGCOUNT; i++) {				// for each layer
    			byte Value[PIXELSIZE];
    			for (byte c=0; c < PIXELSIZE; c++) {				// figure the new PWM values
    				if (++Pixels[c].Step >= Pixels[c].NumSteps) {	//  ... from incremented step
    					Pixels[c].Step = 0;
    				}
    				Value[c] = StepColor(c,i*PlatterPhase);
    			}
    			uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE]);
    			if (false && (i == 0))
    				printf("C: %08lx\r\n",UniColor);
    			for (int j=0; j < LEDSTRIPCOUNT; j++) {				// fill layer with color
    				strip.setPixelColor(Map[i][j],UniColor);
    			}
    		}
    		strip.show();
    
    		MillisThen = MillisNow;
    		digitalWrite(PIN_HEARTBEAT,LOW);
    	}
    	
    }
    

    Apart from the thermal problems, it’s pretty slick…

    [Edit: if you look carefully, you’ll find a not particularly subtle error that completely screws up the timing. The LEDs looks great and work as described, but the colors run too fast. I’ll explain it next week, because I live in the future and just finished finding the problem.]