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.

Author: Ed

  • LED Curve Tracer: Firmware

    The main loop of the LED Curve Tracer firmware waits for a button push, then steps the LED current upward in 5 mA increments, measures a bunch of voltages, and prints the results in the tired old CSV format that feeds into Gnuplot and spreadsheets like nothing else.

    The output looks thuslike:

    # LED Curve Tracer
    # Ed Nisley - KE4ZNU - July 2012
    # VCC at LED: 4872 mV
    # Bandgap reference voltage: 1039 mV
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 1
    0	0	4867	3697	1169	0	0	0	3697
    5	4585	4867	3013	1853	1959	48	1911	2965
    10	9628	4867	2970	1896	2075	101	1973	2869
    15	14672	4867	2917	1949	2166	154	2012	2763
    20	20174	4867	2898	1969	2282	211	2070	2686
    25	25218	4867	2883	1983	2339	264	2075	2619
    30	30262	4867	2864	2002	2407	317	2089	2546
    35	34847	4867	2850	2017	2498	365	2132	2484
    40	40349	4867	2835	2031	2546	423	2123	2412
    45	45393	4867	2821	2046	2614	476	2137	2344
    50	49978	4872	2826	2046	2657	524	2132	2301
    55	54563	4872	2811	2060	2729	572	2156	2238
    60	60066	4872	2802	2070	2792	630	2161	2171
    65	64651	4867	2792	2075	2874	678	2195	2113
    70	69694	4867	2792	2075	2922	731	2190	2060
    75	75197	4867	2777	2089	3004	789	2214	1988
    
    # Insert LED, press button 1 to start...
    

    I suppose calling it a “curve tracer” isn’t quite accurate, because the graphs actually come from the plotting software. Feel free to add one of those gorgeous color LCD panels and draw curves on the fly.

    The code includes a constant for the measured VCC voltage from the regulator on the board, which is not, in general, ever the 5.000 V you expect. This will vary from board to board, so don’t kvetch if you get the wrong results without changing the constant. I assume that the analog reference voltage is just about exactly equal to the supply voltage, because I can’t measure it any more accurately.

    Measuring VCC, however, calibrates all the other voltages, including the LED supply regulator and the bandgap.

    Just for fun, the code measures & reports the 1.1 V bandgap reference voltage, which works out to be a bit on the low side at 1.039 V. That’s well within the hardware’s 10% tolerance spec, but no better than the usual power supply tolerance, so you must measure one or the other to get the right answer.

    The Pro Mini board includes a cap on the analog reference pin, making it as quiet as it’ll get. The datasheet recommends an elaborate LC filter upstream of AVCC that nobody uses and there’s no room for, but it doesn’t really matter for this purpose.

    The function that adjusts the gate voltage to produce a given LED current includes a bailout test to make sure the MOSFET drain-to-source voltage doesn’t drop too low. That happens with blue or violet LEDs (or, maybe white LEDs, which have a similar chemistry) at high currents, where the forward drop exceeds 4 V. There’s not enough headroom between the +5 V LED supply and the 10.5 Ω sense resistor, but the LED supply can’t exceed 5 V because we’re using the Arduino’s internal ADC and that’s limited to the supply voltage.

    The Arduino source code:

    // LED Curve Tracer
    // Ed Nisley - KE4ANU - July 2012
    
    #include <stdio.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_READ_LEDSUPPLY = 0;	// AI - LED supply voltage		blue
    const byte PIN_READ_VDRAIN = 1;		// AI - drain voltage			red
    const byte PIN_READ_VSOURCE = 2;	// AI - source voltage			orange
    const byte PIN_READ_VGATE = 3;		// AI - VGS after filtering		violet
    
    const byte PIN_SET_VGATE = 11;		// PWM - gate voltage			brown
    
    const byte PIN_BUTTON1 = 8;			// DI - button to start tests	green
    const byte PIN_BUTTON2 = 7;			// DI - button for options		yellow
    
    const byte PIN_HEARTBEAT = 13;		// DO - Arduino LED
    const byte PIN_SYNC = 2;			// DO - scope sync output
    
    //----------
    // Constants
    
    const int MaxCurrent = 75;				// maximum LED current - mA
    
    const float Vcc = 4.930;				// Arduino supply -- must be measured!
    
    const float RSense = 10.500;			// current sense resistor
    
    const float ITolerance = 0.0005;		// current setpoint tolerance
    
    const float VGStep = 0.019;				// increment/decrement VGate = 5 V / 256
    
    const byte PWM_Settle = 5;				// PWM settling time ms
    
    #define TCCRxB 0x01						// Timer prescaler = 1:1 for 32 kHz PWM
    
    #define MK_UL(fl,sc) ((unsigned long)((fl)*(sc)))
    #define MK_U(fl,sc) ((unsigned int)((fl)*(sc)))
    
    //----------
    // Globals
    
    float AVRef1V1;					// 1.1 V bandgap reference - calculated from Vcc
    
    float VccLED;					// LED high-side supply
    
    float VDrain;					// MOSFET terminal voltages
    float VSource;
    float VGate;
    
    unsigned int TestNum = 1;
    
    long unsigned long MillisNow;
    
    //-- Read AI channel
    //      averages several readings to improve noise performance
    //		returns value in mV assuming VCC ref voltage
    
    #define NUM_T_SAMPLES    10
    
    float ReadAI(byte PinNum) {
    
      word RawAverage;
    
      digitalWrite(PIN_SYNC,HIGH);                // scope sync
    
      RawAverage = analogRead(PinNum);            // prime the averaging pump
    
      for (int i=2; i <= NUM_T_SAMPLES; i++) {
        RawAverage += (word)analogRead(PinNum);
      }
    
      digitalWrite(PIN_SYNC,LOW);
    
      RawAverage /= NUM_T_SAMPLES;
    
      return Vcc * (float)RawAverage / 1024.0;
    
    }
    
    //-- Set PWM output
    
    void SetPWMVoltage(byte PinNum,float PWMVolt) {
    
    byte PWM;
    
      PWM = (byte)(PWMVolt / Vcc * 255.0);
    
      analogWrite(PinNum,PWM);
      delay(PWM_Settle);
    
    }
    
    //-- Set VGS to produce desired LED current
    //		bails out if VDS drops below a sensible value
    
    void SetLEDCurrent(float ITarget) {
    
      float ISense;				// measured current
      float VGateSet;			// output voltage setpoint
      float IError;				// (actual - desired) current
    
      VGate = ReadAI(PIN_READ_VGATE);					// get gate voltage
      VGateSet = VGate;									//  because input may not match output
    
      do {
    
    	VSource = ReadAI(PIN_READ_VSOURCE);
    	ISense = VSource / RSense;						// get LED current
    
    //	printf("\nITarget: %lu mA",MK_UL(ITarget,1000.0));
    	IError = ISense - ITarget;
    
    //	printf("\nISense: %d mA VGateSet: %d mV VGate %d IError %d mA",
    //		   MK_U(ISense,1000.0),
    //		   MK_U(VGateSet,1000.0),
    //		   MK_U(VGate,1000.0),
    //		   MK_U(IError,1000.0));
    
    	if (IError < -ITolerance) {
    	  VGateSet += VGStep;
    //	  Serial.print('+');
    	}
    	else if (IError > ITolerance) {
    	  VGateSet -= VGStep;
    //	  Serial.print('-');
    	}
    
    	VGateSet = constrain(VGateSet,0.0,Vcc);
    	SetPWMVoltage(PIN_SET_VGATE,VGateSet);
    
    	VDrain = ReadAI(PIN_READ_VDRAIN);		// sample these for the main loop
    	VGate = ReadAI(PIN_READ_VGATE);
    	VccLED = ReadAI(PIN_READ_LEDSUPPLY);
    
    	if ((VDrain - VSource) < 0.020) {			// bail if VDS gets too low
    	  printf("# VDS=%d too low, bailing\n",MK_U(VDrain - VSource,1000.0));
    	  break;
    	}
    
      } while (abs(IError) > ITolerance);
    
    //	Serial.println("# Done");
    }
    
    //-- compute actual 1.1 V bandgap reference based on known VCC = AVcc (more or less)
    //		adapted from http://code.google.com/p/tinkerit/wiki/SecretVoltmeter
    
    float ReadBandGap(void) {
    
      word ADCBits;
      float VBandGap;
    
      ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);	// select 1.1 V input
      delay(2); // Wait for Vref to settle
    
      ADCSRA |= _BV(ADSC);										// Convert
      while (bit_is_set(ADCSRA,ADSC));
    
      ADCBits = ADCL;
      ADCBits |= ADCH<<8;
    
      VBandGap = Vcc * (float)ADCBits / 1024.0;
      return VBandGap;
    }
    
    //-- Print message, wait for a given button press
    
    void WaitButton(int Button,char *pMsg) {
      printf("# %s",pMsg);
      while(HIGH == digitalRead(Button)) {
        delay(100);
        digitalWrite(PIN_HEARTBEAT,!digitalRead(PIN_HEARTBEAT));
      }
    
      delay(50);				// wait for bounce to settle
      digitalWrite(PIN_HEARTBEAT,LOW);
    }
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //------------------
    // Set things up
    
    void setup() {
      pinMode(PIN_HEARTBEAT,OUTPUT);
      digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
      pinMode(PIN_SYNC,OUTPUT);
      digitalWrite(PIN_SYNC,LOW);		// show we arrived
    
      TCCR1B = TCCRxB;					// set frequency for PWM 9 &amp; 10
      TCCR2B = TCCRxB;					// set frequency for PWM 3 &amp; 11
    
      pinMode(PIN_SET_VGATE,OUTPUT);
      analogWrite(PIN_SET_VGATE,0);		// force gate voltage = 0
    
      pinMode(PIN_BUTTON1,INPUT_PULLUP);	// use internal pullup for buttons
      pinMode(PIN_BUTTON2,INPUT_PULLUP);
    
      Serial.begin(9600);
      fdevopen(&s_putc,0);				// set up serial output for printf()
    
      printf("# LED Curve Tracer\n# Ed Nisley - KE4ZNU - July 2012\n");
    
      VccLED = ReadAI(PIN_READ_LEDSUPPLY);
      printf("# VCC at LED: %d mV\n",MK_U(VccLED,1000.0));
    
      AVRef1V1 = ReadBandGap();			// compute actual bandgap reference voltage
      printf("# Bandgap reference voltage: %lu mV\n",MK_UL(AVRef1V1,1000.0));
    
      Serial.println();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
      WaitButton(PIN_BUTTON1,"Insert LED, press button 1 to start...\n");
      printf("# INOM\tILED\tVccLED\tVD\tVLED\tVG\tVS\tVGS\tVDS\t<--- LED %d\n",TestNum++);
      digitalWrite(PIN_HEARTBEAT,LOW);
    
      for (int ILED=0; ILED <= MaxCurrent; ILED+=5) {
    	SetLEDCurrent(((float)ILED)/1000.0);
    	printf("%d\t%lu\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
    		    ILED,
    			MK_UL(VSource / RSense,1.0e6),
    			MK_U(VccLED,1000.0),
    			MK_U(VDrain,1000.0),
    			MK_U(VccLED - VDrain,1000.0),
    			MK_U(VGate,1000.0),
    			MK_U(VSource,1000.0),
    			MK_U(VGate - VSource,1000),
    			MK_U(VDrain - VSource,1000.0)
    		  );
      }
    
      SetPWMVoltage(PIN_SET_VGATE,0.0);
    
    }
    
  • LED Curve Tracer: Hardware

    Based on that comment and faced with two sacks of LEDs, I thought an LED curve tracer might come in handy at some point. While I could modify the MOSFET tester to work with LEDs, they have a higher forward voltage and a much lower current than that hardware can handle without some serious chopping & slicing.

    At least for the cheap 5 mm LEDs I’m considering, a forward drop well under 4 V and current under 75 mA should suffice. That suggests a +5 V supply for the LED, a fairly high current-sense resistor, and an Arduino for a quick-and-dirty controller.

    The overall idea:

    • Run the LED between a regulated supply and the MOSFET drain
    • MOSFET source to current-sense resistor to ground
    • Measure all three MOSFET terminal voltages
    • Set the gate voltage with a PWM-to-DC filtered voltage

    The MOSFET current depends on the gate-to-source voltage, which varies with the current through the sense resistor, so the firmware must measure the actual current and adjust the gate voltage to make the answer come out right. This being a DC application, it can probably monotonically increase VGS and stop when it sees the right current. The MOSFET must have a logic-level gate, so that voltages around +4 V will produce sufficient drain current.

    The PWM must run at 32 kHz to minimize the size of the filter cap.

    If the LED supply is slightly lower than the Arduino’s VCC supply, then the analog input can report the actual voltage and the forward drop is VCCLED - VDrain. Given a regulated supply, that’s as good measuring the voltage against the ground reference.

    The current is VSource / RSense. For a current under, say, 100 mA, a 10 Ω sense resistor will drop 1 V, leaving about 4 V of headroom for VGS. The default 5 V reference means the ADC steps are 5 mV, so the current steps will be 0.5 mA. One could use the Arduino’s internal 1.1 V band-gap reference for higher resolution: 0.11 mA. Changing that is a simple matter of software.

    So, after a bit of doodling and a pair of afternoon thunderstorms that forced a complete computer shutdown (and forced me into the Basement Laboratory), here it is:

    LED Curve Tracer - overview
    LED Curve Tracer – overview

    The Arduino Pro Mini board (it’s actually a cheap knockoff) has female headers for all the usual signals and a male header to match the sockets on the FTDI Basic programmer board, all from my heap. The connections use flying leads stripped from a length of ribbon cable, soldered to male header pins snipped from a stick, and reinforced with heatstink tubing. The Pro Mini isn’t anchored in place and probably never will be.

    Another view, minus cables and FTDI:

    LED Curve Tracer - top
    LED Curve Tracer – top

    The LED leads just jam into an old IC socket. The top pushbutton triggers the test, the bottom one doesn’t do anything yet.

    Nothing fancy at all; I hand-wired it to avoid all the usual DIY PCB hassles.The bottom view shows all the wiring:

    LED Curve Tracer - bottom
    LED Curve Tracer – bottom

    The schematic, such as it is:

    LED Curve Tracer Schematic
    LED Curve Tracer Schematic

    The regulator is a random Fairchild KA278RA05C +5 V LDO, obtained surplus. The 68 kΩ resistor trims the internal divider to pull the output to 4.87 V, just a touch under the Arduino’s 4.93 V regulator. The power supply is a 7.5 V 2 A surplus lump with no pedigree.

    The MOSFET is an IRLZ14 logic-level FET with grossly excessive qualifications.

    The sense resistor is a pair of 21.0 Ω 1% resistors in parallel = 10.5 Ω. That’s just a firmware constant, so I don’t care what the actual value works out to be.

    Next, a dab of firmware…

  • Maximum PCB Platen: Hold-Down Screws

    The whole point of tweaking the Sherline was to get it ready to drill the Wouxun KG-UV3D GPS+voice PCB. While setting up for that, I drilled two #5 holes in the maximum-size PCB platen for 10-32 socket head cap screws to hold it to the tooling plate:

    Sherline with maximum PCB platen
    Sherline with maximum PCB platen

    The sloppy hole fit lets the platen align to the tooling plate with the outer two 6-32 screws on the back edge.

    Most of the PCB boards I make aren’t nearly as wide as the platen, which means the new SHCS won’t get in the way. The screws require a nut (as a spacer) to keep them from bottoming out on the Sherline’s table underneath the tooling plate and the washers are just because I can’t do it any other way; I should just shorten the screws and store them with the platen.

    Masking tape holds small PCBs to the platen reasonably well, probably because I use an unreasonably high 50 mil travel clearance. I have a defunct dehumidifier that might make a dandy low-volume vacuum pump to eliminate any lifting in the middle: a project that has been on the to-do list for far too long…

  • Hot Air Balloon Launch

    The local Chamber of Commerce sponsors a hot-air balloon weekend that always seems to attract terrible weather; we got to see one of the launches at a nearby park on a hot afternoon before the storms.

    The crew cold-inflates the balloon with a roaring gasoline-powered blower:

    Balloon - cold inflation
    Balloon – cold inflation

    Way over there on the left, almost out of sight, one of the ground crew tethers the top of the balloon:

    Balloon - anchoring the top
    Balloon – anchoring the top

    When it’s mostly inflated, they fire the burners for the hot inflation:

    Balloon - hot inflation
    Balloon – hot inflation

    And then the magic happens:

    Balloon - liftoff
    Balloon – liftoff

    The Montgolfier Brothers would be proud:

    Balloon - up and away
    Balloon – up and away

    These are all hand-held with the Canon SX230HS at looong telephoto, with a bit of cropping & tweaking. They’re the usual low-res blog pix, but the originals aren’t much less gritty… the camera you have is better than the camera you don’t: we were out and about on other errands.

  • Turkey Chicks!

    Some years ago we would see two or three turkey hens leading a creche of two dozen chicks. We haven’t seen that many chicks lately, which we attribute to the fox that’s been trotting through the yard and the hawks patrolling the treetops. Recently, a hen guided her five chicks (four visible here) across the front lawn:

    Turkey hen with chicks in grass
    Turkey hen with chicks in grass

    The family proceeded along the flowerbed at the top of the new wall at the driveway, where the chicks showed that their camouflage works really well against leaf mulch:

    Two turkey chicks
    Two turkey chicks

    If they keep their heads down, that is:

    Turkey chick in flower garden
    Turkey chick in flower garden

    The hen jumped off the wall and flapped down to the driveway, which is no big deal for such a large bird. It provoked a bit of discussion and hesitation among the chicks, who eventually followed her lead:

    Turkey chicks can fly
    Turkey chicks can fly

    Except for the last and smallest chick, who walked along the wall until the poor thing ran out of wall. It finally showed that it can fly just as well as its siblings:

    Last turkey chick flying
    Last turkey chick flying

    Admittedly, turkeys don’t fly all that well, but they get the job done; those chicks can fly up to a branch and snuggle under their mother’s wings, safe from the foxes.

  • Sherline Tooling Plate: Protecting the Tapped Holes

    While I had the tooling plate off, I cleaned the crud out of the tapped holes and ran a handful of 1/4 inch stainless steel 10-32 setscrews just below the surface:

    Sherline tooling plate with setscrews
    Sherline tooling plate with setscrews

    They’re pretty much invisible, of course, but they’re all present. FWIW, you need a 3/32 inch hex wrench for 10-32 setscrews.

    In the event that I gouge the aluminum surface (you can see the odd ding and blind hole) through a setscrew, I’ll regret doing this. Not having to remove the plate to dig swarf out of the last clamping hole after carefully aligning a part seems like a win.

  • Sherline CNC Mill Y Axis Home Switch: To The Front!

    Reassembling the mill provided an opportunity to move the Y axis Home switch from the rear of the axis to the front. The key discovery happened during the teardown: I can get the saddle off the Y axis dovetail by removing the gib, without sliding it off the front, which means a front switch can remain firmly glued in place.

    A few random hunks of steel and a wire nut held the switch in position while the epoxy cured:

    Mounting Y axis home switch
    Mounting Y axis home switch

    The switch actuator bottoms out with the saddle just touching the preload nut, so the saddle can’t dislodge the switch: the switch trips just before the saddle hits the nut, at which point all motion stops and the motor stalls.

    Moving the switch means I can remove all the gimcrackery that poked the rear switch with the tooling plate in place; I was never happy with that setup. I also removed the small block that trapped the rear end of the Y leadscrew, under the assumption that, as I haven’t yet dropped anything on the leadscrew, I probably won’t. That adds about 1/4 inch to the maximum travel and allows the tooling plate to whack into the column.

    The switch wire runs along the stepper cable, a tidy technique that hasn’t introduced any glitches into the shared Home signal from the X axis drivers:

    Sherline mill - X and Y axis home switches
    Sherline mill – X and Y axis home switches

    The Y axis now seeks the Home switch in the positive Y direction, so that stanza in Sherline.ini looks like this:

    [AXIS_1]
    TYPE = LINEAR
    MAX_VELOCITY = 0.400
    MAX_ACCELERATION = 5.0
    STEPGEN_MAXACCEL = 10.0
    SCALE = 16000.0
    FERROR = 0.05
    MIN_FERROR = 0.01
    MIN_LIMIT = -0.5
    MAX_LIMIT = 4.90
    BACKLASH = 0.003
    HOME_IS_SHARED = 1
    HOME_SEQUENCE = 2
    HOME_SEARCH_VEL = 0.3
    HOME_LATCH_VEL = 0.016
    HOME_FINAL_VEL = -0.4
    HOME_OFFSET = 5.125
    HOME = 5.0