News:

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

Main Menu

Software i2c in Positron

Started by TimB, Mar 07, 2023, 10:51 AM

Previous topic - Next topic

Frizie

Les, I see you use a procedure for the delay routine I2C_hDelay() which contains only DelayUs 1
Couldn't you write "better":

Symbol I2C_hDelay = 1

and at the various places in the program itself:

DelayUs I2C_hDelay

or, what is the advantage of using a procedure in this case?
Ohm sweet Ohm | www.picbasic.nl

top204

#21
If larger delays are used, the value for the delay is placed in compiler system variables then the compiler's library subroutine will be called to perform the delay itself, using more flash memory every time it is used for a single delay time.

For microcontrollers operating at faster speeds, the DelayUs command will use a block of assembler code that implements a small delay inline so that it is accurate, but for the accuracy, it takes up more flash memory whenever it is used in the program. However, with the procedure, it is a single call that is being made from the program, regardless of the delay required within it.

For example... On an 18F device operating at 64MHz, a DelayUs 1 command will produce the assembler code:

    movlw 5
_pblb__2
    decfsz WREG,F,0
    bra _pblb__2
    nop

Whenever it is used in the program's listing.

But if a procedure is created to delay for 1uS, the assembler code to call it is:

call DelayUS_1

or

rcall DelayUS_1, if the procedure is close to the memory address that called it, saving flash memory and operating faster, thanks to the optimiser pass.

So... Saving quite a bit of flash memory to do the same task if used multiple times in a program.

The DelayUs_1() procedure is:

Proc DelayUS_1()
    DelayUs 1
EndProc

For, not absolutely accurate, delays, a procedure call will save a lot of flash memory if it is used a lot in the program listing, and do the same job. However, it must be remembered that the enhanced 14-bit core devices do not have a large call stack, so always be aware of the call depth from procedures to procedures to procedures. The 18F devices have a larger call stack and the new 18F devices have a larger stack than the original 18F devices, so it is rarely a problem calling procedures from procedures from procedures with them, but it is important to be aware of the call stack size on the device.

JonW

This is excellent information and should be cited in the manual Les.

TimB


I just want to say thanks to Les for the i2c code. After playing with it and replacing the I2C_hDelay() with a NOP and specifying the port directly I have managed to get a read of 1 byte to 24us.  That is talking to a 32mhz pic 16f device acting as a slave. Clock stretching is working well and has made all the difference.

Talking to this slave is important as I do it at 100hz and that means main CPU overhead is reduced. 999.999% All I need is one byte. I needed 2-3 bits of that byte and was seriously thinking I would drop i2c and have it just +5, bit0, bit1, 0v and some other system to force it to talk i2c for a few other bytes I may need to check once in a while.

I have made my system as modules so if one breaks its a plug and play replacement.

As an aside this 16F module has water level sensors and even just reading the ADC channels it uses would take > 100us so well worth the effort to move it to standalone and talk i2c to it.



Its been an adventure for me this i2c stuff but looking at Les's code it now looks simple and understand what is happening.

 

top204

#24
Many thanks Jon and Tim.

Below is the "Software_I2C.inc" procedures made to use fixed pins for the SDA and SCL lines, and I named it "Software_I2C_Fixed_Pins.inc" in the includes directory:

$ifndef _Software_I2C_Fixed_Pins_inc_
$define _Software_I2C_Fixed_Pins_inc_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Software I2C writing and reading routines using fixed pins for the SDA and SCL lines.
'
' Written for the Positron8 and Positron16 BASIC compilers by Les Johnson
'
' Set the pins used for the I2C SDA and SCL lines
'
    Symbol I2C_SDA_Pin = PORTC.3                ' Holds the pin used for the SDA line
    Symbol I2C_SCL_Pin = PORTC.4                ' Holds the pin used for the SCL line
'
' Create some global variables for the routines
'
    Dim I2C_bBitIndex As Byte Access            ' Holds the index for the bits to send/receive on the I2C line
    Dim I2C_bData     As Byte Access            ' Holds the byte sent or received

