Replacing 7-segment displays and LEDs with Arduino Nano / I2C LCD display?

This is a scaled back design attempt from trying to develop a modern replacement for some 1980s test equipment boards.

The image has the circuit I'm starting with with ten 7-segment displays, nine LEDs, and the associated drivers
(a 4514 is used for the cathode end, a 4547 used for the anode end).

The dotted box is my end goal with an Arduino nano and a 4x20 LCD with I2C interface.

What I'm after at this point is wanting to know how difficult it would be to develop or modify existing LCD code.

Breakdown of the important parts of the schematic:

There are 9 inputs to the display section, driven from an 8279 display controller.

Lines A, B, C, and D are the BCD data that are fed into the 7-segment LED driver which feeds the display anodes.

Segment A also provides anode drive to the 5 red LEDs

Segment G also provides anode drive to the 4 green LEDs

The blanking input line into the 7-segment driver is also used.

A0 to A3 is used as the cathode select input (using a 4514)

My thoughts as to the LCD setup:

Test Number is from outputs 09, 08
(planned output: LCD line 1, left: Test: XX)

Data is from outputs 07 through 00 inclusive
(planned output: LCD line 2, left: XXXXXXXX)

Test A is from output 10
(planned output: LCD line 1, right: LD A: ____)

Test B is from output 11
(planned output: LCD line 2, right: LD B: ____)

Test C is from output 12
(planned output: LCD line 3, right: LD C: ____)

Test D is from output 13
(planned output: LCD line 4, right: LD D: ____)

Installing indicator is from output 14
(planned output: LCD line 4, left: Install [normally blank - this line only intended to blink])

Test status is from output 15
(planned output: LCD line 3, left: Stat: ____)

The five ____ lines would be blank when inactive, PASS when green LED is called, and NOGO when red LED is called

Yes it’s completely doable but would be hard for beginners. You need to decode dynamic indication. Not a rocket science bur require at least an intermediate level.
Signals on lines Ax lines get updated with KHz frequency. You shouldn’t bother your I2C LCD so often.
So you need to divide your task on two parts

  • Decoding data to get full number and LED state
  • Display results on LCD

First part is more complex but you can debug it without using an LCD but just printing results using Serial.print.

In principle, you are reading 9 inputs, interpreting them, and displaying the output on an LCD display.
It should be relatively simple. You have to have an internal model of the old display which is continually refreshed from the inputs. You have to scan this model regularly, interpret it, and write it to your LCD.
I haven't checked the LCD representation of the LEDs but important is that it is intuitive for the end user of the device.

I can't read the markings on the display LCD's IC, but if it is I2C you have to respect the Arduino I2C ports of A4 and A5.

Whar does the old code look like?

alesam:
...Signals on lines Ax lines get updated with KHz frequency. You shouldn't bother your I2C LCD so often.

(facepalm) I didn't even think about that.

Railroader:
Whar does the old code look like?

...legacy MC6801 code using a customized MC6801 that likely has on-board ROM.

I think I just gonna move this into the "Never Mind" pile after all.

Get that code and hand translate it into C for Your controller.
Or, make Yourself knowing the signals into the 6801 and write new code for Your controller.

Keep in mind that the reason the Ax lines will be updating in the kHz range is because they need to update all the 7-segs fast enough for persistence of vision to give the illusion they're all on steady.

Depending on the data being displayed, you could probably take periodic snapshots of the Ax, Kx and BLANK lines and update the LCD at a much more leisurely pace.

Just for argument's sake, if you updated the LCD at 250mS intervals (4 times a second), would that be too slow?

These are human-readable digits. Things operate slowly in human-land.

Actually, looking at the unit I have, the majority of the time, the display is static. When the readout changes, it does so at 0.5 second intervals before displaying the status and then going back to a static display.

If I understand the 8279 datasheet, it appears the timing is something like 640uS per digit, giving a scan frequency of 1.5kHz. I don't know if the Arduino would be able to capture the data that fast...

