News:

Let's find out together what makes a PIC Tick!

Main Menu

I2Cin problem at 64MHz

Started by Frizie, Aug 28, 2024, 10:53 AM

Previous topic - Next topic

Frizie

(Extra info: I have I2C_Bus_SCL = On and therefore no pull-up necessary for the CLK line)


I have some timing problems with I2Cin when the 18F45K22 runs at 64MHz (internal).
After years of reading an IR temperature sensor (MLX90614) without problems with I2Cin where the PIC runs on a 22.1184MHz crystal, I am testing whether the program can also be used with the 64MHz internal oscillator.

Everything works fine, also reading a 24C512 EEPROM and a PCF8593 RTC chip works fine on the hardware I²C with HBusin.
Only the temperature sensor via the software I²C with I2Cin does not, it continuously reads 65535.
Enabling I2C_Slow_Bus = On gave a slight improvement, i.e. about random 50% of the time the correct value is read (I measure every second), the other time I get 65535.

I don't know if there is a way to check if I2Cin is reading too fast at 64MHz, and if I2C_Slow_Bus might need to reduce the speed even more at 64MHz?
Or is there another way to read slower?
Ohm sweet Ohm | www.picbasic.nl

top204

#1
The Declare I2C_Slow_Bus adds a few 10s of clock cycle delays to the library subroutine depending on the frequency the microcontroller is operating at. However, not all I2C devices operate as a slave with efficiency, so it may need slowing down even more. In which case, it is better to create custom software I2C routines so they can be altered to suite, and work on many PIC microcontroller family types.

Below is a code listing for software I2C procedures that can be tailored to suite any device, and also have small delays between pin state changes:

$ifndef _I2C_SOFTWARE_INC_
$define _I2C_SOFTWARE_INC_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' I2C_Software.inc
' Software I2C writing and reading routines
' Written for the Positron8 BASIC compiler by Les Johnson
'
' Setup the default pins for SDA and SCL
'
$ifndef I2C_SDA_Pin
    $define I2C_SDA_Pin PORTC.4
    $SendWarning "$define I2C_SDA_Pin missing from main program, so using the default pin of PORTC.4"
$endif

$ifndef I2C_SCL_Pin
    $define I2C_SCL_Pin PORTC.3
    $SendWarning "$define I2C_SCL_Pin missing from main program, so using the default pin of PORTC.3"
$endif

'----------------------------------------------------------------------------------------------
' Delay a fixed number of microseconds and clock cycles.
' These can be changed to suite the speed required by the I2C interface.
'
$define I2C_hDelayCs() DelayCs 2                        ' Delay a number of clock cycles

Proc I2C_hDelayUs()
    DelayUS 3                                           ' Delay a number of microseconds
EndProc

'----------------------------------------------------------------------------------------------
' Send a single byte to the I2C bus.
' Input     : pData holds the byte to write
' Output    : None
' Notes     : MSB first
'
Proc I2C_WriteByte(pData As Byte)
    Dim bIndex As Byte

    For bIndex = 7 Downto 0                             ' Create a loop for the 8-bits
        If pData.7 = 1 Then
            PinInput I2C_SDA_Pin                        ' Make the SDA pin an Input (High)
        Else
            PinOutput I2C_SDA_Pin                       ' Make the SDA pin an Output (Low)
        EndIf
        I2C_hDelayUs()                                  ' Delay between pin state changes
        PinInput I2C_SCL_Pin                            ' Make the SCL pin an Input (High)
        I2C_hDelayUs()                                  ' Delay between pin state changes
        PinOutput I2C_SCL_Pin                           ' Make the SCL pin an Output
        I2C_hDelayUs()                                  ' Delay between pin state changes
        pData = pData << 1
    Next
'
' Close up
'
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
    PinInput I2C_SCL_Pin                                ' Make the SCL pin an Input (High)
    I2C_hDelayUs()                                      ' Delay between pin state changes
    PinOutput I2C_SCL_Pin                               ' Make the SCL pin an Output
    I2C_hDelayUs()                                      ' Delay between pin state changes
EndProc

