I2C pic18f26K40

General discussion relating to the library modules supplied with the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

I2C pic18f26K40

Post by Gunplumber » Tue Nov 22, 2022 11:44 am

Hi guys
I am trying to get the I2C module working on a 18F26K40 with no success at all..
I'm using the following code, but get no output from the standard pins Rc3 and Rc4. I'm not using any pin re-assignments..
What am i missing? Also this PIC has two MSSP modules, how do i address them separately?

Cheers
Lee

Code: Select all

Device = 18F26k40
Clock = 64

Config
    FEXTOSC = OFF,      // Oscillator not enabled
    RSTOSC = HFINTOSC_64MHZ,// HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1 
    CLKOUTEN = OFF,     // CLKOUT function is disabled
    CSWEN = ON,         // Writing to NOSC and NDIV is allowed
    FCMEN = ON,         // Fail-Safe Clock Monitor enabled
    MCLRE = EXTMCLR,    // If LVP = 0, MCLR pin is MCLR; If LVP = 1, RE3 pin function is MCLR 
    PWRTE = OFF,        // Power up timer disabled
    LPBOREN = OFF,      // ULPBOR disabled
    BOREN = SBORDIS,    // Brown-out Reset enabled , SBOREN bit is ignored
    BORV = VBOR_2P45,   // Brown-out Reset Voltage (VBOR) set to 2.45V
    ZCD = OFF,          // ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = ON,       // PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle
    STVREN = ON,        // Stack full/underflow will cause Reset
    DEBUG = OFF,        // Background debugger disabled
    XINST = OFF,        // Extended Instruction Set and Indexed Addressing Mode disabled
     //
    //WDTCPS = WDTCPS_10,  // Divider ratio 1:32768
   // WDTE = ON,          // WDT enabled regardless of sleep
   // WDTCWS = WDTCWS_7,  // window always open (100%); software control; keyed access not required
   // WDTCCS = LFINTOSC,  // WDT reference clock is the 31.0 kHz LFINTOSC
    WDTCPS = WDTCPS_31, // Divider ratio 1:65536; software control of WDTPS
    WDTE = OFF,          // WDT enabled regardless of sleep
    WDTCWS = WDTCWS_7,  // window always open (100%); software control; keyed access not required
    WDTCCS = SC,        // Software Control  
    WRT0 = OFF,         // Block 0 (000800-003FFFh) not write-protected
    WRT1 = OFF,         // Block 1 (004000-007FFFh) not write-protected
    WRT2 = OFF,         // Block 2 (008000-00BFFFh) not write-protected
    WRT3 = OFF,         // Block 3 (00C000-00FFFFh) not write-protected
    WRTC = OFF,         // Configuration registers (300000-30000Bh) not write-protected
    WRTB = OFF,         // Boot Block (000000-0007FFh) not write-protected
    WRTD = OFF,         // Data EEPROM not write-protected
    SCANE = ON,         // Scanner module is available for use, SCANMD bit can control the module
    LVP = OFF,          // HV on MCLR/VPP must be used for programming
    CP = OFF,           // UserNVM code protection disabled
    CPD = OFF,          // DataNVM code protection disabled
    EBTR0 = OFF,        // Block 0 (000800-003FFFh) not protected from table reads executed in other blocks
    EBTR1 = OFF,        // Block 1 (004000-007FFFh) not protected from table reads executed in other blocks
    EBTR2 = OFF,        // Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks
    EBTR3 = OFF,        // Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks
    EBTRB = OFF   
#option LCD_DATA = PORTA.0
#option LCD_RS = PORTA.4
#option LCD_EN = PORTA.5 
#option LCD_RW = PORTA.6
// import libraries...
Include "I2C.bas"
Include "lcd.bas"

// target 24LC128 I2C EEPROM device...
Const I2C_EEPROM = $A0 

// local variables...
Dim
   Value As Byte,
   Address As Word
WriteAt(1,1,"test")   
// program start...

Address = 0
I2C.Initialize (I2C_100_KHZ)

// write some data...
I2C.Start                          
I2C.WriteByte(I2C_EEPROM)              
I2C.WriteByte(Address.Byte1)       
I2C.WriteByte(Address.Byte0)        
I2C.WriteByte("Z")                 
I2C.Stop 

// allow external EEPROM to write data...
DelayMS(10) 

