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.
#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 } }