En el siguiente tutorial vamos a trabajar con un tipo de displays un tanto particulares.
Se trata de un módulo VFD del fabricante ISE ELECTRONICS, -actualmente NORITAKE ITRON- modelo CU169SCPB-T21A.
Este display tiene una única fila de 16 caracteres, cada uno formado por una matriz de 5x7 puntos.
Los displays VFD -Vacuum-Fluoro Display- eran muy populares en décadas anteriores, hasta que las pantallas LCD los desplazaron en gran parte de dispositivos.
Actualmente los displays VFD´s gozan de gran aceptación entre la comunidad maker, ya que tienen ese encanto especial de los dispositivos retro.
Para materializar este proyecto me he basado en el tutorial que John Boxall dedica a los módulos VFD en tronixstuff
Como se aprecia en la imagen, el display tiene dos conectores, CN1 de 15 pines para interfaz paralelo y CN2 para alimentación a +5V y GND e interfaz serie.
También dispone de 6 jumpers para configurar la velocidad de transmisión y el tipo de caracteres a utilizar, japonés o internacional.
Desafortunadamente, no he encontrado en internet el datasheet del display que nos ocupa, pero por suerte los fabricantes suelen emplear la misma interfaz y configuración en los diferentes modelos que ofertan. Así que fijándonos en los datasheets de otros modelos podemos configurar fácilmente el nuestro. Al final de la página hay tres datasheet para descargar.
J0,J1 y J2 determinan la velocidad de transmisión. Los tres abiertos implica 19200 BAUD.
J3 y J4 determinan la paridad. Colocando un jumper en J4 seleccionamos PARITY NONE. J3 no importa.
JA abierto implica INTERNATIONAL FONTS SELECTED.
Para conectar el display al arduino empleamos la interfaz serie. Solo necesitamos un cable desde el pin D7 del Arduino hasta el pin 2 (SIN / TEST) del conector CN2 de alimentación.
Para alimentar el módulo empleamos una fuente externa de +5V ya que los displays VFD´s consumen más de lo que el regulador interno del Arduino puede suministrar. También hay que unir las masas de Arduino y de la fuente externa.
Empezamos por un pequeño sketch -el típico Hello World!- que servirá para comprobar que el display funciona correctamente.
Luego voy a añadir un RTC modelo DS3231 para hacer un reloj-calendario-termómetro.
/*
Basic experiment with ISE ELECTRONICS - NORITAKE ITRON
VFD modules - model CU169SCPB-T21A
Based on the original sketch by John Boxall http://tronixstuff.com/tag/vfd/
Modified by Angel M. https://sites.google.com/site/angmuz/
*/
#include <SoftwareSerial.h>
SoftwareSerial VFD(6,7); // RX, TX, pin 6 not connected
void setup()
{
VFD.begin(19200);
resetVFD();
VFDclearsceen();
VFD.write(0x16); // underline cursor off
}
void resetVFD()
// performs a software reset on the VFD controller
{
VFD.write(0x1B); // ESC
VFD.write(0x49); // software reset
}
void VFDclearsceen()
// moves cursor to top-left and clears display
{
VFD.write(0x0E); // clear display
VFD.write(0x0C); // form feed - cursor to top-left
}
void moveCursor(byte position)
// moves the cursor - top row is 0~15
// vertical scroll mode must be turned off if used
{
VFD.write(0x1B); // ESC
VFD.write(0x48); // move cursor
VFD.write(position); // location
}
void loop()
{
//VFD.print("0123456789012345");
VFDclearsceen();
moveCursor(0);
VFD.print("ISE ELECTRONICS"); // put your text here, max 16 chars.
delay(3000);
VFDclearsceen();
moveCursor(0);
VFD.print("NORITAKE ITRON");
delay(3000);
VFDclearsceen();
moveCursor(0);
VFD.print("VFD Display");
delay(3000);
VFDclearsceen();
moveCursor(0);
VFD.print("CU169SCPB-T21A");
delay(3000);
VFDclearsceen();
moveCursor(0);
VFD.print("sites@angmuz");
delay(3000);
}
/*
Digital Clock-Calendar-Thermometer w Arduino &
RTC DS3231SN, VFD Module ISE ELECTRONICS CU169SCPB-T21A
Angel M. https://sites.google.com/site/angmuz/home
*/
#include <Wire.h>
#include "ds3231.h"
#include <SoftwareSerial.h>
SoftwareSerial VFD(6,7); // RX, TX
#define BUFF_MAX 128
uint8_t time[8];
char recv[BUFF_MAX];
unsigned int recv_size = 0;
unsigned long prev, interval = 1000;
void setup()
{
Serial.begin(9600);
VFD.begin(19200);
resetVFD();
VFDclearsceen();
VFD.write(0x16); // underline cursor off
Wire.begin();
DS3231_init(DS3231_INTCN);
memset(recv, 0, BUFF_MAX);
//parse_cmd("TssmmhhDddmmaaaa",16);
//parse_cmd("T001523715052016",16); // RTC setup,
}
void resetVFD()
// performs a software reset on the VFD controller
{
VFD.write(0x1B); // ESC
VFD.write(0x49); // software reset
}
void VFDclearsceen()
// moves cursor to top-left and clears display
{
VFD.write(0x0E); // clear display
VFD.write(0x0C); // form feed - cursor to top-left
}
void moveCursor(byte position)
// moves the cursor - top row is 0~15
// vertical scroll mode must be turned off if used
{
VFD.write(0x1B); // ESC
VFD.write(0x48); // move cursor
VFD.write(position); // location
}
void loop()
{
char in;
char tempF[6];
float temperature;
char buff[BUFF_MAX];
unsigned long now = millis();
struct ts t;
// show time once in a while
if ((now - prev > interval) && (Serial.available() <= 0)) {
DS3231_get(&t); //Get time
parse_cmd("C",1);
temperature = DS3231_get_treg(); //Get temperature
dtostrf(temperature, 5, 1, tempF);
// dia de la semana
VFDclearsceen();
moveCursor(0);
printDay(t.wday);
// fecha
VFD.print(t.mday);
printMonth(t.mon);
//VFD.print(t.year); no se muestra el año
delay(3000);
// hora
VFDclearsceen();
moveCursor(1);
VFD.print(t.hour);
VFD.print(":");
if(t.min<10)
{
VFD.print("0");
}
VFD.print(t.min);
/* no se muestran los segundos
VFD.print(":");
if(t.sec<10)
{
VFD.print("0");
}
VFD.print(t.sec);
*/
// temperatura
moveCursor(8);
VFD.print(tempF);
VFD.print((char)149);
VFD.print("C");
delay(5000);
prev = now;
}
if (Serial.available() > 0) {
in = Serial.read();
if ((in == 10 || in == 13) && (recv_size > 0)) {
parse_cmd(recv, recv_size);
recv_size = 0;
recv[0] = 0;
} else if (in < 48 || in > 122) {; // ignore ~[0-9A-Za-z]
} else if (recv_size > BUFF_MAX - 2) { // drop lines that are too long
// drop
recv_size = 0;
recv[0] = 0;
} else if (recv_size < BUFF_MAX - 2) {
recv[recv_size] = in;
recv[recv_size + 1] = 0;
recv_size += 1;
}
}
}
void parse_cmd(char *cmd, int cmdsize)
{
uint8_t i;
uint8_t reg_val;
char buff[BUFF_MAX];
struct ts t;
//snprintf(buff, BUFF_MAX, "cmd was '%s' %d\n", cmd, cmdsize);
//Serial.print(buff);
// TssmmhhWDDMMYYYY aka set time
if (cmd[0] == 84 && cmdsize == 16) {
//T355720619112011
t.sec = inp2toi(cmd, 1);
t.min = inp2toi(cmd, 3);
t.hour = inp2toi(cmd, 5);
t.wday = inp2toi(cmd, 6);
t.mday = inp2toi(cmd, 8);
t.mon = inp2toi(cmd, 10);
t.year = inp2toi(cmd, 12) * 100 + inp2toi(cmd, 14);
DS3231_set(t);
Serial.println("OK");
} else if (cmd[0] == 49 && cmdsize == 1) { // "1" get alarm 1
DS3231_get_a1(&buff[0], 59);
Serial.println(buff);
} else if (cmd[0] == 50 && cmdsize == 1) { // "2" get alarm 1
DS3231_get_a2(&buff[0], 59);
Serial.println(buff);
} else if (cmd[0] == 51 && cmdsize == 1) { // "3" get aging register
Serial.print("aging reg is ");
Serial.println(DS3231_get_aging(), DEC);
} else if (cmd[0] == 65 && cmdsize == 9) { // "A" set alarm 1
DS3231_set_creg(DS3231_INTCN | DS3231_A1IE);
//ASSMMHHDD
for (i = 0; i < 4; i++) {
time[i] = (cmd[2 * i + 1] - 48) * 10 + cmd[2 * i + 2] - 48; // ss, mm, hh, dd
}
byte flags[5] = { 0, 0, 0, 0, 0 };
DS3231_set_a1(time[0], time[1], time[2], time[3], flags);
DS3231_get_a1(&buff[0], 59);
Serial.println(buff);
} else if (cmd[0] == 66 && cmdsize == 7) { // "B" Set Alarm 2
DS3231_set_creg(DS3231_INTCN | DS3231_A2IE);
//BMMHHDD
for (i = 0; i < 4; i++) {
time[i] = (cmd[2 * i + 1] - 48) * 10 + cmd[2 * i + 2] - 48; // mm, hh, dd
}
byte flags[5] = { 0, 0, 0, 0 };
DS3231_set_a2(time[0], time[1], time[2], flags);
DS3231_get_a2(&buff[0], 59);
Serial.println(buff);
} else if (cmd[0] == 67 && cmdsize == 1) { // "C" - get temperature register
Serial.print("temperature reg is ");
Serial.println(DS3231_get_treg(), DEC);
} else if (cmd[0] == 68 && cmdsize == 1) { // "D" - reset status register alarm flags
reg_val = DS3231_get_sreg();
reg_val &= B11111100;
DS3231_set_sreg(reg_val);
} else if (cmd[0] == 70 && cmdsize == 1) { // "F" - custom fct
reg_val = DS3231_get_addr(0x5);
Serial.print("orig ");
Serial.print(reg_val,DEC);
Serial.print("month is ");
Serial.println(bcdtodec(reg_val & 0x1F),DEC);
} else if (cmd[0] == 71 && cmdsize == 1) { // "G" - set aging status register
DS3231_set_aging(0);
} else if (cmd[0] == 83 && cmdsize == 1) { // "S" - get status register
Serial.print("status reg is ");
Serial.println(DS3231_get_sreg(), DEC);
} else {
Serial.print("unknown command prefix ");
Serial.println(cmd[0]);
Serial.println(cmd[0], DEC);
}
}
void printMonth(int month)
{
switch(month)
{
case 1: VFD.print(" Ene");break;
case 2: VFD.print(" Feb");break;
case 3: VFD.print(" Mar");break;
case 4: VFD.print(" Abr");break;
case 5: VFD.print(" May");break;
case 6: VFD.print(" Jun");break;
case 7: VFD.print(" Jul");break;
case 8: VFD.print(" Ago");break;
case 9: VFD.print(" Sep");break;
case 10: VFD.print(" Oct");break;
case 11: VFD.print(" Nov");break;
case 12: VFD.print(" Dic");break;
default: VFD.print(" Error");break;
}
}
void printDay(int wday)
{
switch(wday)
{
case 1: VFD.print("Lunes ");break;
case 2: VFD.print("Martes ");break;
case 3: VFD.print("Miércoles ");break;
case 4: VFD.print("Jueves ");break;
case 5: VFD.print("Viernes ");break;
case 6: VFD.print("Sábado ");break;
case 7: VFD.print("Domingo ");break;
default: VFD.print("Error ");break;
}
}
John Boxall for the amazing tutorial about the VFD Modules.
Petre Rodan for the Arduino library for DS3231 RTC.
Descarga aquí el proyecto completo, incluye datasheets, esquemas y los sketchs.