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

TimB


Hi all

Is there a version of i2c in Positron?
I have an issue in that neither Busin or i2cIn support clock stretching.

I can get around it with Busin by adding some delays. You cannot do that with i2cin.
Using i2cIn though allows you to specify the ports on the fly so you can talk to 2 devices that have the same address. Busin is fixed at compile time.

The solution I see is that you have a software version of i2cIn that can be modified to support clock stretching, or even just to add some delays.

Cheers

Tim

david

Hi Tim,
Can you use-
Declare I2C_Slow_Bus On - Off or 1 – 0?

Cheers,
David

TimB

Quote from: david on Mar 07, 2023, 11:50 AMHi Tim,
Can you use-
Declare I2C_Slow_Bus On - Off or 1 – 0?

Cheers,
David


Thanks but it was added to enable devices 8mhz or higher to talk to slow i2c devices. But I'm running at 64mhz 8 x the transition speed that Declare I2C_Slow_Bus On was added for

I need to implement clock stretching.

Tim


david

OK - thanks for that info.   I wondered if it may have an upper working limit.
With devices generally getting faster perhaps we could have a software I2C that reads the Xtal declare and adjusts the clock timing/delays at compile time?

Cheers,
David

tumbleweed

That's probably not needed. Software I2C is usually pretty slow no matter what. I haven't measured the pds implementation, but it's hard to get much more than 200-300khz out of it even at 64Mhz.

TimB

Quote from: tumbleweed on Mar 08, 2023, 01:58 AMThat's probably not needed. Software I2C is usually pretty slow no matter what. I haven't measured the pds implementation, but it's hard to get much more than 200-300khz out of it even at 64Mhz.

Busout (software) is to fast without delays at 64mhz talking to a 32mhz pic slave, I do not mind adding a delay but as I pointed out before it has a fixed SDA SLC pins.

i2cOut etc you can assign many SDA SCL pins but at 64mhz its to fast for a 32mhz pic

Hence why I want to have a software written in Positron version so I can modify it and add clock stretching or even a few delays.

tumbleweed

I agree. If you have a pic i2c slave then it's a lot better to have a master that supports clock stretching than trying to rely on delays.

Clock stretching will let the ACK/NACK mechanism work, so the master doesn't have to guess how long the slave needs to process bytes.

top204

Take a look at the "Software_I2C.inc" file in the includes folder: "C:\Users\User Name\PDS\Includes".

It is software based and has clock stretching implemented. It is based upon the code I wrote for your I2C flow sensor device Tim, so it can be understood and changed to suit.

The default clock stretching is rather short, but can be increased to a 16-bit value count, instead of the 8-bit count to increase the time it waits:

'
' Wait for any clock stretching required, with a small timeout
'
For wTimeout = $FF To 0 Step -1            ' Create a loop for the clock stretching
    PinInput I2C_bSCL_Pin                  ' Make the SCL pin an input
    If GetPin I2C_bSCL_Pin = 1 Then Break  ' Exit the loop when the SCL pin is high
Next                                       ' Until the timeout value times out

The I2C coms can also be slowed down or speeded up by changing the delay in the I2C_hDelay() procedure.

TimB


Thanks so much Les!!!!

