Interfacing with a Dallas DS1307 Real Time Clock


Introduction.

The BX24 provides an onboard real time clock. However, the use of some functions sacrifices its accuracy. Further, in many low power applications, one may wish to turn the BX24 off so as to save power.

Thus, an attractive alternative is to use an outboard real time clock with its own back up battery such that when the +5V power is lost the clock continues to operate.

The following presents four routines using the Dallas DS1307 Real Time Clock which uses the Philips I2C 2-wire protocol. Note that in preparing this tutorial I modifed the I2C routines which were first used with the BX-01 and these new rotuines appear at the bottom of this page. All routines, the four DS1307 routines and the I2C_BX24.Bas routines may be downloaded in zipped format. Click Here.

In DS1307_1.Bas, a date and time are written to the DS1307 and the RTC is then read at nominally one second intervals. Note that the date and time is stored in BCD format which agrees with the architecture of the DS1307, but it somewhat ackward in attempting to perform calculations.

Program DS1307_2.Bas improves on this by working with the date and time quantities in natural binary. Note that in writing to the DS1307, the quantities are converted to BCD and when reading from the DS1307, the BCD is converted to natural binary.

Program DS1307_3.Bas uses this natural binary to perform a DS1307Timer function which provides the number of seconds since midnight much like the BX24's Timer() function.

Program DS1307_4.Bas illustrates how to write to and read from the 56 RAM locations on the DS1307. Assumming the DS1307 uses a battery backup, this might be a useful alternative to using a persistent variable which is frequently written.


' DS1307_1.Bas
'
' Writes a base time and date to DS1307.  About every second reads
' time and date and displays to the terminal.
'
' Note that Dt and Tm are passed as arrays. Thus Dt(Yr) refers to
' the year.  Tm(Se) refers to seconds.  This approach alleviates
' the passing of numerous arguments.
'
' Note that Dt and Tm are in BCD (vs decimal) which agress with the
' method or storing data in the DS1307.  However, this is ackward in
' performing such calculations as calculating the elapsed time.
' Program DS1307_2.Bas improves on this such that the conversion to and
' from BCD is performed in DS1307PutTime, .. PutDate, ... GetTime and
' ... GetDate.
'
' Note that, the Weekday is simply a number which is in the range of 0 - 6.
' I defined Weekday 0 to be Sunday.
'
'    BX-24                         DS1307
'
'  Term 14 ------------------- SCL (term 6) ----- To Other
'  Term 13 ------------------- SDA (term 5) ----- I2C Devices
'
' Note that 4.7K pullup resistor to +5 VDC are required on both the
' SDA and SCL leads.
'
' Note that there is no provision for the user defining the
' secondary I2C address using straps.
'
' Compile with I2C_BX24.Bas and SerialPort.Bas
'
' copyright, Peter H. Anderson, Baltimore, MD, Sept, '00

Public Const SDA_PIN as Byte = 13   ' may be modified as required
Public Const SCL_PIN as Byte = 14

Const Hr as Integer = 1 ' define elements of array Tm
Const Mi as Integer = 2
Const Se as Integer = 3

Const Yr as Integer = 1 ' define elements of array Dt
Const Mo as Integer = 2
Const Da as Integer = 3
Const WkDay as Integer = 4

Sub Main()

   Dim Tm(1 to 3) as Byte
   Dim Dt(1 to 4) as Byte

   Dt(Yr) = &H00    ' Initialize the date to Sept 13, '00
   Dt(Mo) = &H09
   Dt(Da) = &H13
   Dt(WkDay) = &H03 ' Weds

   Tm(Hr) = &H23    ' Init the time to 23:59:00
   Tm(Mi) = &H59
   Tm(Se) = &H00

   Call OpenSerialPort(1, 19200)

   Call DS1307SetUpClk(&H10)    ' sqwe enabled, 1 Hz output

   Call DS1307PutDate(Dt)   ' Init DS1307 date
   Call DS1307PutTime(Tm)   ' and time

   Do               ' continually read date and time
      Call DS1307GetDate(Dt)
      Call DS1307GetTime(Tm)
      Call DisplayDateTime(Dt, Tm)  ' and display
      Sleep(1.0)
   Loop
