Programació en C del PIC 16F690

Referència Trucs Perifèrics   Recursos CITCEA
Tutorial Exemples Projectes   Inici

Pantalla LCD amb connexió sèrie

Anem a afegir una pantalla LCD al nostre microcontrolador. Les pantalles LCD porten normalment un circuit integrat que les controla (un altre microcontrolador) que permet que nosaltres li enviem la configuració i els caràcters a mostrar. Si no hi hagués aquest circuit integrat, nosaltres hauríem de controlar tots els punts (píxels) de la pantalla i ens faltarien ports.

Els controladors de pantalla es poden comunicar amb el microcontrolador en sèrie o en paral·lel.

La comunicació paral·lel requereix un conjunt de quatre o vuit bits de dades i entre dos i quatre bits de control; per tant haurem de reservar entre sis i dotze potes dels ports del microcontrolador. Té l'avantatge que els bits s'envien tots d'un cop i, per tant, la programació és més senzilla.

La comunicació sèrie es fa enviant els bits un darrere l'altre i, per tant, cal gestionar el canal de comunicació. Amb comunicació sèrie la programació és més complexa però es necessiten molt poques potes (normalment dues o tres) per a la comunicació.

Aquest apartat es basa en les pantalles sèrie de 16 per 2 de l'empresa Sparkfun. En el cas de les pantalles, més important que el full de característiques de la pantalla és el full de característiques del controlador. Podeu trobar la taula de caràcters que porta implementats a la pàgina 17 d'aquest full de característiques.

Per connectar la pantalla amb el microcontrolador, cal fer les següents connexions:

Pantalla Microcontrolador
Nom Nom Pota
VDD (+5 V) Vdd (+5 V) 1
GND Vss (+5 V) 20
RX TX (RB7) 10

Per enviar dades a la pantalla ens caldrà una transmissió sèrie a 9600 bauds.

La pantalla ja està preconfigurada de fàbrica. Si ens estan bé els valors per defecte ja podem començar a enviar-li caràcters que ens apareixeran a la primera columna de la primera filera i posicions successives.

Atenció: en alguns casos hem detectat problemes causats perquè s'envien coses (instruccions de configuració o text) a la pantalla abans que aquesta s'acabi d'inicialitzar. Això és més freqüent si s'alimenta la placa directament ja que quan l'alimentem amb el programador, aquest reinicialitza el microcontrolador sense deixar d'alimentar la pantalla. Si es detecta un mal funcionament de la pantalla, és recomanable posar un retard, per exemple de dos segons, a l'inici del programa, abans d'enviar res a la pantalla.

Caràcters i posició a la pantalla

El controlador de la pantalla té una memòria per als caràcters que es visualitzen. Cada posició de la pantalla correspon a un byte a la memòria però no tots els bytes de la memòria es visualitzen.

La primera línia de la pantalla comença a l'adreça 0. En pantalles de dues línies, la segona línia comença a la 64 (decimal). Si la pantalla té més línies, la tercera començarà a 20 i la quarta a 84. En una pantalla de 16 caràcters per línia es visualitzaran a la primera línia les posicions 0 a 15.

La taula de caràcters és, en principi, la dels codis ASCII. Per tant per als caràcters normals podem treballar directament en ASCII. Algunes de les adreces que en els codis ASCII no hi són (més de 7Fh) o algunes de les que corresponen a caràcters de control contenen símbols que el fabricant de la pantalla ha decidit posar-hi.

Enviament de caràcters

L'enviament de caràcters és molt senzill, un cop hem configurat la comunicació sèrie. La funció EnviaL ens permet enviar un sol caràcter. En el següent tros de programa veiem la configuració sèrie i com s'envia un caràcter. A l' exemple PS hi tenim un programa complet.

...
#include <xc.h>					// Carrega el fitxer de funcions necessari per al compilador XC8
#define _XTAL_FREQ  4000000			// La freqüència del rellotge és 4 MHz
						// Definició de les funcions que farem servir 
