Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

ASM: i2c software headache

augustinetez

Well-Known Member
Most Helpful Member
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 :eek:

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
 
Although I did see Mike K8LH had a single wire serial project using a 12F683 to interface to the LCD.
I've used it in my latest project BUT! I drive a bank of LED's.. I used two wires, one for latch one for data
 
Not according to the PCF8574 datasheet:

timing.PNG
 
All those times are min or max, not both. I.E. there is no max time for scl low or high. So Max could be a week or forever which would be DC.

Mike
Edit, max clock frequency is 100k. Is that a problem?
 
Re clock frequency - don't think so, that's set by length of the clock pulse as far as I know and that shows up in the sim tests as being 10μS in K8LH's code and 11-13μS in my bitbanged code.

My modifed version of K8LH's code has the clock pulse timings all over the place like a mad womans wash so not surprised that didn't work.

I just can't get the brain in to analytical mode today - been staring at datasheets and code all morning and nothing is making any sense at the moment.

I know it's just something stupid and simple but can't see it.

Hopefully that cheapy logic analyser will help but it wont be here till next week.
 
Hello again...

In the LCD command / data, iic_start should load the address into the pcf8574. but yours doesn't.

Going through Mikes code, iic_start, goes throught iic_write to complete the address write, you have "modified" the code to overwrite the "working" char inside that routine ergo the address never gets sent.
This is the version that works here. Notice the IIC_START runs into write, which is why he sets the address 0f 0x4E first.
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

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;  Delay macro

DelayCy macro param
        movlw    param
        call    w_mS
        endm

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;  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


;******************************************************************
;  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)              ; delay 30-msecs after power up   |B0

        putNyb (0x30)           ; function set: 8-bit             |B0
        DelayCy(5)               ; required 5-msec delay           |B0

        putNyb (0x30)           ; function set: 8-bit             |B0
        DelayCy(1)               ; required 160-usec delay         |B0
        putNyb (0x30)           ; function set: 8-bit             |B0
        DelayCy(1)              ; required 160-usec delay         |B0
        putNyb (0x20)           ; function set: 4-bit             |B0
        DelayCy(1 )             ; 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(1)              ; 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


; 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
 
More research.... You have re-witten the IIC_WRITE routine. But for some reason the last bit ( needs to be 1 for data) is always 0 which maybe is causing the problem its either setting the RW to 1 or setting command on. Still working on it,
 
That's interesting - I single stepped my code through the sim here and it indicated it was sending the address and the the commands for initialisation - I hand copied the state of the SDA pin while stepping the sim through.

Will try it again tomorrow to make sure I haven't imagined it.

And yes, I moved the sending of the address to the i2c_start routine as that seemed the more logical place to put it.
 
Jesus.... THAT WAS A TOIL!

First... You MUST use tris with I2C secondly I had to use heaps of banksel commands
thirdly one of the setup lines was missing

Try this
Code:
list n=0    ; Remove page breaks in .lst file

; *******************************************************************************
; *                                        *
; *    Filename: VK5TM_16F628_LCD_I2C_Backpack.asm                *
; *    Author: Terry Mowles VK5TM (C)2023                    *
; *    Date: Dec 2023                                *
; *                                        *
; *    16F628A bit-banged I2C demo for PCF8574 I2C LCD Backpack and        *
; *    HD44780 type LCD display.                        *
; *                                        *
; *    This code is for single i2c device connection only            *
; *                                        *
; *    Parts of the code by Mike McLaren, K8LH                    *
; *                                        *
;********************************************************************************
        list P=PIC16F628A 

       #include <P16F628A.INC>

        errorlevel -302
        radix dec

      __config _CP_OFF&_LVP_OFF&_BODEN_OFF&_MCLRE_OFF&_PWRTE_ON&_WDT_OFF&_INTOSC_OSC_NOCLKOUT
;
; *******************************************************************************
; *    Defines                                    *
; *******************************************************************************
;
SDA    equ    0x07        ; I2C Data
SCL    equ    0x06        ; I2C Clock
;
;  LCD DDRAM address constants
;
line1   equ     127+0           ; LCD "line 1" command
line2   equ     127+64          ; LCD "line 2" command
;
;********************************************************************************
; *    Variables                                *
; *******************************************************************************
        cblock  0x20
    delay1
    delay2
    delay3
    bytcnt
    rsflag
    workvar
    w_data
        endc
 ;
; *******************************************************************************
; *    Start                                    *
; *******************************************************************************
    org    0x000

    clrf    STATUS          ; force bank 0 and IRP = 0
    goto    setup           ;

    org    0x004
    goto    setup           ;
;
; *******************************************************************************
; *    main setup                                *
; *******************************************************************************
;
setup
    banksel TRISA 
     movlw    0xFF
    movwf    TRISA        ; all inputs
    bsf    TRISA,SDA    ; Make SDA & SCL outputs
    bsf    TRISA,SCL
    clrf    TRISB        ; PortB all outputs

    bsf    TRISA,SDA    ; i2c idle state = SDA & SCL high
    bsf    TRISA,SCL
    banksel    0
    clrf    PORTA        ;
    clrf    PORTB        ;

    movlw    7
    movwf    CMCON