'----------------------------------------------------------------------------------------------
' Create a very small delay to slow down the I2C interface (if required)
' Input     : None
' Output    : None
' Notes     : None
'
$define I2C_hDelay() DelayCs 1

'----------------------------------------------------------------------------------------------
' Write a byte to the I2C bus
' Input     : pData holds the byte to write
' Output    : None
' Notes     : MSB first
'
Proc I2C_WriteByte(pData As I2C_bData)
    For I2C_bBitIndex = 7 DownTo 0              ' Create a loop for the 8-bits to write
        If pData.7 = 1 Then                     ' Is bit-7 of pData set?
            PinInput I2C_SDA_Pin                ' Yes. So make the SDA pin an input (made high by the pull-up resistor)
        Else                                    ' Otherwise...
            PinLow I2C_SDA_Pin                  ' Make the SDA pin an output low
        EndIf                                   '
        I2C_hDelay()                            ' Create a small delay
        PinInput I2C_SCL_Pin                    ' Make the SCL pin an input (made high by the pull-up resistor)
        I2C_hDelay()                            ' Create a small delay
        PinLow I2C_SCL_Pin                      ' Make the SCL pin an output low
        I2C_hDelay()                            ' Create a small delay
        Rol pData                               ' Rotate pData left by 1 bit
    Next
'
' Close up the interface
'
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
    PinInput I2C_SCL_Pin                        ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SCL_Pin                          ' Make the SCL pin an output low
    I2C_hDelay()                                ' Create a small delay
EndProc

'----------------------------------------------------------------------------------------------
' Read a byte from the I2C bus
' Input     : None
' Output    : Returns the byte received
' Notes     : MSB first
'
Proc I2C_ReadByte(), I2C_bData
    Dim wTimeout As Word

    Result = 0                                  ' Clear the result of the procedure before entering the loop
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
    For I2C_bBitIndex = 7 DownTo 0              ' Create a loop for the 8-bits to read
        Result = Result << 1                    ' Shift the result value left by 1 bit
        '
        ' Wait for any clock stretching required, with a small timeout
        '
        PinInput I2C_SCL_Pin                    ' Make the SCL pin an input (made high by the pull-up resistor)
        wTimeout = $01FF                        ' \
        Repeat                                  ' / Create a loop for the clock stretching
            If I2C_SCL_Pin = 1 Then Break       ' Exit the loop when the SCL pin is high
            Dec wTimeout                        ' \ Until the timeout value reaches 0
        Until wTimeout = 0                      ' /
        '
        ' Read the data
        '
        I2C_hDelay()                            ' Create a small delay
        Result.0 = I2C_SDA_Pin                  ' Read the state of the SDA pin into bit-0 of the result
        PinLow I2C_SCL_Pin                      ' Make the SCL pin an output low
        I2C_hDelay()                            ' Create a small delay
    Next
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus Start condition
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Start()
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SCL_Pin                        ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SDA_Pin                          ' Make the SDA pin an output low
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SCL_Pin                          ' Make the SCL pin an output low
    I2C_hDelay()
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus ReStart condition
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_ReStart()
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SCL_Pin                        ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SDA_Pin                          ' Make the SDA pin an output low
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SCL_Pin                          ' Make the SCL pin an output low
    I2C_hDelay()
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus Stop condition
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Stop()
    PinLow I2C_SDA_Pin                          ' Make the SDA pin an output low
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SCL_Pin                        ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()
EndProc