void EnviaL(char Caracter);			// Envia un caràcter
void main (void) {
	ANSEL = 0b00000101;			// Configura AN0 i AN2 com entrada analògica
	ANSELH = 0;				// Desactiva les altres entrades analògiques
	TRISC = 0;				// Tot el port C és de sortida
	TRISB = 0;				// Tot el port B és de sortida
	__delay_ms(2000);			// Esperem que arrenqui la pantalla
	TXSTAbits.BRGH = 1;			// Configuració de velocitat
	BAUDCTLbits.BRG16 = 0;			// Paràmetre de velocitat de 8 bits
	SPBRG = 25;				// Velocitat de 9600 baud
	TXSTAbits.SYNC = 0;			// Comunicació asíncrona
	TXSTAbits.TX9 = 0;			// Comunicació de 8 bits
	RCSTAbits.SPEN = 1;			// Activa comunicació sèrie
	TXSTAbits.TXEN = 1;			// Activa comunicació
	EnviaL('P');				// Lletra
...
}
void EnviaL(char Caracter) {
	TXREG = Caracter;			// Agafa el caràcter i l'envia
	__delay_ms(1);				// Donem temps
	while (PIR1bits.TXIF == 0)		// Esperem que s'acabi d'enviar
		;				// No fem res
}
...

Enviament de text

La funció EnviaT ens permet enviar un text constant, posat entre cometes. A l' exemple PS hi tenim un programa complet.

...
#include <xc.h>					// Carrega el fitxer de funcions necessari per al compilador XC8
#define _XTAL_FREQ  4000000			// La freqüència del rellotge és 4 MHz
						// Definició de les funcions que farem servir 
void EnviaT(const char* text);			// Envia un text
void main (void) {
	ANSEL = 0b00000101;			// Configura AN0 i AN2 com entrada analògica
	ANSELH = 0;				// Desactiva les altres entrades analògiques
	TRISC = 0;				// Tot el port C és de sortida
	TRISB = 0;				// Tot el port B és de sortida
	__delay_ms(2000);			// Esperem que arrenqui la pantalla
	TXSTAbits.BRGH = 1;			// Configuració de velocitat
	BAUDCTLbits.BRG16 = 0;			// Paràmetre de velocitat de 8 bits
	SPBRG = 25;				// Velocitat de 9600 baud
	TXSTAbits.SYNC = 0;			// Comunicació asíncrona
	TXSTAbits.TX9 = 0;			// Comunicació de 8 bits
	RCSTAbits.SPEN = 1;			// Activa comunicació sèrie
	TXSTAbits.TXEN = 1;			// Activa comunicació
	EnviaT("kit");				// Envia un text
...
}
void EnviaT(const char* text) {
	for (unsigned char i = 0; text[i] != '\0'; i++) {
		EnviaL(text[i]);		// Envia caràcter a caràcter
	}
}
...

En aquest cas el paràmetre de la funció, de fet, no és el text, sinó un punter al seu primer caràcter. Els textos guardats d'aquesta manera tenen un byte més que el número de caràcters (la paraula "kit" ocupa quatre bytes). Aquest darrer byte és sempre un zero (caràcter '\0') i això és el que fem servir per detectar el final del text.

El mateix es podria fer guardant el text en una variable i enviant aquesta variable. Per exemple:

...
#include <xc.h>					// Carrega el fitxer de funcions necessari per al compilador XC8
#define _XTAL_FREQ  4000000			// La freqüència del rellotge és 4 MHz
char txt[] ="kit";
						// Definició de les funcions que farem servir 