You look at the question the wrong way. Forget about that strobing frequency. Assume You have one driver handling the segments and one driver powering the 7 segment units at some 500 Hz update frequency would do fine. You only need to awoid experiencing a flickering, fast blinking, responce from the display.
Data changes will show up nicely updated every 0.5 second.
I would not go for the 8279. Way to expencive, lots of overkill. There are more suitable circuits made for display driving.

I was curious about this so I wrote a quick program for my Mega2560 to do what I think you need:

  • assumes K[0…3] are connected to PB[0…3] and A[0…3] are connected to PB[4…7] (all same port for quick direct port accesses)

  • K0 is connected to PB0 and serves as the interrupt input

  • every 250mS enable PCINT0

  • in the ISR:
    – wait until the lower 4 bits (K[3…0]) show 0; at that point, save the contents of PORTB in array[0] and set a sync flag
    – on each subsequent interrupt, store PORTB in array[1], array[2]…array[15]; on the 16th, disable the interrupt and set a flag for the mainline

  • when the mainline sees this flag, it prints the BCD contents of array[0…15]

  • repeat 250mS later

I tested it with pins driven high and low for A[3…0] and verified the correct values being printed.

I used timer 4 to generate a 5kHz 50% duty test signal to mimic a very rapidly switched K0 input. Assuming K[3…0] count from 0 to 16, each edge of K0 represents a tick of that “clock”; interrupting on each edge, sampling PORTB and then picking out A[3…0] after the fact.

Characterized the time spent in the ISR with time-consuming digitalWrite()s to a test pin. Scoped the output. I was actually quite impressed with the speed.

5kHz K0 rate:

ISR execution time <7uS

Even if the K0 clock rate was faster than this it looks like you’d have a decent amount of headroom.

// Tgt: Mega2560
//
//    0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  ...
//    0  0  1  1  0  0  1  1  0  0  1  1  0  0  1  1  0  ...
//    0  0  0  0  1  1  1  1  0  0  0  0  1  1  1  1  0  ...
//    0  0  0  0  0  0  0  0  1  1  1  1  1  1  1  1  0  ...
//       __    __    __    __    __    __    __    __    __    __   
//K0  __|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__|  |__...
//          _____       _____       _____       _____       _____   
//K1  _____|     |_____|     |_____|     |_____|     |_____|     |__...
//                ___________             ___________             __...
//K2  ___________|           |___________|           |___________|  
//                            _______________________               
//K3  _______________________|                       |______________...


//pin 53    PB0     K0
//pin 52    PB1     K1
//pin 51    PB2     K2
//pin 50    PB3     K3
//pin 10    PB4     A0
//pin 11    PB5     A1
//pin 12    PB6     A2
//pin 13    PB7     A3

const uint8_t pinK0 = 53;           //trigger input on PB0
const uint8_t pinTestSignal = 7;
const uint8_t pinTiming = 2;        //ref only; direct port access used at PE4

volatile uint8_t
    isrIndex;
volatile bool
    isrbSync = false,
    isrbSample = false;
volatile uint8_t
    grAnodes[16];
uint32_t
    timeNow,
    timeSample=0;
        
void setup( void )
{
    Serial.begin(115200);
    
    //set all port B pins to input
    //using direct port access
    DDRB = 0b00000000;      //all PB pins inputs for Ax:Kx
    PORTB = 0b11110000;     //pullups on A[3..0]
    //
    //DDRE = _BV(DDE4);     //PE4 is pin 2 and is output
    pinMode( 2, OUTPUT );   //crude, slow timing test output

    //set up Timer4 to generate 5kHz test signal
    SetupTestSignal();
    
}//setup

void loop( void )
{
    //every 250mS sample the A/K lines
    timeNow = millis();
    if( (timeNow - timeSample) >= 250ul )
    {
        timeSample = timeNow;
        Sample();
               
    }//if
    
}//loop