End Sub

Sub DS1307SetUpClk(ByVal ControlReg as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)  ' address the DS1307
   Call I2C_Nack()
   Call I2C_Out_byte(&H07)  ' control register address
   Call I2C_Nack()
   Call I2C_Out_Byte(ControlReg)
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307PutDate(ByRef Dt() as Byte)
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H03)     ' first address to write
   Call I2C_Nack()
   Call I2C_Out_Byte(Dt(WkDay))
   Call I2C_Nack()
   Call I2C_Out_Byte(Dt(Da))
   Call I2C_Nack()
   Call I2C_Out_Byte(Dt(Mo))
   Call I2C_Nack()
   Call I2C_Out_Byte(Dt(Yr))
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307GetDate(ByRef Dt() as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H03)     ' first address to read
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   Dt(WkDay) = I2C_In_Byte() AND &H07
   Call I2C_Ack()
   Dt(Da) = I2C_In_Byte() AND &H3F
   Call I2C_Ack()
   Dt(Mo) = I2C_In_Byte() AND &H3F
   Call I2C_Ack()
   Dt(Yr) = I2C_In_Byte()   ' no ack prior to stop
   Call I2C_Stop()
End Sub

Sub DS1307PutTime(ByRef Tm() as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H00)     ' first address to write
   Call I2C_Nack()
   Call I2C_Out_Byte(Tm(Se))
   Call I2C_Nack()
   Call I2C_Out_Byte(Tm(Mi))
   Call I2C_Nack()
   Call I2C_Out_Byte(Tm(Hr))
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307GetTime(ByRef Tm() as Byte)
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H00)     ' first address to read, Secs, Mins, Hrs
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   Tm(Se) = I2C_In_Byte() AND &H7F
   Call I2C_Ack()
   Tm(Mi) = I2C_In_Byte() AND &H7F
   Call I2C_Ack()
   Tm(Hr) = I2C_In_Byte() AND &H3F      ' no ack prior to stop
   Call I2C_Stop()
End Sub

Sub DisplayDateTime(ByRef Dt() as Byte, Tm() as Byte)
   Dim Str as String *16

   Select Case Dt(WkDay)    ' output the weekday
      Case 0
        Str = "Sunday"
      Case 1
        Str = "Monday"
      Case 2
        Str = "Tuesday"
      Case 3
        Str = "Wednesday"
      Case 4
        Str = "Thursday"
      Case 5
        Str = "Friday"
      Case Else
        Str = "Saturday"
   End Select

   Call PutStr(Str)
   Call NewLine()

   Call PutHexB(Dt(Mo))     ' MM/DD/YY
   Call PutByte(Asc("/"))
   Call PutHexB(Dt(Da))
   Call PutByte(Asc("/"))
   Call PutHexB(Dt(Yr))
   Call PutByte(Asc(" "))

   Call PutHexB(Tm(Hr))     ' HH:MM:SS
   Call PutByte(Asc(":"))
   Call PutHexB(Tm(Mi))
   Call PutByte(Asc(":"))
   Call PutHexB(Tm(Se))
   Call NewLine()
End Sub


Sub PutHexB(ByVal X as Byte)    ' display a byte in hex format
    Dim Y as Byte

    Y= X \ 16           ' convert high nibble to character
    If (Y < 10) then
      Y = Y + Asc("0")
    Else
      Y = Y - 10 + Asc ("A")
    End If
    Call PutByte(Y)

    Y= X And bx00001111     ' same for low nibble
    If (Y < 10) then
      Y = Y + Asc("0")
    Else
      Y = Y - 10 + Asc ("A")
    End If
    Call PutByte(Y)
End Sub