'----------------------------------------------------------------------------------------------
' Initiate an I2C Acknowledge
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Ack()
    I2C_hDelay()
    PinLow I2C_SDA_Pin                          ' Make the SDA pin an output low
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SCL_Pin                        ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SCL_Pin                          ' Make the SCL pin an output low
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
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 (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SCL_Pin                        ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                ' Create a small delay
    PinLow I2C_SCL_Pin                          ' Make the SCL pin an output low
    I2C_hDelay()                                ' Create a small delay
    PinInput I2C_SDA_Pin                        ' Make the SDA pin an input (made high by the pull-up resistor)
EndProc

$endif      ' _Software_I2C_Fixed_Pins_inc_

It can be made faster still, but will need testing as the code is being altered, in case it operates too fast.

For safety sake, the I2C interface was slowed down in the library, but can be made faster by removing some of the I2C_hDelay() meta-macros when the pins are changing their state. i.e. In the I2C_Start, I2C_ReStart, I2C_Ack, I2C_NAck, I2C_Stop and I2C_ReadByte and I2C_WriteByte procedures.

Notice I have changed the I2C_hDelay to a meta-macro instead of a precedure, because the DelayCs command uses very few mnemonics when the cycles are very low. And DelayCs 1 places a single nop or clrwdt in the assembler listing, and DelayCs 2 will place a bra $+1 mnemonic etc...

Also, note that the clock stretching loop has been made into a Repeat-Until loop for longer time stretches, and the GetPin functions have been changed to standard Port . Pin reads, because they are no longer required.

tumbleweed

It would be a nice addition for writebyte to support stretching as well. You could speed things up by only looking for it during the 9th bit (ACK), since that's when most slaves will use it.

Very few will do it during the 8-bit data phase.

Frizie

For years I have succesfull used the next code to read a MLX90614 IR temperature sensor, running on a PIC18F45K22 on 22MHz:

SYMBOL TempSensorDta = PORTB.4
SYMBOL TempSensorClk = PORTB.7
SYMBOL MLX_Address   = 0
SYMBOL MLX_Tobj1     = $07

DIM SensorTemp AS WORD

I2CIN TempSensorDta, TempSensorClk, MLX_Address, MLX_Tobj1, [SensorTemp.LOWBYTE, SensorTemp.HIGHBYTE]

When I run the PIC18F45K22 on 64MHz, it doesn´t work anymore.

Now, I want to try using INCLUDE "Software_I2C_Fixed_Pins.inc" (see previous posted message from Les above)
But what exactly do I need to do to be able to read a WORD ("SensorTemp") instead of a BYTE in this routine?
And where do I need to place "MLX_Address" and "MLX_Obj1" to get the operation working exactly like I2CIN?

Can someone give me an example of how to call this routine in my main program?

Thanks in advance.
Ohm sweet Ohm | www.picbasic.nl

top204

#27
Many, many thanks for your most generous donation Frizie. You are such a lovely human being.

You can try adding Declare I2C_Slow_Bus = On, which will slow down the I2C interface with the I2CIn and I2COut commands, for slave devices that will not operate at higher speeds. See the I2COut section of the manual for more details about the Declare.

Or you can use procedures for a software (bit-bashed) I2C interface, as listed below:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate Software I2C writing and reading routines using fixed pins for the SDA and SCL lines, by writing and reading an I2C EEPROM.
'
' Written for the Positron8 and Positron16 BASIC compilers by Les Johnson
'
    Device = 18F26K22                                       ' Tell the compiler what device to compile for
    Declare Xtal = 64                                       ' Tell the compiler what frequency the device is running at (in MHz)
    Declare Auto_Variable_Bank_Cross = On                   ' Make sure all multi-byte variables remain within a single RAM bank
'
' Setup the USART (for debugging)
'
    Declare HRSOut1_Pin   = PORTB.7
    Declare HRSIn1_Pin    = PORTB.5
    Declare Hserial1_Baud = 9600
'
' Set the pins used for the I2C SDA and SCL lines
'
$define I2C_SDA_Pin PORTC.3                                 ' Holds the pin used for the SDA line
$define I2C_SCL_Pin PORTC.4                                 ' Holds the pin used for the SCL line
'
' Create some variables for use by the demo
'
    Dim MyLoop As Byte
    Dim MyArray[10] As Byte Heap

'----------------------------------------------------------------------------------------------
' The main loop starts here
' Write and read an I2C serial EEPROM
'
Main:
'
' Transmit bytes to the I2C bus
'
    I2C_Start()                                             ' Send a Start condition
    I2C_WriteByte($A0)                                      ' 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 MyLoop = 48 To 57                                   ' Create a loop containing ASCII 0 to 9
        I2C_WriteByte(MyLoop)                               ' Send the value of MyLoop to the eeprom
    Next                                                    ' Close the loop
    I2C_Stop()                                              ' Send a Stop condition
    DelayMS 5                                               ' Wait for the data to be entered into eeprom matrix
'
' Receive bytes from the I2C bus
'
    I2C_Start()                                             ' Send a Start condition
    I2C_WriteByte($A0)                                      ' 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($A1)                                      ' Target an eeprom and send a Read command
    For MyLoop = 0 To 9                                     ' Create a loop
        MyArray[MyLoop] = I2C_ReadByte()                    ' Load an array with bytes received
        If MyLoop = 9 Then                                  ' \
            I2C_Stop()                                      ' |
        Else                                                ' | Ack or Stop?
            I2C_Ack()                                       ' |
        EndIf                                               ' /
    Next                                                    ' Close the loop
    HRSOutLn Str MyArray                                    ' Transmit the Array as an ASCII String

'----------------------------------------------------------------------------------------------
' *** Software (bit-bashed) I2C procedures ***
'----------------------------------------------------------------------------------------------
' Create a very small delay to slow down the I2C interface (if required)
' Input     : None
' Output    : None
' Notes     : None
'
$define I2C_hDelay() DelayCs 1

'----------------------------------------------------------------------------------------------
' Write a byte to the I2C bus
' Input     : pData holds the byte to write
' Output    : None
' Notes     : MSB first
'
Proc I2C_WriteByte(pData As Byte)
Global Dim I2C_bBitIndex As Byte Access Shared              ' Holds the index for the bits to send/receive on the I2C line

    For I2C_bBitIndex = 7 DownTo 0                          ' Create a loop for the 8-bits to write
        If pData.7 = 1 Then                                 ' Is bit-7 of pData set?
            PinInput I2C_SDA_Pin                            ' Yes. So make the SDA pin an input (made high by the pull-up resistor)
        Else                                                ' Otherwise...
            PinLow I2C_SDA_Pin                              ' Make the SDA pin an output low
        EndIf                                               '
        I2C_hDelay()                                        ' Create a small delay
        PinInput I2C_SCL_Pin                                ' Make the SCL pin an input (made high by the pull-up resistor)
        I2C_hDelay()                                        ' Create a small delay
        PinLow I2C_SCL_Pin                                  ' Make the SCL pin an output low
        I2C_hDelay()                                        ' Create a small delay
        Rol pData                                           ' Rotate pData left by 1 bit
    Next
'
' Close up the interface
'
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
    PinInput I2C_SCL_Pin                                    ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                            ' Create a small delay
    PinLow I2C_SCL_Pin                                      ' Make the SCL pin an output low
    I2C_hDelay()                                            ' Create a small delay
EndProc

'----------------------------------------------------------------------------------------------
' Read a byte from the I2C bus
' Input     : None
' Output    : Returns the byte received
' Notes     : MSB first
'
Proc I2C_ReadByte(), Byte
Global Dim I2C_bBitIndex As Byte Access Shared              ' Holds the index for the bits to send/receive on the I2C line
    Dim wTimeout As Word

    Result = 0                                              ' Clear the result of the procedure before entering the loop
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
    For I2C_bBitIndex = 7 DownTo 0                          ' Create a loop for the 8-bits to read
        Result = Result << 1                                ' Shift the result value left by 1 bit
        '
        ' Wait for any clock stretching required, with a small timeout
        '
        PinInput I2C_SCL_Pin                                ' Make the SCL pin an input (made high by the pull-up resistor)
        wTimeout = $01FF                                    ' \
        Repeat                                              ' / Create a loop for the clock stretching
            If I2C_SCL_Pin = 1 Then Break                   ' Exit the loop when the SCL pin is high
            Dec wTimeout                                    ' \ Until the timeout value reaches 0
        Until wTimeout = 0                                  ' /
        '
        ' Read the data
        '
        I2C_hDelay()                                        ' Create a small delay
        Result.0 = I2C_SDA_Pin                              ' Read the state of the SDA pin into bit-0 of the result
        PinLow I2C_SCL_Pin                                  ' Make the SCL pin an output low
        I2C_hDelay()                                        ' Create a small delay
    Next
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus Start condition
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Start()
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
    PinInput I2C_SCL_Pin                                    ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                            ' Create a small delay
    PinLow I2C_SDA_Pin                                      ' Make the SDA pin an output low
    PinLow I2C_SCL_Pin                                      ' Make the SCL pin an output low
    I2C_hDelay()
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus ReStart condition
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_ReStart()
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
    PinInput I2C_SCL_Pin                                    ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                            ' Create a small delay
    PinLow I2C_SDA_Pin                                      ' Make the SDA pin an output low
    PinLow I2C_SCL_Pin                                      ' Make the SCL pin an output low
    I2C_hDelay()
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus Stop condition
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Stop()
    PinLow I2C_SDA_Pin                                      ' Make the SDA pin an output low
    I2C_hDelay()                                            ' Create a small delay
    PinInput I2C_SCL_Pin                                    ' Make the SCL pin an input (made high by the pull-up resistor)
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()
EndProc

'----------------------------------------------------------------------------------------------
' Initiate an I2C Acknowledge
' Input     : None
' Output    : None
' Notes     : None
'
Proc I2C_Ack()
    PinLow I2C_SDA_Pin                                      ' Make the SDA pin an output low
    I2C_hDelay()                                            ' Create a small delay
    PinInput I2C_SCL_Pin                                    ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                            ' Create a small delay
    PinLow I2C_SCL_Pin                                      ' Make the SCL pin an output low
    I2C_hDelay()                                            ' Create a small delay
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
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 (made high by the pull-up resistor)
    PinInput I2C_SCL_Pin                                    ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                                            ' Create a small delay
    PinLow I2C_SCL_Pin                                      ' Make the SCL pin an output low
    I2C_hDelay()                                            ' Create a small delay
    PinInput I2C_SDA_Pin                                    ' Make the SDA pin an input (made high by the pull-up resistor)
EndProc

I always prefer a procedural approach to interfaces, because it gives a lot more control over them, and with I2C, you can add procedures that automatically add or read Ack/NAck/Stop etc, if required.

Frizie

I still can't get it to work.

Is my code correct when I want to convert the I2Cin code...
I2CIN PORTB.4, PORTB.7, MLX_Address, MLX_Tobj1, [SensorTemp.LOWBYTE, SensorTemp.HIGHBYTE]
...to code for "I2C_Software.inc", is the next code then right?

$define I2C_SDA_Pin PORTB.4
$define I2C_SCL_Pin PORTB.7

I2C_Start()                                            ' Send a Start condition[/font]
I2C_WriteByte(MLX_Address)
I2C_WriteByte(MLX_Tobj1)
I2C_Restart()                                          ' Send a Restart condition
TempSensor.LOWBYTE  = I2C_ReadByte()
I2C_Ack()
TempSensor.HIGHBYTE = I2C_ReadByte()
I2C_Stop()


I'm not sure if I filled in the given variables for I2CIn correctly for the "I2C_Software.inc" variant, or am I missing something?

By the way, I use "I2C_Software.inc", because I don't use the fixed pins C.3 and C.4 but B.4 and B.7

Declare I2C_Slow_Bus = On has little to no effect.
Very occasionally it works.
(When the PIC is running at 22MHz it works 100% fine.)
Ohm sweet Ohm | www.picbasic.nl

top204

#29
The structure of the code to read the device looks slightly incorrect, because you are not telling the slave unit to put itself into read mode.

After sending the slave address and the register to read, it will need to be set to read mode, which will be something like:

I2C_Restart()                              ' Send a Restart condition
I2C_WriteByte(MLX_Address + 1)              ' Set the slave device to read mode (bit-0 set to 1)
TempSensor.LowByte  = I2C_ReadByte()
I2C_Ack()
TempSensor.HighByte = I2C_ReadByte()
I2C_Stop()


Also, try changing the delay to a higher value, so instead of:

$define I2C_hDelay() DelayCs 1

Which will delay the pin toggles by only 1 clock cycle, which is probably too fast for your slave device, use something like:

$define I2C_hDelay() DelayUs 1

Which will slow down the I2C interface a lot, and then alter the delay to a fster time when it is working, until it does not work. Unless you are happy with the microsecond delays, in which case leave it working.

trastikata

Quote from: Frizie on Jun 08, 2025, 11:50 AMCan someone give me an example of how to call this routine in my main program?

Not sure if this will help, but here's how I am using the MLX90640 I2C interface, you can remove the "H" from the Hardware I2C and make it software I2C.

'I2C Read
Proc GetMlx(wAddress As wAddressMlx), GetMlxResult
    HBStart2
        HBusOut2 WriteMlx 
        HBusOut2 wAddress.Byte1
        HBusOut2 wAddress.Byte0
    HBStart2
        HBusOut2 ReadMlx
        Result.Byte1 = HBusIn2
        HBusAck2
        Result.Byte0 = HBusIn2
        HBusNack2
    HBStop2
EndProc

'I2C Write
Proc SetMlx(wAddress As wAddressMlx, wData As Word)
    HBStart2
        HBusOut2 WriteMlx       
        HBusOut2 wAddress.Byte1 
        HBusOut2 wAddress.Byte0    
        HBusOut2 wData.Byte1
        HBusOut2 wData.Byte0          
    HBStop2
EndProc

Frizie

#31
Thanks Les and Trastikata.
I haven't tried your code yet Trastikata (thanks in advance for this) but I'll stop for today.

I can not get the "I2C_Software.inc" variant to work with the PIC at 22MHz either, while it does work with I2CIn.

Along the way I come across something strange.
I've been giving address 0 for the MLX90614 for years and it's been working flawlessly for years (with I2Cin).
But now I read in the datasheet that the address should be 0x5A ($5A).
If I enter $5A as the address, I2CIn does not work either (???)

I'll borrow an oscilloscope from work on Wednesday and see if I get any wiser and I'll try your code (Trastikata) too.

Thanks so far.
Ohm sweet Ohm | www.picbasic.nl

RGV250

Hi Frizie,
The default slave address in this datasheet https://www.melexis.com/-/media/files/documents/datasheets/mlx90614-datasheet-melexis.pdf
is 0x5A but it is programmable. Any chance you made it 0 ages ago and forgot?

Bob

Frizie

I know that the address is programmable, but I have never used it.
In addition, I use the MLX90614 in several projects and new, unused sensors usually work immediately.
With address 0... (strange).

I'll look into this tomorrow.
Ohm sweet Ohm | www.picbasic.nl

steyn

Hi Frizie, i sent you a pm :)

