screwpilot
Published © GPL3+

Vacuum Fluorescent Display Controller

A simple interface circuit to drive VFD displays. No fancy parts required, and runs on serial communication.

AdvancedFull instructions provided25,349
Vacuum Fluorescent Display Controller

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
...or whatever 8 Bits Arduino
×1
VFD Display
our buddy
×1
CD4094
DIP integrated circuit
×2
CD4017
DIP integrated circuit
×2
BC557
...or equivalent transistor (2N3906, 2SA733, ecc) or higher voltage PNP ones
×17
BC547
...or equivalent transistor (2N3904, 2SC945, ecc)
×2
PC817
ubiquitous optocoupler found in most switching PSUs, bottom line part
×1
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
×4
Electrolitic Capacitors
see schematic for values and voltages
×4
470nF Capacitor
whatever type is good
×1
Perfboard
aka: paper board, protoboard, or in case your preferred assembly solution
×1
1/4W Resistors
see schematic for values
×24
Dupont Jumpers
...or your preferred connections and posts
×1
Wire
thin wires for the circuit backside and also to hookup the display, don't use connectors there because pin spacing is variable and a fast soldering is just easier
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire
buy it at a local store, no cheap stuff for this, 0.5mm is preferred
Wire Clipper

Story

Read more

Schematics

General Schematic

I avoided drawing ALL the BC557 transistors because they are just there connected on all the CD4094 outputs, all the same.

There's a jumper selector (running on the red line) you must configure on the 4017 chips, it resets the two 4017 at the completion of the grids scanning, you must connect (or solder) the flying jumper on the output pin AFTER the last grid, so if your display has 10 grids the jumper goes on the 11th output.

The Arduino connections are outlined in red, you can use the USB power for it but it's strongly advised to use a regular old-style transformer for the remaining supplies if you already have a grounded power supply like your PC. These supplies must be all DC except maybe the filament supply, I added some diodes because it's likely you need a separate transformer with a low voltage secondary. The filament power usually ranges around 3V 150mA, a 5V AC transformer will suffice. The "ballast" thing will possibly be a wirevound power potentiometer of like 100ohms, or a fixed resistor, or also some 1N4007 diodes to reduce the voltage.

The BC557 transistors pull the anodes up to anodic voltage and the 100kohm resistors will let the voltage drive fall when the segment is off, while the CD4094 remain at 5V normally.

The CD4017s will be powered with grid voltage and don't require additional transistors. There's a "ghetto" reset circuit for the 4017 that lasts like one tenth of a second, you have to wait for it to settle before running the code.
The "original" schematic for cascading some CD4017 required other logic gates chips, I used instead an NPN transistor and the optocoupler itself to replicate an AND gate, it's fast enough at closing so it's perfect for the task, at releasing it's instead a bit slower but we don't care, especially because the clock inputs on the 4017 are schmitt triggered and the required speed is not "scary" there.

There are various grounds for the chips and stuff as you can see, take a good look.

Code

Arduino code with working routine

Arduino
/*
    Versatile VFD Display Hrdware Interface Arduino Program
    Copyright (C) 2019  Genny A. Carogna

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/



/* this code uses resources from the Arduino in a "behind the curtains" way, the timer1 and SPI modules are both used and busy
 * , so you may have big problems using SPI, SPI pins, and some functionalities based upon timer1... the circuit proposed
 * doesn't have an onboard memory, so the segment setup must be "scanned" continuosly, every grid, 100 times per second
 * , from the Arduino segments array, this translates also on the so-called "overhaed" CPU work, that, for you to know, it's
 * no more than regular use of a micro at the end, it may hit around 3% of CPU work in terms of occupied time, you can also try
 * to use the regular SPI tools of Arduino but it needs an indepth analisys of the outtake and procedure
 * 
 * this code works with whatever 8 bit Arduino, also 3.3V should work ok, maybe reduce the SCK frequency, considering the CD4094 are
 * powered along it and may "undervolt" (yes, exactly the overclock principles to fry your beloved PC)
 */




#define ledLed 13  // it's the led pin that leds a led on to led you know that a led on means some led related stuff (scientist mumbojumbo of course)

#define strobePin 4  // the Arduino pin to dedicate to the "strobe" line
#define strobeHold 10  // microseconds // holding time to switch the optocoupler... not megahertz-fast but acceptable






// data for my displays (a video cassette recorder, an audiophoolery CD reader and a compact stereo)... dont mind, delete, and just define "gridAmount" and "anodeAmount"
#define JVC