// read the data back...
I2C.Start
I2C.WriteByte(I2C_EEPROM)
I2C.WriteByte(Address.Byte1)        
I2C.WriteByte(Address.Byte0)        
I2C.Restart                         
I2C.WriteByte(I2C_EEPROM + 1)
Value = I2C.ReadByte
I2C.Acknowledge(I2C_NOT_ACKNOWLEDGE)
I2C.Stop

WriteAt(2,1,Value)

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: I2C pic18f26K40

Post by Jerry Messina » Tue Nov 22, 2022 1:24 pm

There are a few things...

- For devices with PPS it's a good idea to get into the habit of assigning the pins, even if you use the "standard" ones.
PPS inputs have a default setting, but PPS outputs don't so in most cases you have to map them.
For I2C you have to map each of the SDA/SCL pins as both input and output.
Here I used the PPSTool to derive the settings.

- IO pins default to 'analog' mode, and most times you need them set to 'digital'
You can call SetAllDigital() from the setdigitalio.bas module, and if you set '#option DIGITALIO_INIT' you don't even have to add the call to your code... it'll be done for you at startup

Code: Select all

#option DIGITALIO_INIT = true   // automatically call SetAllDigital at startup
Include "setdigitalio.bas"
- When using the internal osc, include "intosc.bas" and it'll automatically setup the osc settings for you based on your "clock=" frequency

Try this (untested, so if you still have problems let us know)

Code: Select all

// MSSP1 I2C EEPROM test 
Device = 18F26k40
Clock = 64

Config
    FEXTOSC = OFF,      // Oscillator not enabled
    RSTOSC = HFINTOSC_64MHZ,// HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1 
    CLKOUTEN = OFF,     // CLKOUT function is disabled
    CSWEN = ON,         // Writing to NOSC and NDIV is allowed
    FCMEN = ON,         // Fail-Safe Clock Monitor enabled
    MCLRE = EXTMCLR,    // If LVP = 0, MCLR pin is MCLR; If LVP = 1, RE3 pin function is MCLR 
    PWRTE = OFF,        // Power up timer disabled
    LPBOREN = OFF,      // ULPBOR disabled
    BOREN = SBORDIS,    // Brown-out Reset enabled , SBOREN bit is ignored
    BORV = VBOR_2P45,   // Brown-out Reset Voltage (VBOR) set to 2.45V
    ZCD = OFF,          // ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = OFF,      // PPSLOCK bit can be set and cleared multiple times
    STVREN = ON,        // Stack full/underflow will cause Reset
    DEBUG = OFF,        // Background debugger disabled
    XINST = OFF,        // Extended Instruction Set and Indexed Addressing Mode disabled
     //
    //WDTCPS = WDTCPS_10,  // Divider ratio 1:32768
   // WDTE = ON,          // WDT enabled regardless of sleep
   // WDTCWS = WDTCWS_7,  // window always open (100%); software control; keyed access not required
   // WDTCCS = LFINTOSC,  // WDT reference clock is the 31.0 kHz LFINTOSC
    WDTCPS = WDTCPS_31, // Divider ratio 1:65536; software control of WDTPS
    WDTE = OFF,          // WDT enabled regardless of sleep
    WDTCWS = WDTCWS_7,  // window always open (100%); software control; keyed access not required
    WDTCCS = SC,        // Software Control  
    WRT0 = OFF,         // Block 0 (000800-003FFFh) not write-protected
    WRT1 = OFF,         // Block 1 (004000-007FFFh) not write-protected
    WRT2 = OFF,         // Block 2 (008000-00BFFFh) not write-protected
    WRT3 = OFF,         // Block 3 (00C000-00FFFFh) not write-protected
    WRTC = OFF,         // Configuration registers (300000-30000Bh) not write-protected
    WRTB = OFF,         // Boot Block (000000-0007FFh) not write-protected
    WRTD = OFF,         // Data EEPROM not write-protected
    SCANE = ON,         // Scanner module is available for use, SCANMD bit can control the module
    LVP = OFF,          // HV on MCLR/VPP must be used for programming
    CP = OFF,           // UserNVM code protection disabled
    CPD = OFF,          // DataNVM code protection disabled
    EBTR0 = OFF,        // Block 0 (000800-003FFFh) not protected from table reads executed in other blocks
    EBTR1 = OFF,        // Block 1 (004000-007FFFh) not protected from table reads executed in other blocks
    EBTR2 = OFF,        // Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks
    EBTR3 = OFF,        // Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks
    EBTRB = OFF   

