Announcement

Collapse
No announcement yet.

Example Code for Using PIC Hardware I2C

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

  • DavidP
    replied
    This is something I wrote a while back. It should give you a starting point and be somewhat compatible with the 12f683.

    ' WRITTEN BY DAVID PUROLA 06/18/2010
    '
    ' WRITTEN FOR USE W/12F1840-I/P @ 16 Mhz.
    '
    ' THE PROGRAMS PURPOSE IS TO GENERATE PWM SIGNAL FROM RATIOMETRICLY
    'CONNECTED POT TO THE PIC PROCESSOR. THE HIGH AND LOW SIDE OF THE POT ARE
    'CONNECTED TO VSS and VDD. THE CENTER WIPER ARE CONNECTED TO A/D CHANNELS
    '0 and 1 WHICH ARE ON RA0 and RA1. THE OUTPUT ARE ON PIN RA4.
    '
    DEFINE OSC 16
    DEFINE NO_CLRWDT 1

    #CONFIG
    ;----- CONFIG1 Options --------------------------------------------------
    __config _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_ON & _FCMEN_ON

    ;----- CONFIG2 Options --------------------------------------------------
    __config _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_19 & _LVP_OFF
    #ENDCONFIG

    ' ********************************************************************
    ' Declare Port Variables
    ' ********************************************************************
    AZFEEDBK VAR PORTA.0 '1-ANALOG INPUT FROM AZMUNTH POSITION POT
    ELFEEDBK VAR PORTA.1 '1-ANALOG INPUT FROM ELEVATION POSITION POT
    PWMOUT1 VAR LATA.2 '0-PWM OUTPUT TO AZ SERVO
    RST VAR PORTA.3 '1-RESET/MCLR
    OUTENABLE VAR PORTA.4 '1-PWM OUTPUT ENABLE INPUT
    RUNLED VAR LATA.5 '0-RUN LED FOR OPERATOR

    ' ********************************************************************
    ' Define Constants
    ' ********************************************************************
    OFF_ CON 0 'DIGITAL 0 NUMONIC
    ON_ CON 1 'DIGITAL 1 NUMONIC
    SINK CON 0 'SINK CURRENT NUMONIC
    SOURCE CON 1 'SOURCE CURRENT NUMONIC

    FILTER CON 4 'LOWPASS FILTER CONSTANT
    MAXCHANNELS CON 2 'NUMBER OF A/D CHANNELS

    MAXPWM CON 1021

    ' ********************************************************************
    ' Declare Data Variables
    ' ********************************************************************

    ' ********************************************************************
    ' Declare Variables
    ' ********************************************************************
    BITSWORD VAR BYTE BANK0
    COWS_HOME VAR BITSWORD.0 'LOOP FLAG

    LOOPCNTR VAR WORD 'LOOP TIMER WORD FOR TOGGLING LED
    CHANNEL VAR BYTE 'A/D CHANNEL VARIABLE
    VOLTS VAR WORD[MAXCHANNELS] 'A/D READINGS (N-CHANNELS)
    PASTVAR VAR WORD[MAXCHANNELS] 'A/D READINGS (N-CHANNELS)
    OUTVAR VAR WORD[MAXCHANNELS] 'A/D READINGS (N-CHANNELS)
    PWMVALUE VAR WORD '10 BIT PWM VALUE
    SCRATCH VAR WORD 'SCRATCH VARIABLE

    ' ********************************************************************
    ' SYSTEM INITIALIZATION
    ' ********************************************************************
    clear
    PORTA = %00000000
    TRISA = %00011011 'INITIALIZE PORT DIRECTIONS
    OSCCON = %01111000 'PLL DISABLED,16Mhz,FSOSC<2:0>
    INTCON = %00000000 'CLEAR GIE,PEIE,TMR0IE,INTE,RBIE,TMR0IF,INTF,RBIF
    PIE1 = %00000000 'CLEAR ALL INTERRUPT ENABLE BITS
    PIR1 = %00000000 'CLEAR ALL INTERRUPT FLAGS
    PIE2 = %00000000 'CLEAR ALL INTERRUPT ENABLE BITS
    PIR2 = %00000000 'CLEAR ALL INTERRUPT FLAGS
    APFCON = %00000000 'DEFAULT PINS
    ANSELA = %00000011 'AN0,AN1 ANALOG,:AN2,AN4 DIGITAL I/O
    WPUA = %00000000 'WEAK PULLUP'S OFF
    ADCON0 = %00000000 'CHS2:0,GO/DONE,ADOFF
    ADCON1 = %10100000 'RIGHT JUSTIFIED,FOSC/32,VSS,VDD
    SRCON0 = %00000000
    SRCON1 = %00000000
    CM1CON0 = %00000000 'CLEAR/DISABLE COMPARATOR
    CM1CON1 = %00000000 'CLEAR/DISABLE COMPARATOR
    CMOUT = %00000000 'DISABLE OUTPUT REGISTER
    DACCON0 = %00000000 'CLEAR/DISABLE D/A
    DACCON1 = %00000000 'CLEAR/DISABLE D/A
    FVRCON = %00000000 'DISABLE FIXED VOLTAGE REFERENCE
    OPTION_REG = %10000000 'CLEAR ALL PULL-UPS,INTEDG,T0CS,T0SE,PSA,PS
    T1CON = %00000101 'PRESCALER 1/2,START TIMER 1
    T1GCON = %00000000 'NO GATE CONTROL
    T2CON = %00000110 'POSTSCALER 1/1,START,PRESCALER 16
    TMR2 = 0 'CLEAR TMR2 MODULE REGISTER
    PR2 = 255 'SET PERIOD ((((1 / 16,000,000)*4)*1 PRESCALE)*(1+255 PR2)) = 15.625 KHZ.
    CCP1CON = %00001101 'PLACE CCP1 INTO PWM MODE
    CCPR1L = $00 'CLEAR CCP1 LOWER 8 BITS
    CCPR1H = $00 'CLEAR CCP1 UPPER 2 BITS

    GOTO MAINLOOP

    ' ********************************************************************
    ' SUBROUTINES
    ' ********************************************************************

    '*********************************************************************
    READAD: 'READ SYSTEM A/D VOLTAGES (AVERAGE 64 READINGS) 12F1840
    '*********************************************************************
    ADCON0 = $01 | (CHANNEL << 2) ' Set A/D to Channel X, On
    PAUSEUS 40
    VOLTS(CHANNEL) = 0 'CLEAR A/D READINGS
    SCRATCH = 0
    WHILE SCRATCH < 64 'SUM 64 READINGS
    ADCON0.1 = 1 ' START CONVERSION
    WHILE ADCON0.1 = 1 'WAIT FOR A/D TO FINISH
    WEND
    VOLTS(CHANNEL) = VOLTS(CHANNEL) + (((ADRESH & $3) << 8) + ADRESL) 'BUILD SENSOR WORD
    SCRATCH = SCRATCH + 1
    WEND
    VOLTS(CHANNEL) = VOLTS(CHANNEL) >> 6 'TURN INTO 10 BIT RESULT
    RETURN

    ' ********************************************************************
    LOWPASS: 'LOW PASS FILTER (256 = NO FILTER)
    ' ********************************************************************
    PASTVAR(CHANNEL) = (PASTVAR(CHANNEL) - (PASTVAR(CHANNEL) */ FILTER)) + VOLTS(CHANNEL)
    OUTVAR(CHANNEL) = PASTVAR(CHANNEL) */ FILTER 'FINAL DRIVE OUTPUT COMPUTED
    RETURN

    ' ********************************************************************
    PWMOUT: 'UPDATE PWM OUTPUTS
    '*********************************************************************
    CCP1CON = $0C | ((PWMVALUE & 3) << 4) 'UPDATE PWM1 LOWER 2 BITS
    CCPR1L = (PWMVALUE >> 2) 'UPDATE PWM1 UPPER 8 BITS
    RETURN

    ' ********************************************************************
    MAINLOOP:' PROGRAM LOOP
    ' ********************************************************************
    REPEAT
    CHANNEL = 0 'CYCLE THRU ALL CHANNELS
    GOSUB READAD 'READ SYSTEM A/D VOLTAGES (AVERAGE 64 READINGS)
    GOSUB LOWPASS 'LOW PASS FILTER (256 = NO FILTER)
    IF OUTENABLE = 0 THEN
    PASTVAR(CHANNEL) = 0
    OUTVAR(CHANNEL) = 0
    ENDIF
    PWMVALUE = OUTVAR(CHANNEL) MIN MAXPWM 'FULL SCALE A/D TO PWM
    PAUSEUS 1000
    LOOPCNTR = LOOPCNTR + 1 'FLASH RUN LED FOR OPERATOR
    RUNLED = LOOPCNTR.9
    UNTIL COWS_HOME

    ' ********************************************************************
    END 'SHOULD NEVER GET HERE.........
    STOP

    Leave a comment:


  • DellPater
    replied
    Hi...as per my knowledge 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.

    PCB実装
    Last edited by DellPater; 01-31-2019, 02:08 PM.

    Leave a comment:


  • DTBarber
    replied
    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 ?

    Leave a comment:


  • LenBeasley
    replied
    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

    Leave a comment:


  • DTBarber
    replied
    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.

    Leave a comment:


  • tumbleweed
    replied
    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


    Leave a comment:


  • DTBarber
    replied
    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.

    Leave a comment:


  • tumbleweed
    replied
    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.

    Leave a comment:


  • richard
    replied
    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

    Leave a comment:


  • DTBarber
    replied
    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

    Leave a comment:


  • richard
    replied
    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





    Leave a comment:


  • DTBarber
    started a topic Example Code for Using PIC Hardware I2C

    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, 10:30 AM. Reason: Consistent with Microchip I2C tutorial, modified all I2C routines to wait until SSPIF has been set.
Working...
X