Arduino VFDuino
Posted: June 25, 2013

Description

Introduction

VFDuino is a sketch designed to control a Samsung 20T202DA2JA 2×20 VFD using LCD Smartie or other Matrix Orbital compatible applications. The sketch comes with IR Remote (for keypad input) and GPO control.

Fork on GitHub

Code

#include //include SPI.h library for the vfd
#include //include IRremote.h library

First the appropriate libraries must be loaded. Arduino comes with the SPI.h library, the IRremote.h library (linked in the downloads section) must be added to the Arduino libraries directory to compile in IR Remote support.

const int slaveSelectPin = 10; //define additional spi pin for vfd
const int irPin = 8; //define the data pin of the ir receiver

Next we define the hardware pins that will be used with this sketch. Arduino Uno uses pins 11, 12, and 13 for SPI communication, pin 10 can be mapped elsewhere and is required to indicate we wish to speak with the VFD.

IRrecv irrecv(irPin); //setup the IRremote library in receive mode
decode_results results; //setup buffer variable for ir commands
unsigned long lastPress = 0; //variable for tracking the last keypress

Next we initialize the IR Remote library and setup a variable that will contain the last/current IR code received. Lastly the lastPress variable is setup to contain the time of the last IR code received, this is used to create a delay for sending the next keypress code over the serial connection.

bool firstCommand = true; //set variable to clear display with first command
int flowControl = 0; //setup variable to contain current cursor position

The firstCommand variable tracks if there has been a command sent over serial to the sketch. When the first command is received the sketch clears the screen and resets the cursor position. The flowControl variable holds the current position of the cursor, this is used to set the cursor to the 2nd line of the screen when the 21st character in an unbroken (non cursor shifted) stream.

//built in screens (each screen uses 42 bytes of flash)
prog_uchar screens[][41] PROGMEM = {
  "-- XodusAmp Redux ----By Travis Brown --",
  "--  Arduino  VFD  ---- waiting for PC --",
};

Using a char array stored in flash memory using the PROGMEM directive means hundreds of screens can be defined without consuming any additional RAM. A simple function takes this data from flash and copies it into RAM one byte at a time and displays it on the screen.

void setup() {
  Serial.begin(9600); //open a serial connection
  irrecv.enableIRIn(); // start the ir receiver
  vfdInitialize(); //initialize and setup the samsung 20T202DA2JA vfd
  vfdDisplayScreen(1); //display the first screen (startup)
}

First serial is enabled at 9600 baud, next the IR receiver is enabled, and we call the vfdInitialize() function to setup the VFD screen. Lastly we call the vfdDisplayScreen() function to display the first screen on the VFD.

void loop() {
  vfdProcess(); //process any pending requests to update the samsung vfd
  remoteProcess(); //process any pending ir remote commands
  //place additional non-blocking routines here
}

For the uninitiated the loop() function gets called over and over again once the setup() function finishes as long as the microcontroller has power or is otherwise uninterrupted. Using non-blocking functions in this loop allows us to create a sort of multi-tasking on a device that is single threaded. Two functions are called here vfdProcess() which processes any pending commands for the screen, and remoteProcess() which processes any pending IR codes. Other non-blocking functions can be placed here making it possible to integrate other sketches with the VFD and IR functions.

void vfdInitialize() {
  pinMode(slaveSelectPin, OUTPUT); //setup spi slave select pin as output
  SPI.begin(); //start spi on digital pins
  SPI.setDataMode(SPI_MODE3); //set spi data mode 3
  vfdCommand(0x01); //clear all display and set DD-RAM address 0 in address counter
  vfdCommand(0x02); //move cursor to the original position
  vfdCommand(0x06); //set the cursor direction increment and cursor shift enabled
  vfdCommand(0x0c); //set display on,cursor on,blinking off
  vfdCommand(0x38); //set 8bit operation,2 line display and 100% brightness level
  vfdCommand(0x80); //set cursor to the first position of 1st line
}

vfdInitialize() is used to setup the SPI connection to the VFD screen and then send the proper initialization commands to the VFD, these are necessary as the VFD won’t display anything until sent.

void vfdDisplayScreen(int screen) {
  for (int i = 0; i < 20; i++) {
    vfdCommand(0x80 + i);
    vfdData(vfdProcessData(pgm_read_byte(&(screens[(screen - 1)][i]))));
    vfdCommand(0xc0 + (19 - i));
    vfdData(vfdProcessData(pgm_read_byte(&(screens[(screen - 1)][20 + (19 - i)]))));
  }
}