// import libraries...
// intosc control (setup automatically based on 'clock' setting)
Include "intosc.bas"

// pin analog/digital IO mode
#option DIGITALIO_INIT = true   // automatically call SetAllDigital at startup
Include "setdigitalio.bas"

// PPS
include "pps.bas"

// MSSP1 (I2C) on RC3/RC4
#option I2C_SCL = PORTC.3
#option I2C_SDA = PORTC.4
Include "I2C.bas"

// LCD 
#option LCD_DATA = PORTA.0
#option LCD_RS = PORTA.4
#option LCD_EN = PORTA.5 
#option LCD_RW = PORTA.6
Include "lcd.bas"


// setup PPS (settings derived from PPSTool)
public sub InitPPS()
    pps.unlock()

    'Module: MSSP1
    SSP1CLKPPS = $0013    'RC3 > SCL1
    RC3PPS = $000F        'SCL1 > RC3 (bidir)
    SSP1DATPPS = $0014    'RC4 > SDA1
    RC4PPS = $0010        'SDA1 > RC4 (bidir)

    // this is optional (depends on config PPS1WAY setting)
    'pps.lock()
end sub


// target 24LC128 I2C EEPROM device...
Const I2C_EEPROM = $A0 

// local variables...
Dim
   Value As Byte,
   Address As Word

// program start...
InitPPS()
WriteAt(1,1,"test")   

Address = 0
I2C.Initialize (I2C_100_KHZ)

// test to ensure I2C is working and we can see the EEPROM
if (I2C.IsPresent(I2C_EEPROM)) then
    WriteAt(1,1,"I2C_EEPROM ACK")
else
    WriteAt(1,1,"I2C_EEPROM NACK")
endif

// write some data...
I2C.Start                          
I2C.WriteByte(I2C_EEPROM)              
I2C.WriteByte(Address.Byte1)       
I2C.WriteByte(Address.Byte0)        
I2C.WriteByte("Z")                 
I2C.Stop 

// allow external EEPROM to write data...
DelayMS(10) 

// read the data back...
I2C.Start
I2C.WriteByte(I2C_EEPROM)
I2C.WriteByte(Address.Byte1)        
I2C.WriteByte(Address.Byte0)        
I2C.Restart                         
I2C.WriteByte(I2C_EEPROM + 1)
Value = I2C.ReadByte(I2C_NOT_ACKNOWLEDGE)
I2C.Stop

WriteAt(2,1,Value)

If you want to use MSSP2 then you need to use the I2C2.bas module and change various settings/calls...

Code: Select all

// PPS
include "pps.bas"

// MSSP2 (I2C2) on RB1/RB2
#option I2C2_SCL = PORTB.1
#option I2C2_SDA = PORTB.2
include "I2C2.bas"

// setup PPS (settings derived from PPSTool)
public sub InitPPS()
    pps.unlock()

    'Module: MSSP2
    SSP2CLKPPS = $0009    'RB1 > SCL2
    RB1PPS = $0011        'SCL2 > RB1 (bidir)
    SSP2DATPPS = $000A    'RB2 > SDA2
    RB2PPS = $0012        'SDA2 > RB2 (bidir)

    // this is optional (depends on config PPS1WAY setting)
    'pps.lock()
end sub

I2C2.Initialize (I2C_100_KHZ)

// test to ensure I2C is working and we can see the EEPROM
if (I2C2.IsPresent(I2C_EEPROM)) then
    WriteAt(1,1,"I2C_EEPROM ACK")
else
    WriteAt(1,1,"I2C_EEPROM NACK")
endif
Change all the "I2C." references to "I2C2."

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: I2C pic18f26K40

Post by Gunplumber » Wed Nov 23, 2022 11:47 am

Hi Jerry

Thanks for the detailed reply. I have tried again today to get it working but still no progress. :cry:
I've updated the code as you have suggested but i could get no output from the data or clock lines testing with my logic analyzer.
I am using a 24lc256 with the clock and data lines connected directly to the corresponding pins on the PIC. I have tried some 10K pull up resistors on each line, as well as no pull ups at all. Neither seemed to make any difference. I also tried setting Rc3 and Rc4 as input and as outputs but again no difference.
To try and make things simpler i have omitted the write section of the code and have pre-programed the EEprom using my bench programmer with the first address containing a "z" char.