void SetupTestSignal( void )
{
    pinMode( pinTestSignal, OUTPUT );
    TCCR4A = _BV(COM4B1) | _BV(WGM41) | _BV(WGM40);     //WGM 15, compare 4B connected
    TCCR4B = _BV(WGM43) | _BV(WGM42) | _BV(CS40);         ///1 prescaler
    OCR4A = 0x0C80;  //should give 5kHz
    OCR4B = 0x0640;  //50% duty cycle
    
}//SetupTestSignal

void Sample( void )
{
    noInterrupts();
    isrbSync = false;       //we need to find K[3..0] == 0 to say we're sync'd
    isrbSample = false;     //clear complete flag
    
    PCICR |= _BV(PCIE0);                //enable PCINT0 interrupt
    PCMSK0 |= _BV(PCINT0);              //enable PCINT0
    uint8_t wait = PINB & 0x01;         //get the state of K0 now, then wait for it to change
    while( (PINB & 0x01) == wait );
    PCIFR |= _BV(PCIF0);                //now clear the ISR flag
    interrupts();                       //and enable interrupts

    //no check for timeouts for this thought-exercise! If no K0 edges, we die here
    while( !isrbSample );

    //print captured anode patterns
    for( uint8_t i=0; i<16; i++ )
    {
        Serial.write( '0' + ((grAnodes[i] >> 4) & 0x0f) );
        Serial.write( ' ' );        
        
    }//for

    Serial.write( '\n' );
    
}//Sample

ISR( PCINT0_vect )
{    
    static uint8_t
        count;

    //test only (in absence of external Ax/Kx source)    
    count++;
    
    //set test pin 2 for timing
    digitalWrite( 2, HIGH );

    //grab port value
    uint8_t port = PINB;

    //in sync?
    if( !isrbSync )
    {
        //not yet; we sync when K[3..0] == 0
        //if( (port & 0x0f) == 0 )      //uncomment for realz; use count for testing
        if( (count & 0x0f) == 0 )       //test only
        {
            //on sync, zero the index and save the 0th port value
            isrIndex = 0;
            grAnodes[isrIndex++] = port;
            //then indicate sync'd
            isrbSync = true;
            
        }//if

    }//if
    else
    {
        //while sync'd save port value and bump index
        grAnodes[isrIndex++] = port;
        
        //when we have done all 16...
        if( isrIndex == 16 )
        {
            //halt PCINT0 interrupts
            PCICR &= ~_BV(PCIE0);
            
            //and indicate successful sample to mainline
            isrbSample = true;
            
        }//if
                        
    }//else

    //clear pin 2 for timing check
    digitalWrite( 2, LOW );
    
}//isrDigitClock

I guess I’d do it like this:
Represent the entire display by a 16 byte array.
Put the 4 cathodes on pins belonging to the same port on the ATmega328p.
Read the “cathode” port in the loop and when it has been stable for a few iterations, read 4 anode pins and put the value into the array, indexed by the value (0 to 15) read from the cathodes.

Every 250ms or so, read the array, interpret it, write the digits and led values to the lcd in the chosen format, then clean out the array. The interpretation of the 10 digits is direct from the truth table of the mc14547.

I guess the blanking pin is only to control the display brightness and can be ignored.

Edit
Crossed with @blackfin

6v6gt:
I guess I’d do it like this:
Represent the entire display by a 16 byte array.
Put the 4 cathodes on pins belonging to the same port on the ATmega328p.
Read the “cathode” port in the loop and when it has been stable for a few iterations, read 4 anode pins and put the value into the array, indexed by the value (0 to 15) read from the cathodes.

Every 250ms or so, read the array, interpret it, write the digits and led values to the lcd in the chosen format, then clean out the array. The interpretation of the 10 digits is direct from the truth table of the mc14547.

I guess the blanking pin is only to control the display brightness and can be ignored.

Edit
Crossed with @blackfin