' DS1307_2.Bas
'
' Writes a base time and date to DS1307.  About every second reads
' time and date and displays to the terminal.
'
' Note that Dt and Tm are passed as arrays. Thus Dt(Yr) refers to
' the year.  Tm(Se) refers to seconds.  This approach alleviates
' the passing of numerous arguments.
'
' Note that Dt and Tm are in decimal.  The conversion to BCD is made
' in DS1307PutDate and DS1307PutTime.  When reading the date and time
' in DS1307GetDate and DS1307GetTime, the BCD value is converted to
' decimal.
'
' Note that, the Weekday is simply a number which is in the range of 0 - 6.
' I defined Weekday 0 to be Sunday.
'
'    BX-24                         DS1307
'
'  Term 14 ------------------- SCL (term 6) ----- To Other
'  Term 13 ------------------- SDA (term 5) ----- I2C Devices
'
' Note that 4.7K pullup resistor to +5 VDC are required on both the
' SDA and SCL leads.
'
' Note that there is no provision for the user defining the
' secondary I2C address using straps.
'
' Compile with I2C.Bas and SerialPort.Bas
'
' copyright, Peter H. Anderson, Baltimore, MD, Sept, '00

Public Const SDA_PIN as Byte = 13   ' may be modified as required
Public Const SCL_PIN as Byte = 14

Const Hr as Integer = 1 ' define elements of array Tm
Const Mi as Integer = 2
Const Se as Integer = 3

Const Yr as Integer = 1 ' define elements of array Dt
Const Mo as Integer = 2
Const Da as Integer = 3
Const WkDay as Integer = 4

Sub Main()

   Dim Tm(1 to 3) as Byte
   Dim Dt(1 to 4) as Byte

   Dt(Yr) = 00  ' Initialize the date to Sept 13, '00
   Dt(Mo) = 9
   Dt(Da) = 13
   Dt(WkDay) = 03   ' Weds

   Tm(Hr) = 23  ' Init the time to 23:59:00
   Tm(Mi) = 59
   Tm(Se) = 00

   Call OpenSerialPort(1, 19200)

   Call DS1307SetUpClk(&H10)    ' sqwe enabled, 1 Hz output

   Call DS1307PutDate(Dt)   ' Init DS1307 date
   Call DS1307PutTime(Tm)   ' and time

   Do               ' continually read date and time
      Call DS1307GetDate(Dt)
      Call DS1307GetTime(Tm)
      Call DisplayDateTime(Dt, Tm)  ' and display
      Sleep(1.0)
   Loop
End Sub

Sub DS1307SetUpClk(ByVal ControlReg as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)  ' address the DS1307
   Call I2C_Nack()
   Call I2C_Out_Byte(&H07)  ' control register address
   Call I2C_Nack()
   Call I2C_Out_Byte(ControlReg)
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307PutDate(ByRef Dt() as Byte)
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H03)     ' first address to write
   Call I2C_Nack()
   Call I2C_Out_Byte(Dt(WkDay))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Dt(Da)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Dt(Mo)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Dt(Yr)))
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307GetDate(ByRef Dt() as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H03)     ' first address to read
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   Dt(WkDay) = I2C_In_Byte() AND &H07
   Call I2C_Ack()
   Dt(Da) = BCDtoDec(I2C_In_Byte() AND &H3F)
   Call I2C_Ack()
   Dt(Mo) = BCDtoDec(I2C_In_Byte() AND &H3F)
   Call I2C_Ack()
   Dt(Yr) = I2C_In_Byte()   ' no ack prior to stop
   Call I2C_Stop()
End Sub

Sub DS1307PutTime(ByRef Tm() as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H00)     ' first address to write
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Tm(Se)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Tm(Mi)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Tm(Hr)))
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307GetTime(ByRef Tm() as Byte)
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H00)     ' first address to read, Secs, Mins, Hrs
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   Tm(Se) = BCDtoDec(I2C_In_Byte() AND &H7F)
   Call I2C_Ack()
   Tm(Mi) = BCDtoDec(I2C_In_Byte() AND &H7F)
   Call I2C_Ack()
   Tm(Hr) = BCDtoDec(I2C_In_Byte() AND &H3F)    ' no ack prior to stop
   Call I2C_Stop()
End Sub

