Software I2C

General discussion relating to the library modules supplied with the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Francis
Registered User
Registered User
Posts: 314
Joined: Sun Mar 25, 2007 9:40 am
Location: Devon

Software I2C

Post by Francis » Sat Jun 09, 2007 4:49 pm

I'm having trouble with an 18F2520 to DS3232 RTC using SI2C library.
This is my first 'proper' go with Swordfish - so be gentle.

I've checked electrical bits on my pcb several times - looks OK. Note my pcb also has an SD/MMC connector - that bit works perfectly.

I have been trying to move my code from my tests in ME dsBasic where it worked OK.

My code starts:

Code: Select all

// device and clock...
Device = 18F2520
Clock =  8


' Include Libraries:-
Include "SI2C.bas"
Include "SDFileSystem.bas"
Include "usart.bas"
Include "Convert.bas"

//  SD file system settings, usart and soft I2C port numbers
#option SD_SPI = MSSP         // 
#option I2C_SCL = PORTA.4     // Use Software I2C
#option I2C_SDA = PORTA.5
then I initialise

Code: Select all

// Initialise
Sub InitStart()
    SetBaudrate(br9600)
    Address = 0
    SI2C.Initialize
    'adcon1=%1111
End Sub
My routines for setting and reading the time are:

Code: Select all

// Set Time to DS3232
Sub Set_Time()'ByRef TimData() As Byte)

       SI2C.Start                                   ' Do a write so that we
       SI2C.WriteByte(%11010000)                    ' can set to byte (0)
       SI2C.WriteByte($00)
       SI2C.WriteByte(TimData(2))                     ' Seconds   All in Hex
       SI2C.WriteByte(TimData(1))                     ' Minutes
       SI2C.WriteByte(TimData(0))                     ' Hours
       SI2C.WriteByte($01)                            ' Day 1
       SI2C.WriteByte(TimData(3))                     ' Date
       SI2C.WriteByte(TimData(4))                     ' Month
       SI2C.WriteByte(TimData(5))                     ' Year
       SI2C.WriteByte($10)                            ' Set 1Hz pulse on SQWOUT
       SI2C.Stop

End Sub



// READ DS3232 RTC registers for time and temp
Sub Get_Time()
     SI2C.Start
     SI2C.WriteByte(%11010000)                    ' Do a write so that we
     SI2C.WriteByte($00)                          ' can set to byte (0)
     SI2C.Restart                               ' Restart
     SI2C.WriteByte(%11010001)                    ' Send Read command
     For I=0 To 20                                ' Read first 20 bytes
       I2CData(I)= SI2C.ReadByte 
       SI2C.Acknowledge(I2C_ACKNOWLEDGE)     
     Next 
     I = I+1
     I2CData(I)= SI2C.ReadByte 
     SI2C.Acknowledge(I2C_NOT_ACKNOWLEDGE)
     SI2C.Stop
     ' Now the temp bytes
     TempHB = I2CData(17)<<1                        ' Shift back and forward by
     TempHB=TempHB>>1                             ' one byte to remove bit 8
     TempLB=I2CData(18)>>6                       ' Only top 2 bits used
     TempLB = TempLB * 3
     TempSign = I2CData(17)>>7                    ' Get bit 8 of first byte
     If TempSign = 1 Then                         ' Negative temp
       TempHB =  I2CData(17) Xor $FF              ' Twos compliment convert
     End If                                       ' Bit 8 now 0
End Sub
then my test code:-

Code: Select all

InitStart()
USART.Write("Starting..", 13, 10)

' Set time  HH:MM:SS  DD:mm:YY
TimData(0) =  $12
TimData(1) =  $00
TimData(2) =  $00
TimData(3) =  $09
TimData(4) =  $06
TimData(5) =  $07
Set_Time()


Get_Time()
For I = 0 To 20
   GenByte = I2CData(I)
   USART.Write("Time Val: ",DecToStr(GenByte,3), 13, 10)
Next

Sorry for great long listing, but this forum doesn't seem to give the scroll bars for code inserts like the Proton forum. Please excuse the comments and odd code, they are leftover from all sorts of trials-and-errors.

Anyway, when I print out the array TimData() all I get is 255 followed by a pile of zeroes.

So, I 'scoped the pins A.4 and A.5 directly on the PIC which I had configured for soft SCL and SDA. Nothing. There seems to be no activity at these pins.

SCL and SDA are tied high via 5k6 res. Please note: I have touched SCL and SDA down to ground via 4K7 res and viewed scope to poved that the pins weren't accidentally shorted high. All OK. Power and ground OK. Backup battery connected - yes, I have read DS3232 data sheet.