vfdDisplayScreen() accepts as a parameter the number of the screen you wish to display, index starts at 1. This function uses the built in Arduino function pgm_read_byte() to read one byte of flash from a particular memory address. In this case screens[] is a pointer to the flash memory address containing its data.

void vfdCommand(unsigned char temp_2) {
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0xf8);
  SPI.transfer(temp_2);
  digitalWrite(slaveSelectPin, HIGH);
}

vfdCommand() accepts as a parameter one byte of data to send to the VFD preceded by the byte 0xf8 signaling a command to the VFD screen.

void vfdData(unsigned char temp_1) {
  digitalWrite(slaveSelectPin, LOW);
  SPI.transfer(0xfa);
  SPI.transfer(temp_1);
  digitalWrite(slaveSelectPin, HIGH);
}

vfdData() accepts as a parameter one byte of data to send to the VFD preceded by the byte 0xfa signaling that this is data, normally this is a character to display on the screen however when used with other screen commands can contain custom character data for example.

void vfdProcess() {
  if (Serial.available()) {
    if (firstCommand) {
      vfdCommand(0x01); //clear all display and set DD-RAM address 0 in address counter
      vfdCommand(0x02); //move cursor to the original position
      firstCommand = false;
    }
    byte rxbyte = serialGet();
    if (rxbyte == 254) { //code 0xfd signals matrix orbital command
      vfdProcessCommand(); //call function to process the pending command
    } else {
      if (flowControl == 20) {
        vfdCommand(0xc0); //set cursor to the first position of 2nd line
      } else if (flowControl == 40) {
        vfdCommand(0x80); //set cursor to the first position of 1st line
        flowControl = 0; //reset flow control back to start position
      }
      flowControl++;
      vfdData(vfdProcessData(rxbyte)); //process the character and then send as data
    }
  }
}

vfdProcess() is called continuously by loop() and determines if there is pending data for the screen and calls the command and data functions for the VFD.

void vfdProcessCommand() {
  byte temp, temp2;
  switch (serialGet()) {
    //implemented commands for controlling screen
    case 38: //poll keypad
      remoteProcess();
      break;
    case 55: //read module type
      Serial.print(0x56); //report matrix orbital VK202-25-USB
      break;
    case 66: //backlight on (minutes)
      temp = serialGet();
      vfdCommand(0x38);
      break;
    ...
    //not implemented, no extra parameters
    case 35: //read serial number
    case 36: //read version number
  }
}

vfdProcessCommand() reads incoming data from serial and maps to the appropriate command/function for the VFD, the command set above emulates a matrix orbital serial display and reports as a 2×20 matrix orbital VFD. Not every command was necessary to make this compatible with the LCD Smartie matrix.dll driver though care is taken to properly escape the extra parameters these commands may use, otherwise this may generate junk data on the screen.

byte vfdProcessData(byte rxbyte) {
  //case: (input) -> return (desired output)
  switch (rxbyte) {
    case 1: //swap cgram(1) with char 0x14
      return 0x14;
    case 2: //swap cgram(2) with char 0x10
      return 0x10;
    ...
    case 186: //swap right bracket with closed circle
      return 0x94;
  }
}

vfdProcessData() takes as a parameter one byte of data, the VFD’s character map differs from standard ascii and some characters need to be mapped to their proper address in the VFD’s character map.

byte serialGet() {
  int incoming;
  while (!Serial.available()) { }
  incoming = Serial.read();
  return (byte) (incoming &0xff);
}

serialGet() pauses the current program execution until there is data available from serial, the first available byte is returned.

void remoteProcess() {
  if (irrecv.decode(&results)) { //check if the decoded results contain an ir code
    byte keyPress = remoteProcessKeycode(); //return the proper key pressed based on ir code
    if ((keyPress) && (millis() > (lastPress + 200))) { //check if keypress is outside threshold
      Serial.print((char)keyPress); //print the command received to serial
      lastPress = millis(); //update the last key press time
    }
    irrecv.resume(); //clear the results buffer and start listening for a new ir code
  }
}
byte remoteProcessKeycode() {
  //keycodes setup for apple mini remote
  switch (results.value) {
    case 2011275437: //play/pause button
      return 65; //ascii character A
    case 2011283629: //menu button
      return 66; //ascii character B
    case 2011254957: //volume up button
      return 67; //ascii character C
    case 2011246765: //volume down button
      return 68; //ascii character D
    case 2011271341: //track left button
      return 69; //ascii character E
    case 2011259053: //track right button
      return 70; //ascii character F
    default:
      return 0; //no valid code received
  }
}