Sunday, 25 August 2013

A new LED display for a Fluke 8050A multimeter

Filed under: Uncategorized — Michael @ 22:38

Keeping old test equipment alive is not just fun, it is how I can support my electronics hobby with good gear on the cheap. Most of the equipment on my bench is over 25 years old. One piece of equipment that sees a lot of use is a Fluke 8050A multimeter.

The Fluke 8050A multimeter was made in the early 1980’s and was a popular instrument so a lot of them are still around. The meter uses an LCD display and it is this display that has proved to be the weak point. Many otherwise functional meters are unuseable because the LCD has gone black or is unresponsive. A few people have rescued their meters by replacing the LCD with an LED display. The results are good, looking even better than the original LCD. My 8050A is still working but the LCD is showing its age and has very low contrast. I thought an LED replacement was in order so here is my take on an LED upgrade for the 8050A.

8050A with LED display

The approaches I’ve seen involve wiring the individual LED segments directly to the LCD driver chips. (For example here and here.) This has the advantage of few additional active components. The main disadvantage is that there is a lot of wiring to the 8050A, soldering directly to the IC pins with a current-limiting resistor for every segment. Also, since the LCD drivers can only sink a couple of milliamps this limits the display brightness (but since the display is not multiplexed, in the upgrades I’ve seen this doesn’t seem to be a problem).

My approach differs from the others by using a microcontroller (ATmega328P, I used an Arduino UNO for development) and LED driver (MAX7219) to decode the display drive information from the 8050A (just 11 lines) and multiplex the LEDs. This description focuses mainly on the software, the Arduino sketch, and not so much on the hardware. In my prototype, I used what I had on-hand. It’s all through-hole parts on perfboard. I am sure that a PC board with surface mount components based on this circuit or a similar approach could result in an almost drop-in replacement for the LCD. If there is any interest and/or you would like to help with this, let me know.

The Project Code

You can download the Arduino project here. There are two sketches, LED_8050A, which is described below, and an 8050A emulator, Strobe_8050A, that generates strobe and data signals for testing.

Inside the Fluke 8050A

The Fluke 8050A drives the LCD display using multiplexed (strobed) data from a 3870 microcontroller. There are five strobe lines and six data lines. These lines are all available at connector J1 on the display board. Also, power for the LCD is supplied (system ground and -5V) and there’s a voltage reference for the low battery indicator (not used in my meter).

8050A display board J1

The following table lists the applicable connector pin numbers. (In the image, pin 1 is in the upper-right corner in the box.)

J1 Pin Strobe/Data Line
2 GND
3 -5V
6 batt Vref
10 ST4
11 ST3
12 ST2
13 ST1
14 Z
15 Y
16 X
17 W
18 ST0
20 HV
21 DP

The lines ST0-4 are the five strobe lines. The W, X, Y and Z lines carry BCD number data, W is the most-significant-bit, and the DP and HV lines are for the decimal points and high voltage indicator.

The LCD digit segments and indicators are driven by seven LCD driver ICs, type 4054 and 4056, that decode and latch the strobed data. The 4054/4055/4056 datasheet indicates that the data is latched on the strobe falling edge. The strobe timing, as measured in my 8050A, is as follows.

8050A display strobe timing

Pin Change Interrupts

The five strobes are handled by pin change interrupts on the Arduino. A little background. (Refer to the ATmega328 datasheet for more detailed information.) To specify the interrupt pin (or pins) to listen to, you have to set which of three banks of pins and the pin(s) in that bank. A separate ISR (interrupt service routine, i.e., the code you want to run when an interrupt occurs) can be set for each bank. It breaks down like this.

ATmega328 Bank Name ATMega328 Pin Names Arduino UNO Pins
PCIE0 PCINT0-7 8-13 (14,15 not available)
PCIE1 PCINT8-14 (15 not used) A0-5 (6,7 not available)
PCIE2 PCINT16-23 0-7