Any idea what's gone wrong?
Am I missing anything other than a brain?

Thanks.

P.S. Typing is very tricky here as keystroke characters keep being ignored, so please excuse any typos. I blame Explorer.

User avatar
SteveB
Posts: 23
Joined: Fri Oct 06, 2006 1:40 am
Location: Del Rio, TX

Re: Software I2C

Post by SteveB » Sat Jun 09, 2007 5:32 pm

One thing right off the top, put your #option lines BEFORE the include. The compiler only looks at the first declaration, and the others are ignored. Since the include files have "default" #option declarations, then those end up taking precedence. Look under "language Reference\Preprocessor" in the help file for more explaination.

Like So...

Code: Select all

// device and clock...
Device = 18F2520
Clock =  8

//  SD file system settings, usart and soft I2C port numbers
#option SD_SPI = MSSP         // 
#option I2C_SCL = PORTA.4     // Use Software I2C
#option I2C_SDA = PORTA.5

' Include Libraries:-
Include "SI2C.bas"
Include "SDFileSystem.bas"
Include "usart.bas"
Include "Convert.bas"
SteveB

User avatar
SteveB
Posts: 23
Joined: Fri Oct 06, 2006 1:40 am
Location: Del Rio, TX

Re: Software I2C

Post by SteveB » Sat Jun 09, 2007 5:44 pm

Something else...
Francis wrote:

Code: Select all

// Initialise
Sub InitStart()
    SetBaudrate(br9600)
    Address = 0
    SI2C.Initialize
    'adcon1=%1111        <<---------------- You need to make the pins digital
End Sub
Here you have your ADCON1 settings dim'ed out. Since your using PortA for your I2C lines, that's going to cause problems.

SteveB

Francis
Registered User
Registered User
Posts: 314
Joined: Sun Mar 25, 2007 9:40 am
Location: Devon

Post by Francis » Sat Jun 09, 2007 6:40 pm

Hi SteveB,

Well I never. I actually had the things as you suggested but not at the same time.

Now I've done it as you suggested it works perfectly.

Many thanks.

Francis.

User avatar
SteveB
Posts: 23
Joined: Fri Oct 06, 2006 1:40 am
Location: Del Rio, TX

Post by SteveB » Sun Jun 10, 2007 2:40 am

Glad things worked out :D

Now for something really cool about Swordfish, Overloading and Compounding....

1. Save a copy of the SI2C.bas module to your "User Library" folder in the Swordfish installation folder.

2. Open this new copy and add the following code to the bottom of the file (under the WriteByte subroutine):

Code: Select all

{
****************************************************************************
* Name    : WriteData (OVERLOAD)                                           *
* Purpose : Write a single byte to the I2C bus                             *
****************************************************************************
}         
Private Sub WriteData (pValue As Byte)
   SI2C.WriteByte(pValue)
End Sub
{
****************************************************************************
* Name    : WriteData (OVERLOAD)                                           *
* Purpose : Write a WORD to the I2C bus                                    *
****************************************************************************
}         
Private Sub WriteData (pValue As Word)
   SI2C.WriteByte(pValue.Byte0)
   SI2C.WriteByte(pValue.Byte1)
End Sub
{
****************************************************************************
* Name    : WriteData (OVERLOAD)                                           *
* Purpose : Write a LONGWORD to the I2C bus                                *
****************************************************************************
}         
Private Sub WriteData (pValue As LongWord)
   SI2C.WriteByte(pValue.Byte0)
   SI2C.WriteByte(pValue.Byte1)
   SI2C.WriteByte(pValue.Byte2)
   SI2C.WriteByte(pValue.Byte3)
End Sub
{
****************************************************************************
* Name    : I2COut (COMPOUND)                                              *
* Purpose : Writes Data to the I2C bus                                     *
****************************************************************************
}         
Public Compound Sub I2COut(WriteData)
3. Now, the compiler will use this "custom" version of SI2C.bas. You can change this:

Code: Select all

SI2C.Start                                   ' Do a write so that we
SI2C.WriteByte(%11010000)                    ' can set to byte (0) 
SI2C.WriteByte($00) 
SI2C.WriteByte(TimData(2))                     ' Seconds   All in Hex 
SI2C.WriteByte(TimData(1))                     ' Minutes 
SI2C.WriteByte(TimData(0))                     ' Hours 
SI2C.WriteByte($01)                            ' Day 1 
SI2C.WriteByte(TimData(3))                     ' Date 
SI2C.WriteByte(TimData(4))                     ' Month 
SI2C.WriteByte(TimData(5))                     ' Year 
SI2C.WriteByte($10)                            ' Set 1Hz pulse on SQWOUT 
SI2C.Stop 
To this:

Code: Select all

SI2C.Start                                   		
SI2C.I2COut(%11010000,$00)                          ' Write to DS3232, Reg $00
SI2C.I2COut(TimData(2),TimData(1),TimData(0),1)     ' Sec, Min, Hours, Day 1
SI2C.I2COut(TimData(3),TimData(4),TimData(5),$10)   ' Date, Mon, Year, 1Hz on SQWOUT
SI2C.Stop 
Or this:

Code: Select all

SI2C.Start                                   		
SI2C.I2COut(%11010000,$00)                          ' Write to DS3232, Reg $00
SI2C.I2COut(TimData(2),TimData(1),TimData(0),1)     ' Sec, Min, Hours, Day 1
SI2C.I2COut(TimData(3).AsWord,TimData(5),$10)       ' Date, Mon, Year, 1Hz on SQWOUT
SI2C.Stop 
If your TimData array matched the DS3232 Registers (ie. (0) for sec, (1) for min, etc), you could write the whole array with something like:

Code: Select all

SI2C.I2COut(TimData(0),TimData(1).AsWord,TimData(3).AsLongWord)  ' Sec, Min/Hrs, Day/Date/Mn/Yr
Lots of flexibility! This is especially useful when working with an EEPROM (or other device) and you have bigger than Byte sized variables to save. You can just send them out as Words, or whatever, and you don't need a bunch of seperate lines of code.

Have Fun,
SteveB
Last edited by SteveB on Sun Jun 10, 2007 6:49 pm, edited 1 time in total.

Francis
Registered User
Registered User
Posts: 314
Joined: Sun Mar 25, 2007 9:40 am
Location: Devon

Post by Francis » Sun Jun 10, 2007 1:14 pm

Thank you Steve,

I shall put that in my 'grimoire'.
This is darned clever.

Francis.

User avatar
SteveB
Posts: 23
Joined: Fri Oct 06, 2006 1:40 am
Location: Del Rio, TX

Post by SteveB » Sun Jun 10, 2007 8:04 pm

Something interesting I've come across playing with the above overloaded routines is the way the compiler handles Word and LongWord variables (vs. how it handles promotion of Byte arrays to Words and LongWords).

It looks like Words and LongWords are stored LSB first. For example, If I store $01020304 in a LongWord (i.e. dim TempLong as LongWord), it is stored in 4 consecutive memory registers as | $04 | $03 | $02 | $01 |. So if this is sent out using the routines I posted above, it will be sent in "reverse" order (if that makes sense).

However, if I have an array of bytes (i.e. dim Temp(4) as Byte) , and store in the values as follows:
Temp(0) = $01
Temp(1) = $02
Temp(2) = $03
Temp(3) = $04
Then I use the promotional modifier .asLongWord to send it with with above routines, it will be sent as $01, $02, $03, $04.

As such, I think the Word and LongWord routines I posted above in the previous post would be more appropriately constructed as follows:

Code: Select all

{
****************************************************************************
* Name    : WriteData (OVERLOAD)                                           *
* Purpose : Write a WORD to the I2C bus                                    *
****************************************************************************
}         
Private Sub WriteData (pValue As Word)
   SI2C.WriteByte(pValue.Byte1)
   SI2C.WriteByte(pValue.Byte0)
End Sub

Code: Select all

{
****************************************************************************
* Name    : WriteData (OVERLOAD)                                           *
* Purpose : Write a LONGWORD to the I2C bus                                *
****************************************************************************
}         
Private Sub WriteData (pValue As LongWord)
   SI2C.WriteByte(pValue.Byte3)
   SI2C.WriteByte(pValue.Byte2)
   SI2C.WriteByte(pValue.Byte1)
   SI2C.WriteByte(pValue.Byte0)
End Sub
This will correctly send out the Word and LongWord Variables.

If one wants to use an array with promotional modifiers, care must be used in handling how data is arranged, to maintain consistency. Using appropriate modifies for this will help. For example, if TempLong = $01020304 then,
Temp(0) = TempLong.Byte0
Temp(1) = TempLong.Byte1
Temp(2) = TempLong.Byte2
Temp(3) = TempLong.Byte3
Results is the correct arrangement of the individual bytes. Using the new subroutines, the resulting output will be the same for either of the following statements:
SI2C.I2COut(TempLong)
Or
SI2C.I2COut(Temp(0).asLongWord)

Hopefully this makes sense and explains the issue more than it confuses the issue.

SteveB

Post Reply