'----------------------------------------------------------------------------------------------
' Read a single byte from the I2C bus.
' Input     : None
' Output    : Returns the byte received
' Notes     : MSB first, sample before clock
'
Proc I2C_ReadByte(), Byte
    Dim bIndex   As Byte
    Dim wTimeOut As Word

    Result = 0
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
    For bIndex = 7 Downto 0                             ' Create a loop for the 8-bits
        Result = Result << 1
        '
        ' Wait for any clock stretching
        '
        wTimeOut = $FFFF
        Repeat                                          ' Create a loop
            PinInput I2C_SCL_Pin                        ' Make the SCL pin an Input
            Dec wTimeOut                                ' \
            If wTimeOut = 0 Then Break                  ' / Exit the loop if the timeout reaches 0
        Until I2C_SCL_Pin = 1                           ' Until the SCL pin is high
        '
        ' Read the data
        '
        I2C_hDelayUs()                                  ' Delay between pin state changes
        Result.0 = I2C_SDA_Pin
        PinOutput I2C_SCL_Pin                           ' Make the SCL pin an Output (Low)
        I2C_hDelayUs()                                  ' Delay between pin state changes
    Next
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus Start condition.
' Input     : None
' Output    : None
' Notes     : A Start condition is high to low of SDA when SCL is high
'
Proc I2C_Start()
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinInput I2C_SCL_Pin                                ' Make the SCL pin an Input (High)
    I2C_hDelayCs()                                      ' Delay a few clock cycles between pin state changes
    PinOutput I2C_SDA_Pin                               ' Make the SDA pin an Output (Low)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinOutput I2C_SCL_Pin                               ' Make the SCL pin an Output (Low)
    I2C_hDelayCs()                                      ' Delay a few clock cycles between pin state changes
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus ReStart condition.
' Input     : None
' Output    : None
' Notes     : None
'
$define I2C_ReStart() I2C_Start()

'----------------------------------------------------------------------------------------------
' Send an I2C bus Stop condition.
' Input     : None
' Output    : None
' Notes     : A Stop condition is low to high of SDA when SCL is high
'
Proc I2C_Stop()
    PinOutput I2C_SDA_Pin                               ' Make the SDA pin an Output (Low)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinInput I2C_SCL_Pin                                ' Make the SCL pin an Input (High)
    I2C_hDelayCs()                                      ' Delay a few clock cycles between pin state changes
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
    I2C_hDelayCs()                                      ' Delay a few clock cycles between pin state changes
EndProc

'----------------------------------------------------------------------------------------------
' Initiate an I2C Acknowledge.
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Ack()
    I2C_hDelayCs()
    PinOutput I2C_SDA_Pin                               ' Make the SDA pin an Output (Low)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinInput I2C_SCL_Pin                                ' Make the SCL pin an Input (High)
    I2C_hDelayCs()                                      ' Delay a few clock cycles between pin state changes
    PinOutput I2C_SCL_Pin                               ' Make the SCL pin an Output (Low)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
EndProc

'----------------------------------------------------------------------------------------------
' Initiate an I2C Not Acknowledge.
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_NAck()
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinInput I2C_SCL_Pin                                ' Make the SCL pin an Input (High)
    I2C_hDelayCs()                                      ' Delay a few clock cycles between pin state changes
    PinOutput I2C_SCL_Pin                               ' Make the SCL pin an Output (Low)
    DelayCs 1                                           ' Delay a clock cycle between pin state changes
    PinInput I2C_SDA_Pin                                ' Make the SDA pin an Input (High)
EndProc

'----------------------------------------------------------------------------------------------
' Setup the SDA and SCL pins for the software I2C interface.
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_SetupPins()
    PinLow I2C_SDA_Pin                                  ' Make the I2C SDA pin an output low
    PinLow I2C_SCL_Pin                                  ' Make the I2C SCL pin an output low
EndProc

'----------------------------------------------------------------------------------------------
' Startup section
'
_I2C_Main_:
    I2C_SetupPins()                                     ' Setup the SDA and SCL pins for the software I2C interface

$endif      ' _I2C_SOFTWARE_INC_

Create a file named "I2C_Software.inc" from the code listing above, and place it in the compiler Includes folder: "C:\Users\Computer Name\PDS\Includes\". At this location, all programs can see it and use it with a simple Include directive.

If it is still operating too fast for the I2C slave device, change the values for the delays.

