LIST PROCESSOR 16C84 __CONFIG _CP_OFF & _WDT_OFF & _XT_OSC ;***************************************************************************** ; ; Questo programma interfaccia l'IC decoder DTMF Mitel MT8870 con un ; display LCD alfanumerico basato sul controller Hitachi HD44780. ; In questa versione 1 linea * 8 caratteri. ; Il programma legge i dati dal decoder e li invia sul display usando un ; buffer circolare (quando si raggiunge il limite superiore, si ricomincia ; dall'inizio sovrascrivendo i vecchi dati) di grandezza definibile (nei ; limiti della ram disponibile). ; Con un pulsante è possibile far scorrere i dati sul display. ; Questo programma usa il formato di interfaccia a 4 bit (High Nibble). ; Vedere l'application note AN587 sul sito www.microchip.com per una completa ; descrizione delle interfacce a 4 e 8 bit. ; ; Programma LCD.ASM ; Ultimo aggiornamento 28/12/98 ; Autore Enrico Mazzetti ; ;***************************************************************************** ; Fosc = 4MHz include RESET_V EQU 0x0000 ; indirizzo del vettore di RESET ISR_V EQU 0x0004 ; indirizzo del vettore di Interrupt OSC_FREQ EQU D'4000000' ; La frequenza dell'oscillatore è 4 MHz LCD_DATA EQU PORTB ; definizione della porta dati LCD_DATA_TRIS EQU TRISB LCD_CTRL EQU PORTB ; definizione della porta di controllo LCD_CTRL_TRIS EQU TRISB ; (in questo caso è la stessa) ; PORTA bits BUTTON EQU 4 ; collegamento al pulsante di scorrimento ; PORTB bits DB7 EQU 7 ; LCD dataline 7 (Nibble MSBit) DB6 EQU 6 ; LCD dataline 6 DB5 EQU 5 ; LCD dataline 5 DB4 EQU 4 ; LCD dataline 4 (Nibble LSBit) LCD_RW EQU 3 ; LCD Read/Write control line LCD_RS EQU 2 ; LCD Register-Select control line LCD_E EQU 1 ; LCD Enable control line DB0 EQU 0 ; interrupt dal decoder ; miscellanea MAXBUFF EQU 0x0F ; lunghezza del buffer dati nel pic STARTBUF EQU 0x01A ; indirizzo di partenza del buffer dati ENDBUFF EQU STARTBUF+MAXBUFF; indirizzo di fine del buffer dati STARTDIS EQU 0x00 ; indirizzo di partenza della ram nel display MAXDISP EQU MAXBUFF ; lunghezza della zona dati nel display ;variabili ORG 0x0C ; saltiamo la zona dei registri del pic LCD_TEMP RES 1 ; variabile di servizio LCD_TEM1 RES 1 ; variabile di servizio DELAY RES 1 ; usata nella routine DELAYxxx X_DELAY RES 1 ; usata nella routine X_DELAYxxx BPOINTER RES 1 ; puntatore per la gestione del buffer dati BCOUNT RES 1 ; contatore per la gestione del buffer dati DPOINTER RES 1 ; puntatore al carattere nella ram del display DCOUNT RES 1 ; contatore per la gestione della ram del display BFLAG RES 1 ; segnala dati disponibili nel buffer dati ;***************************************************************************** ; Program start ;***************************************************************************** ORG RESET_V ; indirizzo del vettore di RESET RESET GOTO START ;***************************************************************************** ; Routine di gestione dell'interrupt ;***************************************************************************** ORG ISR_V ; indirizzo del vettore di Interrupt INTERRUPT MOVFW BPOINTER ; carica il valore attuale del puntatore MOVWF FSR ; lo copia in FSR per l'indirizzamento indiretto MOVFW PORTA ; prende il dato in ingresso MOVWF INDF ; lo mette nel buffer INCF BPOINTER,F ; incrementa il puntatore DECFSZ BCOUNT,F ; decrementa il contatore GOTO DONE ; se > 0 va alle operazioni finali MOVFW STARTBUF ; altrimenti 'chiude' il buffer circolare MOVWF BPOINTER ; riposiziona il puntatore MOVLW MAXBUFF MOVWF BCOUNT ; ricarica il contatore DONE INCF BFLAG,F ; incrementa il flag dati pronti BCF INTCON,INTF ; azzera il flag di interrupt da RB0 RETFIE ; ritorna con interrupt abilitati ;***************************************************************************** ; Inizializzazioni: registri del pic ;***************************************************************************** START ; POWER_ON Reset (inizio del programma) CLRF STATUS ; seleziona il banco 0 dei registri CLRF INTCON ; disabilita gli interrupt CLRF PCLATH ; si mantiene nei primi 2K BSF STATUS,RP0 ; seleziona il banco 1 dei registri MOVLW 0x0FF ; RA4-0 come input MOVWF TRISA MOVLW 0x01 ; RB7-1 come output, RB0 input MOVWF TRISB BSF OPTION_REG,NOT_RBPU ; disabilita i PORTB pull-up BSF OPTION_REG,INTEDG ; fronte di salita per l'interrupt RB0 BCF STATUS,RP0 ; seleziona il banco 0 dei registri CLRF PORTB ; azzera tutte le uscite della PORTB BSF PORTA,BUTTON ; mette alto RA4 CALL LCDINIT ; va a inizializzare il display ;***************************************************************************** ; Inizializzazioni: buffer dati ;***************************************************************************** BUFFINIT MOVLW MAXBUFF MOVWF BCOUNT ; settaggio iniziale del contatore MOVLW STARTBUF MOVWF BPOINTER ; settaggio iniziale del puntatore MOVWF FSR ; lo copia in FSR per l'indirizzamento indiretto NEXT CLRF INDF ; azzera l'elemento puntato INCF FSR,F ; incrementa il puntatore DECFSZ BCOUNT,F ; incrementa il puntatore GOTO NEXT ; se > 0 prossimo elemento CLRF BFLAG ; altrimenti fine e azzera il flag dati pronti ; notare che il puntatore punta ancora all'inizio del buffer MOVLW MAXBUFF MOVWF BCOUNT ; ricarica anche il contatore ;***************************************************************************** ;inizializzazioni: puntatore e contatore per la gestione del display ;***************************************************************************** MOVLW STARTDIS ; MOVWF DPOINTER ; puntatore al primo carattere nel display MOVLW MAXDISP ; MOVWF DCOUNT ; carica il contatore ;***************************************************************************** ;Comincia ad attendere gli interrupt ;***************************************************************************** BSF INTCON,INTE ; abilita l'interrupt on change su RB0 BSF INTCON,GIE ; abilitazione globale degli interrupt ;***************************************************************************** ;legge i dati dal buffer e li visualizza ;***************************************************************************** DISPLAY MOVFW BFLAG ; verifica se ci sono nuovi dati nel buffer BZ SCROLL ; se no (flag=0) va alla routine di scorrimento MOVFW DPOINTER ; carica il puntatore al carattere attuale CALL LCDSDDA ; setta il display per questo MOVFW DPOINTER ; carica il puntatore al carattere attuale (ancora) ADDLW STARTBUF ; e ci somma l'inizio del buffer ; notare quindi che si hanno 2 puntatori: ; 1 = (bpointer) per la scrittura nel buffer da parte della routine di interrupt ; 2 = (dpointer + startbuf) per la lettura dei dati da parte di questa routine MOVWF FSR ; copia quest'ultimo in FSR per l'indirizzamento indiretto MOVFW INDF ; legge il dato puntato nel buffer ANDLW 0x0F ; ne toglie i bit alti (lo stato del pulsante) CALL MSG1 ; lo usa per generare il giusto carattere con la tabella CALL LCDPUTCHAR ; lo manda al display INCF DPOINTER,F ; incrementa il puntatore DECFSZ DCOUNT,F ; decrementa il contatore, salta se zero GOTO DDONE ; va subito alla fase finale, oppure... MOVFW STARTDIS ; reinizializza puntatore e contatore MOVWF DPOINTER ; puntatore al primo carattere nel display MOVLW MAXDISP ; MOVWF DCOUNT ; carica il contatore ; DDONE DECF BFLAG,F ; decrementa il flag dei dati pronti GOTO DISPLAY ; ricomincia ;***************************************************************************** ;routine per lo scorrimento del display ;***************************************************************************** SCROLL BTFSC PORTA,BUTTON ; pulsante premuto ? GOTO DISPLAY ; no, ritorna a vedere se ci sono dati MOVLW 0x0C8 ; sì, carica 200 (decimale) CALL X_DELAY500 ; aspetta circa 100 mS BTFSC PORTA,BUTTON ; pulsante (ancora) premuto ? GOTO SCROLL ; no, riprova MOVLW 0x018 ; scorre il display di 1 carattere a sinistra CALL LCDPUTCMD ; manda questo comando al display GOTO DISPLAY ; ricomincia ; ;***************************************************************************** ; Routine(s) per la gestione del display ;***************************************************************************** ; ; ;***************************************************************************** ; Inizializza il display (anche nel caso non eseguisse correttamente il reset) ;***************************************************************************** LCDINIT ; per capire meglio vedere il datasheet del display. MOVLW 0x01 ; Qui il flag busy non è ancora valido ANDWF LCD_CTRL,F ; CTRL PORT (RB3-1) deve essere 'bassa' ; ritardo per il power-up MOVLW 0x01E CALL X_DELAY500 ; 30 * 0.5mS = 15mS MOVLW 0x030 ; IORWF LCD_DATA,F ; CALL LCD_ENABLE ; impulso sull'ingresso di comando E MOVLW 0x0A ; CALL X_DELAY500 ; aspetta più di 4.1 mS CALL LCD_ENABLE ; impulso sull'ingresso di comando E MOVLW 0x01 ; CALL X_DELAY500 ; aspetta più di 0.1 mS CALL LCD_ENABLE ; impulso sull'ingresso di comando E ; Qui il flag busy dovrebbe essere valido MOVLW 0x020 ; interfaccia su 4 bit CALL LCDPUTCMD MOVLW 0x020 ; interfaccia su 4 bit & 1 linea,font set 1 CALL LCDPUTCMD MOVLW 0x000 ; display off, cursore off, no-blink CALL LCDDMODE CALL LCDCLEAR MOVLW 0x004 ; display off, cursore off CALL LCDDMODE MOVLW 0x02 ; auto-incremento CALL LCDEMODE RETURN ; fatto, ritorna ;***************************************************************************** ; LCD_ENABLE ; impulso sull'ingresso di comando E ;***************************************************************************** LCD_ENABLE BSF LCD_CTRL,LCD_E ; linea di comando E 'alta' BCF LCD_CTRL,LCD_E ; linea di comando E 'bassa' RETURN ; fatto, ritorna ;***************************************************************************** ; LCDBUSY ; Testa il flag busy, torna quando il flag è azzerato ; Agisce su: LCD_TEM1 - Returna con il flag e l'indirizzo interno attuale ;***************************************************************************** ; LCDBUSY BSF STATUS,RP0 ; seleziona il banco 1 dei registri MOVLW 0x0F0 ; Predispone il nibble alto di PORTB per l'input IORWF LCD_DATA_TRIS,F ; il nibble basso rimane inalterato BCF STATUS,RP0 ; seleziona il banco 0 dei registri BCF LCD_CTRL,LCD_RS ; predispone il display per un Comando BSF LCD_CTRL,LCD_RW ; predispone il display per lettura RETRY BSF LCD_CTRL,LCD_E ; alza E MOVF LCD_DATA,W ; Legge il nibble alto di (busy flag+DDRam address) BCF LCD_CTRL,LCD_E ; abbassa E ANDLW 0xF0 ; toglie la parte bassa MOVWF LCD_TEM1 ; copia la parte alta su TEM1 BSF LCD_CTRL,LCD_E ; alza E, poi *scambiando* i nibble di PORTB ... SWAPF LCD_DATA,W ; legge il nibble basso di (busy flag+DDRam address) BCF LCD_CTRL,LCD_E ; abbassa E ANDLW 0x0F ; toglie la parte alta IORWF LCD_TEM1,F ; Combina insieme i nibble su TEM1 BTFSC LCD_TEM1,7 ; Testa il flag busy (1 = busy) GOTO RETRY ; se 1 riprova, se no ... BCF LCD_CTRL,LCD_RW ; predispone il display per scrittura BSF STATUS,RP0 ; seleziona il banco 1 dei registri MOVLW 0x0F ANDWF LCD_DATA_TRIS,F ; Predispone il nibble alto di PORTB per l'output BCF STATUS,RP0 ; seleziona il banco 0 dei registri RETURN ; fatto, ritorna ; ;***************************************************************************** ; LCDCLEAR ; Pulisce il display e posiziona sulla posizione home (angolo a sinistra, in alto). ;***************************************************************************** ; LCDCLEAR MOVLW 0x001 CALL LCDPUTCMD RETURN ; ;***************************************************************************** ; LCDHOME ; riposiziona sulla posizione home o rimette sulla posizione iniziale (quando si è fatto scorrere). ;***************************************************************************** ; LCDHOME MOVLW 0x002 CALL LCDPUTCMD RETURN ; ;***************************************************************************** ; LCDEMODE ; imposta l'entry mode del display. Il modo va caricato prima in W ; b0 : 0 = no display shift 1 = display shift ; b1 : 0 = auto-decrement 1 = auto-increment ; b2-7 : non utilizzati ;***************************************************************************** ; LCDEMODE ANDLW 0x003 ; toglie i 6 bit più alti IORLW 0x004 ; imposta l'entry mode CALL LCDPUTCMD ; invia il comando RETURN ; fatto, ritorna ; ;***************************************************************************** ; LCDDMODE ; imposta i controlli del display. Il modo va caricato prima in W ; b0 : 0 = cursor blink off 1 = cursor blink on ; b1 : 0 = cursor off 1 = cursor on ; b2 : 0 = display off 1 = display on (i dati rimangono nella DDRAM) ; b3-7 : non utilizzati ;***************************************************************************** ; LCDDMODE ANDLW 0x007 ; toglie i 5 bit più alti IORLW 0x008 ; imposta la funzione CALL LCDPUTCMD ; invia il comando RETURN ; fatto, ritorna ; ;***************************************************************************** ; LCDSCGA ; Imposta l'indirizzo della ram della mappa dei caratteri.La CGRAM è letta o scritta dopo ; questa impostazione. L'indirizzo deve essere prima messo in W. ; b0-5 : Indirizzo richiesto per la CGRAM ; b6-7 : non utilizzati ;***************************************************************************** ; LCDSCGA ANDLW 0x03F ; toglie i bit più alti IORLW 0x040 ; imposta la funzione CALL LCDPUTCMD ; invia il comando RETURN ; fatto, ritorna ; ;***************************************************************************** ; LCDSDDA ; Imposta l'indirizzo della ram dati del display. La DDRAM è letta o scritta dopo ; questa impostazione. L'indirizzo deve essere prima messo in W. ; b0-6 : Indirizzo richiesto per la DDRAM ; b7 : non utilizzato ;***************************************************************************** ; LCDSDDA ANDLW 0x07F ; toglie il bit più alto IORLW 0x080 ; imposta la funzione CALL LCDPUTCMD ; invia il comando RETURN ; fatto, ritorna ; ;***************************************************************************** ; LCDPUTCHAR ; Invia un carattere al display. Il carattere deve essere messo prima in W ;***************************************************************************** ; LCDPUTCHAR MOVWF LCD_TEMP ; salva il dato in temp CALL LCDBUSY ; aspetta che il display sia pronto MOVLW 0x0F ; ANDWF LCD_DATA,F ; azzera data port, ctrl port immutata BSF LCD_CTRL, LCD_RS; imposta il display in modo dati BCF LCD_CTRL, LCD_RW; imposta il display in scrittura BSF LCD_CTRL, LCD_E ; alza E MOVF LCD_TEMP, W ; riprende il dato da temp ANDLW 0xF0 ; prende solo il nibble alto IORWF LCD_DATA,F ; lo manda al display BCF LCD_CTRL, LCD_E ; abbassa E MOVLW 0x0F ANDWF LCD_DATA,F ; azzera data port, ctrl port immutata BSF LCD_CTRL, LCD_E ; alza E SWAPF LCD_TEMP,W ; riprende il dato da temp, scambiando i nibble ANDLW 0xF0 ; prende solo il nibble alto (ma è il basso) IORWF LCD_DATA,F ; lo manda al display BCF LCD_CTRL, LCD_E ; abbassa E RETURN ; fatto, ritorna ; ;***************************************************************************** ; LCDPUTCMD ; Invia un comando al display. Il comando deve essere messo prima in W ;***************************************************************************** ; LCDPUTCMD MOVWF LCD_TEMP ; salva il comando in temp CALL LCDBUSY ; aspetta che il display sia pronto MOVLW 0x0F ANDWF LCD_DATA,F ; azzera data port, ctrl port immutata BCF LCD_CTRL, LCD_RW; imposta il display in lettura BCF LCD_CTRL, LCD_RS; imposta il display in modo comando BSF LCD_CTRL, LCD_E ; alza E MOVF LCD_TEMP,W ; riprende il comando da temp ANDLW 0xF0 ; prende solo il nibble alto IORWF LCD_DATA,F ; lo manda al display BCF LCD_CTRL, LCD_E ; abbassa E MOVLW 0x0F ANDWF LCD_DATA,F ; azzera data port, ctrl port immutata BSF LCD_CTRL, LCD_E ; LCD E-line High SWAPF LCD_TEMP,W ; riprende il comando da temp, scambiando i nibble ANDLW 0xF0 ; prende solo il nibble alto (ma è il basso) IORWF LCD_DATA,F ; lo manda al display BCF LCD_CTRL, LCD_E ; abbassa E RETURN ; fatto, ritorna ;***************************************************************************** ; Routine per generare un ritardo prefissato ;***************************************************************************** ; Delay_time = ((DELAY_value * 3) + 4) * Cycle_time ; DELAY_value = (Delay_time - (4 * Cycle_time)) / (3 * Cycle_time) ; ; i.e. (@ 4MHz crystal) ; Delay_time = ((32 * 3) + 4) * 1uSec ; = 100uSec ; DELAY_value = (500uSec - 4) / 3 ; = 165.33 ; = 165 ;***************************************************************************** DELAY500 MOVLW D'165' ; +1 1 ciclo MOVWF DELAY ; +2 1 ciclo DELAY500_LOOP DECFSZ DELAY, F ; step 1 1 ciclo GOTO DELAY500_LOOP ; step 2 2 cicli DELAY500_END RETURN ; +3 2 cicli ; ; X_DELAY500 MOVWF X_DELAY ; +1 1 ciclo X_DELAY500_LOOP CALL DELAY500 ; step1 aspetta 500uSec DECFSZ X_DELAY, F ; step2 1 ciclo GOTO X_DELAY500_LOOP ; step3 2 cicli X_DELAY500_END RETURN ; +2 2 cicli ; ;***************************************************************************** ; Tabella dei caratteri per la conversione dati->caratteri ;***************************************************************************** MSG1 addwf PCL ,F ; salta al carattere il cui valore sta in W retlw 'Z' ; carattere per dato=0 (non usato) retlw '1' ; carattere per dato=1 retlw '2' ; carattere per dato=2 retlw '3' ; carattere per dato=3 retlw '4' ; carattere per dato=4 retlw '5' ; carattere per dato=5 retlw '6' ; carattere per dato=6 retlw '7' ; carattere per dato=7 retlw '8' ; carattere per dato=8 retlw '9' ; carattere per dato=9 retlw '0' ; carattere per dato=10 retlw '*' ; carattere per dato=11 retlw '#' ; carattere per dato=12 retlw 'A' ; carattere per dato=13 retlw 'B' ; carattere per dato=14 retlw 'C' ; carattere per dato=15 retlw 'D' ; carattere per dato=16 retlw 'X' ; carattere per dato=17 (impossibile) MSG1_END retlw 0 ; controllo che la tabella usata non sia più grande di una pagina IF ( (MSG1 & 0x0FF) >= (MSG1_END & 0x0FF) ) MESSG "Attenzione - Tabella MSG1 definita dall'utente attraversa i confini di pagina nel salto calcolato" ENDIF END ; Fine del programma