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

  • Random LED Dots: Hardware SPI vs. Data Layout

    The LED panel requires multiplexing: turning on one of the PNP transistors activates a single row, with the column shift registers determining which of the 24 LEDs in that row will light up. Because each row remains lit until the next one appears, it will be about 1/8 as bright as a “DC” display.

    Random LED Dots - Row Drivers
    Random LED Dots – Row Drivers

    Although the hardware allows turning on more than one row at a time, that’s a Bad Idea that will produce Bad Results: the column shift registers can’t sink that much current.

    Bitmapping the whole array requires 8 x 4 = 32 bytes, which isn’t all that much for an ATmega328 with 2 KB of RAM and nothing else on its mind:

    typedef struct {
    	byte Row;
    	byte ColR;
    	byte ColG;
    	byte ColB;
    } LED_BYTES;
    
    #define NUMROWS 8
    #define NUMCOLS 8
    
    LED_BYTES LEDs[NUMROWS] = {
    	{0x80,0,0,0},
    	{0x40,0,0,0},
    	{0x20,0,0,0},
    	{0x10,0,0,0},
    	{0x08,0,0,0},
    	{0x04,0,0,0},
    	{0x02,0,0,0},
    	{0x01,0,0,0},
    };
    

    I decided to use positive logic in the array, then invert the bits on their way to the SPI hardware.

    Setting a single LED to a color value requires chopping the color into its three component RGB bits, clearing the appropriate bits in the array, then stuffing the new ones in place:

    void SetLED(unsigned long Value) {
    
    byte Row,Col,Color,BitMask;
    
    	Row =   (Value >>  8) & 0x07;
    	Col =   (Value >> 16) & 0x07;
    	Color = (Value >> 24) & 0x07;
    
    	BitMask = (0x80 >> Col);
    
    //	printf("%u %u %u %u\r\n",Row,Col,Color,BitMask);
    
    	LEDs[Row].ColR &= ~BitMask;
    	LEDs[Row].ColR |= (Color & 0x04) ? BitMask : 0;
    
    	LEDs[Row].ColG &= ~BitMask;
    	LEDs[Row].ColG |= (Color & 0x02) ? BitMask : 0;
    
    	LEDs[Row].ColB &= ~BitMask;
    	LEDs[Row].ColB |= (Color & 0x01) ? BitMask : 0;
    
    }
    

    The Value comes from a radiation-based random number source that produces 32 bits at a time. I suppose you could just slap 24 of the bits into the column values in a row selected by three other bits to update All! The! Dots! in one shot, but it seemed less exciting to update a single LED on each iteration; the update timing is also an interesting random quantity.

    Each iteration of the main() loop squirts the (inverted) bits for a single row through the SPI hardware:

    void WaitSPIF(void) {
    	while (! (SPSR & (1 << SPIF))) {
    //		TogglePin(PIN_HEARTBEAT);
    		continue;
    	}
    }
    
    byte SendRecSPI(byte Dbyte) {			// send one byte, get another in exchange
    	SPDR = Dbyte;
    	WaitSPIF();
    	return SPDR;						// SPIF will be cleared
    }
    
    void UpdateLEDs(byte i) {
    
    	SendRecSPI(~LEDs[i].ColB);			// low-active outputs
    	SendRecSPI(~LEDs[i].ColG);
    	SendRecSPI(~LEDs[i].ColR);
    	SendRecSPI(~LEDs[i].Row);
    
    	analogWrite(PIN_DIMMING,LEDS_OFF);	// turn off LED to quench current
    	PulsePin(PIN_LATCH);				// make new shift reg contents visible
    	analogWrite(PIN_DIMMING,LEDS_ON);
    
    }
    

    I don’t do anything with the returned bytes, but perhaps that’ll be a way to get some random numbers into the program later on.

    It turned out that all the green LEDs in a column with one lit LED glowed very, very dimly if they weren’t turned off for a while; a few microseconds while pulsing the shift register parallel load clock seems to work reasonably well. I think the glow comes from microamp-level leakage current through the turned-off PNP transistors, but I haven’t tracked it down yet.

    The hardware SPI runs at 1 µs/bit with short gaps while cuing up the next byte:

    Hardware SPI - SCLK SDAT
    Hardware SPI – SCLK SDAT

    The last byte out (over on the right) contains the row select bits, of which only one can be active (low) at a time.

    The main() loop doesn’t have much else to do, so the rows refresh at 10 kHz:

    Hardward SPI - Refresh
    Hardward SPI – Refresh

    That means the LEDs in each row are active for only 100 µs and, given a whole-panel refresh of 1250 kHz (!), the LEDs appear to shimmer slightly during eye saccades. It’s a much nicer effect than the flicker produced by slower refresh intervals and has much the same eye-magnet attraction as coherent laser light.

    The code emits a scope sync pulse just after Row 7 goes out the door, so you can get ready for the next iteration:

    	UpdateLEDs(RowIndex);
    	if (++RowIndex >= NUMROWS) {
    		RowIndex = 0;
    		PulsePin(PIN_SYNC);
    	}
    

    All in all, it worked right the first time…

  • Random LED Dots: Circuitry

    This is pretty much a classic Arduino project, albeit with hardware-assisted SPI:

    Random LED Dots - Block Connections
    Random LED Dots – Block Connections

    Using SPI for the outputs means the Arduino Pro Mini doesn’t need all that many other connections:

    Random LED Dots - Power - Arduino Pro Mini
    Random LED Dots – Power – Arduino Pro Mini

    You should use a regulated 5 V DC supply and discard the regulator; I was planning something else that didn’t come to pass. Backfeeding the Pro Mini’s regulator seems to be No Problem. Do not attempt to feed the LEDs from the Pro Mini’s regulator, OK?

    The white heartbeat LED replaces the standard Arduino D13 LED that the hardware uses for the SPI clock output.

    The input from a classic Aware Electronics RM-60 Geiger interface provides random ticks. More on that later.

    The LED panel has common-anode rows and separate RGB columns. This part layout makes it look far more symmetrical than it should be, but it’d be a page wide with all 24 column lines along the bottom, where they should be:

    Random LED Dots - 2388RGB LED panel
    Random LED Dots – 2388RGB LED panel

    The row drivers use 2N2907A PNP transistors from my lifetime supply:

    Random LED Dots - Row Drivers
    Random LED Dots – Row Drivers

    The column drivers abuse 74HC595 shift registers (because SPI) as current sinks:

    Random LED Dots - RGB Column Drivers
    Random LED Dots – RGB Column Drivers

    This thing will become a desk toy, so the LEDs need be only bright enough for direct viewing at short range. The panel (or some similar panel) can run at 50 mA continuous current, enough to put spots on your retinas, so throttling it back seemed prudent.

    In any event, the shift registers can’t handle all that much current, so I set the blue LEDs at a nominal 10 mA and picked the other ballast resistors to produce more-or-less the same brightness. In round numbers, the red LEDs run at 5 mA and the green at 3 mA:

    Random LED Dots - LED colors - detail
    Random LED Dots – LED colors – detail

    An all-on blue row will dump 80 mA (more or less) into a single 74HC595, which seems to be within its specs, and the registers for the other colors will loaf along at a few tens of mA. In real life, you’d use actual LED drivers, perhaps with PWM intensity control, and be done with it. Here, I’m going cheap and easy with eight colors, one of which is off.

    The LED chips sit at the bottom of a white funnel (OK, a frustum) under a clear flat lens, with the red and blue chips washing the closest sides. All-on white sites (R+G+B, lower left) came out slightly blue overall, with a reddish tinge toward the top. Magenta sites (R+B, upper left) seem slightly more reddish than they should be. Cyan sites (G+B, middle right) have too little green.

    The granularity of SMD resistor values in my heap limits the amount of fine tuning; I am so not going to stack SMD resistors for color adjustment.

    None of that matters in this application, of course.

  • Random LED Dots: Hardware Layout

    Quite some time ago, Sophi gave me a common-anode RGB LED panel and told me to make something of it. In a spate of desk-clearing, I hammered out a quick-and-dirty multiplexed display from found materials: 2N2907 transistors as row source drivers and 74HC595 shift registers abused as column sink drivers, plus the obligatory Arduino Pro Mini and 3D printed holder:

    Random LED Dots - circuit board
    Random LED Dots – circuit board

    A sheet of milk-white acrylic diffuses the glittery LED dots into pastel disks. I think it might look better without the diffuser; it certainly has a harder-edged tech look:

    Random LED Dots - overview - no diffuser
    Random LED Dots – overview – no diffuser

    A neutral-density filter would boost the contrast without hiding any of the details.

    It obviously needs an enclosure, but, around here, that’s in the nature of fine tuning.

    The transistors and shift registers  cower under the panel:

    Random LED Dots - circuit layout - top
    Random LED Dots – circuit layout – top

    The bottom view exposes the hand wiring, plus the slot required to adapt the LED panel’s non-100-mil layout to the protoboard’s holes. I chopped out the slot with a Dremel saw, attached socket strips to the panel, and epoxied the floating strip in place:

    Random LED Dots - circuit layout - bottom
    Random LED Dots – circuit layout – bottom

    The prospect of wiring 32 discrete resistors filled me with dread, so I just tombstoned SMD resistors onto the protoboard solder blobs:

    Random LED Dots - circuit layout - RB SMD resistors
    Random LED Dots – circuit layout – RB SMD resistors

    The slot required slightly longer bridge wires:

    Random LED Dots - circuit layout - G SMD resistors
    Random LED Dots – circuit layout – G SMD resistors

    The layout, such as it is, made those short, direct wires possible. A PCB with SMD chips would be even better.

    More on the circuitry tomorrow…

  • Monthly Image: Bootleg Bell Ringers

    MHVLUG meetings end around 8 pm and, depending on this-and-that, the bell atop Old Main on the Vassar College campus will be tolling the hour as we emerge. Here’s a scene-setting photo from Wikimedia, taken from about where I parked the car:

    Vassar College Old Main Building
    Vassar College Old Main Building

    Although the bell didn’t have its usual steady rhythm after the most recent meeting, I didn’t expect this:

    Bell Ringers atop Vassar Old Main
    Bell Ringers atop Vassar Old Main

    The tree grows in the near foreground, not over Old Main.

    Two of them realized the risk of permanent hearing damage, but do you see the real hazard?

    Take a closer look:

    Bell Ringers atop Vassar Old Main - detail
    Bell Ringers atop Vassar Old Main – detail

    No, it’s not the guy leaning against the historic-but-flimsy railing. That folded-dipole antenna over on the right side most likely connects to Vassar’s 45 W UHF EMS repeater; at that range, RF can burn deeply.

    Obviously, the student body needs more amateur radio operators…

    Taken with the Canon SX230HS braced on the side of the Forester and zoomed all the way.

  • Proto Board Holder: 80×110 mm Version

    A simple holder for 80×110 mm prototyping boards:

    Random LED Dots - circuit board
    Random LED Dots – circuit board

    It’s similar to the holder for the LED current controller board, minus the center screws, plus nicely rounded corners and a cutout for wires emerging from underneath:

    Proto board holder
    Proto board holder

    Slic3r’s Hilbert Curve infill definitely looks better than the usual straight-line pattern:

    Circuit Board Holder - Slic3r preview
    Circuit Board Holder – Slic3r preview

    The OpenSCAD source code:

    // Test support frame for Hall Effect LED Blinky Light
    // Ed Nisley KE4ZNU - Sept 2013
    
    Layout = "Fancy";				// Fancy Plain
    
    PlainColor = "LightBlue";
    
    ClampFlange = true;
    
    //- Extrusion parameters - must match reality!
    
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;
    
    HoleWindage = 0.2;
    
    //- Screw sizes
    
    inch = 25.4;
    
    Tap4_40 = 0.089 * inch;
    Clear4_40 = 0.110 * inch;
    Head4_40 = 0.211 * inch;
    Head4_40Thick = 0.065 * inch;
    Nut4_40Dia = 0.228 * inch;
    Nut4_40Thick = 0.086 * inch;
    Washer4_40OD = 0.270 * inch;
    Washer4_40ID = 0.123 * inch;
    
    //- PCB sizes
    
    PCBSize = [110.0,80.0,1.5];
    PCBShelf = 2.0;
    
    Clearance = 2*[ThreadWidth,ThreadWidth,0];
    
    WallThick = IntegerMultiple(5.0,ThreadWidth);
    FrameHeight = 8.0;
    
    ScrewOffset = 0.0 + Clear4_40/2;
    
    OAHeight = FrameHeight + Clearance[2] + PCBSize[2];
    
    FlangeExtension = 5.0;
    FlangeThick = IntegerMultiple(2.0,ThreadThick);
    Flange = PCBSize
    			+ 2*[ScrewOffset,ScrewOffset,0]
    			+ 2*[Washer4_40OD,Washer4_40OD,0]
    			+ [2*FlangeExtension,2*FlangeExtension,(FlangeThick - PCBSize[2])]
    			;
    
    echo("Flange: ",Flange);
    NumSides = 4*5;
    
    WireChannel = [Flange[0],15.0,3.0 + PCBSize[2]];
    WireChannelOffset = [Flange[0]/2,25.0,( + FrameHeight + PCBSize[2] - WireChannel[2]/2)];
    
    //- Adjust hole diameter to make the size come out right
    
    module PolyCyl(Dia,Height,ForceSides=0) {			// based on nophead's polyholes
    
      Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    
      FixDia = Dia / cos(180/Sides);
    
      cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    
    //- Put peg grid on build surface
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      RangeX = floor(100 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //- Build it
    
    ShowPegGrid();
    
    difference() {
    	union() {									// body block and screw bosses
    		translate([0,0,OAHeight/2])
    			color(PlainColor)
    			cube(PCBSize + Clearance + [2*WallThick,2*WallThick,FrameHeight],center=true);
    		for (x=[-1,1], y=[-1,1]) {
    			translate([x*(PCBSize[0]/2 + ScrewOffset),
    						y*(PCBSize[1]/2 + ScrewOffset),
    						0])
    				color((Layout == "Fancy") ? "Orchid" : PlainColor)
    				cylinder(r=Washer4_40OD,h=OAHeight,$fn=NumSides);
    		}
    		if (ClampFlange)
    			color((Layout == "Fancy") ? "SeaGreen" : PlainColor)
    			linear_extrude(height=Flange[2])
    				hull()
    					for (i=[-1,1], j=[-1,1]) {
    						translate([i*(Flange[0]/2 - Washer4_40OD/2),j*(Flange[1]/2 - Washer4_40OD/2)])
    							circle(d=Washer4_40OD,$fn=NumSides);
    					}
    	}
    
    	for (x=[-1,1], y=[-1,1]) {				// screw holes and washer recesses
    		translate([x*(PCBSize[0]/2 + ScrewOffset),
    					y*(PCBSize[1]/2 + ScrewOffset),
    					-Protrusion])
    			rotate((x-1)*90)
    			PolyCyl(Tap4_40,(OAHeight + 2*Protrusion));
    		translate([x*(PCBSize[0]/2 + ScrewOffset),
    					y*(PCBSize[1]/2 + ScrewOffset),
    					OAHeight - PCBSize[2]])
    			PolyCyl(1.2*Washer4_40OD,(PCBSize[2] + Protrusion),NumSides);
    	}
    
    	translate([0,0,OAHeight/2])					// through hole below PCB
    		cube(PCBSize - 2*[PCBShelf,PCBShelf,0] + [0,0,2*OAHeight],center=true);
    
    	translate([0,0,(OAHeight - (PCBSize[2] + Clearance[2])/2 + Protrusion/2)])	// PCB pocket on top
    		cube(PCBSize + Clearance + [0,0,Protrusion],center=true);
    
    	translate(WireChannelOffset)									// clearance for cable on solder side
    		cube(WireChannel + [0,0,Protrusion],center=true);
    }
    
  • HP 7475A Plotter: Serial Cable for Hardware Handshaking

    The HP 7475A wakes up with hardware handshaking enabled: DTR starts high and goes low when the internal 1 KB buffer has less than 80 bytes remaining. The plotter also supports XON/XOFF handshaking, a sad software thing you’d use only if you had no other choice.

    The Chiplotle doc provides a wiring diagram for a suitable 9-to-25 pin cable, so I printed one and doodled on it while pondering the Great Cable Stash:

    HP7475A Plotter - Serial Cable
    HP7475A Plotter – Serial Cable

    The color codes over on the left of the top diagram match a prebuilt cable I hoped to repurpose, but it had only five conductors, none of which were DSR or CTS. Pfui!

    So I used a hank of gorgeous flexy 9-conductor cable (which came with premolded DE-9 ends of the wrong gender, now amputated into pigtails and back in the GCS), which supported the connections redrawn on the bottom in proper numeric order, used the obvious color sequence (Bn R O Y G Bl V W K), then soldered suitable connectors on each end:

    HP 7475A Plotter - serial cable
    HP 7475A Plotter – serial cable

    And it worked the first time…

  • HP 7475A Plotter: Rehabilitation

    [Update: Wecome Hackaday! You may want to look at:

    Searching for 7475a will bring up many, many other posts]

    After mentioning that I wished I still had my HP 7475A plotter, Dithermaster sent me one from his heap. As he explained, a mouse family had used it as a combination hotel-granary-latrine:

    HP 7475A - chassis latrine
    HP 7475A – chassis latrine

    For whatever it’s worth, if you must get a bazillion seeds out of a plotter, ship it halfway across the continent: UPS performs a lengthy three-axis vibration test that shakes all the loose bits through the vents.

    You’ll probably want the original HP 7475A documentation from the (unofficial) HP Computer Museum before digging in. Not mentioned anywhere: the two washers at the rear edge of the case are not identical. The one holding the power supply in place is slightly longer than the one at the serial connector. Mine are now color-coded to their locations.

    A critter whizzed on U13, the serial adapter chip, just beyond the big black filter capacitor:

    HP 7475A - PCB latrine area
    HP 7475A – PCB latrine area

    I rinsed everything (except, no fool I, the membrane keypad at the front of the PCB) with warm water, flushed the latrine areas with dilute baking soda (alkaline, to neutralize the urea), rinsed with hot water, blew-dry with compressed air, then let the pieces sit for a few days.

    After reassembly, the plotter didn’t start up. It’s a third of a century old, what did you expect?

    Measuring the electrolytic capacitors showed they were all in surprisingly good condition, with only C27 and C34 (on this Option 001 = RS-232 board) having moderately high ESR. They’re the pale blue axial caps just right of the heatsink, both 22 μF 25 V:

    • C27: Processor Reset timing (U14 – p. 6-27/6-28)
    • C34: +5 V filter cap (U21 – power supply p. 6-31)

    The corresponding caps on the Option 002 = HP-IB board are C20 and C25. FWIW, if you have an HP-IB plotter, you should probably just hack an Arduino into the motor control connections and run it with Grbl; you’d get a bare-bones plotter eating G-Code, not HP-GL, but that’s not entirely a Bad Thing. Adapting the tool change code to handle the pen carousel is left as an exercise for the desperate.

    I replaced the offending caps with 33 μF 50 V radial caps from the heap:

    HP 7475A - re-capped PCB
    HP 7475A – re-capped PCB

    And then it performed its Demonstration Plot (load paper, hold down P1 + P2 buttons, turn on power) perfectly. The fossilized pens left no trace behind; we all expected that.

    The serial port connection on the back required, from bottom to top:

    All of which came from the Big Box o’ Serial Adapters and produced this rather unsteady ziggurat:

    HP 7475A - serial port adapters - typical
    HP 7475A – serial port adapters – typical

    Seeing as how I’ve been adapting serial connections since before the HP 74754A was a thing, the Adapter Box has All! The! Adapter! Genders! plus Der Blinkenlights! They don’t come in nearly as handy nowadays, though, which is a Good Thing.

    Some optimization pared down the ziggurat and added a short extension cable:

    HP 7475A - serial port adapters - hardcore
    HP 7475A – serial port adapters – hardcore

    Eventually, I’ll build a custom cable, but it’s good enough for now.

    The switches select 9600 b/s serial data in 8N1 format. Yes, the plotter tops out at 9600 b/s, but remember we’re dealing with a pen plotter that executes terse ASCII commands. It offers both XON/XOFF and DTR/DSR hardware handshaking to prevent overruning the internal 1 kB buffer, plus a myriad other software-selectable options relevant to long-forgotten datacomm systems.

    Lest I forget, dots now mark the switch settings for 9600 8N1, A (letter) paper, US (inch) units, direct serial connection:

    HP 7475A - DIP switch settings
    HP 7475A – DIP switch settings

    And then it Just Worked: type IN;SP1; into minicom and the plotter grabs Pen 1. The rest is a simple matter of software.

    Now, to deal with the pen situation…