Below is a generic demonstration of using the "I2C_Software.inc" library, that writes and reads an I2C EEPROM:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Write and read an I2C EEPROM using the software I2C library routines
' Written for the Positron8 BASIC compiler by Les Johnson
'
    Device = 18F25K22                           ' Tell the compiler what device to compile for
    Declare Xtal = 64                           ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Setup the pins to use for the I2C interface
'
$define I2C_SDA_Pin PORTC.4                     ' Set the SDA pin for the I2C library
$define I2C_SCL_Pin PORTC.3                     ' Set the SCL pin for the I2C library

    Include "I2C_Software.inc"                  ' Load the software I2C library routines
'
' Create variables for the demo
'
    Dim bAddress As Byte
    Dim ByteIn   As Byte
    Dim ByteOut  As Byte

'---------------------------------------------------------------------------------------------
Main:
'
' Write bytes to the I2C EEPROM
'
    I2C_Start()                                 ' Send a Start condition
    I2C_WriteByte(%10100000)                    ' Target an EEPROM, and send a Write command
    I2C_WriteByte(0)                            ' Send the High Byte of the address
    I2C_WriteByte(0)                            ' Send the Low Byte of the address
    For ByteIn = 0 To 9                         ' Create a loop
        I2C_WriteByte(ByteIn)                   ' Send the value of ByteIn to the EEPROM
    Next                                        ' Close the loop
    I2C_Stop()                                  ' Send a Stop condition
    DelayMS 5                                   ' Wait for the data to be entered into the EEPROM matrix
'
' Read bytes from the I2C EEPROM and display them on a serial terminal
'
    I2C_Start()                                 ' Send a Start condition
    I2C_WriteByte(%10100000)                    ' Target an EEPROM, and send a Write command
    I2C_WriteByte(0)                            ' Send the High Byte of the address
    I2C_WriteByte(0)                            ' Send the Low Byte of the address
    I2C_ReStart()                               ' Send a Restart condition
    I2C_WriteByte(%10100001)                    ' Target an EEPROM, and send a Read command
    For bAddress = 0 To 9                       ' Create a loop
        ByteOut = I2C_ReadByte()                ' Load ByteOut with the byte received
        If bAddress = 9 Then                    ' Are we at the end of the reads?
            I2C_Stop()                          ' Yes. So send a Stop command
        Else                                    ' Otherwise...
            I2C_Ack()                           ' Send an Ack command
        EndIf
        HRSOutLn Dec ByteOut                    ' Display ByteOut on the serial terminal
    Next                                        ' Close the loop
    HRsout 13

'------------------------------------------------------------------------------
' Setup the config fuses for a PIC18F25K22 device to operate with an external crystal with the 4xPLL enabled
'
Config_Start
    FOSC     = HSHP                             ' HS oscillator (high power > 16 MHz)
    PLLCFG   = On                               ' Oscillator multiplied by 4
    PRICLKEN = On                               ' Primary clock enabled
    FCMEN    = Off                              ' Fail-Safe Clock Monitor disabled
    IESO     = Off                              ' Internal/External Oscillator Switchover mode disabled
    PWRTEN   = On                               ' Power up timer enabled
    BOREN    = SBORDIS                          ' Brown-out Reset enabled in hardware only (SBOREN is disabled)
    BORV     = 190                              ' Brown Out Reset Voltage set to 1.90 V nominal
    WDTEN    = Off                              ' Watch dog timer is always disabled. SWDTEN has no effect.
    WDTPS    = 128                              ' Watchdog Timer Postscale 1:128
    CCP2MX   = PORTC1                           ' CCP2 input/output is multiplexed with RC1
    PBADEN   = Off                              ' PORTB<5:0> pins are configured as digital I/O on Reset
    CCP3MX   = PORTB5                           ' P3A/CCP3 input/output is multiplexed with RB5
    HFOFST   = On                               ' HFINTOSC output and ready status are not delayed by the oscillator stable status
    T3CMX    = PORTC0                           ' Timer3 Clock Input (T3CKI) is on RC0
    P2BMX    = PORTB5                           ' ECCP2 B (P2B) is on RB5
    MCLRE    = EXTMCLR                          ' MCLR pin enabled, RE3 input pin disabled
    STVREN   = Off                              ' Stack full/underflow will not cause Reset
    LVP      = Off                              ' Single-Supply ICSP disabled
    XINST    = Off                              ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    Debug    = Off                              ' Debug disabled
    Cp0      = Off                              ' Block 0 (000800-001FFF) not code-protected
    CP1      = Off                              ' Block 1 (002000-003FFF) not code-protected
    CP2      = Off                              ' Block 2 (004000-005FFF) not code-protected
    CP3      = Off                              ' Block 3 (006000-007FFF) not code-protected
    CPB      = Off                              ' Boot block (000000-0007FF) not code-protected
    CPD      = Off                              ' Data EEPROM not code-protected
    WRT0     = Off                              ' Block 0 (000800-001FFF) not write-protected
    WRT1     = Off                              ' Block 1 (002000-003FFF) not write-protected
    WRT2     = Off                              ' Block 2 (004000-005FFF) not write-protected
    WRT3     = Off                              ' Block 3 (006000-007FFF) not write-protected
    WRTC     = Off                              ' Configuration registers (300000-3000FF) not write-protected
    WRTB     = Off                              ' Boot Block (000000-0007FF) not write-protected
    WRTD     = Off                              ' Data EEPROM not write-protected
    EBTR0    = Off                              ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
    EBTR1    = Off                              ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
    EBTR2    = Off                              ' Block 2 (004000-005FFF) not protected from table reads executed in other blocks
    EBTR3    = Off                              ' Block 3 (006000-007FFF) not protected from table reads executed in other blocks
    EBTRB    = Off                              ' Boot Block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