I really must check the examples before I post  :-[

TimB


Hmm

It seems that this include in the sources folder must be new as my instal does not have it in there.

I will dig it out from my old code

Thanks

Tim

top204

#10
I've just checked that the "Software_I2C.inc" file is in the installers of the compiler and it is, and is also added when a corrections update is installed, so that it also keeps the .inc libraries upto date.

Remember, the "Includes" folder is at: "C:\Users\User Name\PDS\Includes\". The original compilers includes directory within the compilers folder is no longer used for any user include files, and the ones remaining in there are for backward compatability only. All new library ".inc" files are placed in the above file location, and any include files in there are automatically found by the compiler, so all that is needed in a program listing is: Include "Software_I2C.inc".

JonW

Its there but here it is

$ifndef _Software_I2C_inc_
$define _Software_I2C_inc_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Software I2C writing and reading routines with 'dynamic' Pins. The I2C routines use pins that can be changed at run time.
' This means other I2C units on other pins can be used by the library's procedures. Much the same as the compiler's I2Cin and I2Cout commands.
'
' Written for the Positron8 and Positron16 BASIC compilers by Les Johnson
'
' 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
    Dim I2C_bSDA_Pin As Pin Access         ' Holds the value of the pin used for the SDA line
    Dim I2C_bSCL_Pin As Pin Access         ' Holds the value of the pin used for the SCL line

'----------------------------------------------------------------------------------------------
' Create a small delay to slow down the I2C interface (if required)
' Input     : None
' Output    : None
' Notes     : An actual delay may not be required here.
'           : If the I2C slave device shows innaccuracies with long leads to it, increase the DelayUs command's value to slow down the I2C interface more
'
Proc I2C_hDelay()
    DelayUS 1
EndProc

'----------------------------------------------------------------------------------------------
' 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 To 0 Step -1      ' Create a loop for the 8-bits to write
        If pData.7 = 1 Then                 ' Is bit-7 of pData set?
            PinInput I2C_bSDA_Pin           ' Yes. So make the SDA pin an input (made high by the pull-up resistor)
        Else                                ' Otherwise...
            PinLow I2C_bSDA_Pin             ' Make the SDA pin an output low
        EndIf                               '
        I2C_hDelay()                        ' Create a small delay
        PinInput I2C_bSCL_Pin               ' Make the SCL pin an input (made high by the pull-up resistor)
        I2C_hDelay()                        ' Create a small delay
        PinLow I2C_bSCL_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_bSDA_Pin                   ' Make the SDA pin an input (made high by the pull-up resistor)
    PinInput I2C_bSCL_Pin                   ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinLow I2C_bSCL_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 = $03FF

    Result = 0                              ' Clear the result of the procedure before entering the loop
    PinInput I2C_bSDA_Pin                   ' Make the SDA pin an input (made high by the pull-up resistor)
    For I2C_bBitIndex = 7 To 0 Step -1      ' 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
        '
        For wTimeout = $FF To 0 Step -1     ' Create a loop for the clock stretching
            PinInput I2C_bSCL_Pin           ' Make the SCL pin an input
            If GetPin I2C_bSCL_Pin = 1 Then Break  ' Exit the loop when the SCL pin is high
        Next                                ' Until the timeout value times out
        '
        ' Read the data
        '
        I2C_hDelay()                        ' Create a small delay
        Result.0 = GetPin I2C_bSDA_Pin      ' Read the state of the SDA pin into bit-0 of the result
        PinLow I2C_bSCL_Pin                 ' Make the SCL pin an output low
        I2C_hDelay()                        ' Create a small delay
    Next
EndProc

'----------------------------------------------------------------------------------------------
' Send an I2C bus Start condition
' Input     : pSDA holds the pin number used for the SDA line
'           : pSCL holds the pin number used for the SCL line
' Output    : None
' Notes     : Because all I2C coms start with a "Start" command being sent
'           : It is the only procedure that requires the SDA/SCL pins, because the other I2C procedures will use the same pins.
'
Proc I2C_Start(pSDA As Pin, pSCL As Pin)
    I2C_bSDA_Pin = pSDA                     ' Transfer the SDA pin to the global variable, so the other I2C procedures can share and use it
    I2C_bSCL_Pin = pSCL                     ' Transfer the SCL pin to the global variable, so the other I2C procedures can share and use it
    PinInput pSDA                           ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinInput pSCL                           ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinLow pSDA                             ' Make the SDA pin an output low
    I2C_hDelay()                            ' Create a small delay
    PinLow pSCL                             ' 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_bSDA_Pin                   ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSCL_Pin                   ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinLow I2C_bSDA_Pin                     ' Make the SDA pin an output low
    I2C_hDelay()                            ' Create a small delay
    PinLow I2C_bSCL_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_bSDA_Pin                     ' Make the SDA pin an output low
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSCL_Pin                   ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSDA_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_bSDA_Pin                     ' Make the SDA pin an output low
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSCL_Pin                   ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinLow I2C_bSCL_Pin                     ' Make the SCL pin an output low
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSDA_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_bSDA_Pin                   ' Make the SDA pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSCL_Pin                   ' Make the SCL pin an input (made high by the pull-up resistor)
    I2C_hDelay()                            ' Create a small delay
    PinLow I2C_bSCL_Pin                     ' Make the SCL pin an output low
    I2C_hDelay()                            ' Create a small delay
    PinInput I2C_bSDA_Pin                   ' Make the SDA pin an input (made high by the pull-up resistor)
EndProc

$endif


top204

#12
Thanks Jon.

With the library, the only procedure that needs the pins designated is I2C_Start, then the other procedures use the same pins for SDA and SCL. I created it this way so that it saves on flash memory and time, because the pins do not need to be designated for every I2C function, and every I2C function starts with a START, so the pins remain the same until STOP. :-)