void EnviaT(const char* text);			// Envia un text
void main (void) {
	ANSEL = 0b00000101;			// Configura AN0 i AN2 com entrada analògica
	ANSELH = 0;				// Desactiva les altres entrades analògiques
	TRISC = 0;				// Tot el port C és de sortida
	TRISB = 0;				// Tot el port B és de sortida
	__delay_ms(2000);			// Esperem que arrenqui la pantalla
	TXSTAbits.BRGH = 1;			// Configuració de velocitat
	BAUDCTLbits.BRG16 = 0;			// Paràmetre de velocitat de 8 bits
	SPBRG = 25;				// Velocitat de 9600 baud
	TXSTAbits.SYNC = 0;			// Comunicació asíncrona
	TXSTAbits.TX9 = 0;			// Comunicació de 8 bits
	RCSTAbits.SPEN = 1;			// Activa comunicació sèrie
	TXSTAbits.TXEN = 1;			// Activa comunicació
	EnviaT(txt);				// Envia un text
...
}
void EnviaT(const char* text) {
	for (unsigned char i = 0; text[i] != '\0'; i++) {
		EnviaL(text[i]);		// Envia caràcter a caràcter
	}
}
...

Cal tenir present, però, que el contingut de la variable txt no es pot modificar al llarg del programa.

Caràcters de control

Hi ha uns quants caràcters que permeten canviar les característiques de la pantalla o de la visualització. Cal enviar un caràcter de control FE (254 decimal) seguit del codi de control que ens fa falta. Els codis estan a la taula següent:

Primer caràcter de control Segon caràcter de control Funció
decimal hexadecimal decimal hexadecimal
254 FE 1 01 Esborra la pantalla i posa el cursor a l'inici
254 FE 20 14 Mou el cursor una posició a la dreta
254 FE 16 10 Mou el cursor una posició a l'esquerra
254 FE 28 1C Desplaça a la dreta
254 FE 24 18 Desplaça a l'esquerra
254 FE 12 0C Activa la visualització
254 FE 8 08 Desactiva la visualització
254 FE 14 0E Activa la visualització del cursor subratllat
254 FE 13 0D Activa la visualització del cursor intermitent
254 FE 12 0C Desactiva la visualització del cursor

Les adreces de la memòria són de set bits (0 a 6). Per dir-li al controlador a quina adreça volem escriure cal enviar-li un caràcter de control FE (254 decimal) seguit del valor de l'adreça però amb el bit 7 a 1. Així per demanar-li que es posi a la primera posició (adreça 0, o sigui 00000000) li enviarem el caràcter de control seguit de 10000000. Un cop li hem dit la posició i hem escrit un caràcter, el següent es posarà al costat. Per tant només hem de canviar l'adreça quan la posició no sigui consecutiva. A continuació hi ha un tros de programa com a exemple:

						// Definició de les funcions que farem servir 
void EnviaL(char Caracter);			// Envia un caràcter
void Esborra(void);				// Esborra la pantalla i posa el cursor a l'inici
void Cursor(char Filera, char Columna);		// Posiciona el cursor (filera 1 a 2 i columna 1 a 32, segons pantalla)
...
	Cursor(2, 1);				// Posició
...
void EnviaL(char Caracter) {
	TXREG = Caracter;			//  Agafa el caràcter i l'envia
	__delay_ms(1);				// Donem temps
	while (PIR1bits.TXIF == 0)		// Esperem que s'acabi d'enviar
		;				// No fem res
}
void Esborra(void) {
	EnviaL(254);				// Caràcter de control
	EnviaL(1);				// Esborra la pantalla i posa el cursor a l'inici
}
void Cursor(char Filera, char Columna) {
	char Posicio = 0;			// Variable per a calcular la posició
	if (Filera == 2) {
		Posicio = 64;			// La primera columna de la segona fila és 64;
	}
	if (Columna > 0 && Columna < 33) {	// Comprovem que sigui un valor raonable
		Posicio = Posicio + Columna;	// Sumem les adreces
		Posicio = Posicio - 1;		// Restem 1 perquè numera des de 0
	}
	Posicio = Posicio + 128;		// Posa el bit de posicionat a 1
	EnviaL(254);				// Control de la posició del cursor
	EnviaL(Posicio);			// Canvia el cursor de lloc
}

Consells d'ús

Quan s'han d'enviar paraules o altres textos predefinits el programa es pot tornar força llarg si es va posant cada caràcter com a paràmetre quan es crida la funció EnviaL. Pot ser una bona idea guardar els textos a la memòria de programa en un vector i fer una funció que els agafi seqüencialment i els enviï a la pantalla. Com a referència, es pot consultar l'exemple MP.

