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

  • Arduino Snippets: Temperature Measurement

    Temperature seems an obvious thing to measure, so a bit of rummaging disgorged a classic LM335 temperature sensor that produce an output voltage directly calibrated in Kelvin at 10 mV/K: room temperature runs 296 K = 2.96 V. Nothing could be easier than this:

    LM335 Temperature Sensor

    The downside: a 1 °C temperature change corresponds to only 10 mV, which is barely two LSB of the Arduino ADC. In round numbers, a 1 °F change = 1 LSB, which doesn’t leave much room for measurement noise. I average five successive readings, which may be excessive, but the result seems stable enough:

    const float AVREF = 4.94;                    // Arduino analog reference
    
    #define TAVG 5
    
    float ReadLM335(byte Pin) {
    float Kelvin;
    
    	Kelvin = (float)analogRead(Pin);
    	for (byte i = 1; i < TAVG; i++)
    		Kelvin += (float)analogRead(Pin);
    
    	return Kelvin * (100.0 * AVREF) / (TAVG * 1024.0);
    }
    

    For better accuracy, you must measure VCC on the Arduino board and plug that into the AVREF constant, because the ADC reference voltage comes from the power supply. If you’re powering the Arduino from a USB port, then don’t bother worrying about analog conversion accuracy, because VCC depends on which PC you use, the USB cable length, what load current you draw from the regulator, and probably the phase of the moon.

    The magic number 100.0 converts 10 mV/K to K.

    The four character DL1414 LED display works well enough for the kind of temperatures you might find around a human being and, if you have an LED bargraph display, you may as well throw that into the mix, too.

    LM335 Temperature Sensor – 19 C

    The bargraph has RRYYGGYYRR LEDs, so I scaled the temperature at 5 °C/bar and put 0 °C on the bottom of the display, which means 15-19 and 20-24 °C occupy the green bars in the middle. Fingertip temperatures light up the two yellow bars and body heat gets you into the red, so it’s a reasonable display. Just to show it works, here’s a closer look (0 °C is on the right, but you can reverse that easily enough):

    LM335 Temperature Sensor – 25 C

    The Arduino source code:

    // LM335 Temperature sensor sensor
    // Ed Nisley - KE4ANU - November 2012
    
    //#include <stdio.h>
    //#include <math.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_TEMPERATURE = A1;			// Temperature sensor - LM335 = 10 mV/K
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
    const byte PIN_RCKC  = 12;			// latch clock for LED character display
    
    const byte PIN_HEARTBEAT = 13;				// DO - Arduino LED
    
    //----------
    // Constants
    
    const int UPDATEMS = 1000;					// update LEDs only this many ms apart
    
    const float AVREF = 4.94;					// Arduino analog reference
    const float KTOC = -273.2;					// Kelvin to Centigrade offset
    
    const float BARSCALE = 5.0;					// degrees per bar increment
    
    #define TCCRxB 0x02							// Timer prescaler
    
    #define LED_SIZE		4				// chars per LED
    #define LED_DISPLAYS	1				// number of displays
    #define LED_CHARS		(LED_DISPLAYS * LED_SIZE)
    
    union DL1414_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    	unsigned int Addr:2;
    	unsigned int NotWrite:1;
    	unsigned int Ctl3_7:5;			// unused bits
    	unsigned int Data:7;
    	unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    
    //----------
    // Globals
    
    int Temperature, BaseTemperature;
    
    word LEDBits;
    
    char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Send bits to LED bar driver register
    
    void SetBarBits(word Pattern) {
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern & 0x00ff);
    
    	digitalWrite(PIN_RCKB,HIGH);
    	digitalWrite(PIN_RCKB,LOW);
    
    }
    
    void PulsePinHigh(byte PinID) {
    	digitalWrite(PinID,HIGH);
    	digitalWrite(PinID,LOW);
    }
    
    //-- Write single char to DL1414
    
    void WriteLEDChar(char Char,char CharID) {
    
    	union DL1414_ DL1414;
    
    	DL1414.ShiftBits.Data = Char & 0x7F;
    	DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	DL1414.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 0;				// write the character
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	digitalWrite(PIN_RCKC,HIGH);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 1;				// disable write
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    }
    
    void WriteLEDString(char *pString) {
    
    	for (byte i=0; (i < LED_CHARS) && *pString; ++i)
    		WriteLEDChar(*pString++,i);
    
    	return;
    }
    
    //-- Sample temperature with a dab of averaging
    
    #define TAVG 5
    
    float ReadLM335(byte Pin) {
    float Kelvin;
    
    	Kelvin = (float)analogRead(Pin);
    	for (byte i = 1; i < TAVG; i++)
    		Kelvin += (float)analogRead(Pin);
    
    	return Kelvin * (100.0 * AVREF) / (TAVG * 1024.0);
    }
    
    //------------------
    // Set things up
    
    void setup() {
      pinMode(PIN_HEARTBEAT,OUTPUT);
      digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
    //  TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //  TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
      pinMode(PIN_MOSI,OUTPUT);
      digitalWrite(PIN_MOSI,LOW);
    
      pinMode(PIN_SCK,OUTPUT);
      digitalWrite(PIN_SCK,LOW);
    
      pinMode(PIN_RCKB,OUTPUT);
      digitalWrite(PIN_RCKB,LOW);
    
      pinMode(PIN_RCKC,OUTPUT);
      digitalWrite(PIN_RCKB,LOW);
    
      Serial.begin(9600);
      fdevopen(&s_putc,0);				// set up serial output for printf()
    
      printf("Temperature sensor - LM335\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
      BaseTemperature = KTOC + ReadLM335(PIN_TEMPERATURE);
    
      WriteLEDString(LEDCharBuffer);
    
      LEDBits = 0x5555;
      SetBarBits(LEDBits);
    
      printf("Base Temperature: %d C\n",(int)BaseTemperature);
    
      delay(1000);
    
      MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    
    		Temperature = KTOC + ReadLM335(PIN_TEMPERATURE);
    
    		printf("Temperature: %d C\n",(int)Temperature);
    
    		LEDBits = 0x0200 >> (1 + (int)(Temperature/BARSCALE));	// move upward on display!
    		SetBarBits(LEDBits);
    
    		sprintf(LEDCharBuffer,"%-3dC",(int)Temperature);
    		WriteLEDString(LEDCharBuffer);
    
    		digitalWrite(PIN_HEARTBEAT,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }
    
  • Arduino Snippets: Quadrature Knob

    Any gadget goes better with a knob and what better knob than one with detents all the way around? This one even has a push-on momentary switch on the shaft, so you can switch gain / change modes / control power while turning:

    Quadrature Knob with Push Switch
    Quadrature Knob with Push Switch

    Using the Arduino’s internal pullups means you just plug the wires into the header sockets and it works:

    Quadrature Knob and Switch - breadboard
    Quadrature Knob and Switch – breadboard

    The code uses the same interrupt-driven state machine I described there to avoid glitches and twitches. The test code dumps the accumulated knob rotation count and switch state out the serial port:

    Knob encoder
    Ed Nisley - KE4ZNU - November 2012
    Knob count: 0 Switch: 0
    Knob count: 0 Switch: 1
    Knob count: 1 Switch: 1
    Knob count: 1 Switch: 0
    Knob count: 2 Switch: 0
    Knob count: 3 Switch: 0
    Knob count: 4 Switch: 0
    Knob count: 4 Switch: 1
    Knob count: 3 Switch: 1
    Knob count: 2 Switch: 1
    Knob count: 1 Switch: 1
    Knob count: 0 Switch: 1
    Knob count: -1 Switch: 1
    Knob count: -2 Switch: 1
    Knob count: -3 Switch: 1
    Knob count: -4 Switch: 1
    Knob count: -5 Switch: 1
    Knob count: -5 Switch: 0
    Knob count: -4 Switch: 0

    Yes, you (well, I) can spin the knob fast enough to accumulate a few counts between the 10 ms updates and it will return to zero at the same physical location, so it’s not losing any counts in the process.

    Knob encoder
    Ed Nisley - KE4ZNU - November 2012
    Knob count: 0 Switch: 0
    Knob count: -4 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -7 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -4 Switch: 0
    Knob count: -3 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -8 Switch: 0
    Knob count: -9 Switch: 0
    Knob count: -10 Switch: 0
    Knob count: -12 Switch: 0
    Knob count: -18 Switch: 0
    Knob count: -19 Switch: 0
    Knob count: -20 Switch: 0
    Knob count: -19 Switch: 0
    Knob count: -18 Switch: 0
    Knob count: -15 Switch: 0
    Knob count: -9 Switch: 0
    Knob count: -7 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -4 Switch: 0
    Knob count: -3 Switch: 0
    Knob count: -2 Switch: 0
    Knob count: -1 Switch: 0
    Knob count: 0 Switch: 0

    Not very exciting as it stands, but it’s got plenty of upside potential.

    The Arduino source code:

    // Quadrature knob with switch
    // Ed Nisley - KE4ANU - November 2012
    // Based on:
    // https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/
    
    //----------
    // Pin assignments
    
    const byte PIN_KNOB_A = 2;			// knob A switch - must be on ext interrupt 2
    const byte PIN_KNOB_B = 4;			//  .. B switch
    const byte PIN_KNOB_SW  = A5;		//  .. push-close momentary switch
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 10;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    //----------
    // Globals
    
    enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};
    
    volatile char KnobCounter = 0;
    volatile char KnobState;
    
    char PrevKnobCounter = 0;
    char KnobSwitch, PrevSwitch = 0;
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Knob interrupt handler
    
    void KnobHandler(void)
    {
    	byte Inputs;
    	Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A);  // align raw inputs
    //	Inputs ^= 0x02;                             // fix direction
    
    	switch (KnobState << 2 | Inputs) {
    	case 0x00 : 				// 0 00 - glitch
            break;
    	case 0x01 : 				 // 0 01 - UP to 1
            KnobCounter++;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x03 : 				 // 0 11 - DOWN to 1
            KnobCounter--;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x02 : 				 // 0 10 - glitch
            break;
    	case 0x04 : 				 // 1 00 - DOWN to 0
            KnobCounter--;
    		KnobState = KNOB_CLICK_0;
    		break;
    	case 0x05 : 				 // 1 01 - glitch
            break;
    	case 0x07 : 				 // 1 11 - glitch
            break;
    	case 0x06 : 				 // 1 10 - UP to 0
            KnobCounter++;
    		KnobState = KNOB_CLICK_0;
    		break;
    	default :  					// something is broken!
            KnobCounter = 0;
    		KnobState = KNOB_CLICK_0;
    	}
    }
    
    //-- Read switch, flip sense
    
    char ReadSwitch(int PinNumber) {
    	return !digitalRead(PinNumber);
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_KNOB_B,INPUT_PULLUP);
    	pinMode(PIN_KNOB_A,INPUT_PULLUP);
    	pinMode(PIN_KNOB_SW,INPUT_PULLUP);
    
    	KnobState = digitalRead(PIN_KNOB_A);
    	PrevSwitch = digitalRead(PIN_KNOB_SW);
    	attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Knob encoder\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) &amp;gt; UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    
    		KnobSwitch = ReadSwitch(PIN_KNOB_SW);
    		if ((PrevKnobCounter != KnobCounter) || (PrevSwitch != KnobSwitch)) {
    			printf("Knob count: %d Switch: %d\n",KnobCounter,KnobSwitch);
    			PrevKnobCounter = KnobCounter;
    			PrevSwitch = KnobSwitch;
    		}
    
    		digitalWrite(PIN_SYNC,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }
    
  • Arduino Snippets: Hall Effect Sensor

    Attaching a linear Hall effect sensor to an Arduino doesn’t require much effort at all:

    Linear Hall Sensor
    Linear Hall Sensor

    Despite what I observed on that breadboard lashup, the output will need a load resistor (output-to-ground, across pins 3 and 2) if there’s no internal constant-current sink; anything around 10 kΩ should suffice. The one I have works fine with or without the resistor; I added a 10 KΩ resistor that’s not shown here. The output voltage does, as you’d expect, change slightly with the resistor in place.

    The sensor lives on a different part of the same breadboard now:

    49E Linear Hall Effect Sensor - breadboard
    49E Linear Hall Effect Sensor – breadboard

    The test code drives the RGB LED strip: red for positive field strength and blue for negative. The maximum and minimum values track the extremes, for plenty of color regardless of how weak a magnet it sees. It works great with the one on my fingernail… and random screwdrivers, digital calipers, scissors, and suchlike.

    The OpenSCAD source code:

    // Hall sensor
    // Ed Nisley - KE4ANU - November 2012
    
    //----------
    // Pin assignments
    
    const byte PIN_RED = 9;				// PWM - LED driver outputs +active
    const byte PIN_GREEN = 10;
    const byte PIN_BLUE = 11;
    
    const byte PIN_FIELD = A0;			// Hall sensor input, 0 field = 2.5 v, more or less
    
    const byte PIN_HEARTBEAT = 13;		// DO - Arduino LED
    
    //----------
    // Constants
    
    const int UPDATEMS = 5;					// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    //----------
    // Globals
    
    float FieldHigh, FieldLow, FieldRange, FieldBase, Field;
    
    byte Red,Blue,Green;
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    int sign_float(float val) {
    	if (val < 0.0)
    		return -1;
    	else if (val > 0.0)
    		return 1;
    
    	return 0;
    }
    
    //-- Sample magnetic field with a dab of averaging
    
    #define FIELDAVERAGE 5
    
    float ReadSensor(byte Pin) {
    float Field;
    
    	Field = (float)analogRead(Pin);
    	for (byte i = 1; i < FIELDAVERAGE; i++)
    		Field += (float)analogRead(Pin);
    
    	return Field / (FIELDAVERAGE * 1024.0);
    }
    
    //------------------
    // Set things up
    
    void setup() {
      pinMode(PIN_HEARTBEAT,OUTPUT);
      digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
      TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
      TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
      pinMode(PIN_RED,OUTPUT);
      analogWrite(PIN_RED,0);			// force gate voltage = 0
    
      pinMode(PIN_GREEN,OUTPUT);
      analogWrite(PIN_GREEN,0);
    
      pinMode(PIN_BLUE,OUTPUT);
      analogWrite(PIN_BLUE,0);
    
      Serial.begin(9600);
      fdevopen(&s_putc,0);				// set up serial output for printf()
    
      printf("Hall effect sensor\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
      Field = ReadSensor(PIN_FIELD);	// prime the field sensor pump
      FieldBase = Field;
      FieldHigh = 1.1 * Field;
      FieldLow = 0.9 * Field;
      FieldRange = FieldHigh - FieldLow;
    
      printf("Average field: %d\n",(int)(1024.0 * Field));
    
      MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    
    		Field = ReadSensor(PIN_FIELD);
    		FieldHigh = max(FieldHigh,Field);
    		FieldLow = min(FieldLow,Field);
    		FieldRange = FieldHigh - FieldLow;
    
    //		printf("Field: %d\n",(int)(1024.0 * Field));
    
    		switch (sign_float(Field - FieldBase)) {
    			case -1:
    				Blue = (byte)(255.0*(FieldBase - Field)/FieldRange);
    				Red = 0;
    				break;
    			case 1:
    				Red = (byte)(255.0*(Field - FieldBase)/FieldRange);
    				Blue = 0;
    				break;
    			case 0:
    				Red = Blue = 0;
    				break;
    			default:
    				printf("Whoops!\n");
    				delay(1000);
    		}
    		Green = 0;
    
    		analogWrite(PIN_RED, Red);
    		analogWrite(PIN_BLUE,Blue);
    		analogWrite(PIN_GREEN,Green);
    
    		digitalWrite(PIN_HEARTBEAT,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }
    
    
  • Garden Sprayer Nozzle: Cleaned

    So I finally got around to spraying some 10% bleach on an inconspicuous section of the roof (*) to see whether it would have any effect on that black fungus / mildew / crud. It’s too soon to tell, but in the process I discovered that the sprayer nozzle didn’t produce the nice, round pattern it used to. I completed the mission and took the nozzle to the basement. The problem was obvious:

    Sprayer nozzle - with crud
    Sprayer nozzle – with crud

    Soaking it in vinegar didn’t have any effect; whatever made those deposits wasn’t soluble in water or mild acid. A few minutes with an awl and a (manually turned!) Dremel grinding point restored it to good condition:

    Sprayer nozzle - cleaned
    Sprayer nozzle – cleaned

    You’d be more careful cleaning the orifice of a fine spray nozzle, but this is for a hand-pumped garden sprayer: Good Enough.

    As soon as the weather clears, we’ll see if the situation up on the roof has improved. If so, I get to spray the rest of it.

    (*) The whole north slope over the garage, in case you’re in the market…

  • Tea Ball Revivial: Bleaching

    As promised, pix of the tea ball bleaching process (it’s plant pot bleaching time again). Before:

    Tea ball – before bleaching

    And After a few minutes in a 10% bleach solution:

    Tea ball – after bleaching

    The pix don’t do it justice; the thing comes out looking like new. Every half-year, like clockwork!

    Of course, one could argue that tea does even worse things to my interior, but …

  • Arduino Snippets: DL1414 LED Character Display

    OK, I couldn’t help myself… running two more ‘595 shift registers in parallel with the ones for the LED bargraph provides enough bits for a DL1414 four character ASCII display:

    LED Character Display
    LED Character Display

    The serial data input (SER) and serial clock (SCK) lines drive both shift register strings with the same values at the same time. The two strings have different parallel register clocks (RCK), so you twiddle only the one you want; the other string gets the same data, but it never reaches those parallel outputs.

    This being a breadboard lashup, there’s no red filter to boost the contrast; :

    DL1414 LED Character Display - breadboard
    DL1414 LED Character Display – breadboard

    A red filter would really help, as shown on my old Tek ROM readout board.

    The DL1414 datasheet seems very hard to find; that’s a clean version I fetched from datasheetarchive.com.

    They’ve also become insanely expensive these days and you don’t want to use them for a new design, but if you happen to have a NOS tube lying around and hadn’t thought of monetizing your assets, well…

    To simplify debugging, the test code blips both RCK lines after shifting the data out, which means the LED bargraph shows the actual bits driving the DL1414. Slow down the update rate, un-comment the delay(1000) statements, and you can watch the DL1414 pins in “real time”. Cheaper than a logic analyzer…

    The test routine displays HELO, then shows the low word of the running millis() value in hex every 100 ms, for lack of anything smarter.

    You could use a single shift register string with two ‘595 chips driving both the LED bargraph and the DL1414; the bars would flicker briefly when updating the DL1414, but that might not be even slightly objectionable for some geek-chic projects.

    You could put the two shift registers in series, which means you must shift twice as many bits to update the DL1414 display; you’d need only one  RCK line for both sets if you shifted the unchanging bits every time. That might not be such a big deal, even though writing each character requires three complete shift sequences.

    I used a union to overlay a word variable with a bitfield structure:

    union DL1414_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    		unsigned int Addr:2;
    		unsigned int NotWrite:1;
    		unsigned int Ctl3_7:5;			// unused bits
    		unsigned int Data:7;
    		unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    

    Bit-twiddling operations then work on the bitfield elements and shifting uses byte-wide chunks:

    union DL1414_ DL1414;
    
    	DL1414.ShiftBits.Data = Char & 0x7F;
    	DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	DL1414.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    

    I briefly thought about overlaying two bytes instead of a word, but came to my senses. I assume the compiler can optimize those byte-sized shifts out of existence, but it doesn’t really matter.

    The DL1414 address layout puts character 0 on the right, but we want it on the left to match the string subscript; just invert the address bits and move on.

    The complete Arduino source code:

    // LED Character Display
    // Ed Nisley - KE4ANU - November 2012
    // Uses obsolete DL1414 LED displays
    // Display pinout:
    // https://softsolder.com/2009/08/05/arduino-using-ancient-litronix-dl-1414-led-displays/
    // https://softsolder.com/2012/12/07/arduino-snippets-dl1414-led-character-display/
    //----------
    // Pin assignments
    // These are *software* pins for shiftOut(), not the *hardware* SPI functions
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    
    const byte PIN_RCKC  = 12;			// latch clock for LED character shift reg
    
    const byte PIN_RCKB  = 7;			// latch clock for LED bar bits shift reg
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 100;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    #define LED_SIZE		4				// chars per LED
    #define LED_DISPLAYS	1				// number of displays
    #define LED_CHARS		(LED_DISPLAYS * LED_SIZE)
    
    //----------
    // Globals
    
    char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string
    
    union DL1414_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    		unsigned int Addr:2;
    		unsigned int NotWrite:1;
    		unsigned int Ctl3_7:5;			// unused bits
    		unsigned int Data:7;
    		unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Write single char to DL1414
    
    void WriteLEDChar(char Char,char CharID) {
    
    union DL1414_ DL1414;
    
    	DL1414.ShiftBits.Data = Char & 0x7F;
    	DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	DL1414.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	digitalWrite(PIN_RCKC,HIGH);
    	digitalWrite(PIN_RCKC,LOW);
    	digitalWrite(PIN_RCKB,HIGH);
    	digitalWrite(PIN_RCKB,LOW);
    
    //	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 0;				// write the character
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	digitalWrite(PIN_RCKC,HIGH);
    	digitalWrite(PIN_RCKC,LOW);
    	digitalWrite(PIN_RCKB,HIGH);
    	digitalWrite(PIN_RCKB,LOW);
    
    //	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 1;				// disable write
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	digitalWrite(PIN_RCKC,HIGH);
    	digitalWrite(PIN_RCKC,LOW);
    	digitalWrite(PIN_RCKB,HIGH);
    	digitalWrite(PIN_RCKB,LOW);
    
    //	delay(1000);
    
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_MOSI,OUTPUT);
    	digitalWrite(PIN_MOSI,LOW);
    
    	pinMode(PIN_SCK,OUTPUT);
    	digitalWrite(PIN_SCK,LOW);
    
    	pinMode(PIN_RCKC,OUTPUT);
    	digitalWrite(PIN_RCKC,LOW);
    
    	pinMode(PIN_RCKB,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("LED Character Display - DL1414\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
    	for (byte i=0; i < LED_CHARS; ++i)
    		WriteLEDChar(LEDCharBuffer[i],i);
    	delay(1000);
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    		sprintf(LEDCharBuffer,"%04X",(word)MillisNow);
    		for (byte i=0; i < LED_CHARS; ++i)
    			WriteLEDChar(LEDCharBuffer[i],i);
    
    		digitalWrite(PIN_SYNC,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    
    }
    

    Update: A question arrived about power for those displays …

    In your older post about driving ancient DL1414s from Arduino, did you manage to drive these solely from a USB-powered Arduino, or did they need some external power supply help?

    I got a couple of 2416s from A1, Toronto’s last surviving major surplus store. For the life of me, they won’t light. Looking back at the failure, I’m guessing the total current draw is hitting the Uno’s USB “MaxPower” [sic; it’s what lsusb says] of 100 mA.

    Still looking out for the rare DL3422, which now commands utterly pointless pricing. It can do lower case, though …

    As it happens, those displays suck juice like nothing else around!

    I have two datasheets offering either 65 or 130 mA for a DL-1414 with all the lights on; the HP datasheet says their HPDL-2416 displays cook at 170 mA and 232 mA (!) when showing four block cursors. Take your pick, any of those will drain a piddly Pi USB port dry.

    Having been burned often enough, I run Arduinos from the wall wart that also powers whatever gimmick I’m building. If the gimmick requires a relatively high voltage (9-ish V and up), I put a 7 V pre-regulator in front of the Arduino’s SMD regulator to reduce its power dissipation.

    For the vacuum tube lights I’m using now, I jam +5 V into the Nano’s VCC pin, straight into the regulator’s /output/, and just hold my nose. It works well enough for my simple needs, even if I’d have a hard time justifying that sort of thing as “common engineering practice”.

    So, yeah, you’re gonna need a separate supply for those puppies.

  • Arduino Snippets: LED Bargraph Display

    An LED bargraph display comes in handy for displaying a tuning center point, monitoring a sensor above-and-below a setpoint, and suchlike.

    Given the number of output bits, this requires a pair of ‘595 shift registers. There’s no particular need for speed, so the shiftOut() function will suffice. That means the register control bits don’t need the dedicated SPI pins and won’t soak up the precious PWM outputs required by, say, the RGB LED strip drivers.

    LED Bargraph Display
    LED Bargraph Display

    It’s Yet Another Solderless Breadboard Hairball:

    LED Bargraph Display - breadboard
    LED Bargraph Display – breadboard

    The upper LED bargraph is an HPSP-4836 RRYYGGYYRR block, the lower one is an all-green GBG1000. The bottom four LEDs aren’t connected; you could add another ‘595 shift register, but then you’d have four bits left over and you’d be forced to add more LEDs. Four bricks and five ‘595 chips would come out even, if you’re into that.

    The LEDs run at about 4 mA, which would be enough for decoration in a dim room and seems about the maximum the poor little 74HC595 chips can supply. If you need more juice, you need actual LED drivers with dimming and all that sort of stuff.

    You need not use LED bargraphs, of course, and discrete LEDs for the lower six bits make more sense. They’ll be good for mode indicators & suchlike.

    The demo code loads & shifts out alternating bits, then repeatedly scans a single bar upward through the entire array. Note that the bar is just a bit position in the two bytes that get shifted out every time the array updates (which is every 100 ms); the array is not just shifting a single position to move the bar. Verily, the bar moves opposite to the register shift direction to demonstrate that.

    The Arduino source code:

    // LED Bar Light
    // Ed Nisley - KE4ANU - November 2012
    
    //----------
    // Pin assignments
    // These are *software* pins for shiftOut(), not the *hardware* SPI functions
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    const byte PIN_RCK  = 7;			// latch clock
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 100;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    //----------
    // Globals
    
    word LEDBits;
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Send bits to LED bar driver register
    
    void SetBarBits(word Pattern) {
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern & 0x00ff);
    
    	digitalWrite(PIN_RCK,HIGH);
    	digitalWrite(PIN_RCK,LOW);
    
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_MOSI,OUTPUT);
    	digitalWrite(PIN_MOSI,LOW);
    
    	pinMode(PIN_SCK,OUTPUT);
    	digitalWrite(PIN_SCK,LOW);
    
    	pinMode(PIN_RCK,OUTPUT);
    	digitalWrite(PIN_RCK,LOW);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("LED Bar Light\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
    	LEDBits = 0x5555;
    	SetBarBits(LEDBits);
    	delay(1000);
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    		SetBarBits(LEDBits);
    		digitalWrite(PIN_SYNC,LOW);
    
    		LEDBits = LEDBits >> 1;
    		if (!LEDBits) {
    			LEDBits = 0x8000;
    			printf("LEDBits reset\n");
    		}
    
    		MillisThen = MillisNow;
    	}
    
    }