However, the library could easily be changed. To make it more efficient and faster to operate, designate two fixed pins by making the I2C_bSDA_Pin and I2C_bSCL_Pin symbols actual Port . Pin designations using either $define or Symbol, and remove the parameters from the I2C_Start procedure.

However, remember to change the name of the modified ".inc" library file, otherwise, other programs will use the modified version. Or place the modified ".inc" file in the program listing's folder and it will use that one instead of the generic one in the "Includes" folder.

JonW

I have not used the PIN variable, but I can see its power for building libraries now. Does the pin variable use two registers?  One for the port and the other for the port pin?

top204

#14
The Pin variable is an 8-bit type that holds a value that represents a Port.Pin combination. So PORTA.0 or GPIO.0 is represented by the value 0 and PORTB.0 is represented by the value 8 etc...

It is less efficient because it uses routines that create a port address and bit mask from the value, but it does offer some flexability to a library, and is what object oriented compilers use all the time, which is why they are so inefficient. If you look at the end of each device's ".def" file, you will see a list of pin names that help make a program listing more readable. For example:

'-----------------------------------------------------------------------------------
' Pin Numbers for PORTA
'
$define Pin_A0 0
$define Pin_A1 1
$define Pin_A2 2
$define Pin_A3 3
$define Pin_A4 4
$define Pin_A5 5
$define Pin_A6 6
$define Pin_A7 7
'
' Pin Numbers for PORTB
'
$define Pin_B0 8
$define Pin_B1 9
$define Pin_B2 10
$define Pin_B3 11
$define Pin_B4 12
$define Pin_B5 13
$define Pin_B6 14
$define Pin_B7 15
'
' Pin Numbers for PORTC
'
$define Pin_C0 16
$define Pin_C1 17
$define Pin_C2 18
$define Pin_C3 19
$define Pin_C4 20
$define Pin_C5 21
$define Pin_C6 22
$define Pin_C7 23
'
' Pin Numbers for PORTD
'
$define Pin_D0 24
$define Pin_D1 25
$define Pin_D2 26
$define Pin_D3 27
$define Pin_D4 28
$define Pin_D5 29
$define Pin_D6 30
$define Pin_D7 31
'
' Pin Numbers for PORTE
'
$define Pin_E0 32
$define Pin_E1 33
$define Pin_E2 34
$define Pin_E3 35
$define Pin_E4 36
$define Pin_E5 37
$define Pin_E6 38
$define Pin_E7 39

You can see how each Port . Pin is represented by a value.

Should I start using the term "Cartoon" instead of "Program Listing"? :-) Because other programs use silly terms for a Program Listing, and it seems to have caught on with the sheep. :-) Sorry... I couldn't stop myself from writing that. LOL.



TimB

Quote from: top204 on Mar 09, 2023, 09:28 AMI've just checked that the "Software_I2C.inc" file is in the installers of the compiler and it is, and is also added when a corrections update is installed, so that it also keeps the .inc libraries upto date.

Remember, the "Includes" folder is at: "C:\Users\User Name\PDS\Includes\". The original compilers includes directory within the compilers folder is no longer used for any user include files, and the ones remaining in there are for backward compatability only. All new library ".inc" files are placed in the above file location, and any include files in there are automatically found by the compiler, so all that is needed in a program listing is: Include "Software_I2C.inc".

Ohh Sorry looking in the wrong folder  :-[

John Lawton

Yeah, I've made a shortcut to that folder so I have ready access even if I can't remember the file path.

TimB

#17
The routine is very neat but I fear I cannot use it as it is around 4x slower than busout etc 1.3ms to read 13bytes opposed to approx 0.3ms using busin and some delays

I may have to mix and match my i2cin and busin to suit my hardware requirements


top204

Make the pins into Symbols that represent the actual Port . Pin, and remove or alter the delay procedure in the code.

The Pin variable uses an address and a mask, so it is not very fast to operate. So if the pins are made as aliases, the routines will operate at full speed. It will need some code tweaks in the I2C_Start procedure for the parameters, and the replacement of the GetPin functions, but the changes are very straightforward.

The compiler's commands that require pins placed as parameters also use a similar method of port address and pin mask, but because they are all in the same routine and written for pull optimised speed, the indirect access to them is very fast.

TimB

#19
Thanks Les

I was just coming back to say I did that but just replaced the Pinlow with Low etc. A few Search and replaces and code is running and it seems to be faster.

I will just include a version that addresses the problematic device as that is on another set of pins


Tim