;****************************************************************** ; * ; Filename: 16F690_LCD_I2C_Backpack_x2.asm * ; Author: Mike McLaren, K8LH * ; (C)2018: Micro Applications Consultants * ; Date: 31-Jul-2018 * ; * ; * ; 16F690 bit-banged I2C demo for PCF8574 I2C LCD Backpack and * ; HD44780 type LCD display. Only basic I2C 'start', 'write', * ; and 'stop' routines included (enough to drive the backpack) * ; as well as LCD 'init', 'putCmd', and 'putDat' routines. * ; * ; * ; MPLab: 8.92 (tabs=8) Target Device: 16F690 * ; MPAsm: 5.51 (absolute mode) * ; * ;****************************************************************** #include list st=off errorlevel -302 radix dec __config _FCMEN_OFF& _IESO_OFF& _MCLRE_OFF& _WDT_OFF& _INTOSCIO ; _FCMEN_OFF ; -- fail safe clock monitor enable off ; _IESO_OFF ; -- int/ext switch over enable off ; _BOR_ON ; default, brown out reset on ; _CPD_OFF ; default, data eeprom protection off ; _CP_OFF ; default, program code protection off ; _MCLR_OFF ; -- use MCLR pin as digital input ; _PWRTE_OFF ; default, power up timer off ; _WDT_OFF ; -- watch dog timer off ; _INTOSCIO ; -- internal osc, OSC1 and OSC2 I/O ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; variables ; cblock 0x20 workvar ; putCmd(), putDat(), putNyb() endc cblock 0x70 loopctr ; iic_write() payload ; iic_write() delayhi ; DelayCy() subsystem variable endc rsflag equ delayhi ; putCmd(), putDat(), putNyb() ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; define 'scl' and 'sda' port and pins ; iic_tris equ TRISC ; TRIS reg for scl/sda pins iic_port equ PORTC ; PORT reg for scl/sda pins sclndx equ RC0 ; index for scl pin (RC0) sdandx equ RC1 ; index for sda pin (RC1) ; ; LCD DDRAM address constants ; line1 equ 128+0 ; LCD "line 1" command line2 equ 128+64 ; LCD "line 2" command ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; LCD helper macros ; putNyb macro param ; movlw param ; |B0 call lcdNyb ; |B0 endm putCmd macro param ; movlw param ; |B0 call lcdCmd ; |B0 endm putDat macro param ; movlw param ; |B0 call lcdDat ; |B0 endm ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; K8LH DelayCy() subsystem macro generates four instructions ~ ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ radix dec clock equ 8 ; 4, 8, 12, 16, 20 (MHz), etc. usecs equ clock/4 ; cycles/microsecond multiplier msecs equ usecs*1000 ; cycles/millisecond multiplier dloop equ 5 ; loop size, 5 to 8 cycles ; ; -- loop -- -- delay range -- -- memory overhead ---------- ; 5-cyc loop, 11..327690 cycles, 9 words (+4 each macro call) ; 6-cyc loop, 11..393226 cycles, 10 words (+4 each macro call) ; 7-cyc loop, 11..458762 cycles, 11 words (+4 each macro call) ; 8-cyc loop, 11..524298 cycles, 12 words (+4 each macro call) ; DelayCy macro cycles ; range, see above if (cycles<11)|(cycles>(dloop*65536+10)) error " DelayCy range error " else movlw high((cycles-11)/dloop)+1 movwf delayhi movlw low ((cycles-11)/dloop) ; rcall uLoop-(((cycles-11)%dloop)*2) ; (18F version) call uLoop-((cycles-11)%dloop) ; (16F version) endif endm ;****************************************************************** ; reset vector * ;****************************************************************** org 0x000 v_res clrf STATUS ; force bank 0 and IRP = 0 |B0 goto setup ; |B0 ;****************************************************************** ; interrupt vector * ;****************************************************************** org 0x004 v_irq ;****************************************************************** ; main setup * ;****************************************************************** setup bcf STATUS,RP0 ; | bsf STATUS,RP1 ; bank 2 |B2 clrf ANSEL ; turn off analog pin functions |B2 clrf ANSELH ; |B2 bcf STATUS,RP1 ; bank 0 |B0 bsf STATUS,RP0 ; bank 1 |B1 movlw b'01110000' ; setup INTOSC for 8-MHz |B1 movwf OSCCON ; |B1 btfss OSCCON,HTS ; osc stable? yes, skip, else |B1 goto $-1 ; test again |B1 clrf TRISC ; RC5..RC0 all outputs |B1 clrf TRISB ; RB7..RB4 all outputs |B1 clrf TRISA ; all outputs, except RA3 |B1 bcf STATUS,RP0 ; bank 0 |B0 clrf PORTC ; |B0 clrf PORTB ; |B0 ; ; setup I2C interface, scl = RC0, sda = RC1 ; bsf STATUS,RP0 ; bank 1 |B1 bsf iic_tris,sclndx ; make scl pin input |B1 bsf iic_tris,sdandx ; make sda pin input |B1 bcf STATUS,RP0 ; bank 0 |B0 ; bcf iic_port,sclndx ; scl output latch = 0 |B0 ; bcf iic_port,sdandx ; sda output latch = 0 |B0 ; ; LCD "initialize by instruction" procedure for 4-bit interface ; DelayCy(30*msecs) ; delay 30-msecs after power up |B0 putNyb (0x30) ; function set: 8-bit |B0 DelayCy(5*msecs) ; required 5-msec delay |B0 putNyb (0x30) ; function set: 8-bit |B0 ; n/a DelayCy(160*usecs) ; required 160-usec delay |B0 putNyb (0x30) ; function set: 8-bit |B0 ; n/a DelayCy(160*usecs) ; required 160-usec delay |B0 putNyb (0x20) ; function set: 4-bit |B0 ; n/a DelayCy(160*usecs) ; required 160-usec delay |B0 ; ; now we're in 4-bit mode and can handle 8-bit transactions by ; sending both hi & lo nibbles using putCmd & putDat macros. ; putCmd (0x28) ; 4-bit, 2-lines, 5x7 font |B0 putCmd (0x08) ; display, cursor, blink all off |B0 putCmd (0x01) ; clear display |B0 DelayCy(1530*usecs) ; required 1.53-msec delay |B0 putCmd (0x06) ; cursor inc, shift off |B0 putCmd (0x0C) ; display on, leave cursor off |B0 ;****************************************************************** ; main loop * ;****************************************************************** loop putCmd (line1+2) ; LCD line 1, column 3 |B0 putDat ('H') ; |B0 putDat ('e') ; |B0 putDat ('l') ; |B0 putDat ('l') ; |B0 putDat ('o') ; |B0 putDat (' ') ; |B0 putDat ('W') ; |B0 putDat ('o') ; |B0 putDat ('r') ; |B0 putDat ('l') ; |B0 putDat ('d') ; |B0 putDat ('!') ; |B0 goto $ ; ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; low-level LCD drivers for PCF8574 based I2C LCD backpack ~ ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ lcdCmd ; entry point for "cmd" data clrf rsflag ; wreg = data, RS = 0 (cmd) |B0 skpz ; skip |B0 lcdDat ; entry point for "dat" data bsf rsflag,0 ; wreg = data, RS = 1 (data) |B0 call lcdNyb ; send hi nybble |B0 swapf workvar,W ; send lo nybble |B0 ; ; ~~ 'lcdNyb' sequence ~~~~~~~~~ ~~< PCF8574 format >~~~ ; () I2C 'start' + I2C 'address' P0(b0) -> LCD 'RS' ; () write nibble, E = 1 P1(b1) -> LCD 'RW' (0) ; () write nibble, E = 0 P2(b2) -> LCD 'E' ; () I2C 'stop' P3(b3) -> LCD Backlight ; P4(b4) -> LCD 'D4' ; ~617-usecs (isochronous) for P5(b5) -> LCD 'D5' ; each lcdCmd and lcdDat call. P6(b6) -> LCD 'D6' ; P7(b7) -> LCD 'D7' lcdNyb movwf workvar ; save temporarily |B0 movlw 0x4E ; PCF8574 I2C address + 0 (wr) |B0 call iic_start ; I2C 'start' + 'address' + 'rw' |B0 movf workvar,W ; |B0 andlw 0xF0 ; use left nybble (b7..b4) bits |B0 btfsc rsflag,0 ; RS = 0? yes, skip, else |B0 iorlw b'00000001' ; RS(b0) = 1 |B0 iorlw b'00001100' ; Backlight(b3) = 1, E(b2) = 1 |B0 call iic_write ; data + bl, en, rw, rs bits |B0 xorlw b'00000100' ; clr E bit (b2) |B0 call iic_write ; data + bl, en, rw, rs bits |B0 goto iic_stop ; I2C 'stop' |B0 ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; bit-banged I2C backpack macros and low-level driver functions ~ ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sda macro param ; 5 cycles (2.5-us @ 8-MHz clock) bsf STATUS,RP0 ; bank 1 |B1 if param == 0 bcf iic_tris,sdandx ; sda = 0 |B1 else bsf iic_tris,sdandx ; sda = 1 |B1 endif movf PCL,F ; 2 cycles |B1 nop ; |B1 endm ; ; scl(0) -> output, save wreg to 'payload' variable, 3 cycles ; scl(1) -> hi-z input, clear carry flag, 5 cycles ; scl macro param ; bsf STATUS,RP0 ; bank 1 |B1 if param == 0 ; scl(0) uses 3 cycles movwf payload ; payload = wreg |B1 bcf iic_tris,sclndx ; scl = 0 (output '0') |B1 else ; scl(1) uses 5 cycles movf PCL,F ; 2 cycles |B1 clrc ; 1 cycle |B1 bsf iic_tris,sclndx ; scl = 1 (hi-z input) |B1 endif endm ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; I2C 'Start' condition. WREG = I2C address << 1 + r/w (1 or 0) ; iic_start scl(1) ; assure 'idle' condition |B1 sda(1) ; " |B1 sda(0) ; sda = 0 |B1 ; scl(0) ; scl = 0 |B1 ; bcf STATUS,RP0 ; bank 0 |B0 ; return ; |B0 ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; I2C 'write' (Rd/Wr) byte. maintain 10 cycle (5.0-us) 'scl' ; pin transitions for a 100-kHz I2C clock. ; ; enter with wreg = write value or 0xFF if reading a byte ; from slave device. wreg is saved to 'payload' variable in ; the scl(0) macro. ; ; 190 cycles (95.0-us), including call & return (isochronous) ; iic_write clrf loopctr ; loopctr = 8 (preserve wreg) |Bx bsf loopctr,3 ; " |Bx ; ; write and read 8 bits. send 0xFF to read and return 8-bits ; from the I2C slave. the return value is collected directly ; from the 'sda' pin so when writing to the slave device the ; return value is the same as the value that was sent. ; rdwrbit scl(0) ; scl = 0 (3 cycle macro) < |B1 movf iic_tris,W ; |B1 iorlw 1< 0 nop ; (cycles-11)%dloop entry points |00 a -= 1 endw uLoop addlw -1 ; subtract 'dloop' loop time |00 skpc ; borrow? no, skip, else |00 decfsz delayhi,F ; done? yes, skip, else |00 ; bra uLoop-dloop*2+10 ; do another loop (18F version) | goto uLoop-dloop+5 ; do another loop (16F version) |00 return ; |00 ;****************************************************************** end