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
 
I know the hardware is OK as it works fine with the original unmodified software.

There's going to one obscure thing we're missing that is the culprit.

And after all this, I may have cured the problem in the other program that this i2c stuff was going to be merged in to. :rolleyes:
 
Sunny, this is for a PIC micro project.

Thankyou for the offer

サニー、これはPICマイクロプロジェクト用なんだ。

ありがとうございます。
Terry, I have banned the JA QRM.

JimB
 
Jim, thanks, I was in two minds about answering so thought I would be polite and do so.

Ian, I copied the code straight from your post.

I did a quick mash up code that instead of flip flopping the in /out state of the pins, left them as outputs and just set and cleared them.

Same thing, appears to be doing the right thing in the sim but not on hardware.

I'm going to set the clock out pin and measure exactly what frequency the PIC is running at (I know when using it in frequency read mode it reads the frequency about 8kHz low - that's part of the other software I'm working on).

Won't be until later on as other things to do at the moment.
 
OK, PIC is running at close enough to 3.958MHz so a little slow but not slow enough (I think) to make any real difference.

Just to be sure, I'll stick a 4MHz xtal on it and rerun the software. If it does turn out to be the problem, I'll put this aside until I get a new set of pcbs.

Thunderstorm just arrived so going off line for a while.
 
RA6 & 7 all good.

Will tack in a xtal at some stage today but will need to do a bit of pcb hacking first to move SCL & SDA to different pins.

Bleeding thunderstorm hung around until this morning and I think it might be coming back again.
 
I know this sounds mad... But on a pic16f690 RC1 and RC0 are on the same physical pins as RA6 and RA7??

Weird as the pinouts are very different!! I thought you said it was same hardware
 
So all in all.. The PIC16F628A was working tickety boo until you put the I2C in?

If so, something is blocking ie.. waiting response.

Do you want me to sim your complete code ( in confidence... PM maybe )
 
The i2c bit in it's original form worked almost completely properly when integrated in to my existing hardware/software.

It would bring up the "Hello World" message on initialization and the rest of my software ran, but I couldn't get the i2c routine to display the 'w' register contents at various points in my bit of software that I was trying to inspect - most likely something I have mucked up.

There's nothing secret re the software I'm working on as it is an upgrade of an already published project on my website and will be published there when finished but this won't have the i2c stuff in it - phase 3 of the project will have it but that is a much expanded version for later.

But if you would be so kind as to check my bit bashed i2c routine I mentioned a few posts back, it would be very much appreciated.

Just need to tidy it up and make sure it is commented properly before I post it here (I'm happy to put it here on the forum as it may help someone else in the future).

Thankyou very much for the help you have provided so far.
 
This my bit banged version of the i2c code - currently not working in hardware but seems ok as far as being run in oshonsoft sim.

Note that this is for a single i2c device on the bus only - in this instance for the PCF8574 LCD backpack.

I've attached as a zip file to save copying from the post.
 

Attachments

  • VK5TM_16F628A_LCD_I2C_Backpack.zip
    2.5 KB · Views: 188
I modified the hardware to run a 4MHz xtal and ran the your code from post #16 and same result - not working on the hardware.

So your comment above re awaiting response might be appropriate to check, as in there might not be enough delay in writing to the LCD and waiting for it to do it's thing before the next write.

I'll see if I can check in the sim what the delay between writes actually is.

Edit:- Cancel that, it's 1233μS between writes when sending characters to the LCD - more than enough time.
 
Last edited:
I bought one of these to see if it will help sort out what's going on.
Capture.PNG
 
Sorry, I was referring to my hardware as in the project I'm working on, not the hardware/PIC listed in the original software.

I'm trying to incorporate the i2c stuff in to my code to help with troubleshooting.
You're really making hard work of this :D

Why not just use a single PIC pin as a serial output (either hardware or software), and use that to display debugging information on a PC, FAR better than a small LCD with little space. And you can scroll back through all the information that's been sent.

I've been doing it that way for ever, and I certainly wasn't the first :D

If you want assembler software serial routines, they are included in my PIC tutorials.
 
LCD data or command... Mike used SKPZ You used BTFSS STATUS,Z This is all I can see different, but My code works and yours doesn't... I'm going to go through meticulously and find the culprit.

There appears to be some logic contention between your code on the backpack. Which isn't there on Mikes code.
 
SKPZ assembles as BTFSS STATUS,Z - I just find it easier decipher when I run through a code listing rather than having to stop and look up what the pseudo code means.

Nigel I'm beginning to think along those lines for troubleshooting, but I'm going to need the i2c stuff when I get to the next iteration of my project anyway.

Trouble is, I'm using a secretaries desk as the workbench at the moment and there ain't no room to stick a computer or laptop as well as everything else on it.

Workbench and PC desk are 10 feet apart on either side of a doorway so running a cable that far ain't going to work.

Although I did see Mike K8LH had a single wire serial project using a 12F683 to interface to the LCD.
 
SKPZ assembles as BTFSS STATUS,Z - I just find it easier decipher when I run through a code listing rather than having to stop and look up what the pseudo code means.

Nigel I'm beginning to think along those lines for troubleshooting, but I'm going to need the i2c stuff when I get to the next iteration of my project anyway.

Trouble is, I'm using a secretaries desk as the workbench at the moment and there ain't no room to stick a computer or laptop as well as everything else on it.

Workbench and PC desk are 10 feet apart on either side of a doorway so running a cable that far ain't going to work.

Although I did see Mike K8LH had a single wire serial project using a 12F683 to interface to the LCD.
Yes, it's easy to make a little serial LCD board if needed - however, you could simply connect the PIC serial pin to a BlueTooth module, and display it on your phone (I've done that for setting devices up in the field).
 

Latest threads

New Articles From Microcontroller Tips

Back
Top