You specify the banks you want to listen on for interrupts using the ATmega328 register PCICR. The bank name in the table is actually the name of a bit in that register. So, in your code you would enable pin change interrupts for Arduino pins A0-A5 by setting the PCIE1 bit in the PCICR register. For example.

PCICR = _BV( PCIE1 );

(The macro _BV(bit) converts a bit number to a byte value with the corresponding bit set. It is implemented as (1<<(bit)). The Arduino language also has bit manipulation functions such as
bitSet(), bit(), etc.)

Once you have enabled a bank of pins for pin change interrupts, you need to tell the ATmega which pins in the bank to actually listen to. (You can listen to one or to multiple pins.) There are three registers for this, one for each bank, named PCMSK0, PCMSK1 and PCMSK2. In my code, I’m using bank 1 (PCIE1, Arduino pins A0-A5) so I need to set the appropriate bits in PCMSK1. I’m using the pin change interrupts to trigger a read of the data lines for each strobe. So, I need to listen to each strobe line sequentially. I initialize the PCMSK1 register with the bit corresponding to the pin assigned to ST0 (strobe 0). In the code, ST0 is on Arduino pin A0 which is the bit position PCINT8.

PCMSK1 = _BV( PCINT8 );

The Interrupt Service Routine (ISR)

The operation is simple. The ISR is triggered by a change on the specified pin for the current strobe number (held in the variable st). An interrupt is generated for any change on the pin. We’re interested in the falling edge, so a quick test. The scancode lines are read into a two-dimensional array, strobes by scancodes. In order to sample the data for each strobe line, the ISR switches to the next strobe (or wraps around back to ST0) after reading the data. Once all five strobes have been read, the updateDisplay flag is set. This causes the displayUpdate() function, which is called in the loop(), to format the display digits and write them to the LED driver. The displayUpdate() function then clears the updateDisplay flag. There’s plenty of time to update the display between the five strobes so, the formatting is pretty straight ahead.

Here is the ISR code.

// pin change interrupt service routine
// - reads the scancode values from the 8050A
// - sets the displayUpdate flag when all strobes are read
ISR( STROBE_INTERRUPT_VECTOR )
{
  if ( digitalRead( StrobePins[ st ] ) == LOW )
  {
    // that was a falling transition
    // (In the 4054/55/56 LCD drivers datasheet, it shows
    // the data being latched on the strobe falling edge.)
    // now, read the scan code values
    for ( uint8_t i = SC0; i < NUM_SCANCODES; i++ )
    {
      scanCodes[ st ][ i ] = digitalRead( ScanCodePins[ i ] );
    }
    // next time, next strobe
    st++;
    if ( st >= NUM_STROBES )
    {
      // scancodes for all strobes are read
      st = ST0; // reset strobe counter
      // now is the time to update the display
      displayUpdate = true;
    }
    // enable the appropriate strobe pin
    PCMSK0 = StrobePin2PCINT[ st ];
  }
}

The pins for the scancodes are in the array ScanCodePins[] and the pins (the PCINTn values) corresponding to the 8050A strobe lines are held in the array Strobe2PCINT[]. This allows for flexible pin assignments, but the strobe pins must all be in the same PCIEn/PCMSKn bank.

Before anyone says "hey, you can use port reads for the scancodes and store the results in a single byte." Well, I wrote a version that did that and it really didn’t save any time or space. And since there is plenty of both (the ISR runs in less than 90 microseconds with an 8MHz clock and the whole sketch is a little more than 1800 bytes), I figured that making the code a little easier to understand and allowing for more flexible pin assignments was more important than a few microseconds or bytes.

Driving the MAX7219 LED Driver