Sub DisplayDateTime(ByRef Dt() as Byte, Tm() as Byte)
   Dim Str as String *16

   Select Case Dt(WkDay)    ' output the weekday
      Case 0
        Str = "Sunday"
      Case 1
        Str = "Monday"
      Case 2
        Str = "Tuesday"
      Case 3
        Str = "Wednesday"
      Case 4
        Str = "Thursday"
      Case 5
        Str = "Friday"
      Case Else
        Str = "Saturday"
   End Select

   Call PutStr(Str)
   Call NewLine()

   Call PutBCDB(Dt(Mo))     ' MM/DD/YY
   Call PutByte(Asc("/"))
   Call PutBCDB(Dt(Da))
   Call PutByte(Asc("/"))
   Call PutBCDB(Dt(Yr))
   Call PutByte(Asc(" "))

   Call PutBCDB(Tm(Hr))     ' HH:MM:SS
   Call PutByte(Asc(":"))
   Call PutBCDB(Tm(Mi))
   Call PutByte(Asc(":"))
   Call PutBCDB(Tm(Se))
   Call NewLine()
End Sub


Sub PutBCDB(ByVal X as Byte)    ' display a dec quantity BCD format
    Dim Y as Byte

    Y= X \ 10           ' convert tens to character
    Y = Y + Asc("0")
    Call PutByte(Y)

    Y= X MOD 10         ' same for units
    Y = Y + Asc("0")
    Call PutByte(Y)
End Sub

Function BCDtoDec(ByVal X as Byte) as Byte
    Dim H as Byte, L as Byte
    H = X \ 16
    L = X MOD 16
    BCDtoDec = H*10 + L
End Function

Function DectoBCD(ByVal X as Byte) as Byte
    Dim H as Byte, L as Byte
    H = X \ 10
    L = X MOD 10
    DectoBCD = H * 16 + L
End Function



' DS1307_3.Bas
'
' Writes a base time and date to DS1307.  About every second reads
' time which has elapsed since midnight and displays to the terminal.
'
' Note that Dt and Tm are passed as arrays. Thus Dt(Yr) refers to
' the year.  Tm(Se) refers to seconds.  This approach alleviates
' the passing of numerous arguments.
'
' Note that Dt and Tm are in decimal.  The conversion to BCD is made
' in DS1307PutDate and DS1307PutTime.  When reading the date and time
' in DS1307GetDate and DS1307GetTime, the BCD value is converted to
' decimal.
'
' Note that, the Weekday is simply a number which is in the range of 0 - 6.
' I defined Weekday 0 to be Sunday.
'
'    BX-24                         DS1307
'
'  Term 14 ------------------- SCL (term 6) ----- To Other
'  Term 13 ------------------- SDA (term 5) ----- I2C Devices
'
' Note that 4.7K pullup resistor to +5 VDC are required on both the
' SDA and SCL leads.
'
' Note that there is no provision for the user defining the
' secondary I2C address using straps.
'
' Compile with I2C.Bas and SerialPort.Bas
'
' copyright, Peter H. Anderson, Baltimore, MD, Mar, '00

Public Const SDA_PIN as Byte = 13   ' may be modified as required
Public Const SCL_PIN as Byte = 14

Const Hr as Integer = 1 ' define elements of array Tm
Const Mi as Integer = 2
Const Se as Integer = 3

Const Yr as Integer = 1 ' define elements of array Dt
Const Mo as Integer = 2
Const Da as Integer = 3
Const WkDay as Integer = 4

Sub Main()

   Dim Tm(1 to 3) as Byte
   Dim Dt(1 to 4) as Byte
   Dim ElapsedTime as Long

   Dt(Yr) = 00  ' Initialize the date to Sept 13, '00
   Dt(Mo) = 9
   Dt(Da) = 13
   Dt(WkDay) = 03   ' Weds

   Tm(Hr) = 23  ' Init the time to 23:59:00
   Tm(Mi) = 59
   Tm(Se) = 00

   Call OpenSerialPort(1, 19200)

   Call DS1307SetUpClk(&H10)    ' sqwe enabled, 1 Hz output

   Call DS1307PutDate(Dt)   ' Init DS1307 date
   Call DS1307PutTime(Tm)   ' and time

   Do               ' continually read the elapsed time
      ElapsedTime =  DS1307Timer()
      Call PutL(ElapsedTime)    ' and display
      Call NewLine()
      Sleep(1.0)
   Loop
