Announcement

Collapse
No announcement yet.

Example Code for Using PIC Hardware I2C

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Example Code for Using PIC Hardware I2C

    Hardware I2C EEPROM.pbp

    The attached file illustrates implementing a hardware I2C bus using a PIC MSSP module. It was written for use with a 16F877 PIC, LabX-1 development board, and a 24LC64 EEPROM. Included are hardware routines for transmitting and receiving byte and word size variables. For debugging purposed there are I2C routines implemented using PBP I2C Read/Write commands. If using the code with other PICs it is likely that the MSSP Module Variables will need to be adjusted consistent with the specific PIC datasheet. If clocking the PIC at other than 4MHZ it will be necessary to adjust the SSPADD value. If a bus rate higher than 100 KHz it will be necessary to adjust the SSPSTAT value. Consult the specific pic datasheet for details.

    It is simpler to implement I2C using PBP commands, but sometimes a hardware solution is necessary. In my case it was using a sensor from Honeywell.
    Last edited by DTBarber; 01-28-2018, 11:30 AM. Reason: Consistent with Microchip I2C tutorial, modified all I2C routines to wait until SSPIF has been set.

  • #2
    not a bad effort Daniel

    my logic analyser indicates a slight error here
    Code:
    Hardware_I2C_RW:  ;Read word sized data using hardware I2C
    ;Word is the result of seqentially reading two consecutive memory addresses
    SSPEN = 1 ;MSSP module on
    
     Lcdout Cmd, Clr
     lcdout CMD, Line_1 + 2,"Hardware I2C"
     lcdout CMD, Line_2 + 1,"Read Word Values"
     pause 2000
    
     For I = 0 to 10 step 2 ;Word values occupy two bytes
         address_W = I                    
    
    gosub I2C_START   ;Begin I2C sequence
    
    I2C_DATA = CTRL_W  ;Send Control + write command
    gosub I2C_TX
    
    I2C_DATA = address_W.byte1  ;Address high byte
    gosub I2C_TX
    
    I2C_DATA = address_W.byte0  ;Address low byte
    gosub I2C_TX
    
    gosub I2C_Restart ;Reset I2C bus before sending read command
    
    I2C_DATA = CTRL_R  ;Send Control + read command
    gosub I2C_TX
    
    gosub I2C_RX    ;receive byte
    Data_IN_w.byte1 = SSPBUF   ;High byte of word variable
    
    gosub I2C_ACK   ;Signal EEPROM to send next byte
    
    gosub I2C_RX  ;receive byte
    Data_IN_w.byte0 = SSPBUF  ;Low byte of word variable
    gosub I2C_NACK  ;  analyser says this signal missing
    gosub  I2C_STOP   ;Stop Read sequence
    
    Lcdout Cmd, Clr
        lcdout CMD, Line_1,  "Read Address = ", dec address_W
            lcdout CMD, Line_2, "Word Data = ", dec Data_In_W
                pause 1000
    Next I
    
    return
    the whole thing could do without any of the pause 1 statements in the i2c routines they serve no useful purpose


    the tx routine can be made more efficient like this, the "tries" can uncommented and used to act as a timeout if no ack
    recieved




    Code:
    I2C_TX:
    ;tries=5   ;see below
    SSPBUF = I2C_DATA ; Move data to SSPBUF
    WHILE TX_Stat  : WEND
    WHILE ACKSTAT : WEND  ;or use something like this for a timeout
     ;timeout if no ack
    'WHILE (ACKSTAT&&tries)   ;wait for slave to respond
    'tries=tries-1
    'pauseus 15
    'WEND
    'if !tries then i2c_error=1
    Return

    the other routines can also be improved slightly using the relevant hw flags directly


    Code:
    I2C_Restart:  ;Send Restart Sequence
    RSEN = 1 ; RSEN - Repeat Start Condition Enable Bit
    WHILE rsen : WEND ; Wait for Re-start sequence
    RETURN
    
    ;=====================================
    I2C_START:  ;Send Start Sequence
    SEN = 1 ; SEN - Start Condition Enable Bit
    WHILE Sen = 1 : WEND ; Wait for  Start sequence
    RETURN
    
    ;=====================================
    I2C_RX:
    RCEN = 1 ; RCEN - Enable receive mode
    WHILE rcen : WEND ; Wait for  Receive sequence
    WHILE ACKSTAT = 1 : WEND  ;wait for ack
    Return
    
    
    
    
    ;=====================================
    I2C_STOP: ;Send Sotp Sequence
    PEN = 1 ; PEN - send stop bit
    WHILE pen =1 : WEND ; Wait for  Stop sequence
    Return
    
    ;=====================================
    I2C_NACK:  ;Send NACK
    ACKDT = 1 ; ACKDT - Set Ack bit to NotAck
    ACKEN = 1 ; ACKEN - send ACKDT bit
    WHILE acken : WEND ; Wait for  NACK sequence
    Return
    
    ;=====================================
    I2C_ACK:  ;Send ACK
    ACKDT = 0 ; ACKDT - Set Ack bit to Ack
    ACKEN = 1 ; ACKEN - send ACKDT bit
    WHILE acken : WEND ; Wait for  ACK sequence
    Return





    Comment


    • #3
      Richard,

      I appreciate the feedback. I did not include the NACK routine in the sequential byte read routine because page 10, section 6.2 of the
      24LC64 datasheet
      states "Following the final byte transmitted to the master, the master will NOT generate an acknowledgement but will generate a STOP condition." That is in conflict with Figure 6-3, page 11, which does illustrate a NACK following the last transmitted byte, followed by a STOP. Since the code worked, I went with the text, not the illustration. I will add the NACK and test the code. Is the use of a NACK following the last byte received part of the I2C standard? The ability to detect missing signals could be very useful. Does your logic analyzer detect missing signals based on a specific communication bus standard? What logic analyzer are you using?

      I agree with your recommendation to remove the 'pause' statements. They are artifacts of early code testing.

      The addition of a timeout routine is a great suggestion. Without it, the program could hang.

      I will try your suggestion to use the hardware flags directly.


      Dan

      Comment


      • #4
        Is the use of a NACK following the last byte received part of the I2C standard?
        not sure , i'm no expert. but the saleae analyser reports a missing nak/ack error without it .
        the eprom works without it but other devices may not


        The ability to detect missing signals could be very useful. Does your logic analyzer detect missing signals based on a specific communication bus standard?
        yes it does , it has quite a number of protocols that it can analyse

        What logic analyzer are you using?
        a saleae logic 8 , I find it invaluable





        https://www.saleae.com/?gclid=CjwKCA...xoCCtwQAvD_BwE

        Comment


        • #5
          WHILE ACKSTAT = 1 : WEND ;wait for ack
          You shouldn't loop waiting for ACK.

          It's not something you wait for... you'll either get an ACK or a NACK as part of the transfer. It won't happen later on.

          Comment


          • #6
            Richard/Tumbleweed;

            Thanks for the assistance in improving the code. After reviewing Microchip's tutorial on implementing I2C I revised the I2C routines to wait until the SSPI bit had been set. Per page 74 of the tutorial, "This flag is set by the MSSP module when any MSSP action completes". http://ww1.microchip.com/downloads/jp/DeviceDoc/i2c.pdf

            The TX error routine still needs work. It was fashioned after the error routine described in page 73 of the Microchip tutorial. The error routine works if the I2C chip is removed, but the program simply hangs if either SCL or SDA lines are shorted to ground. Per the tutorial, shorting either SCL or SDA should initiate the error routine. If you see what I am missing, please suggest a correction to the code.

            Richard,

            Thanks for the link to your logic analyzer. It looks like a very useful tool. I use a Discovery 2 for an oscilloscope. I will have to delve into its logic module.

            Comment


            • #7
              If you would like to see this code run, try removing the EEPROM or ground the SCL or SDA line.
              This will force an error which is handled by this code.
              I don't think that's true. The sample code in that tutorial is oversimplified.
              A missing device can be detected by ACKSTAT seeing a NACK. Grounding one of the bus lines is different.

              The WaitMSSP routine waits for SSPIF. If you have a major failure like a stuck bus then SSPIF will never get set.
              This should be detected by using the BCLIF bus collision flag, which isn't even mentioned in that tutorial.

              If you're not using interrupts then instead of using SSPIF it's better to use the individual condition flags
              like Richard showed and check for BCLIF. How you handle this can get messy, which is why they rarely show it in examples.
              Code:
              ;=====================================
              I2C_START:  ;Send Start Sequence
              SEN = 1 ; SEN - Start Condition Enable Bit
              WHILE SEN = 1 : WEND ; Wait for  Start sequence to end
              ; if there's a stuck bus the SEN flag should have been cleared and BCLIF will be set
              IF BCLIF = 1 THEN
                   ; START was aborted. major error.
                   ; you need to clear BCLIF and abort
              ENDIF
              RETURN

              AN735 is a better example http://www.microchip.com/wwwAppNotes...pnote=en011802


              Comment


              • #8
                Tumbleweed,

                Thanks for the feedback and additional resource from Microchip. I had expected the Microchip tutorial to be a reliable discussion of implementing its I2C module, but apparently it provides just the basic information necessary to implement I2C. I have only had time for a cursory review of AN735, but I did see that it provides a more in depth discussion of error handling.

                If you're not using interrupts then instead of using SSPIF it's better to use the individual condition flags
                like Richard showed and check for BCLIF. How you handle this can get messy, which is why they rarely show it in examples."

                I will follow your suggestion to use individual conditional flags as suggested by Richard and check for BCLF. However, my understanding of implementing interrupts is limited to using Darrel Taylor's Instant Interrupts (DTII), which works great for my projects, but the underlying code it generates is not readily transparent. For the example code I think it best if a user can follow all of the routines. You point out that handling bus errors can get messy. I will likely leave the "Messy" part of the code to the experts.

                Comment


                • #9
                  I am new to the ME Labs programmer and using the Picbasic Pro compiler.

                  I am trying to control a small DC motor with the PWM output of a PIC12F683. I have tried reading the data sheet and various other areas but would appreciate some help with this.

                  The PWM works fine but the A/D is where my problem is.

                  Thanks in advance.

                  Len Beasley

                  Comment


                  • #10
                    Len,

                    Your description does not provide enough detail for assisting you with your effort. Sounds like you have PWM working, What are you trying to do that involves the A/D peripheral ?

                    Comment

                    Working...
                    X