top204

#35
Remember, the $5A is the 7-bit slave address, so it will need to be shifted left 1 bit, because bit-0 of the slave address is the read or write bit.

Something like:

Symbol cMLX_Address = ($5A << 1)

A quick read of the device's datasheet also shows it is a rather slow SMbus interface (10KHz to 100KHz), so make sure the delays in the I2C procedures are around 1us, or a few hundred clock cycles, for a slightly faster toggling of the SDA and SCL lines, if 1us is too slow. For example:

$define I2C_hDelay() DelayCs 500

Also, you may need to increase the timeout value in the I2C_ReadByte procedure, from:

wTimeout = $01FF

To a larger 16-bit value for a longer delay while waiting, such as:

wTimeout = $1FFF

Frizie

Many thanks for help Les (and others).

An oscilloscope has made me a lot wiser AND the MLX90614 temperature sensor now works on the PIC running at 64MHz!
A combination of circumstances was the reason that I couldn't get it to work.

To start with, Les' comment, who wrote that the address should be $5A << 1, because (indeed) it's a 7-bit address because of the read/write bit that comes after it.
Oh, of course, I had overlooked that and it is the reason that when I specified just $5A the sensor gave no response.
But address 0 also appears to work, although it is not mentioned in the MLX90614 datasheet.