End Sub

Sub DS1307SetUpClk(ByVal ControlReg as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)  ' address the DS1307
   Call I2C_Nack()
   Call I2C_Out_Byte(&H07)  ' control register address
   Call I2C_Nack()
   Call I2C_Out_Byte(ControlReg)
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307PutDate(ByRef Dt() as Byte)
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H03)     ' first address to write
   Call I2C_Nack()
   Call I2C_Out_Byte(Dt(WkDay))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Dt(Da)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Dt(Mo)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Dt(Yr)))
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307GetDate(ByRef Dt() as Byte)
' Not used in this routine
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H03)     ' first address to read
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   Dt(WkDay) = I2C_In_Byte() AND &H07
   Call I2C_Ack()
   Dt(Da) = BCDtoDec(I2C_In_Byte() AND &H3F)
   Call I2C_Ack()
   Dt(Mo) = BCDtoDec(I2C_In_Byte() AND &H3F)
   Call I2C_Ack()
   Dt(Yr) = I2C_In_Byte()   ' no ack prior to stop
   Call I2C_Stop()
End Sub

Sub DS1307PutTime(ByRef Tm() as Byte)

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H00)     ' first address to write
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Tm(Se)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Tm(Mi)))
   Call I2C_Nack()
   Call I2C_Out_Byte(DectoBCD(Tm(Hr)))
   Call I2C_Nack()
   Call I2C_Stop()
End Sub

Sub DS1307GetTime(ByRef Tm() as Byte)
   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(&H00)     ' first address to read, Secs, Mins, Hrs
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   Tm(Se) = BCDtoDec(I2C_In_Byte() AND &H7F)
   Call I2C_Ack()
   Tm(Mi) = BCDtoDec(I2C_In_Byte() AND &H7F)
   Call I2C_Ack()
   Tm(Hr) = BCDtoDec(I2C_In_Byte() AND &H3F)    ' no ack prior to stop
   Call I2C_Stop()
End Sub

Function DS1307Timer() as Long  ' returns the number of seconds since
                ' midnight
   Dim Tm(1 to 3) as Byte
   Dim ET as Long
   Call DS1307GetTime(Tm)   ' fetch hours, minutes, secs

   ET = 3600 * CLng(Tm(Hr)) + 60 * CLng(Tm(Mi)) + CLng(Tm(Se))
   DS1307Timer = ET
End Function

Sub DisplayDateTime(ByRef Dt() as Byte, Tm() as Byte)
' Not used in this routine
   Dim Str as String *16

   Select Case Dt(WkDay)    ' output the weekday
      Case 0
        Str = "Sunday"
      Case 1
        Str = "Monday"
      Case 2
        Str = "Tuesday"
      Case 3
        Str = "Wednesday"
      Case 4
        Str = "Thursday"
      Case 5
        Str = "Friday"
      Case Else
        Str = "Saturday"
   End Select

   Call PutStr(Str)
   Call NewLine()

   Call PutBCDB(Dt(Mo))     ' MM/DD/YY
   Call PutByte(Asc("/"))
   Call PutBCDB(Dt(Da))
   Call PutByte(Asc("/"))
   Call PutBCDB(Dt(Yr))
   Call PutByte(Asc(" "))

   Call PutBCDB(Tm(Hr))     ' HH:MM:SS
   Call PutByte(Asc(":"))
   Call PutBCDB(Tm(Mi))
   Call PutByte(Asc(":"))
   Call PutBCDB(Tm(Se))
   Call NewLine()
End Sub

Sub PutBCDB(ByVal X as Byte)    ' display a dec quantity BCD format
    Dim Y as Byte

    Y= X \ 10           ' convert tens to character
    Y = Y + Asc("0")
    Call PutByte(Y)

    Y= X MOD 10         ' same for units
    Y = Y + Asc("0")
    Call PutByte(Y)
End Sub