A quick look at the MAX7219 LED driver and the code to talk to it. (Refer to the datasheet for more detailed information.) The device is capable of scanning up to eight 7-segment LEDs plus decimal points. We’ll use six. The segment decode can be set for Code B, regular 7-segment numbers from BCD (binary coded decimal), or no decode for individual segment control. I chose to call digit 0 the most significant. The sketch uses digits 0 and 5 in no-decode mode and digits 1-4 in Code B (BCD) mode. The LED brightness is determined by setting the internal current source with a resistor and can also be changed with PWM. All of these things are programmed by writing to various registers.

When powered up, the MAX7219 initializes to a minimalist state and enters shutdown mode. It only scans one digit, it doesn’t do any decode and the PWM brightness is at minimum. So, we need to set these things for our use and bring it out of shutdown mode in the Arduino setup() function.

void setup()
{
  . . . 
  // initialize MAX7219 registers
  writeRegister( REGISTER_SCAN_LIMIT, REGISTER_DATA_SCAN_LIMIT );
  writeRegister( REGISTER_DECODE_MODE, REGISTER_DATA_DECODE_MODE );
  writeRegister( REGISTER_INTENSITY, REGISTER_DATA_INTENSITY );
  // load initial digit data
  writeDigits();
  // turn on the display
  writeRegister( REGISTER_SHUTDOWN, 0x01 );
  . . . 
}

The 7219 registers are sixteen bits with the high-order byte the register address and the low-order the register data. Writing the registers is straightforward using a three-wire protocol. Here is the writeRegister() function.

void writeRegister( uint8_t regAddr, uint8_t data ) {
  // combine the address and data to a 16-bit value
  uint16_t reg = regAddr;
  reg <<= 8;
  reg += data;
  uint16_t mask = 0x8000;
  // (from the datasheet)
  // For the MAX7219, serial data at DIN, sent in 16-bit packets,
  // is shifted into the internal 16-bit shift register with each
  // rising edge of CLK regardless of the state of LOAD. The data
  // is then latched...on the rising edge of LOAD.
  digitalWrite( PIN_LOAD, LOW );
  while ( mask )
  {
    digitalWrite( PIN_CLK, LOW );
    digitalWrite( PIN_DIN, reg & mask ? HIGH : LOW );
      // The test to HIGH/LOW is needed because reg is 16-bit
      // and function expects (truncates to) 8-bit. 
    digitalWrite( PIN_CLK, HIGH );
    mask >>= 1;
  }
  digitalWrite( PIN_LOAD, HIGH );
}

Testing

To test the circuit, I also wrote an emulator to send 8050A signals to the display breadboard. It is a very simple Arduino sketch that loops the strobes and takes user input to send the appropriate scancodes.

Hardware notes

The display in the 8050A is 4-1/2 digits with indicators for dB (decibel mode), HV (high voltage warning), REL (relative mode) and BT (low battery indicator for battery equipped meters, mine is not). I used six green 7-segment LEDs. Digit 0, the MSD, is the minus sign and numeral 1, digits 1-4 display 0-9 and digit 5 uses the a, g and d segments for db, HV and REL, respectively. (You could use the a segment of digit 0 for the BT indicator.) This mimics the original 8050A display. The LEDs are wired on a small bit of perfboard and the two ICs, with a 14-pin header and bypass capacitors on another bit of board that is affixed behind the LED board. This two-board package fills the space that was occupied by the original LCD and its plastic housing. I used a bit of green plastic fitted to the original display frame piece. Except for the 14-conductor ribbon cable soldered to the J1 vias, the 8050A is not modified in any way.