Per escriure valors a la pantalla caldrà passar-los a format BCD (per tenir-los separats en xifres) i convertir a ASCII cada una de les xifres.

Caràcters especials

La pantalla pot mostrar fins a 8 caràcters definits per l'usuari. Aquests caràcters són adreçables amb els codis ASCII del 0 (00h) al 7 (07h).

Per crear els caràcters cal enviar una seqüencia de vuit bytes amb el patró corresponent. Els caràcters són de 5 per 8 píxels (punts) o sigui que hem d'enviar vuit valors en els que els tres primers bits (5, 6 i 7) són 0 i els altres cinc (0 a 5) són 1 si volem el punt activat i 0 si el volem desactivat. La següent figura mostra els 8 bytes que caldrien per crear la lletra Ω.

Caràcter especial omega

Els caràcters especials es guarden a una altra part de la memòria de la pantalla. El primer caràcter s'ha de colocar a les adreces 00h a 07h, el segon a les 08h a 0Fh i així successivament. El vuitè anirà de la 38h a la 3Fh.

El vuitè byte normalment ha d'estar a zero ja que allí és on ha d'aparèixer el subratllat del cursor. És recomanable que estigui a zero perquè en alguns programes hem detectat caràcters anòmals a la pantalla si no ho estaven.

Les adreces de la memòria on es guarden els caràcters especials són de sis bits (0 a 5). Per dir-li al controlador a quina adreça volem escriure cal enviar-li un caràcter de control (254, o sigui FEh) seguit del valor de l'adreça però amb el bit 7 a 0 i el 6 a 1. Així per demanar-li que es posi a la primera posició (adreça 00h, 00000000) li enviarem el caràcter de control 01000000. Un cop li hem dit la posició i hem escrit un byte, el següent es posarà al costat. Per tant només hem de canviar l'adreça quan la posició no sigui consecutiva.

A continuació hi ha un tros de programa com a exemple:

						// Definició de les funcions que farem servir 
void EnviaL(char Caracter);			// Envia un caràcter
void Esborra(void);				// Esborra la pantalla i posa el cursor a l'inici
void Cursor(char Filera, char Columna);		// Posiciona el cursor (filera 1 a 2 i columna 1 a 32, segons pantalla)
void DefCarac(char Numero, char Fileres[8]);	// Crea un caràcter definit per l'usuari
char Omega[8] = {0b00000000, 0b00001110, 0b00010001, 0b00010001, 0b00001010, 0b00001010, 0b00011011, 0b00000000};
...
	DefCarac(0, Omega);			// Defineix caràcter
...
	Cursor(1, 1);				// Posició
	EnviaL(0);				// Omega
...	
}
void EnviaL(char Caracter) {
	TXREG = Caracter;			//  Agafa el caràcter i l'envia
	__delay_ms(1);				// Donem temps
	while (PIR1bits.TXIF == 0)		// Esperem que s'acabi d'enviar
		;				// No fem res
}
void Esborra(void) {
	EnviaL(254);				// Caràcter de control
	EnviaL(1);				// Esborra la pantalla i posa el cursor a l'inici
}
void Cursor(char Filera, char Columna) {
	char Posicio = 0;			// Variable per a calcular la posició
	if (Filera == 2) {
		Posicio = 64;			// La primera columna de la segona fila és 64;
	}
	if (Columna > 0 && Columna < 33) {	// Comprovem que sigui un valor raonable
		Posicio = Posicio + Columna;	// Sumem les adreces
		Posicio = Posicio - 1;		// Restem 1 perquè numera des de 0
	}
	Posicio = Posicio + 128;		// Posa el bit de posicionat a 1
	EnviaL(254);				// Control de la posició del cursor
	EnviaL(Posicio);			// Canvia el cursor de lloc
}
void DefCarac(char Numero, char Fileres[8]) {
	char Posicio = 0;			// Variable per a calcular la posició
	if (Numero  < 8) {			// Comprovem que sigui un valor raonable
		Posicio = Numero * 8;		// Calculem l'adreça de memòria
	}
	Posicio = Posicio + 64;			// Posa el bit de caràcter especial a 1
	EnviaL(254);				// Control de la posició del cursor
	EnviaL(Posicio);			// Canvia el cursor de lloc
	for (signed char k = 0; k < 8; k++){
		EnviaL(Fileres[k]);		// Canvia el cursor de lloc
	}
}