;
;  LCD "initialize by instruction" procedure for 4-bit interface K8LH
;
    movlw    30        ; delay 30-msecs after power up
    call    w_mS
    movlw    0x30        ; function set: 8-bit
    call    lcdNyb
    movlw    5        ; required 5-msec delay
    call    w_mS
    movlw    0x30        ; function set: 8-bit
    call    lcdNyb
    movlw    1        ; 
    call    w_mS
    movlw    0x30        ; function set: 8-bit
    call    lcdNyb
    movlw    1        ; 
    call    w_mS
    movlw    0x20        ; function set: 4-bit
    call    lcdNyb
    movlw    1        ; 
    call    w_mS
;
;  now we're in 4-bit mode and can handle 8-bit transactions by
;  sending both hi & lo nibbles using lcdCmd & lcdDat.
;
    movlw    0x28        ; 4-bit, 2-lines, 5x7 font
    call    lcdCmd
    movlw    1        ; 
    call    w_mS
    movlw    0x08        ; display, cursor, blink all off
    call    lcdCmd
    movlw    1        ; 
    call    w_mS
    movlw    0x01        ; clear display
    call    lcdCmd
    movlw    2        ; required delay
    call    w_mS
     movlw    0x06        ; cursor inc, shift off
    call    lcdCmd
    movlw    1        ; 
    call    w_mS
    movlw    0x0C        ; display on, leave cursor off
    call    lcdCmd

;******************************************************************
;  main loop                                                      *
;******************************************************************

loop
        movlw    line1+3        ; LCD line 1, column 3
    call    lcdCmd
        movlw    'H'
    call    lcdDat
        movlw    'e'
    call    lcdDat
        movlw    'l'
    call    lcdDat
        movlw    'l'
    call    lcdDat
        movlw    'o'
    call    lcdDat
        movlw    ' '
    call    lcdDat
        movlw    'W'
    call    lcdDat
        movlw    'o'
    call    lcdDat
        movlw    'r'
    call    lcdDat
        movlw    'l'
    call    lcdDat
        movlw    'd'
    call    lcdDat
        movlw    '!'
    call    lcdDat

        goto    $

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;  low-level LCD drivers for PCF8574 based I2C LCD backpack K8LH  ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

lcdCmd                          
        banksel 0                ; entry point for "cmd" data
        clrf    rsflag          ; wreg = data, RS = 0 (cmd)       |B0
        btfss    STATUS,Z        ; skip 
lcdDat    
;        banksel 0                ; entry point for "dat" data
        bsf     rsflag,0        ; wreg = data, RS = 1 (data)      |B0
        call    lcdNyb          ; send hi nybble                  |B0
        swapf   w_data,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
    banksel 0
    movwf    w_data        ; save temporarily                |B0
    ;movlw   0x4E            ; PCF8574 I2C address + 0 (wr) moved to i2c_start|B0 
    call    i2c_start    ; I2C 'start' + 'address' + 'rw'  |B0
    movf    w_data,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    i2c_write    ; data + bl, en, rw, rs bits      |B0
    xorlw    b'00000100'    ; clr E bit (b2)                  |B0
    call    i2c_write    ; data + bl, en, rw, rs bits      |B0
    call    i2c_stop    ; I2C 'stop'                      |B0

    return
;
; *******************************************************************************
; *                I2C ROUTINES                    *
; *******************************************************************************
; Start condition; SDA goes from hi to low with SCL high
; Normal state is SDA high, SCL high
i2c_start
    banksel TRISA
    bsf    TRISA,SCL    ; Set SDA High    ensure idle state
    nop
    nop
    nop
    bsf    TRISA,SCL    ; Set SCL hi
    goto    $+1
    goto    $+1        ;\
    goto    $+1        ; To set timing 4MHz clock
    goto    $+1        ;/
    goto    $+1
    bcf    TRISA,SDA    ; SDA low
    nop
    nop
    nop    
    banksel 0
    movlw    0x4E        ; Send i2c address
    
;
; *******************************************************************************    
; *    Output a byte in workvar and then get an ACK                *
; *******************************************************************************
;
i2c_write
    banksel 0
    movwf    workvar

    clrf    bytcnt    
    bsf    bytcnt,3    ; byte counter = 8 (preserve w)

    bcf    STATUS,C
    
send_data
    banksel TRISA
    bcf    TRISA,SCL    ; SCL low
    banksel 0
    rlf    workvar,f
    banksel TRISA
    btfss    STATUS,C
    goto    bit_0            
    bsf    TRISA,SDA    ; SDA hi
    nop
    goto    bit_1
bit_0
    
    bcf    TRISA,SDA    ; SDA low
    nop