8 Comments »

  1. Sounds cool! What’s the next project?

    Comment by Thomas — Wednesday, 11 September 2013 @ 12:58

  2. Hi, great work. Where do you obtain +5V for the AtMega?

    Comment by Joakim — Wednesday, 11 December 2013 @ 7:34

  3. Hi Joakim,
    thanks!
    The Fluke supplies the display board with the meter’s ground and -5V (relative to the meter’s ground) to power the LCD display drivers. I just used that.
    (That is, the -5V from the Fluke is my circuit’s “ground” and the meter ground is my +5V. I hope that helps.)
    – Michael

    Comment by Michael — Wednesday, 11 December 2013 @ 20:53

  4. Thank you Michael. The possibility to switch -5V and ground seems like a good approach. However, I am just wondering whether this could lead to some problems when programming the Atmega over FTDI/USB since the GND will be different on the computer and the Fluke. I guess it will be ok if I remember to unplug the Fluke before programming?

    Nevertheless, I am attempting to copy your design on two of my Flukes. I will report here when I am done 🙂

    Comment by Joakim — Wednesday, 11 December 2013 @ 22:53

  5. Hi Joakim,
    There really is no problem. The earth ground (third wire on the plug) on the Fluke is not connected to the common input terminal (the Fluke’s internal ground). So, you can think of the the GND and -5 levels in the Fluke as simply a 5V potential. And anyway, I did all of the program loading stuff with my display board disconnected.
    Unfortunately, I didn’t take any pictures of my completed module before I installed it. But here is a pic of the logic board layout. The 14-pin header is for connection to J1 on the Fluke. And the 6-pin header is the ICSP header for programming.
    click for image
    Also, I should mention that I reprogrammed the fuse in the ATMega to run on its internal 8MHz oscillator.
    Please let us know how it goes!

    Comment by Michael — Thursday, 12 December 2013 @ 0:15

  6. Hi, Michael.

    I’ve decided to replace one of my 8050A’s LCD with your fantastic design, I like more the LED displays than LCDs, LCDs are a Little bit difficult to read in low illuminated rooms.

    I don’t have an Arduino Uno, but a Nano v3.0 powered with an Atmega328. I thought it was a valid solution for testing the circuit.

    It was my first experience with MAX7219, so I decided to run a demo sketch of the LedControl library to test if all the things are running OK, as it was. All the digits are working without problems and the communication Nano-MAX was succesful.

    I am experiencing this issue when I connect the 8050 to the Arduino: Display does updates so slow (several seconds between updates) and at not the same rate all the times that it does. Sometimes it updates when I change the range in the meter, or simply if disconnect the resistor who I am measuring and connecting again. Other times it does no update nothing in several seconds.

    Is it possible that the Nano is not compatible with the code?

    Will the code work with an Arduino Mega2560, changing just pin banks?

    Thanks in advance.

    Comment by Alejandro — Friday, 13 June 2014 @ 0:20

  7. I’ not sure if I understand how the hardware is implemented. I’d like to use the Arduino ProMini, so are these pins correct?
    ST0 – ST4 == pins A0 – A4 on Pro Mini
    W -Z == pins 2 – 5 on Pro Mini
    Dp, Hv == pins 6, 7 ”
    Din, Clk, CS of Max 7219 go to pins 10,9,8 of Pro Mini

    Is this correct?

    Comment by Roy Seberg — Thursday, 8 June 2017 @ 17:52

  8. Hello Roy
    Thank you for your post. It’s great to see that this project is still of interest.
    If the ProMini uses the same pin assignments as the Uno, then you are correct. In the sketch, I assigned the pins as so (which matches with you).

    // MAX7219 communication pins
    #define PIN_LOAD 8
    #define PIN_CLK 9
    #define PIN_DIN 10

    // 8050A strobe lines
    #define PIN_ST0 A0
    #define PIN_ST1 A1
    #define PIN_ST2 A2
    #define PIN_ST3 A3
    #define PIN_ST4 A4

    // 8050A scancode lines
    #define PIN_Z 2
    #define PIN_Y 3
    #define PIN_X 4
    #define PIN_W 5
    #define PIN_HV 6
    #define PIN_DP 7

    As a check, I tried compiling the sketch for the ProMini board and it succeeded without any errors or warnings. So, you should be OK.

    Please post with your progress or any further questions.
    – Michael

    Comment by Michael — Friday, 9 June 2017 @ 9:34

RSS feed for comments on this post. | TrackBack URI

Leave a comment

XHTML ( You can use these tags): <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> .