En aquest exemple hem fet servir EnviaL(0) per enviar la lletra omega, indicant el codi ASCII 0. Podríem haver fet servir també EnviaL('\0') amb el mateix resultat. En el cas d'enviar text, podem enviar el caràcter especial guardat a la posició 1 així:

...
	EnviaT("k\1it");				// Envia un text
...
}

i funcionaria. Però, en canvi, no podem enviar el caràcter especial 0 d'aquesta manera:

...
	EnviaT("k\0it");				// Envia un text
...
}

perquè '\0' és com es codifica el final del text. Si fem això, rebrem només la k i no la resta de caràcters. Només es poden posar en forma de text els caràcters especials de l'1 al 7, no el 0. No obstant, si necessitem menys de vuit caràcters especials, podem guardar-los a partir de la posició 1 i ja no tenim cap problema. En cas de necessitar el 0, caldrà enviar-lo com a caràcter, no dins un text.

Lluminositat de la pantalla

En algunes versions de la pantalla, no en totes, es pot variar la seva lluminositat. Per canviar la lluminositat de la pantalla s'envia el caràcter de control 7Ch (124 decimal) seguit d'un valor decimal entre 128 i 157; per tant, tenim 30 possibles valors de la lluminositat. A la taula següent tenim alguns valors de lluminositat aproximats:

Caràcter de control Valor Lluminositat
decimal hexadecimal decimal hexadecimal
124 7C 128 80 0 % (apagat)
124 7C 136 88 25 %
124 7C 143 8F 50 %
124 7C 151 97 75 %
124 7C 157 9D 100 %

Canvi de la configuració

El controlador de la pantalla porta configurat, de fàbrica, el tipus de pantalla al qual està connectat. Aquesta configuració està guardada en una memòria no volàtil i, per tant, és permanent. Esporàdicament, però, pot succeir que aquesta configuració es malmeti. En aquest cas, podem fer servir les següents comandes per ajustar els paràmetres apropiats:

Primer caràcter de control Segon caràcter de control Funció
decimal hexadecimal decimal hexadecimal
124 7C 3 03 Amplada de vint caràcters
124 7C 4 04 Amplada de setze caràcters
124 7C 5 05 Quatre línies
124 7C 6 06 Dues línies

Selecció de la velocitat

En algunes aplicacions pot ser necessari modificar la velocitat del canal sèrie que fem servir per enviar els caràcters a la pantalla. Cal tenir present que immediatament després de rebre el segon caràcter de la comanda es canviarà la velocitat i, per tant, els següents caràcters s'hauran d'enviar a la nova velocitat seleccionada. Par a canviar la velocitat podem fer servir les següents comandes:

Primer caràcter de control Segon caràcter de control Funció
decimal hexadecimal decimal hexadecimal
124 7C 11 0B 2400 bits per segon (baud)
124 7C 12 0C 4800 bits per segon (baud)
124 7C 13 0D 9600 bits per segon (baud)
124 7C 14 0E 14400 bits per segon (baud)
124 7C 15 0F 19200 bits per segon (baud)
124 7C 16 10 38400 bits per segon (baud)
18 12 Reinicia a 9600 bits per segon

Aquesta comanda està pensada per a aquells casos en els quals desconeixem quina és la velocitat que hi ha configurada en un moment donat. Enviant el caràcter 12h (18 en decimal) durant els 500 ms posteriors a l'alimentació de la pantalla es produirà un reinici de la velocitat a 9600 bits per segon. Aquest caràcter s'ha d'enviar a 9600 baud, independentment de quina sigui la velocitat que hi ha configurada. És recomanable tornar a reiniciar la pantalla després de fer servir aquesta comanda.

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.