I'm trying to integrate Mike K8LH's i2c software in to a program so I can check where something is going wrong by displaying various registers on an LCD.
Now, as my program already has it's own arrangement to generate time delays, I tried to modify the i2c part of the program to use my delays.
Doing this causes it to stop working on real hardware and I'm going spare trying to work out what is going on, simulating the program with my delays shows it doing all the right things but the LCD is not initialising.
The line I changed that is causing the problem is highlighted in red in "; LCD "initialize by instruction procedure for 4-bit interface" section.
The problem bit is a 5mS delay - exactly 5mS with original code and 5.19mS with my change and all it does as far as I can see is to put a delay between the first two instructions for initialising the display.
My delay code is right at the end of the program.
Note that the following is just the original K8LH code with my change and it also suffers the same problem and no, I don't have anything better to do on Christmas Day
Now, as my program already has it's own arrangement to generate time delays, I tried to modify the i2c part of the program to use my delays.
Doing this causes it to stop working on real hardware and I'm going spare trying to work out what is going on, simulating the program with my delays shows it doing all the right things but the LCD is not initialising.
The line I changed that is causing the problem is highlighted in red in "; LCD "initialize by instruction procedure for 4-bit interface" section.
The problem bit is a 5mS delay - exactly 5mS with original code and 5.19mS with my change and all it does as far as I can see is to put a delay between the first two instructions for initialising the display.
My delay code is right at the end of the program.
Note that the following is just the original K8LH code with my change and it also suffers the same problem and no, I don't have anything better to do on Christmas Day
Code:
list n=0
;******************************************************************
; *
; 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) *
; *
;******************************************************************
list P=PIC16F628A
; #include <P16F690.INC>
#include <P16F628A.INC>
errorlevel -302
radix dec
; __config _FCMEN_OFF& _IESO_OFF& _MCLRE_OFF& _WDT_OFF& _INTOSCIO
__config _CP_OFF&_LVP_OFF&_BODEN_OFF&_MCLRE_OFF&_PWRTE_ON&_WDT_OFF&_INTOSC_OSC_NOCLKOUT
; _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
delay1
delay2
delay3
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)
iic_tris equ TRISA ; TRIS reg for scl/sda pins
iic_port equ PORTA ; PORT reg for scl/sda pins
sclndx equ RA6 ; index for scl pin (RC0)
sdandx equ RA7 ; 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 4 ; 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
banksel TRISA
clrf TRISB ; RB7..RB4 all outputs |B1
clrf TRISA ; all outputs, except RA3 |B1
banksel 0
clrf PORTA ; |B0
clrf PORTB ; |B0
movlw 7
movwf CMCON
;
; setup I2C interface, scl = RC0, sda = RC1
;
banksel TRISA
bsf iic_tris,sclndx ; make scl pin input |B1
bsf iic_tris,sdandx ; make sda pin input |B1
banksel 0 ; 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
movlw 50
call w_mS
putNyb (0x30) ; function set: 8-bit |B0
; DelayCy(5*msecs) ; required 5-msec delay |B0
movlw 5
call w_mS
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<<sdandx ; sda = 1 |B1
btfss payload,7 ; msb = 1? yes, skip, else |B1
xorlw 1<<sdandx ; sda = 0 |B1
movwf iic_tris ; < |B1
scl(1) ; scl = 1, carry = 0 < |B1
bcf STATUS,RP0 ; bank 0 |B0
rlf payload,W ; shift a '0' bit into payload |B0
btfsc iic_port,sdandx ; SDA pin = 0? yes, skip, else |B0
iorlw b'00000001' ; make it a '1' |B0
decfsz loopctr,F ; done? yes, skip, else |B0
goto rdwrbit ; branch (next bit) |B0
nop ; |B0
scl(0) ; scl = 0 (3 cycle macro) < |B1
;
; 9th clock for ACK / NACK. this section needs work if you
; want to support both write and read operations.
;
sda(1) ; sda = 1 (hi-z input) |B1
scl(1) ; scl = 1 (9th clock) |B1
nop ; test for 'ACK' here ??? |B1
movf PCL,F ; 2 cycles |B1
movf PCL,F ; 2 cycles |B1
movf PCL,F ; 2 cycles |B1
scl(0) ; scl = 0 (3 cycle macro) |B1
bcf STATUS,RP0 ; bank 0 |B0
return ; return with wreg = payload |B0
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; I2C 'Stop' condition.
;
iic_stop
sda(0) ; sda = 0 |B1
scl(1) ; scl = 1 |B1
sda(1) ; sda = 1 |B1
bcf STATUS,RP0 ; bank 0 |B0
return ; |B0
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; K8LH DelayCy() subsystem 16-bit uLoop timing subroutine ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
a = dloop-1
while a > 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
;
; *******************************************************************************
;
; DELAY LOOP w x milliseconds - not optimised
w_mS
movwf delay3
w_mS_loop
call one_mS
decfsz delay3,f
goto w_mS_loop
return
;
; DELAY LOOP 1 millisecond @ 4MHz
;
one_mS
movlw d'4'
movwf delay1
loop1
movlw d'81'
movwf delay2
loop2
decfsz delay2,f
goto loop2
decfsz delay1,f
goto loop1
goto $+1
goto $+1
goto $+1
nop
return
;******************************************************************
end