Next:
Then I have the I²C clock on PORTB.7.
And that is also the port where the PICkit2 programmer is connected...
You understand, the sensor does not work when the PICkit2 programmer is connected ::)
But why do I only notice it now?
Because in the PIC program I always have DECLARE IC2_BUS_SCL set to ON and then it doesn't matter whether the PIC programmer is connected to it or not.
I have never had any problems with it in all those years.
But with the experiments I set the IC2_BUS_SCL to OFF, not immediately taking the programmer into account.

Next:
With the PIC back to 22MHz (where the sensor works with I2CIN) see what the timing of I²C should be approximately.
Adjusted the "I2C_Software.inc" to this and indeed, now it also works with this, albeit at 22MHz.

Then the PIC to 64MHz.
Adjusted "I2C_Software.inc" again and -hurray-, the sensor also works at 64MHz  :)
I am happy ;D

Next:
Then let's see why I2CIN doesn't work at 64MHz.
The I2C_SLOW_BUS to ON and HUH:o

The timing is the same as on 22MHz.
Look at the display and again HUH?
The sensor now also works with I2CIN on 64MHz!

How is this possible?

After some research, the cat is out of the bag.
If I set I2C_BUS_SCL to ON (which I have had for years), it no longer works on 64MHz (but does on 22MHz).
Conclusion:
From now on, always use a 4k7 pull-up resistor on the clock line and I2C_BUS_SCL to OFF.