Hehe. Given the timing, I also think this could be done with simple polling rather than the interrupts though the PCINT0 is pretty handy.

I did think about interrupts, but the problem could be you get multiple interrupts until the cathode pins are stable. I don’t know the controller 8279? which the OP mentioned but it could be predictable enough and maybe even guarantee that all 4 pins are set simultaneously. I guess it must handle the “anode” pins more or less simultaneously otherwise there would be visible ghosting. Alternatively, maybe the blanking pin is used as a sort of clock and the display is “unblanked” when all is stable and it may be sufficient to watch for the right edge to trigger the capture of the pin values.

Anyway, it is certainly an interesting project the OP has there and I also enjoy watching projects like that. It is clear you made a good effort to back up you proposed solution with an experiment which was also interesting to read.

Hm? I do have an I2C LCD on the way, and did source a NOS 8279 (I figure it would be better than trying to monkey with the firmware, as I said, it was written to work with a customized 6801 processor, and I'd just as soon avoid monkeying with the main logic board I will be using, beyond doing a firmware swap and jumper change).

Anyway, I'm actually a bit relieved to hear that this is in the realm of possibility, and it is a sufficiently interesting project. I'll get a Nano on order as well.

Good thing too, as when I was looking at just getting the needed parts for the existing 7-segment circuitry, I found two ICs are obsolete and a third is now scarce.

I don't see where an additional Intel 8279 display controller fits into your scheme. Your schematic with the Nano would have worked (subject to changing some pin assignments) and that simply interfaced with the output of the existing display controller (A0..A4, K0..K4 and Blank). Or is the existing 8279 also on the board which you wish to replace ?

I fully agree that you don't attempt to touch the existing software, even if you had all the source code.

Incidentally, some LCD models including cheap I2C capable LCD1602 models, which use the Hitachi HD44780 LCD controller, allow you to define up to 8 custom characters. Maybe this helps to design a representation of the LEDs (say a tick and a cross to indicate success or failure respectively). Maybe the one you have ordered has such a feature.

Edit

If you haven't already got a logic analyser, then I recommend you get one for this project. It would be very interesting to see the timings at A0..A4, K0..K4 and Blank when there is something stable on the display. See here for some ideas: https://forum.arduino.cc/index.php?topic=712406

There's only the one 8279 - sorry for not being clear. As I go along, I'm working on layout etc. of the replacement boards I need to convert a spare load management unit into a testing unit.

The front board would have the LCD, the nano, and a keypad. The rest of the circuitry I'm working on will reside on the second board (unless... I lay it all out to use both sides of the same board).

It would be nice to have a logic analyzer - at work I once had the use of a massive boat-anchor of an analyzer: a HP1610. When one customer wrapped up their relationship with us, the board for which we used the analyzer was long obsolete. I'd hoped they would let us do as we see fit with it, but no. They wanted the HP1610 brought back up to their branch office. Drat!

I'll check out the linked thread later, but right now, I needs me a bit of sleep.

6V6: Analyzer ordered. Thank you.

I have since analyzed the firmware and find that three addresses are used in communication between the logic board and the 8279 on the display board in the original unit (it has been shifted to the I/O processor board in my redesign).

One address (specifically, bit 0) is used as a flag tied to the 8279's EIRQ pin to alert the logic there are key presses in the buffer.

Another address is merely used as a flag to force the A00 pin to read mode and fetch incoming key codes.

The last of the three addresses is the actual data port, and depending on the state of the A00 pin, is used to feed commands or display data to the 8279.

Seems something like this would be easy to implement in Arduino? I have a Nano and a serial LCD on hand.

Now that I can see how the 8279 is communicated with, I would like to see if and how I can get rid of the 8279 and stick the Nano in its place, and to use it to get rid of ten 7-segment displays, six LEDs, and their support ICs.

So, the nano would need these pins configured:
EIRQ - output
A00 - input
D0:D7 - data bus
8 pins for 4x4 keyboard matrix
I2C bus for LCD

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.