Function BCDtoDec(ByVal X as Byte) as Byte
    Dim H as Byte, L as Byte
    H = X \ 16
    L = X MOD 16
    BCDtoDec = H*10 + L
End Function

Function DectoBCD(ByVal X as Byte) as Byte
    Dim H as Byte, L as Byte
    H = X \ 10
    L = X MOD 10
    DectoBCD = H * 16 + L
End Function



' DS1307_4.Bas
'
' Illustrates use of the 56 RAM locations on the DS1307.
'
' Sub DS1307PutRAM(A() as Byte, Adr as Byte, NumBytes as Byte)
'    Writes NumBytes of data in array A beginning at address Adr
'    Reads NumBytes of data beginning at address Adr into Array A
'
'    BX-24                         DS1307
'
'  Term 14 ------------------- SCL (term 6) ----- To Other
'  Term 13 ------------------- SDA (term 5) ----- I2C Devices
'
' Note that 4.7K pullup resistor to +5 VDC are required on both the
' SDA and SCL leads.
'
' Note that there is no provision for the user defining the
' secondary I2C address using straps.
'
' Compile with I2C.Bas and SerialPort.Bas
'
' copyright, Peter H. Anderson, Baltimore, MD, Sept, '00

Public Const SDA_PIN as Byte = 13   ' may be modified as required
Public Const SCL_PIN as Byte = 14

Sub Main()
   Dim A(1 to 56) as Byte

   Call OpenSerialPort(1, 19200)
   Call DummyArray(A, 56)   ' dummy up the array with some values
   Call PrintArray(A, 56)

   Call DS1307PutRAM(A, 8, 56)  ' write 56 bytes to RAM

   Call DS1307GetRAM(A, 8, 56)
   Call PrintArray(A, 56)

End Sub

Sub DS1307PutRAM(ByRef A() as Byte, ByVal Adr as Byte, ByVal Num as Byte)
   Dim N as Integer

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(Adr)     ' first address to write
   Call I2C_Nack()

   For N = 1 to CInt(Num)
      Call I2C_Out_Byte(A(N))
      Call I2C_Nack()
   Next

   Call I2C_Stop()
End Sub

Sub DS1307GetRAM(ByRef A() as Byte, ByVal Adr as Byte, ByVal Num as Byte)
   Dim N as Integer

   Call I2C_Start()
   Call I2C_Out_Byte(&HD0)
   Call I2C_Nack()
   Call I2C_Out_Byte(Adr)     ' first address to read
   Call I2C_Nack()
   Call I2C_Stop()

   Call I2C_Start()
   Call I2C_Out_Byte(&HD1)  ' read
   Call I2C_Nack()

   For N = 1 to CInt(Num)
      A(N) = I2C_In_Byte()
      If (N <> CInt(Num)) Then
         Call I2C_Ack()     ' no ack after the last byte
      End If
   Next

   Call I2C_Stop()
End Sub

Sub DummyArray(ByRef A() as Byte, ByVal Num as Byte)
' simply fill the array with some numbers
   Dim N as Integer
   For N = 1 to CInt(Num)
      A(N) = 100 + CByte(N)
   Next
End Sub

Sub PrintArray(ByRef A()as Byte, ByVal Num as Byte)
   Dim N as Integer
   For N = 1 to CInt(Num)
      Call PutBFixed2(A(N))
      Call PutByte(Asc(" "))
      If (((N+1) MOD 8) = 0) Then
         Call NewLine()
      End If
   Next
End Sub

Sub PutBFixed2(ByVal X as Byte)
   Dim Y as Byte
   Y = X\10 + Asc("0")
   Call PutByte(Y)
   Y = (X MOD 10) + Asc("0")
   Call PutByte(Y)
End Sub



' I2C_BX24.Bas
Attribute VB_Name = "I2C_BX24"
'-------------------
Option Explicit