I prefer to use I2CIN over "I2C_Software.inc" because it is a lot smaller.
But I am happy that "I2C_Software.inc" is there, because I can play with the times for possible future problems.

Here's what I'm specifying now to get the MLX90614 talking with instruction I2CIN:

DECLARE I2C_SLOW_BUS = ON
DECLARE I2C_BUS_SCL  = OFF              ;Use the standard I²C pull-up resistor

SYMBOL TempSensorDta = PORTB.4
SYMBOL TempSensorClk = PORTB.7
SYMBOL MLX_Address   = 0                ;Or ($5A << 1)
SYMBOL MLX_Tobj1     = $07              ;Measure the temperature from the object

DIM SensorTemp  AS WORD
DIM Temperature AS BYTE

I2CIN TempSensorDta, TempSensorClk, MLX_Address, MLX_Tobj1, [SensorTemp.LOWBYTE, SensorTemp.HIGHBYTE]
Temperature = (SensorTemp / 50) - 273  ;Convert from Kelvin to Celsius

____________________________________

If people want to use the  "I2C_Software.inc"  routine:

Write in the  "I2C_Software.inc"  routine:
$define I2C_SDA_Pin    PORTB.4
$define I2C_SCL_Pin    PORTB.7
$define I2C_hDelayUs() DELAYUS 3
$define I2C_hDelayCs() DELAYCS 100

And to use the  "I2C_Software.inc"  routine, write in the main program:
I2C_Start()
I2C_WriteByte(MLX_Address)
I2C_WriteByte(MLX_Tobj1)
I2C_Restart()
I2C_WriteByte(MLX_Address)
SensorTemp.LOWBYTE  = I2C_ReadByte()
I2C_Ack()
SensorTemp.HIGHBYTE = I2C_ReadByte()
I2C_Stop()

Thanks to all!
Ohm sweet Ohm | www.picbasic.nl

Pepe

#37
demo proteus #40

Frizie

Nice demo Pepe.
But you could read in the posts hereabove, the address should be 0 or ($5A << 1), only $5A does not work (more info read post above from Les #35 and me #36).
Ohm sweet Ohm | www.picbasic.nl

Pepe

#39
demo fixed