The code below executes the "I2C.Initialize (I2C_100_KHZ)" command but seems to get stuck in the "i2c.start" command.
The I2C.start command does two things, first it checks and waits for the idle status to be true (which i have checked and confirmed the idle status bits are set to 0), and then it sets bit.0 of the SSP1CON2.0 register (start condition enable) and then waits for the hardware to reset it. I think its here where thing are going wrong as it seems to just sit there waiting for the SSP1CON2.0 bit to be cleared by the hardware.
After discovering this i thought perhaps i may have had a faulty device so i tried another new from the packet PIC18f26k40 but it has the same results..
I don't know where to go from here?

Cheers
Lee




Code: Select all

{
****************************************************************
*  Name    : I2C EEPROM                                        *
*  Author  : David John Barker                                 *
*  Notice  : Copyright (c) 2006 Mecanique                      *
*          : All Rights Reserved                               *
*  Date    : 04/04/2006                                        *
*  Version : 1.0                                               *
*  Notes   :                                                   *
*          :                                                   *
****************************************************************
}

// if device and clock are omitted, then the compiler defaults to 
// 18F452 @ 20MHz - they are just used here for clarity...
Device = 18F26k40
Clock = 64

Config
    FEXTOSC = OFF,      // Oscillator not enabled
    RSTOSC = HFINTOSC_64MHZ,// HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1 
    CLKOUTEN = OFF,     // CLKOUT function is disabled
    CSWEN = ON,         // Writing to NOSC and NDIV is allowed
    FCMEN = ON,         // Fail-Safe Clock Monitor enabled
    MCLRE = EXTMCLR,    // If LVP = 0, MCLR pin is MCLR; If LVP = 1, RE3 pin function is MCLR 
    PWRTE = OFF,        // Power up timer disabled
    LPBOREN = OFF,      // ULPBOR disabled
    BOREN = SBORDIS,    // Brown-out Reset enabled , SBOREN bit is ignored
    BORV = VBOR_2P45,   // Brown-out Reset Voltage (VBOR) set to 2.45V
    ZCD = OFF,          // ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = ON,       // PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle
    STVREN = ON,        // Stack full/underflow will cause Reset
    DEBUG = OFF,        // Background debugger disabled
    XINST = OFF,        // Extended Instruction Set and Indexed Addressing Mode disabled
     //
    //WDTCPS = WDTCPS_10,  // Divider ratio 1:32768
   // WDTE = ON,          // WDT enabled regardless of sleep
   // WDTCWS = WDTCWS_7,  // window always open (100%); software control; keyed access not required
   // WDTCCS = LFINTOSC,  // WDT reference clock is the 31.0 kHz LFINTOSC
    WDTCPS = WDTCPS_31, // Divider ratio 1:65536; software control of WDTPS
    WDTE = OFF,          // WDT enabled regardless of sleep
    WDTCWS = WDTCWS_7,  // window always open (100%); software control; keyed access not required
    WDTCCS = SC,        // Software Control  
    WRT0 = OFF,         // Block 0 (000800-003FFFh) not write-protected
    WRT1 = OFF,         // Block 1 (004000-007FFFh) not write-protected
    WRT2 = OFF,         // Block 2 (008000-00BFFFh) not write-protected
    WRT3 = OFF,         // Block 3 (00C000-00FFFFh) not write-protected
    WRTC = OFF,         // Configuration registers (300000-30000Bh) not write-protected
    WRTB = OFF,         // Boot Block (000000-0007FFh) not write-protected
    WRTD = OFF,         // Data EEPROM not write-protected
    SCANE = ON,         // Scanner module is available for use, SCANMD bit can control the module
    LVP = OFF,          // HV on MCLR/VPP must be used for programming
    CP = OFF,           // UserNVM code protection disabled
    CPD = OFF,          // DataNVM code protection disabled
    EBTR0 = OFF,        // Block 0 (000800-003FFFh) not protected from table reads executed in other blocks
    EBTR1 = OFF,        // Block 1 (004000-007FFFh) not protected from table reads executed in other blocks
    EBTR2 = OFF,        // Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks
    EBTR3 = OFF,        // Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks
    EBTRB = OFF   
#option LCD_DATA = PORTA.0
#option LCD_RS = PORTA.4
#option LCD_EN = PORTA.5 
#option LCD_RW = PORTA.6
#option digitalio_init=true
#option I2C_SCL = PORTC.3         
#option I2C_SDA = PORTC.4 
// import libraries...

