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

Active 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
 
I can't see the line in red.
This theory is just a guess as I can't see the original code commented out followed by your code that replaces it.
I suspect the original code calls a subroutine which does the delay without calling an other subroutine as your delay routine does. (It seems to call a 1 mS delay.) I am wondering if you are eithr getting a stack overflow or you are not saving a register that is changed in the subroutine.

Les.
 
Argh, the formatting disappeared when the thread got posted :mad:

The initialise section is just under halfway down the listing.

Hardly likely to be a stack overflow (but I will double check) as it's only 16 instructions from the programs beginning and the first call is fully returned before the problem one.
 
It looks like the I2C output routines switch banks - are your delay variables getting lost in the switching??

Try putting them in the 0x70 - 0x7F shared section??
 
Everything up until it switches to 4 bit mode is in Bank 0 and the i2c routines themselves drop back to bank 0 on exit as far as I can see.

I think I need to simulate the part where it has switched to 4 bit mode again although that is way past the area that's causing the problem.
 
Just a guess.
This is the LCD 4 bit initialisation requirement.
1703534223673.png

You appear to have the 100uS delay removed. I say appear because you have four writes of 0x30 instead of the three required.

Mike.
Note, modern LCDs don't need the delays which Mike may have had.
 
Last edited:
To point out - the original software works without problem in it's standalone state - the initialisation routine is OK

The only change I have made is to replace the first 30mS delay with a 50mS delay using my subroutine - software continues to work fine on hardware.

Then :-

Replaced the second 5mS delay with my subroutine and it all goes gaga - hopefully the below extract is easier to see where I made the change. The |B0 |B1 notations throughout the program are the bank it is in.

;
; 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
 
Another possibility is that I²C hates to be interrupted. When you compile(/assemble) the routine can be interrupted mid transfer and this can cause it to hang. To get around this in the initialisation routine, I check the data line and if high, send clock pulses until low. To test if this could be the cause, power off the system (pic only) and try it from power up.

Mike.
 
The problem somehow seems to be related to this line (at the bottom of the cblock list) -

rsflag equ delayhi ; putCmd(), putDat(), putNyb()

If I comment out that line and make rsflag it's own register in the shared section and leave the rest of the program as original, the problem shows up. delayhi is used in the original timing loop routine.

This gets even more confusing as rsflag and delayhi are used in different routines and rsflag is either cleared or set to 1 in one routine (sending data to the LCD) - no obvious interaction anywhere else in the program between them.
 
I'm guessing that Mike's delay leaves delayhi as zero or 255 therefore setting or clearing the flag.

Mike.
Edit. Looked through the code and can't see it.
 
Last edited:
delayhi is in:-

'K8LH DelayCy() subsystem macro generates four instructions' - near the top

'K8LH DelayCy() subsystem 16-bit uLoop timing subroutine' - right near the bottom

rsflag is in:-

'low-level LCD drivers for PCF8574 based I2C LCD backpack' - a bit over midway down
 
Used your routine for delays.. Close enough..

Just remember your pullups... Also A0~A3 need to be high.

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

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

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;  K8LH DelayCy() subsystem macro generates four instructions      ~
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


;******************************************************************
;  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
 
As I suspected - same result doesn't work on hardware.

I've been using the oscilloscope in the Oshonsoft Sim to try and check on the timing of the SDA & SCL lines but it's pretty much useless so I set it to log the full program.

Again mostly useless in terms of it uses a time reference instead of a program line # reference to identify where event changes occur, but it does show what is probably the problem - a few lines from the log below.

Firstly the original program - nice neat lengths of the SCL signal.

Original software
Log for oscilloscope channel 2: PORTA.
2.00µs 0 ---> 1 (0 for 2.00µs)
8.00µs 1 ---> 0 (1 for 6.00µs)
17.00µs 0 ---> 1 (0 for 9.00µs)
30047.00µs 1 ---> 0 (1 for 30030.00µs)
30057.00µs 0 ---> 1 (0 for 10.00µs)
30067.00µs 1 ---> 0 (1 for 10.00µs)
30077.00µs 0 ---> 1 (0 for 10.00µs)
30087.00µs 1 ---> 0 (1 for 10.00µs)
30097.00µs 0 ---> 1 (0 for 10.00µs)
30107.00µs 1 ---> 0 (1 for 10.00µs)
30117.00µs 0 ---> 1 (0 for 10.00µs)
30127.00µs 1 ---> 0 (1 for 10.00µs)
30137.00µs 0 ---> 1 (0 for 10.00µs)
30147.00µs 1 ---> 0 (1 for 10.00µs)
30157.00µs 0 ---> 1 (0 for 10.00µs)
30167.00µs 1 ---> 0 (1 for 10.00µs)
30177.00µs 0 ---> 1 (0 for 10.00µs)
30187.00µs 1 ---> 0 (1 for 10.00µs)
30197.00µs 0 ---> 1 (0 for 10.00µs)
30207.00µs 1 ---> 0 (1 for 10.00µs)
30217.00µs 0 ---> 1 (0 for 10.00µs)
30227.00µs 1 ---> 0 (1 for 10.00µs)
30252.00µs 0 ---> 1 (0 for 25.00µs)

And second the modified program which shows the dogs breakfast the changes have made of the timings.

Modified software
Log for oscilloscope channel 2: PORTA.6
2.00µs 0 ---> 1 (0 for 2.00µs)
10.00µs 1 ---> 0 (1 for 8.00µs)
17.00µs 0 ---> 1 (0 for 7.00µs)
30109.00µs 1 ---> 0 (1 for 30092.00µs)
30126.00µs 0 ---> 1 (0 for 17.00µs)
30132.00µs 1 ---> 0 (1 for 6.00µs)
30147.00µs 0 ---> 1 (0 for 15.00µs)
30153.00µs 1 ---> 0 (1 for 6.00µs)
30168.00µs 0 ---> 1 (0 for 15.00µs)
30174.00µs 1 ---> 0 (1 for 6.00µs)
30189.00µs 0 ---> 1 (0 for 15.00µs)
30195.00µs 1 ---> 0 (1 for 6.00µs)
30210.00µs 0 ---> 1 (0 for 15.00µs)
30216.00µs 1 ---> 0 (1 for 6.00µs)
30231.00µs 0 ---> 1 (0 for 15.00µs)
30237.00µs 1 ---> 0 (1 for 6.00µs)
30252.00µs 0 ---> 1 (0 for 15.00µs)
30258.00µs 1 ---> 0 (1 for 6.00µs)
30273.00µs 0 ---> 1 (0 for 15.00µs)
30279.00µs 1 ---> 0 (1 for 6.00µs)
30287.00µs 0 ---> 1 (0 for 8.00µs)
30293.00µs 1 ---> 0 (1 for 6.00µs)
30328.00µs 0 ---> 1 (0 for 35.00µs)

Time to try and tidy up the timings I think. o_O
 
I'll give that a run shortly thanks Ian.

I'm using one of those Ebay PCF 8574 backpack modules so pullup's and address lines are all set up.
If it works on the sim but not in real situations, its normally the clock system Are you on a 4Mhz internal?
AND!! Make sure there are pullups on the SDA and SCL lines... 10k should work.
 

Latest threads

New Articles From Microcontroller Tips

Back
Top