One of the limitations of the Raspberry Pi is that you can’t easily find the current date and time (unless you’re connected to a network, which isn’t something that’s necessarily always going to be the case). One solution to this problem is to attach a clock module to your device, and a very commonly used option is the Maxim DS1307 Real-Time clock module.

Getting Started

The DS1307 is an I2C device. so I use the Magellanic.I2C NuGet package to simplify C# development. Key information to know about the module is:

  • The I2C slave address is 0x68.
  • The time is held in an array of 7 bytes (second, minute, hour, day-of-week, day, month, year.
  • The values held in these bytes are encoded in binary coded decimal format.

Getting the seven bytes containing the time is a piece of cake – the code below shows a class which populates these 7 bytes into a variable named readBuffer:

public class DS1307 : AbstractI2CDevice
{
    private byte I2C_ADDRESS = 0x68;
 
    public override byte GetI2cAddress()
    {
        return I2C_ADDRESS;
    }
 
    public DateTime GetCurrentTime()
    {
        byte[] readBuffer = new byte[7];
 
        this.Slave.WriteRead(new byte[] { 0x00 }, readBuffer);
 
        //...
    }
}

But how do we convert the byte buffer into a DateTime?

Converting from BCD format to decimal

I wrote a simple function which:

  • Splits each byte into an upper and lower nibble
  • Multiplies the value of the upper nibble by ten
  • Adds the numbers together to get a decimal value.

The function is shown below:

private int BinaryCodedDecimalToInteger(int value)
{
    var lowerNibble = value & 0x0F;
    var upperNibble = value >> 4;
 
    var multipleOfOne = lowerNibble;
    var multipleOfTen = upperNibble * 10;
 
    return multipleOfOne + multipleOfTen;
}

So from the 7 bytes of information returned from the DS1307, I can use this function to get the actual date and time values:

private DateTime ConvertByteBufferToDateTime(byte[] dateTimeBuffer)
{
    var second = BinaryCodedDecimalToInteger(dateTimeBuffer[0]);
    var minute = BinaryCodedDecimalToInteger(dateTimeBuffer[1]);
    var hour = BinaryCodedDecimalToInteger(dateTimeBuffer[2]);
    var dayofWeek = BinaryCodedDecimalToInteger(dateTimeBuffer[3]);
    var day = BinaryCodedDecimalToInteger(dateTimeBuffer[4]);
    var month = BinaryCodedDecimalToInteger(dateTimeBuffer[5]);
    var year = 2000 + BinaryCodedDecimalToInteger(dateTimeBuffer[6]);
 
    return new DateTime(year, month, day, hour, minute, second);
}

Which means the GetCurrentTime() method can now become like the code below:

public DateTime GetCurrentTime()
{
    byte[] readBuffer = new byte[7];
 
    this.Slave.WriteRead(new byte[] { 0x00 }, readBuffer);
 
    return ConvertByteBufferToDateTime(readBuffer);
}

But what about setting the time?

Setting the time on the DS1307

To set the time, we have to reverse some of the operations we carried out above.

  • We get date and time values as integers
  • Convert them from this format to binary coded decimal, and then
  • We write these values to the DS1307 as a byte array.

To convert from integers to binary coded decimal, we need to split the integer into the different multiples of powers of 10, convert to nibbles, and add them together.

private byte IntegerToBinaryCodedDecimal(int value)
{
    var multipleOfOne = value % 10;
    var multipleOfTen = value / 10;
 
    // convert to nibbles
    var lowerNibble = multipleOfOne;
    var upperNibble = multipleOfTen << 4;
 
    return (byte)(lowerNibble + upperNibble);
}

This method makes it simple to convert date and time components into binary coded decimal format, and write them to device in an array of bytes.

public void SetDateTime(DateTime dateTime)
{
    this.Slave.Write(ConvertTimeToByteArray(dateTime));
}
 
private byte[] ConvertTimeToByteArray(DateTime dateTime)
{
    var dateTimeByteArray = new byte[8];
 
    dateTimeByteArray[0= 0;
    dateTimeByteArray[1= IntegerToBinaryCodedDecimal(dateTime.Second);
    dateTimeByteArray[2= IntegerToBinaryCodedDecimal(dateTime.Minute);
    dateTimeByteArray[3= IntegerToBinaryCodedDecimal(dateTime.Hour);
    dateTimeByteArray[4= IntegerToBinaryCodedDecimal((byte)dateTime.DayOfWeek);
    dateTimeByteArray[5= IntegerToBinaryCodedDecimal(dateTime.Day);
    dateTimeByteArray[6= IntegerToBinaryCodedDecimal(dateTime.Month);
    dateTimeByteArray[7= IntegerToBinaryCodedDecimal(dateTime.Year - 2000);
            
    return dateTimeByteArray;
}

Reading the time

With this class in place, I can now connect my DS1307 breakout board to my Raspberry Pi 3.

  • 5v to Pin 4
  • Ground to Pin 6
  • SCL (serial clock) to Pin 5
  • SDA (serial data) to Pin 3

The code I use to read the time is simple, and follows the pattern I’ve used in previous posts.

private async Task WriteDateAndTimeToDebug()
{
    var clock = new DS1307();
 
    await clock.Initialize();
 
    // set the time if you need to
    clock.SetDateTime(DateTime.UtcNow);
 
    while (true)
    {
        var time = clock.GetCurrentTime();
 
        Debug.WriteLine("Time = " + time);
 
        Task.Delay(1000).Wait();
    }
}

As usual, I’ve uploaded all of this code to GitHub – I hope it helps someone out with their project.