bit_1

    bsf    TRISA,SCL    ; SCL hi
    goto    $+1
    goto    $+1
    goto    $+1
    goto    $+1
    goto    $+1
    bcf    TRISA,SCL    ; SCL low
    goto    $+1
    banksel 0
    decfsz    bytcnt,f            
    goto    send_data
;
; *******************************************************************************
; *    Read acknowledge signal.                        *
; *******************************************************************************
;
getack
    banksel    TRISA    
    bsf    TRISA,SDA    ; SDA is Input
    bsf    TRISA,SCL    ; SCL hi  
    goto    $+1
    goto    $+1
    goto    $+1
    goto    $+1
    goto    $+1
    bcf    TRISA,SCL    ; SCL low 
    nop
    banksel    TRISA    
;    bcf    TRISA,SDA    ; SDA is output
    banksel    0    

    return
;
; *******************************************************************************
; *    Stop condition; SDA goes from low to hi with SCL high            *
; *    Normal state is SDA high SCL high                    *
; *******************************************************************************
;
i2c_stop
    banksel    TRISA    
    bcf    TRISA,SDA    ; SDA low 
    nop
    bsf    TRISA,SCL    ; SCL hi  
    goto    $+1
    goto    $+1
    goto    $+1
    goto    $+1
    goto    $+1
    bsf    TRISA,SDA    ; SDA hi 
    banksel    0
    return
;
; *******************************************************************************
; *    DELAY LOOP w x milliseconds - not optimised                *
; *******************************************************************************
;
w_mS
    banksel 0
    movwf    delay3        ; Enter with 'w' holding required mS
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
    return

        end
 
Using Mikes code would be better as he combines all bankselects in the macro so it doesn't cause an issue.
If you use the port pins (PORTA) they can clash as the nak cannot be driven high. When you tris the port you get no contention.
 
OK, finally some movement - the display is at least now initialising using the code in post #50.

Did find three errors in it though (two of which were mine :():

First, the initial port setup wasn't working ie SCL & SDA wouldn't appear as high when set as input and also wouldn't send the i2c_start message although it did feed out the correct code for the address in the i2c_start sequence and the first LCD inialisation value (didn't sim any further than that to start with) .

Changed to the following although I'm not sure why it made a difference (set same as Mike's original now):

Code:
setup
    banksel    TRISA
    clrf    TRISA        ; all outputs
    clrf    TRISB        ; all outputs
    banksel    0

    clrf    PORTA
    clrf    PORTB

    movlw    7
    movwf    CMCON

    banksel    TRISA
    bsf    TRISA,SDA    ; i2c idle state = SDA & SCL high
    bsf    TRISA,SCL
    banksel    0

Next, third line - SCL should be SDA

Code:
i2c_start
    banksel TRISA
    bsf    TRISA,SCL    ; <-- Error  Set SDA High ensure idle state
    nop        ; Should be SDA
    nop
    nop
    bsf    TRISA,SCL    ; Set SCL hi
    goto    $+1
    goto    $+1        ;\
    goto    $+1        ; To set timing 4MHz clock
    goto    $+1        ;/
    goto    $+1
    bcf    TRISA,SDA    ; SDA low
    nop
    nop
    nop   
    banksel 0
    movlw    0x4E        ; Send i2c address

Third - I put an F when it should have been a W (final line in this bit):

Code:
send_data
    banksel    TRISA
    bcf    TRISA,SCL    ; SCL low (output)
    banksel    0
;    rlf    workvar,f
    rlf    workvar,w    ; <--

Still not displaying the message, but as per your comment re the rsflag not being correct, I'll go through and see where I've mucked that up.

Sorry it's been such a hard slog and I do very much appreciate your perseverance with this.

Once I've got that sorted and it works, I'll have a go at putting Mike's various bits back in that you mention to sort all the banking.
 
In that case I have absolutely no idea what's going on.

Programming just that code as posted (other than changing the pins that SDA &SCL are on, 2 & 3 respectively, and running XT OSC) the result was the same as before - no LCD intialisation or anything else.

I'll revert the pcb hacks back so I can use pins 6 &7 and the internal osc and re-run the code as posted.

Be a day or two as I'm out tomorrow and I'm sure the missus has something she wants done the next day.
 
Just to explain the conflict bit (hopefully not sucking eggs). A pin (SCL & SDA) can be in one of two states. Input or output low. Never output high. High can only be achieved by the pullup. This enables two devices to pull the line low without there being a conflict. The Logic Analyser will see output high and input with pullup as the same level - logic high. The other thing I noticed in Mikes code is that the send ID always sets the R/W bit so always write. Hopefully this is still the same - I haven't studied the code so don't know.

Note, to output logic zero requires zero to be written to the (LATCH) bit and, of course, zero to be written to the TRISS bit.


Mike.
 
Thanks Mike.

I sorta knew that but need to double check that zero is indeed being written to the latch to output the logic zero.

That can wait until the analyser turns up.

The reason for trying to do all this has sort of evaporated, temporarily - I've fixed the other software it was going to help troubleshoot.
 

Latest threads

New Articles From Microcontroller Tips

Back
Top