Close

The MAX6920 Shift Register

A project log for VFDuino

An arduino VFD clock

brad-arnettBrad Arnett 01/30/2017 at 04:183 Comments

I guess I did it again. Between work, moving into a new apartment, and the holidays, I let this go again. But it's update time.

Let's talk about the MAX6920. The only one Mouser has listed is the MAX6920AWP+, which is probably the one I used, but I bought these some years ago, so I'm not entirely sure. Current price is 4.33 USD, so they're quite pricey compared to the other components. The fact that they're SOIC-20 (eep!) means that you're probably going to want to get a few of them, because they're tricky little guys who solder down. I'm not great at soldering, but I was able to get the process nailed down after frying one of them.

Datasheet is at https://datasheets.maximintegrated.com/en/ds/MAX6920.pdf.

Pins:

1. VBB. VFD Tube Supply Voltage. 8-76V.

2. DOUT. Serial-Clock output. This is used for multiplexing another shift register and won't be used for our project.

3-8, 13-18. OUT0 to OUT 11. VFD anode and grid drivers. Push-pull between VBB and GND.

9. BLANK. Forces output pins low when voltage goes high.

10. GND. Ground.

11. CLK. Serial Clock Input.

12. LOAD. Load input. This loads values into your shift register

19. DIN. Serial-Data input.

20. VCC. Logic supply voltage.

That's a lot of pins. To make matters worse, the datasheet describes the function of the pins like an electrical engineer would expect them to be, not a scrappy developer out of his element. VCC is just logic voltage. Nothing exciting there. Give it a 3.3V or 5V from the arduino and let's move on. GND is ground. That's easy enough too. VBB is tube supply. That's what we want getting passed over to the grid and filament pins on the VFD, and the OUT pins is how we get them there. So we'll wire the OUT pins to the filament/grid pins and wire the output of the NCP1403 to VBB. BLANK has behavior for high and low. There's no expected behavior for it being left floating, and we want output right now, so lets ground that out, and consider uses for it later. We're not going to use DOUT, and it looks like we can leave it floating, so that's fine. Now DIN, LOAD, and CLK. Uh, well, they'll need to be wired to digital arduino pins so we can write to them. Hmm...

That's okay though, lets look at the following process carefully (datasheet, page 7):

The MAX6920 is written using the following sequence:

1) Take CLK low.

2) Clock 12 bits of data in order D11 first to D0 last into DIN, observing the data setup and hold times.

3) Load the 12 output latches with a falling edge on LOAD. LOAD may be high or low during a transmission. If LOAD is high, then the data shifted into the shift register at DIN appears at the OUT0 to OUT11 outputs. CLK and DIN may be used to transmit data to other peripherals. Activity on CLK always shifts data into the MAX6920’s shift register. However, the MAX6920 only updates its output latch on the rising edge of LOAD, and the last 12 bits of data are loaded. Therefore, multiple devices can share CLK and DIN as long as they have unique LOAD controls.

This is further augmented by figure 3 on page 6. So let's try to turn this, between the two, into something more arduino friendly.

1. Set CLK low

2. Set DIN high or low as appropriate for D11.

3. Set CLK high.

4. Repeat Steps 1-3 until having worked from D11 to D0.

5. Set CLK low

6. Set LOAD high, then low to "load" the logic into the registers.

Well, that helps, but it's still cumbersome. Let's actually turn it into code.

Our constants:

// change these if your arduino pins are wired 
#define loadPin 8
#define clkPin 7
#define dinPin 12
// these are shift register pins I've selected. Change them if you're using different ones.
#define grid1 7
#define grid2 8
#define grid3 9
#define grid4 10
#define grid5 11

In setup(), we'll need to define outputs:

  pinMode(dinPin, OUTPUT);
pinMode(clkPin, OUTPUT);
pinMode(loadPin, OUTPUT);

Now lets make this process simple through a function:

void writeToShift(int segment[], int grid)
{
for (int i = 11; i >=0; i--)
{
bool writeNum = false;
for (int j = 0; j <= 6; j++)
{
if (i == segment[j])
{
writeNum = true;
break;
}
}
if (writeNum || grid == i)
{
digitalWrite(dinPin, HIGH);
}
else
{
digitalWrite(dinPin, LOW);
}
digitalWrite(clkPin, HIGH);
digitalWrite(clkPin, LOW);
}
digitalWrite(loadPin, HIGH);
digitalWrite(loadPin, LOW);
}

Now I wire up segments from DOUT0 to DOUT7, and if I create an int array containing [0,1,2,3,4,5,6,7] and pass it with a grid number to the function, it should give me a "8" on that digit. Cool. That's still kind of bulky, but at least it's getting better. Lets see if we can make that easier still with a function to help us build digits:

void makeDigit(int digit, int *myArray)
{
switch(digit)
{
case 0:
myArray[0] = 0;
myArray[1] = 1;
myArray[2] = 2;
myArray[3] = 4;
myArray[4] = 5;
myArray[5] = 6;
myArray[6] = -1;
break;
case 1:
myArray[0] = 2;
myArray[1] = 5;
myArray[2] = -1;
myArray[3] = -1;
myArray[4] = -1;
myArray[5] = -1;
myArray[6] = -1;
break;
case 2:
myArray[0] = 0;
myArray[1] = 2;
myArray[2] = 3;
myArray[3] = 4;
myArray[4] = 6;
myArray[5] = -1;
myArray[6] = -1;
break;
case 3:
myArray[0] = 0;
myArray[1] = 2;
myArray[2] = 3;
myArray[3] = 5;
myArray[4] = 6;
myArray[5] = -1;
myArray[6] = -1;
break;
case 4:
myArray[0] = 1;
myArray[1] = 3;
myArray[2] = 2;
myArray[3] = 5;
myArray[4] = -1;
myArray[5] = -1;
myArray[6] = -1;
break;
case 5:
myArray[0] = 0;
myArray[1] = 1;
myArray[2] = 3;
myArray[3] = 5;
myArray[4] = 6;
myArray[5] = -1;
myArray[6] = -1;
break;
case 6:
myArray[0] = 0;
myArray[1] = 1;
myArray[2] = 3;
myArray[3] = 4;
myArray[4] = 5;
myArray[5] = 6;
myArray[6] = -1;
break;
case 7:
myArray[0] = 0;
myArray[1] = 2;
myArray[2] = 5;
myArray[3] = -1;
myArray[4] = -1;
myArray[5] = -1;
myArray[6] = -1;
break;
case 8:
myArray[0] = 0;
myArray[1] = 1;
myArray[2] = 2;
myArray[3] = 3;
myArray[4] = 4;
myArray[5] = 5;
myArray[6] = 6;
break;
case 9:
myArray[0] = 0;
myArray[1] = 1;
myArray[2] = 2;
myArray[3] = 3;
myArray[4] = 5;
myArray[5] = -1;
myArray[6] = -1;
break;
default:
myArray[0] = -1;
myArray[1] = -1;
myArray[2] = -1;
myArray[3] = -1;
myArray[4] = -1;
myArray[5] = -1;
myArray[6] = -1;
break;
}
return;
}

So this might need a little explanation. First of all, I'm passing the array by reference, since you can't directly return arrays in C. The value -1 means that you've hit the end of useful data in the array, so you can just blank out the rest of the segnemts. Otherwise, each member of the array corresponds to one of the DOUT pins that we would like to light up. You'll note that "1" has only two segments that light up, the top right and bottom right, while "8" corresponds to every segment in the digit, since that's what it takes to light it up. Clear as mud, right? I might try to throw a diagram up better illustrating which segments correspond with which DOUT in my wiring, but by wiring them up and changing my numbers for each case to the ones that represent that segment, you should be able to get proper function easily enough.

If you're trying to run this mess, you would put something in your loop like follows:

int val[8];
makeDigit(h1, val);
writeToShift(val, grid1);

Where h1 is an integer that you want to display on grid1.

You'd probably want to display all digits, not just the first one, so you'd want to insert a sleep inbetween each one so that you can it persists long enough to make the digit viewable. Something like this:

    int val[8];
makeDigit(h1, val);
writeToShift(val, grid1);
delay(4);
makeDigit(h2, val);
writeToShift(val, grid2);
delay(4);
makeDigit(min1, val);
writeToShift(val, grid4);
delay(4);
makeDigit(min2, val);
writeToShift(val, grid5);
delay(4);

We're skipping grid3 because (for me) that corresponds to the colon, which I have hardwired to be always on (for now). Fun things I've thought about doing with it include having it blink every second or alternate between one pip in the colon being lit up at a time. I'll probably put the full code I have written up on github at some point in the future, but it's not there yet.

At this point, you should have everything you need to light up the display. I'll talk about the real time clock next I guess. My prototype is using a chronodot, but I think the real deal will use a DS3231.

Discussions

Ken Yap wrote 07/13/2020 at 01:27 point

I know you haven't touched this for a while but you can eliminate all those array assignments and shorten the code with a couple of methods.

1. Define makedigit to take an int **myArray, and in the function simply return a pointer to one element of statically initialised array of vectors. The caller then passes the address of the variable, so &val instead of val. This will take a bit of thinking to get your head around.

2. Same as currently but instead of doing 7 assignments, do a memcpy from one element of a statically initialised array of vectors. Remember that the len in memcpy should be 7 * sizeof int.

  Are you sure? yes | no

meuviolino wrote 07/13/2020 at 01:12 point

Great! We look forward to the next chapter!

  Are you sure? yes | no

AgentPothead wrote 03/26/2019 at 02:08 point

Oh no and it ends? Is there another part coming? This is really helpful!

  Are you sure? yes | no