#define mitsubishiGrids 10
#define mitsubishiAnodes 9
#define marantzGrids 9
#define marantzAnodes 14
#define JVCGrids 11
#define JVCAnodes 19

#if defined mitsubishi
#define gridAmount mitsubishiGrids
#define anodeAmount mitsubishiAnodes

#elif defined marantz
#define gridAmount marantzGrids
#define anodeAmount marantzAnodes

#elif defined JVC
#define gridAmount JVCGrids
#define anodeAmount JVCAnodes
#endif





// this array, segments[x], stores the segment bits
// segments[0] means grid zero, and the least significative bit is segment zero in that grid
// you can access and modify this array as you like, from wherever, without doing anything else, the effect is immediately visible on
// the display, and stays on as long you don't change the bits, for more than 8 segments per grid displays, you need a
// 16 or 32 bit container per grid, this is managed automatically from the #define(s) above
#if anodeAmount > 16
volatile uint32_t segments[gridAmount] = {0};  // more than 16 segments per grid (32 bit needed)
#elif anodeAmount > 8
volatile uint16_t segments[gridAmount] = {0};  // more than 8 segments per grid (16 bit, twin transfer)
#else
volatile uint8_t segments[gridAmount] = {0};  // 8 or less segments per grid (8 bit)
#endif






void setup(){
  // turns SPI pins and some functional ones as output in a fast\direct way
  *portModeRegister(digitalPinToPort(PIN_SPI_SS)) |= digitalPinToBitMask(PIN_SPI_SS);
  *portModeRegister(digitalPinToPort(PIN_SPI_MOSI)) |= digitalPinToBitMask(PIN_SPI_MOSI);
  *portModeRegister(digitalPinToPort(PIN_SPI_SCK)) |= digitalPinToBitMask(PIN_SPI_SCK);
  *portModeRegister(digitalPinToPort(strobePin)) |= digitalPinToBitMask(strobePin);
  *portOutputRegister(digitalPinToPort(strobePin)) &= ~digitalPinToBitMask(strobePin);  // put it low
  *portModeRegister(digitalPinToPort(ledLed)) |= digitalPinToBitMask(ledLed);
  *portOutputRegister(digitalPinToPort(ledLed)) &= ~digitalPinToBitMask(ledLed);  // put it low

  delay(800);  // some delay to wait for the CD4017 reset to complete (you need to run the code AFTER powering up the 4017s, this makes it automatic in case you power all in once)

  cli();  // disables interrupts to let us tweak some stuff without cats and dogs escaping everywhere

  // timer1 configuration, it makes an interrupt happen "grids times 100" per second... this gives a 100Hz total refresh that is ok
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A = 160000 / gridAmount;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS10);
  TIMSK1 |= (1 << OCIE1A);

  // SPI configuration, we cannot use the Arduino SPI tools bcs they rely on interrupts AND we
  // need the communication happen INSIDE an interrupt..
  // change SPR0 for SPR1 and uncomment the last line to have half SPI clock speed
  SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);  // 1MHz stock SCK speed
  // SPSR = (1 << SPI2X);  // uncomment this and mod above for half clock speed

  sei();  // interrupts are a go again

  // it's better to put your addictional setup() code from here
}





  
// you can do whatever in loop(), delays, pollings, but disabling interrupts or making the micro halt completely will
// freeze the display on a grid, and if the voltage is at top power you may also burn some segments (!!)
void loop() {
  show();  // kinda screensaver, you can delete
}







// interrupt service routine, this happens at every grid scan switch and times 100 per second
ISR(TIMER1_COMPA_vect){
  /*
  ATTENTION
  ATTENTION: i dunno why i need to start "turn" from 1 for proper functionality (i power the Arduino after and it may flip a grid), in case it keeps missing the correct grid at statup, vary this
  ATTENTION
  */
  static uint8_t turn = 1;  // indestructible variable to remind at what grid we were the next interrupt
  
#if anodeAmount > 24
  SPDR = ~uint8_t(segments[turn] >> 24);  // we need negated bits on the CD4094 (cos of the transistors arrangement)
  while (!(SPSR & (1 << SPIF))) ;  // lets wait it completes the transfer
#endif
#if anodeAmount > 16
  SPDR = ~uint8_t(segments[turn] >> 16);  // we need negated bits on the CD4094 (cos of the transistors arrangement)
  while (!(SPSR & (1 << SPIF))) ;  // lets wait it completes the transfer
#endif
#if anodeAmount > 8
  SPDR = ~uint8_t(segments[turn] >> 8);  // we need negated bits on the CD4094 (cos of the transistors arrangement)
  while (!(SPSR & (1 << SPIF))) ;  // lets wait it completes the transfer
#endif
  SPDR = ~uint8_t(segments[turn]);  // we need negated bits on the CD4094 (cos of the transistors arrangement)
  while (!(SPSR & (1 << SPIF))) ;  // lets wait it completes the transfer... yyyYYYAAAAWWWWNNNnnn...........

  // strobing (finally!!) this also switches the grid on the CD4017s
  *portOutputRegister(digitalPinToPort(strobePin)) |= digitalPinToBitMask(strobePin);  // fast pin drive ON
  delayMicroseconds(strobeHold);
  *portOutputRegister(digitalPinToPort(strobePin)) &= ~digitalPinToBitMask(strobePin);  // fast pin drive OFF

  // grid turn reminder at rotation
  turn ++;
  if (turn == gridAmount) turn = 0;
}