Frizie

Thank you for the given solution Les!  :)

By the way, I have multiple MLX90614 IR temperature sensors in this project (all with the same address) on different ports.
That is the reason I chose the software I²C variant for the temperature sensors, I can easily switch between them.
They all share the same clock line, only the data line have for each a separate PORT.
Furthermore, I do not have a pull-up with the clock line on the PCB (DECLARE I2C_Bus_SCL = On).

I think I will stay with the 22MHz crystal.
I2Cin TempSensorData1, TempSensorClk, MLX_Address, MLX_Tobj1, [SensorTemp1.LowByte, SensorTemp1.HighByte]

It is precisely the simplicity of the I2Cin and I2Cout instructions of the Positron PIC Basic compiler that makes me go for simplicity.

Anyway, I will try your solution later.
Thank a lot in advance  :D
Ohm sweet Ohm | www.picbasic.nl

top204

#3
I have added a few extra clock cycle delays to the I2Cin and I2Cout commands for the slow bus declare, so the next compiler update may cure the problem.

It may not be the speed of the interface, it may be that the peripheral requires longer delays for clock stretching, and at a higher speed, the loop within the I2C commands may be timing out too quickly.

Frizie

#4
Thanks again Les.
I don't know if it is already like this with I2C_Slow_Bus, but actually the delays should depend on the chosen XTAL value.
The larger the XTAL value, the more clock cycles so that the time ratio remains approximately the same for all possible XTAL values.
Ohm sweet Ohm | www.picbasic.nl

top204

#5
That is just what it does within the compiler's assembler code I2C subroutine generation routines:

if(Global_tI2C_Slow == true)
    {
        if(XTAL >= 16)
            {
                bra("$ + 2");
                bra("$ + 2");
            }
        if(XTAL >= 20)
            {
                bra("$ + 2");
                bra("$ + 2");
            }
        if(XTAL >= 24)
            {
                bra("$ + 2");
                bra("$ + 2");
                bra("$ + 2");
                bra("$ + 2");
            }
        if(XTAL >= 32)
            {
                bra("$ + 2");
                bra("$ + 2");
            }
        if(XTAL >= 40)
            {
                bra("$ + 2");
                bra("$ + 2");
            }
        if(XTAL >= 48)
            {
                bra("$ + 2");
                bra("$ + 2");
            }
        if(XTAL >= 64)
            {
                bra("$ + 2");
                bra("$ + 2");
                bra("$ + 2");
            }
    }

And a 'bra $ + 2' will delay 2 clock cycles on an 18F device, and 'bra $ + 1' on an enhanced 14-bit core device, and 'Goto $ + 1' on a standard 14-bit core device because they do not have a bra mnemonic.