24 junio 2011

Como hacer funcionar un display spi VFD (Vacuum Fluorescent Display) Pinguino (o arduino)+16LF01UA3






PARA REPASAR: Datasheet del display 16LF01UA3 (lo he consiguido en pdf buscando en la web por "16lf01ua3 untitle"); Pinguino Pic (lo mismo que arduino pero con microcontrolador de Microchip del que he comentado en una entrada anterior, software libre y hardware libre fácil de construir, que puedes ver si buscas en la web "pinguino pic hackinglab", fíjate expecialmente en el tutorial; conocimientos básicos de programación en C, tener un Manual de progamción Arduino que puedes también descargar en pdf desde la red para conocer que ordenes de c se pueden usar con arduino.



Esta vez se trata de hacer funcionar un display alfanumerico VFD (Display Fluorescente de Vacio). He conseguido alguno de despieces, y he encontrado documentación de éste en internet (16LF01UA3). Mi idea original es tratar de imitar un luminoso publicitario que pase continuamente un mensaje de derecha a izquierda.
Con una ojeada a la hoja de características del display Samsumg 16LF01UA3 nos hacemos con los pines y una idea de su funcionamiento.

Pin1 y pin2: Vcc(+5v).
Pin3,4,5,6,7: No conectados. El 3 falta como "llave" que impida conectar al revés.
Pin8: SCLK Activa por flanco de bajada el bit que señale DATA.
Pin9: DATA El bit (0 ó 1) se va añadiendo a un registro cada vez que SCLK baja de 1 a 0 hasta que forma un byte (8 bits) como código de control del display.
Pin10: /RST A nivel bajo activa el reset de todo el display.
Pin11 y pin12: GND


Así que tendremos que alimentar con 5 voltios al display (pines1-2, 11-12), y el "Pinguino" (placa similar a Arduino) que usaremos para manejar los tres pines: SCLK, DATA, y RESET.

Monto todo sobre una protoboard. En la programación del Pinguino "definiremos" como salidas: la 7 para el SCLK, la 6 para el RESET, y la 5 para el DATA.






Como mi experiencia con el pinguino es mínima, y aquí se trata de aprender, no me preocuparé de librerias previas, ni aprovecharé lo que hayan hecho otros. Programaremos "como los hombres", a pelo, usando las ordenes más elementales y genéricas para ir montando nuestras funciones... Creo que he descrito ampliamente con notas en el código pero aclararé como he ido planteando el asunto.
El display se maneja, basicamente, "emitiendo" secuencias de bits, que en grupos de 8 forman "palabras" que el display va entendiendo de determinada manera. Los códigos vienen en la hoja de características del display. El display alamacenará un bit cuando la señal SCLK pase de nivel alto (5v) a bajo (0v) y ese bit será el que en ese momento tenga el pin DATA. La señal /RESET deberá permanecer a nivel alto. A nivel bajo reseteará todo y tendremos que volver a empezar.
Lo primero ha sido la funcion sclkfall() que hace que la señal de SCLK pase a nivel bajo y por tanto el display "acepte" lo que hay en DATA. Y luego, usando ésta defino l() para meter un uno (5v), y o() para "meter" un cero (0v). OJO: l() parece un uno pero es una "L"minúscula, y o() parece un cero pero es una "O"minúscula. Uso el parecido porque no puedo hacer una función que empiece por un número y de esta forma , a posteriori es muy cómodo, porque es fácil ver el byte que queiro enviar en cada momento. Luego todo es enviar bytes. ¿Cúal?, se sabe mirando la hoja de características, aunque voy explicando en el código.


// Probando display vfd 16LF01UA3

// Usaremos un pinguino con pic18f2550
// Anadir la linea siguiente si es con pic18f4550
// #define PIC18F4550


#define SCLK 7 //manejaremos SCLK por pin7
#define RST 6 // /RST por pin6
#define DATA 5 // DATA por pin5

int colocarPuntero(int En);//Prototipado porque pasa valor
////////////////////////////////////////////////////////////
// TEXTO PARA EL DISPLAY en modo scroll AQUI
char Mensaje[100]="Jueves, 16 de junio ";// 3" "al final
int posicionUltLetra=22;
// TAMAÑO ARRAY Mensaje[x] DEBE SER MAYOR QUE EL NUMERO DE CARACTERES
// posicionUltLetra INDICARA EL NUMERO DE CARACTERES.
////////////////////////////////////////////////////////////
int scroll=1;// 1=>Desplazamiento continuo del texto izq-der
// 0=>No se controla el desplazamiento.
// 2=>Prueba (poniendo codigo en su sitio) cosas nuevas
////////////////////////////////////////////////////////////

char digDisplay[17]=" ";// 17 espacios
//Representa los caracteres en cada
// posicion del display, usaremos [1...16]
// uno por cada digito. El [0] no lo usamos
int posicionMensaje,i;
//No.1er.caracter escribir en display
// i contador que no puedo definir en loop()


/////////////////////////////////////////////
void setup()
{
pinMode(SCLK,OUTPUT); //Todas seran salidas hacia
pinMode(RST,OUTPUT); //el display
pinMode(DATA,OUTPUT);

resetdisplay(); // La primera vez reseteamos display
numdigit(); // configuramos el numero de digitos
brillo(); // la intensidad de los digitos
BorrarDisplay(); // Borrar
posicionMensaje=0;//Por donde empezaremos a leer el Mensaje[]
}

void loop()
{

if(scroll==1)
{
// Guardamos en cada digDisplay[i] el caracter que le toca a ese digito
// Pretendemos que el texto corra de derecha a izquierda continuamente
for(i=1;i<16;i++)
{
digDisplay[i]=digDisplay[i+1];
//Cada digito corre de derecha a izquierda
}
digDisplay[16]=Mensaje[posicionMensaje];
//El ultimo digito de la derecha es el nuevo que leemos de Mensaje[]

if (posicionMensaje==posicionUltLetra)
// Si era el ultimo caracter hay que volver a leer por el principio
{
posicionMensaje=0;
}
else
// Si todavia no llegamos preparamos para leer el siguiente
{
posicionMensaje=posicionMensaje+1;
//La siguiente vez leeremos el caracter siguiente de Mensaje[]
}



// Representamos cada caracter en su digDisplay
for(i=1;i<17;i++)
{
colocarPuntero(i);
switch(digDisplay[i])
{
case '@':Arroba();break;
case 'A':case 'a':A();break;
case 'B':case 'b':B();break;
case 'C':case 'c':C();break;
case 'D':case 'd':D();break;
case 'E':case 'e':E();break;
case 'F':case 'f':F();break;
case 'G':case 'g':G();break;
case 'H':case 'h':H();break;
case 'I':case 'i':I();break;
case 'J':case 'j':J();break;
case 'K':case 'k':K();break;
case 'L':case 'l':L();break;
case 'M':case 'm':M();break;
case 'N':case 'n':N();break;
case 'O':case 'o':O();break;
case 'P':case 'p':P();break;
case 'Q':case 'q':Q();break;
case 'R':case 'r':R();break;
case 'S':case 's':S();break;
case 'T':case 't':T();break;
case 'U':case 'u':U();break;
case 'V':case 'v':V();break;
case 'W':case 'w':W();break;
case 'X':case 'x':X();break;
case 'Y':case 'y':Y();break;
case 'Z':case 'z':Z();break;
case '(':AbreParentesis();break;
case '\\':BarraIzqDer();break;
case '/':BarraDerIzq();break;
case ')':CierraParentesis();break;
case '_':GuionBajo();break;
case '\"':Comillas();break;
case '#':Almohadilla();break;
case '$':Dolar();break;
case '%':Porcentaje();break;
case '&':Andpersand();break;
case '\'':Apostrofe();break;
case '<':MenorQue();break;
case '>':MayorQue();break;
case '*':Asterisco();break;
case '+':Mas();break;
case ',':case ';':Espacio();PuntoyComa();break;// El ; y el .
case '.':Espacio();Punto();break;//se asocian al ultimo
case '0':Cero();break; //caracter. No hay coma pero queda
case '1':Uno();break; //parecido el punto y coma.
case '2':Dos();break;
case '3':Tres();break;
case '4':Cuatro();break;
case '5':Cinco();break;
case '6':Seis();break;
case '7':Siete();break;
case '8':Ocho();break;
case '9':Nueve();break;
case '?':Interrogacion();break;
case '=':Igual();break;
case ' ':Espacio();break;
default:Espacio();
}
}
delay(400); //Retraso para poder visualizar
BorrarDisplay();
}


if (scroll==0)
{
// Poniendo al principio la constante scroll a 0
// cada letra (llamada a su funcion) ira despues
// de la anterior y al acabar empezara linea nueva.
BorrarDisplay();
H();O();L();A();Espacio();
M();U();N();D();O();Punto();Espacio();
delay(1000);BorrarDisplay();
Q();U();E();Espacio();
T();A();L();Espacio();
E();S();T();A();M();O();S();Interrogacion();
delay(1500);
}
if (scroll==2)
{
// Poniendo la constante scroll a 2 aquí puedes
// hacer pruebas si arriesgar demasiado...
}
}//////////////////////////////////////////////////////////


// Funciones para controlar el display
// Ver datasheet 16lf01ua3.
void sclkfall() // SCLK activa por flanco
{ // de bajada lo que marque DATA
digitalWrite(SCLK,HIGH);
digitalWrite(SCLK,LOW); // y preparando la siguiente bajada
digitalWrite(SCLK,HIGH);// vuelve a quedar a nivel alto
}


void o() // La funcion o() ("o" minuscula) marca un 0
{
digitalWrite(DATA,LOW);
sclkfall();
}
void l() // La funcion l() ("L" minuscula) marca un 1
{
digitalWrite(DATA,HIGH);
sclkfall();
}



void resetdisplay() // resetea el display
{ // activando a nivel bajo RST mas
digitalWrite(RST,LOW); // de 1ms y volvieno a nivel alto
delay(2); // para funcionamiento normal
digitalWrite(RST,HIGH);
delay(2);
}


void numdigit() // Define cuantos digitos usaremos
{ // los 16 en este caso "11000000"
l();l();o();o();o();o();o();o();
}


void brillo() // Para configurar la intensidad del display
{ // Maxima 11111111
l();l();l();l();l();l();l();l();
}


int colocarPuntero(int En)// Elege el digito que se va a escribir
{
l();o();l();o();// Primer cuarteto ordena control de puntero 1010

switch(En) // Segundo cuarteto indica dígito. De izq. a der.
{ // El primero 1111 y siguientes 0000,0001,...1110
case 1:l();l();l();l();break;
case 2:o();o();o();o();break;
case 3:o();o();o();l();break;
case 4:o();o();l();o();break;
case 5:o();o();l();l();break;
case 6:o();l();o();o();break;
case 7:o();l();o();l();break;
case 8:o();l();l();o();break;
case 9:o();l();l();l();break;
case 10:l();o();o();o();break;
case 11:l();o();o();l();break;
case 12:l();o();l();o();break;
case 13:l();o();l();l();break;
case 14:l();l();o();o();break;
case 15:l();l();o();l();break;
case 16:l();l();l();o();break;
default:l();l();l();l();
}
return(0);
}


void BorrarDisplay() // Borra escribiendo 16 espacios
{
int c;
colocarPuntero(1);
for (c=1;c<17;c=c+1)
{
Espacio();
}
}

void Retraso()
{
delay(0); // Ajustar restraso para poder leer
}



////////// Definiciones de letras //////////////

void Arroba() // Codigo para escribir @ 00000000
{o();o();o();o();o();o();o();o();Retraso();}
void A() // Codigo para escribir A 00000001
{o();o();o();o();o();o();o();l();Retraso();}
void B() // Codigo para B 00000010
{o();o();o();o();o();o();l();o();Retraso();}
void C() // Codigo para C 00000011
{o();o();o();o();o();o();l();l();Retraso();}
void D() // Codigo para escribir D 00000100
{o();o();o();o();o();l();o();o();Retraso();}
void E() // Codigo para escribir E 00000101
{o();o();o();o();o();l();o();l();Retraso();}
void F() // Codigo para escribir F 00000110
{o();o();o();o();o();l();l();o();Retraso();}
void G() // Codigo para escribir G 00000111
{o();o();o();o();o();l();l();l();Retraso();}
void H() // Codigo para escribir H 00001000
{o();o();o();o();l();o();o();o();Retraso();}
void I() // Codigo para escribir I 00001001
{o();o();o();o();l();o();o();l();Retraso();}
void J() // Codigo para escribir J 00001010
{o();o();o();o();l();o();l();o();Retraso();}
void K() // Codigo para escribir K 00001011
{o();o();o();o();l();o();l();l();Retraso();}
void L() // Codigo para escribir L 00001100
{o();o();o();o();l();l();o();o();Retraso();}
void M() // Codigo para escribir M 00001101
{o();o();o();o();l();l();o();l();Retraso();}
void N() // Codigo para escribir N 00001110
{o();o();o();o();l();l();l();o();Retraso();}
void O() // Codigo para escribir O 00001111
{o();o();o();o();l();l();l();l();Retraso();}
void P() // Codigo para escribir P 00010000
{o();o();o();l();o();o();o();o();Retraso();}
void Q() // Codigo para escribir Q 00010001
{o();o();o();l();o();o();o();l();Retraso();}
void R() // Codigo para escribir R 00010010
{o();o();o();l();o();o();l();o();Retraso();}
void S() // Codigo para escribir S 00010011
{o();o();o();l();o();o();l();l();Retraso();}
void T() // Codigo para escribir T 00010100
{o();o();o();l();o();l();o();o();Retraso();}
void U() // Codigo para escribir U 00010101
{o();o();o();l();o();l();o();l();Retraso();}
void V() // Codigo para escribir V 00010110
{o();o();o();l();o();l();l();o();Retraso();}
void W() // Codigo para escribir W 00010111
{o();o();o();l();o();l();l();l();Retraso();}
void X() // Codigo para escribir X 00011000
{o();o();o();l();l();o();o();o();Retraso();}
void Y() // Codigo para escribir Y 00011001
{o();o();o();l();l();o();o();l();Retraso();}
void Z() // Codigo para escribir Z 00011010
{o();o();o();l();l();o();l();o();Retraso();}
void AbreParentesis() // Codigo para escribir ( 00011011
{o();o();o();l();l();o();l();l();Retraso();}
void BarraIzqDer() // Codigo para escribir \ 00011100
{o();o();o();l();l();l();o();o();Retraso();}
void BarraDerIzq() // Codigo para escribir / 00101111
{o();o();l();o();l();l();l();l();Retraso();}
void CierraParentesis() // Codigo para escribir ) 00011101
{o();o();o();l();l();l();o();l();Retraso();}
void GuionBajo() // Codigo para escribir _ 00011111
{o();o();o();l();l();l();l();l();Retraso();}
void Comillas() // Codigo para escribir " 00100010
{o();o();l();o();o();o();l();o();Retraso();}
void Almohadilla() // Codigo para escribir # 00100011
{o();o();l();o();o();o();l();l();Retraso();}
void Dolar() // Codigo para escirbir $ 00100100
{o();o();l();o();o();l();o();o();Retraso();}
void Porcentaje() // Codigo para escribir % 00100101
{o();o();l();o();o();l();o();l();Retraso();}
void Andpersand() // Codigo para escribir & 00100110
{o();o();l();o();o();l();l();o();Retraso();}
void Apostrofe() // Codigo para escribir ' 00100111
{o();o();l();o();o();l();l();l();Retraso();}
void MenorQue() // Codigo para escribir < 00101000
{o();o();l();o();l();o();o();o();Retraso();}
void MayorQue() // Codigo para escribir > 00101001
{o();o();l();o();l();o();o();l();Retraso();}
void Asterisco() // Codigo para escribir * 00101010
{o();o();l();o();l();o();l();o();Retraso();}
void Mas() // Codigo para escribir + 00101011
{o();o();l();o();l();o();l();l();Retraso();}
void PuntoyComa() // Codigo para escribir ; 00101100
{o();o();l();o();l();l();o();o();Retraso();}
void Menos() // Codigo para escribir - 00101101
{o();o();l();o();l();l();o();l();Retraso();}
void Punto() // Codigo para escribir . 00101110
{o();o();l();o();l();l();l();o();Retraso();}
void Cero() // Codigo para escribir 0 00110000
{o();o();l();l();o();o();o();o();Retraso();}
void Uno() // Codigo para escribir 1 00110001
{o();o();l();l();o();o();o();l();Retraso();}
void Dos() // Codigo para escribir 2 00110010
{o();o();l();l();o();o();l();o();Retraso();}
void Tres() // Codigo para escribir 3 00110011
{o();o();l();l();o();o();l();l();Retraso();}
void Cuatro() // Codigo para escribir 4 00110100
{o();o();l();l();o();l();o();o();Retraso();}
void Cinco() // Codigo para escribir 5 00110101
{o();o();l();l();o();l();o();l();Retraso();}
void Seis() // Codigo para escribir 6 00110110
{o();o();l();l();o();l();l();o();Retraso();}
void Siete() // Codigo para escribir 7 00110111
{o();o();l();l();o();l();l();l();Retraso();}
void Ocho() // Codigo para escribir 8 00111000
{o();o();l();l();l();o();o();o();Retraso();}
void Nueve() // Codigo para escribir 9 00111001
{o();o();l();l();l();o();o();l();Retraso();}
void Interrogacion() // Codigo para escribir ? 00111111
{o();o();l();l();l();l();l();l();Retraso();}
void Igual() // Codigo para escribir = 00111101
{o();o();l();l();l();l();o();l();Retraso();}
void Espacio() // Codigo para el espacio 00100000
{o();o();l();o();o();o();o();o();}

/////////////////////////////////////Sancos 16-6-2011
////////////////////////////////////

El código sale aquí un poco enmarañado porque si copio y pego pierdo los tabulados. Pero para seguir un poco las explicaciones sirve.
Si quieres acceder al "archivo.pde" que puede leer directamente el IDE del pinguino, puedes descargarlo desde aquí.
Actualmente yo estoy usando la versión "Pinguino beta 9.04" y es con la que he probado todo esto.
También me ha parecido útil el que puedas descargar el programa como "documento.doc" para que sea más legible con colores y sangrados. Pero recuerda que esto no correrá en el editor del Pinguino. Tendrías que pasarlo escribiendo a pelo.

También añado aquí (a Diciembre del 2014) si quieres descargar una versión revisada para arduino ("archivo.ino"). Probado con arduino uno y el ide 1.0.4 a raiz de un comentario anónimo del  6 de diciembre de 2014 (léelo para observaciones) advirtiendo de que el código para pinguino no compilaba en arduino.

Al final he usado un if para, según el valor de la constante scroll, escoger un código que hace pasar de derecha a izquierda lo que pongamos en Mensaje[x] (video que pone una fecha); o no controlar la posición de cada letra y que sea el display el que automaticamente ponga una tras otra (video "Hola mundo, como estamos?"); o en una tercera opción, podemos hacer pruebas sin estropear lo que sabemos que funciona. En esta última parte no he puesto código.

Supongo que se entiende facilmente que las funciones l(), y o() envían, respectivamente, un 1 y un 0 al display.

Para cada letra se llama a su función correspondiente. Esta envía ceros y unos con el código correcto, según la hoja de características. Observa que el código que corresponde a escribir una letra tiene como bit más significativos (los primeros que se emiten) dos ceros, y los seis restantes indican la letra. Los que no empiecen por dos ceros son códigos de control: el brillo (yo sólo uso el maximo- brillo() -),número de dígitos (yo uso los 16- numdigit() -),la posición del siguiente caracter (aquí la función no es tan simple porque para conseguir que parezca que se desplacen tengo que posicionar cada letra- colocarPuntero() -y uso todos los códigos).
Algunas funciones especiales son: resetdisplay() en la que activo la linea de reset, o BorrarDisplay() que escribe 16 espacios. La función retraso() ,que aquí parecerá inútil, permite hacer pruebas con diferentes velocidades. Si escribes todo sin perder tiempo no verás nada.

Si al principio del código pones scroll=0 el asunto solo se tratará de ir "llamando a letras" estas irán apareciendo en la posición 1, luego 2, luego 3,...luego 16, luego 1, luego 2... Esto lo hace automaticamente el display 16LF01UA3. Al poner una letra suma la siguiente posición un paso, excepto para la coma y el punto.

Si el scroll=1, se complica un poco la cosa. Porque intento que el texto (el que quieras poner en Mensaje[x]) se desplace continuamente como en un luminoso publicitario. Mensaje[x] es un array. Necesitas que el texto quepa dentro, así que x debe de ser al menos el número de caracteres que quieras representar. El primer carácter será Mensaje[0] y el último Mensaje[x-1]. Observese que Mensaje[x] debería ser un carácter especial que marque el final de la cadena. He tenido problemas con esto. Así que en vez de "calcular" el final de la cadena, he optado por utilizar una constante posicionUltLetra que me marca cual es la última letra.
Luego el truco será en usar la cadena puente digDisplay[17] que se corresponderá a cada caracter real del display (1...16). Será 17, porque la posición 0 y 17 no se usan. Al principio esta cadena tiene espacios. Luego iremos pasando cada letra un puesto a la izquierda. ¿Y que pasa con la última? Pues que será la nueva, que vamos controlando con posicionMensaje teniendo en cuenta que después del final, vendrá el principio.
Una vez que tenemos la cadena digDisplay[17] llena de forma apropiada, escribimos cada elemento en su posición, llamando a las función más apropiada.
Y repetimos...



Espero no haberme liado mucho.
Me gustaría que hicieseis comentarios, aunque no sean demasiado buenos. Si son constructivos prenderé más. Y también me gustaría saber si alguien lo lee...


Bueno os dejo los videos.

Con desplazamiento, tipo luminoso publicitario:

Y sin desplacamiento:

POR FAVOR, valoraría comentarios... y siento la posible publicidad en mis enlaces pero es lo que tienen los alojamientos gratuitos...
Saludos.

7 comentarios:

  1. Este es un video que explica lo que es Pinguino:
    http://www.youtube.com/watch?v=LE7tRvUzgXM&feature=player_embedded

    ResponderEliminar
  2. Muy interesante, hacia tiempo ya que tengo esta pantalla por casa y aun no le habia dado uso.Gracias.Un saludo.

    ResponderEliminar
  3. Hola. Llevo tiempo buscando la forma de conectar este display que recuperé de una vieja tragaperras. He intentado usarlo con arduino y no consigo que mande caracteres al display , ¿alguien sabría como hacerlo? P.D: el código de arriba no me compila para arduino.
    Gracias

    ResponderEliminar
    Respuestas
    1. No lo he probado directamente con arduino pero yo creo que debería funcionar con arduino. Usa código muy estandar. Comenta que errores te da al intentar compilar. Entre tanto intentaré probarlo con aruduino uno. Déjame un tiempo y comentaré algo. Un saludo.

      Eliminar
    2. He probado y efectivamente con arduino no compila. En su momento no disponía de un arduino para probar pero tenía el código por totalmente compatible. Ahora he visto que no es tan precisa la cosa.

      1- En arduino ya existe una función F() así que he tenido que redefinir mi función F() como f(). Tenlo en cuenta cuando llames a la función para representar una F en el display. Ha de ser minúscula.

      2- En manejo de las matrices y sus finales o tamaños tampoco son iguales. Con Arduino la cadena digDisplay[17] ha de tener 16 espacios y no 17.

      3- La representación del carácter comillas (") cambia. Con Pinguino ha de ser CASE ' \ " ' pero con Arduino será CASE ' " '

      4- El Arduino parece más rápido que el Pinguino y aparecen bastantes caracteres raros, así que he añadido unos pequeños retrasos en las funciones o(), l(), y sclkfall() para respetar con holgura los requisitos de tiempos de las señales.

      Si quieres he añadido un enlace, cerca de las anteriores descargas para Pinguino, que descarga el nuevo código para Arduino (archivo.ino)

      Eliminar
  4. Hola, Gracias por poner el código para Arduino. Probado y funcionando a la primera. Tengo 6 displays de este tipo y solo les pude hacer correr (de mala manera) con PIC 16F877a y 18F4550 en C, pero me gusta mucho más Arduino y ahora ya lo tengo. Mucho ojo a las necesidades de alimentación de estas pantallas; si las conectamos directamente a las tomas + y - de la placa, el regulador se pondrá a echar humo a los pocos segundos. Conviene alimentar los +5v desde una fuente externa o nos cargamos la placa (o peor aún el puerto USB). Saludos.

    ResponderEliminar
  5. Hi, see my Arduino library to drive this display: https://github.com/DnaX/Samsung_16LF01_VFD

    ResponderEliminar

Agradezco comentarios, aportaciones, o críticas...