////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////FROM NOW THE SKETCH IS REFERRED TO MY DISPLAYS AND VISUAL GAMES, YOU CAN DELETE FROM HERE ON/////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////




// this is just a demo, delete as you like
void show() {
  
  // numbers flip
#ifdef mitsubishi
  for (uint8_t i = 0; i < 9; i ++) for (uint8_t j = 0; j < 11; j ++) {
    delay(200);
    mitsubishiPrintNum(j, i);
  }
  for (uint8_t i = 0; i < 9; i ++) for (uint8_t j = 0; j < 11; j ++) {
    delay(20);
    mitsubishiPrintNum(j, i);
  }
#elif defined marantz
  for (uint8_t i = 0; i < 6; i ++) for (uint8_t j = 0; j < 11; j ++) {
    delay(200);
    marantzPrintNum(j, i);
  }
  for (uint8_t i = 0; i < 6; i ++) for (uint8_t j = 0; j < 11; j ++) {
    delay(20);
    marantzPrintNum(j, i);
  }
#elif defined JVC /*
  for (uint8_t i = 0; i < 9; i ++) for (uint8_t j = 0; j < 11; j ++) {
    delay(200);
    JVCPrintNum(j, i);
  }
  for (uint8_t i = 0; i < 9; i ++) for (uint8_t j = 0; j < 11; j ++) {
    delay(20);
    JVCPrintNum(j, i);
  }*/
#endif

  delay(500);




  // segments flip
  for (uint8_t i = 0; i < gridAmount; i ++) for (uint8_t j = 0; j < anodeAmount + 1; j ++) {
    delay(100);
    segments[i] = (uint32_t(1) << j);
  }

  delay(500);




  // all the display blinkng
  for (uint8_t i = 0; i < 5; i ++) {
    delay(200);
    for (uint8_t j = 0; j < gridAmount; j ++) {
      segments[j] = 0xFFFFFFFF;
    }
    delay(200);
    for (uint8_t j = 0; j < gridAmount; j ++) {
      segments[j] = 0;
    }
  }

  delay(500);
}



// easy peasy seven segment number visualization
void mitsubishiPrintNum(uint8_t val, uint8_t pos) {
  if (pos > 2) pos ++;
  
  uint16_t mask = 0xFF80;
  uint8_t numbers[] = {B00111111, B00000110, B01011011, B01001111, B01100110, B01101101, B01111101, B00100111, B01111111, B01101111, B00000000};
  
  switch (pos) {
    case 0: case 1: case 4: case 5: case 6: case 7: case 8: segments[pos] &= mask; segments[pos] |= numbers[val]; break;
    case 2: if (val == 1) segments[1] |= (1 << 8); else segments[1] &= ~(uint16_t(1) << 8); break;
    case 9: segments[9] &= 0xFFF0; if (val == 1) segments[9] |= 3; else if (val == 2) segments[9] |= B1101;
  }
}




// easy peasy seven segment number visualization
void marantzPrintNum(uint8_t val, uint8_t pos) {
  if (pos > 5) return;
  pos ++;
  pos ++;
  
  uint16_t mask = 0xFF80;  // bitmask to delete the digit
  uint8_t numbers[] = {B00111111, B00000110, B01011011, B01001111, B01100110, B01101101, B01111101, B00100111, B01111111, B01101111, B00000000};
  
  segments[pos] &= mask;  // delete whatever digit
  segments[pos] |= numbers[val];  // set digit
}




// easy peasy seven segment number visualization
void JVCPrintNum(uint8_t val, uint8_t pos) {
  // to come (you really don't care)
}



















//

Credits

screwpilot

screwpilot

2 projects • 5 followers

Comments