Include "I2C.bas"
Include "lcd.bas"
Include "setdigitalio.bas"
Include "intosc.bas"
Include "pps.bas"
Include "convert.bas"

Public Sub InitPPS()    
    pps.unlock()
    'Module: MSSP1
    SSP1CLKPPS = $0013    'RC3 > SCL1
    RC3PPS = $000F        'SCL1 > RC3 (bidir)
    SSP1DATPPS = $0014    'RC4 > SDA1
    RC4PPS = $0010        'SDA1 > RC4 (bidir
    'Module: MSSP1 extra settings
    Output(PORTC.4)    'Set I2C pin as output
    Output(PORTC.3)    'Set I2C pin as output    
End Sub

// target 24LC128 I2C EEPROM device...
Const I2C_EEPROM = $A0 

// local variables...
Dim   
   Value As Byte,
   Address As Word

Dim TEST As Byte
Dim Test_string As String(2)
Dim LED As PORTB.7
Output (PORTB.7)
Low(LED)

Write("a")   

// program start...
Address = 0
I2C.Initialize (I2C_100_KHZ)

Write("b")  

//I2C.Start                          
// Write("c")
//I2C.WriteByte(I2C_EEPROM)              
//Write("d")
//I2C.WriteByte(Address.Byte1)       
//I2C.WriteByte(Address.Byte0)        
//I2C.WriteByte("Z")                 
//I2C.Stop 
// Write("e")  
// allow external EEPROM to write data...
//DelayMS(10) 
// read the data back...

High (LED)
DelayUS(5)
Low (LED)

I2C.Start

High (LED)
Low (LED)

I2C.WriteByte(I2C_EEPROM)

High (LED)
Low (LED)

I2C.WriteByte(Address.Byte1)        
I2C.WriteByte(Address.Byte0)        

High (LED)
Low (LED)

I2C.Restart                         

High (LED)
Low (LED)

I2C.WriteByte(I2C_EEPROM + 1)

Value = I2C.ReadByte
High (LED)
Low (LED)
I2C.Acknowledge(I2C_NOT_ACKNOWLEDGE)
I2C.Stop

WriteAt(2,1,Value)
WriteAt(2,8,"g")


Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: I2C pic18f26K40

Post by Jerry Messina » Wed Nov 23, 2022 1:54 pm

You definitely need to have pullup resistors on both the SDA and SCL lines.
10K usually works at that speed, but it may be a bit high... try around 5K.

You have to call InitPPS()
The simplest program would be something like

Code: Select all

InitPPS()

I2C.Initialize (I2C_100_KHZ)

// test to detect a device at addr I2C_EEPROM ($A0)
if (I2C.IsPresent(I2C_EEPROM)) then
    WriteAt(1,1,"I2C_EEPROM ACK")		' device detected
else
    WriteAt(1,1,"I2C_EEPROM NACK")		' device not detected
endif
I2C.IsPresent(addr) will send a START-ADDR-STOP sequence and return true if the device ACKs
Does your logic analyzer show any activity on SDA/SCL? Are they both high after the call to I2C.Initialize?

I'll see if I can dig up a K40 to try it on, but it might take me a day or two to get to it...

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: I2C pic18f26K40

Post by Gunplumber » Thu Nov 24, 2022 10:49 am

Hi Jerry

Well it seems i have it sorted out! Thanks once again for all your help and patience. The issue was that i simply neglected to call the InitPPS() sub. :oops: I assumed it behaved like the include Setdigitalio.bas and automatically called the sub.
I now have it reading and writing to the EEprom.
One thing i founder interesting though was that i could monitor the clock signal with my logic analyzer, but if it tried to monitor the data pin, the data would not read correctly and would return a null character. My logic analyzer is a cheap USB type so i'm not sure if its an issue with it or something else..

Many thanks again..

Cheers
Lee

BTW i have another question regarding variable aliasing in the compiler forum..

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: I2C pic18f26K40

Post by Jerry Messina » Thu Nov 24, 2022 1:37 pm

I assumed it behaved like the include Setdigitalio.bas and automatically called the sub
Since InitPPS() is a sub that YOU have to write (even if you use the PPSTool to generate the code for it), there's no easy way to get the compiler to automatically call it for you.

Post Reply