' This module is a collection of low level I2C routines intended to be
' included in a project using such I2C devices as the Microchip 24 series
' EEPROMs, MAX518 D/A, PCF8574 I/O Expander, DS1803 Digital Pot, PCF8591
' A/D plus D/A and many other devices.
'
' Public Sub I2C_out_byte(ByVal O_byte As Byte) - sends O_byte to I2C
' slave, most significant bit first.
'
' Public Sub I2C_nack() - provides high Z on SDA for one clock pulse
' allowing slave to acknowledge receipt of a byte.
'
' Public Function I2C_in_byte(ByRef I_byte As Byte) - receives a byte from
' I2C slave.
'
' Public Sub I2C_ack() - send ack to slave by bringing SDA low for one
' clock pulse.
'
' Public Sub I2C_start() - initiates sequence by bringing SDA low while
' SCL is high.  (Could be Private rather than Public).
'
' Public Sub I2C_stop() - terminates sequence by bringing SDA high
' while SCL is high. (Could be Private rather than Public).
'
' Public Sub I2C_high_sda() - bring SDA high (high Z)
'
' Public Sub I2C_high_scl() - bring SCL high (high Z)
'
' Public Sub I2C_low_sda() - bring SDA low, hard logic zero
'
' Public Sub I2C_low_scl() - bring SCL low, hard logic zero
'
' Note that SDA_PIN and SCL_PIN should be publicly defined in the calling
' module
'
' For example;
'    Const SDA_PIN as Byte = 13
'    Const SCL_PIN as Byte = 14
'
' Copyright, Peter H. Anderson, Baltimore, MD, Sept, '99
' Nov, '99 - Revised for BX24.
' Mar, '00 - I2C_In_Byte was made a function.  Prior to this time it was a
' Sub where the result was passed by reference.
'
' ----------
Public Sub I2C_out_byte(ByVal O_byte As Byte)

   DIM N as Byte

   For N = 1 TO 8 Step 1
      If (O_byte >= 128) then   ' most sig bit is a one
         Call I2C_high_sda()
         'Call PutB(1)      ' used for debugging
      Else
         Call I2C_low_sda() ' set SDA and then clock
         'Call PutB(0)      ' used for debugging
      End If

      Call I2C_high_scl()
      Call I2C_low_scl()

      O_byte = O_byte * 2   ' shift left

   Next
   ' Call NewLine()     ' used for debugging
End Sub

Public Function I2C_in_byte() as Byte

   DIM N as Byte, Y as Byte, I_Byte as Byte

   I_Byte = 0

   For N = 1 to 8 Step 1
      Call I2C_high_scl()   ' bring clock high
      Y =  GetPin(SDA_PIN)  ' read SDA
      ' Call PutB(Y)
      Call I2C_low_scl()
      I_byte = I_byte * 2 + Y   ' shift left and insert Y
   Next
   ' Call NewLine()
   I2C_In_Byte = I_Byte
End Function

Public Sub I2C_nack()       ' allows slave to acknowledge

   Call I2C_high_sda()      ' SDA high
   Call I2C_high_scl()      ' and then clock
   Call I2C_low_scl()

End Sub

Public Sub I2C_ack()        ' send ack to slave by bringing SDA low

   Call I2C_low_sda()
   Call I2C_high_scl()
   Call I2C_low_scl()
   Call I2C_high_sda()      ' be sure SDA is again high

End Sub

Public Sub I2C_start()      ' bring SDA low while SCL is high

   Call I2C_low_scl()
   Call I2C_high_sda()
   Call I2C_high_scl()
   Call I2C_low_sda()
   Call I2C_low_scl()

End Sub

Public Sub I2C_stop()       ' bring SDA high while SCL is high

   Call I2C_low_scl()
   Call I2C_low_sda()
   Call I2C_high_scl()
   Call I2C_high_sda()

End Sub

Public Sub I2C_high_sda()

   Call PutPin(SDA_PIN, 2)  ' tristate

End Sub

Public Sub I2C_high_scl()   ' tristate

   Call PutPin(SCL_PIN, 2)

End Sub

Public Sub I2C_low_sda()

   Call PutPin(SDA_PIN, 0)  ' hard logic zero

End Sub

Public Sub I2C_low_scl()

   Call PutPin(SCL_PIN, 0)  ' hard logic zero

End Sub