Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Real-Time Clock (DS1307) for I2C


  • Please log in to reply
8 replies to this topic

#1 Eric Falsken

Eric Falsken

    Member

  • Members
  • PipPip
  • 25 posts
  • LocationSan Francisco, CA, USA

Posted 01 December 2011 - 12:00 AM

Hey guys, I got one of the DS1307 breakouts from Sparkfun and spent the weekend getting it working.

Attached File  Untitled Sketch 2_bb.png   34.46KB   288 downloads

The 5 wires (from top of the black part) are SDA, SCL, SQW, GND, 5V. The resistors are both 1k Ohm from the SDA/SCL lines to 5V.

For the code, I started with what I found over in the NetduinoHelpers project, but I enhanced and refactored it a lot. The new code supports the 12/24 flag properly. It also will share the I2CDevice with other classes that might need to use it at the same time by locking the I2CDevice instance before interacting with it.

My version of the DS1307 class is attached below too for easy downloading.

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace netduino.helpers.Hardware {
	/// <summary>
	/// This class implements a complete driver for the Dallas Semiconductors / Maxim DS1307 I2C real-time clock: http://pdfserv.maxim-ic.com/en/ds/DS1307.pdf
	/// </summary>
	public class IcDS1307 : IDisposable
	{
		[Flags]
		// Defines the frequency of the signal on the SQW interrupt pin on the clock when enabled
		public enum SQWFreq { SQW_1Hz, SQW_4kHz, SQW_8kHz, SQW_32kHz, SQW_OFF };

		[Flags]
		// Defines the logic level on the SQW pin when the frequency is disabled
		public enum SQWDisabledOutputControl { Zero, One };

		// Real time clock I2C address
		public const int DS1307_I2C_ADDRESS = 0x68;
		// I2C bus frequency for the clock
		public const int DS1307_I2C_CLOCK_RATE_KHZ = 100;
		
		// Allow 10ms timeouts on all I2C transactions
		public const int DS1307_I2C_TRANSACTION_TIMEOUT_MS = 10;
		
		// Start / End addresses of the date/time registers
		public const byte DS1307_RTC_START_ADDRESS = 0x00;
		public const byte DS1307_RTC_END_ADDRESS = 0x06;
		public const byte DS1307_RTC_SIZE = 7;

		// Square wave frequency generator register address
		public const byte DS1307_SQUARE_WAVE_CTRL_REGISTER_ADDRESS = 0x07;

		// Start / End addresses of the user RAM registers
		public const byte DS1307_RAM_START_ADDRESS = 0x08;
		public const byte DS1307_RAM_END_ADDRESS = 0x3f;

		// Total size of the user RAM block
		public const byte DS1307_RAM_SIZE = 56;

		// Instance of the I2C clock
		readonly bool privateClock = false;
		readonly I2CDevice clock;
		readonly I2CDevice.Configuration config = new I2CDevice.Configuration(DS1307_I2C_ADDRESS, DS1307_I2C_CLOCK_RATE_KHZ);

		public IcDS1307() {
			privateClock = true;
			clock = new I2CDevice(config);
		}

		public IcDS1307(I2CDevice device) {
			clock = device;
		}

		/// <summary>
		/// Gets or Sets the current date / time.
		/// </summary>
		/// <returns>A DateTime object</returns>
		public DateTime CurrentDateTime {
			get {
				byte[] clockData = ReadRegister(DS1307_RTC_START_ADDRESS, DS1307_RTC_SIZE);
				return ParseDate(clockData);
			}
			set {
				WriteRegister(DS1307_RTC_START_ADDRESS, EncodeDate(value, ClockHalt, TwelveHourMode));
			}
		}
  
		private DateTime ParseDate(byte[] clockData) {
			int hour = 0;
			if ((clockData[0x02] & 0x40) == 0x40) { // 12-hour mode
				if ((hour & 0x20) == 0x20)
					hour += 12;
				hour += BcdToDec((byte)(clockData[0x02] & 0x1f)) + 1;
			}
			else { // 24-hour mode
				hour += BcdToDec((byte)(clockData[0x02] & 0x3f));
			}

			return new DateTime(
				BcdToDec(clockData[0x06]) + 2000, // year
				BcdToDec(clockData[0x05]), // month
				BcdToDec(clockData[0x04]), // day
				hour, // hour
				BcdToDec(clockData[0x01]), // minutes
				BcdToDec((byte)(clockData[0x00] & 0x7f)));
		}

		private byte[] EncodeDate(DateTime value, bool clockHalt, bool hourMode) {
			byte seconds = 0x00;
			if (clockHalt) seconds |= 0x80;
			seconds |= (byte)(DecToBcd(value.Second) & 0x7f);

			byte hour = 0x00;
			if (hourMode) { // 12-hour mode
				hour |= 0x40; //set the 12-hour flag
				if (value.Hour >= 12)
					hour |= 0x20;
				hour |= DecToBcd((value.Hour % 12) + 1);
			}
			else { //24-hour mode
				hour |= DecToBcd(value.Hour);
			}

			return new byte[DS1307_RTC_SIZE] { 
				seconds, 
				DecToBcd(value.Minute), 
				hour, 
				DecToBcd((int)value.DayOfWeek), 
				DecToBcd(value.Day), 
				DecToBcd(value.Month), 
				DecToBcd(value.Year - 2000)};
		}

		/// <summary>
		/// Enables / Disables the square wave generation function of the clock.
		/// Requires a pull-up resistor on the clock's SQW pin.
		/// </summary>
		/// <param name="Freq">Desired frequency or disabled</param>
		/// <param name="OutCtrl">Logical level of output pin when the frequency is disabled (zero by default)</param>
		public void SetSquareWave(SQWFreq Freq, SQWDisabledOutputControl OutCtrl = SQWDisabledOutputControl.Zero) {
			byte SqwCtrlReg = (byte) OutCtrl;
			
			SqwCtrlReg <<= 3;   // bit 7 defines the square wave output level when disabled
								// bit 6 & 5 are unused

			if (Freq != SQWFreq.SQW_OFF) {
				SqwCtrlReg |= 1;
			}

			SqwCtrlReg <<= 4; // bit 4 defines if the oscillator generating the square wave frequency is on or off.
							  // bit 3 & 2 are unused
			
			SqwCtrlReg |= (byte) Freq; // bit 1 & 0 define the frequency of the square wave
			
			WriteRegister(DS1307_SQUARE_WAVE_CTRL_REGISTER_ADDRESS, SqwCtrlReg);
		}

		/// <summary>
		/// Halts / Resumes the time-keeping function on the clock.
		/// The value of the seconds register will be preserved.
		/// (True: halt, False: resume)
		/// </summary>
		public bool ClockHalt {
			get {
				var seconds = this[0x00];
				return (seconds & 0x80) == 0x80;
			}
			set {
				lock (clock) {
					var seconds = this[0x00];

					if (value)
						seconds |= 0x80; // Set bit 7
					else
						seconds &= 0x7f; // Reset bit 7

					WriteRegister(0x00, seconds);
				}
			}
		}

		/// <summary>
		/// Gets/Sets the Hour mode.
		/// The current time will be corrected. 
		/// (True: 12-hour, False: 24-hour)
		/// </summary>
		public bool TwelveHourMode {
			get {
				var hours = this[0x02];
				return (hours & 0x40) == 0x40;
			}
			set {
				lock (clock) {
					var rtcBuffer = ReadRegister(DS1307_RTC_START_ADDRESS, 7);
					var currentDate = ParseDate(rtcBuffer);
					rtcBuffer = EncodeDate(currentDate, false, value);

					WriteRegister(0x02, rtcBuffer[0x02]);
				}
			}
		}

		/// <summary>
		/// Writes to the clock's user RAM registers as a block
		/// </summary>
		/// <param name="buffer">A byte buffer of size DS1307_RAM_SIZE</param>
		public void SetRAM(byte[] buffer) {
			if (buffer.Length != DS1307_RAM_SIZE)
				throw new ArgumentOutOfRangeException("Invalid buffer length");

			WriteRegister(DS1307_RAM_START_ADDRESS, buffer);
		}

		/// <summary>
		/// Reads the clock's user RAM registers as a block.
		/// </summary>
		/// <returns>A byte array of size DS1307_RAM_SIZE containing the user RAM data</returns>
		public byte[] GetRAM() {
			return ReadRegister(DS1307_RAM_START_ADDRESS, DS1307_RAM_SIZE);
		}

		public byte this[byte address] {
			get { return ReadRegister(address, 1)[0]; }
			set { WriteRegister(address, value); }
		}

		/// <summary>
		/// Reads an arbitrary RTC or RAM register
		/// </summary>
		/// <param name="address">Register address between 0x00 and 0x3f</param>
		/// <param name="length">The number of bytes to read</param>
		/// <returns>The value of the bytes read at the address</returns>
		public byte[] ReadRegister(byte address, int length = 1) {
			if (length < 1) throw new ArgumentOutOfRangeException("length", "Must read at least 1 byte");
			if (address + length -1 > DS1307_RAM_END_ADDRESS) throw new ArgumentOutOfRangeException("Invalid register address");

			var buffer = new byte[length];

			lock (clock) {
				clock.Config = config;
				// Read the RAM register @ the address
				var transaction = new I2CDevice.I2CTransaction[] {
						I2CDevice.CreateWriteTransaction(new byte[] {address}),
						I2CDevice.CreateReadTransaction(buffer) 
					};

				if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0) {
					throw new Exception("I2C transaction failed");
				}
			}

			return buffer;
		}

		/// <summary>
		/// Writes an arbitrary RTC or RAM register
		/// </summary>
		/// <param name="address">Register address between 0x00 and 0x3f</param>
		/// <param name="val">The value of the byte to write at that address</param>
		public void WriteRegister(byte address, byte data) {
			WriteRegister(address, new byte[] { data });
		}

		public void WriteRegister(byte address, byte[] data) {
			if (address > DS1307_RAM_END_ADDRESS)
				throw new ArgumentOutOfRangeException("Invalid register address");
			if (address + data.Length > DS1307_RAM_END_ADDRESS)
				throw new ArgumentException("Buffer overrun");

			byte[] txData = new byte[data.Length + 1];
			txData[0] = address;
			data.CopyTo(txData, 1);

			lock (clock) {
				clock.Config = config;
				var transaction = new I2CDevice.I2CWriteTransaction[] {
					I2CDevice.CreateWriteTransaction(txData)
				};

				if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0) {
					throw new Exception("I2C write transaction failed");
				}
			}
		}

		/// <summary>
		/// Takes a Binary-Coded-Decimal value and returns it as an integer value
		/// </summary>
		/// <param name="val">BCD encoded value</param>
		/// <returns>An integer value</returns>
		protected int BcdToDec(byte val) {
			int ones = (val & 0x0f);
			int tens = (val & 0xf0) >> 4;
			return (tens * 10) + ones;
		}

		/// <summary>
		/// Takes a Decimal value and converts it into a Binary-Coded-Decimal value
		/// </summary>
		/// <param name="val">Value to be converted</param>
		/// <returns>A BCD-encoded value</returns>
		protected byte DecToBcd(int val) {
			byte lowOrder = (byte)(val % 10);
			byte highOrder = (byte)((val / 10) << 4);
			return (byte)(highOrder | lowOrder);
		}
		
		/// <summary>
		/// Releases clock resources
		/// </summary>
		public void Dispose() {
			if(privateClock)
				clock.Dispose();
		}
	}
}

Attached Files



#2 Fabien Royer

Fabien Royer

    Advanced Member

  • Members
  • PipPipPip
  • 406 posts
  • LocationRedmond, WA

Posted 02 December 2011 - 05:26 AM

Eric, Thanks for your contribution. In the future, can you please contribute changes in Codeplex instead of duplicating code in the forums? This will avoid copies of the code randomly floating around the forums and will ensure proper attribution of the original code / changes. As I or others make changes or bug fixes in Codeplex, whatever code is posted in the forums is bound to get crusty very quickly... Cheers, -Fabien.

#3 Eric Falsken

Eric Falsken

    Member

  • Members
  • PipPip
  • 25 posts
  • LocationSan Francisco, CA, USA

Posted 02 December 2011 - 08:15 PM

Eric,

Thanks for your contribution.

In the future, can you please contribute changes in Codeplex instead of duplicating code in the forums?
This will avoid copies of the code randomly floating around the forums and will ensure proper attribution of the original code / changes.
As I or others make changes or bug fixes in Codeplex, whatever code is posted in the forums is bound to get crusty very quickly...

Cheers,
-Fabien.


Codeplex doesn't allow code check-ins from non-project members without forking, which I'm not interested in doing and is the exact same thing as I've done here. I've got no problem posting the code here until it is integrated into the NetduinoHelpers project. I can always come back and edit this post to remove the code and attachments to avoid fragmentation.

#4 Fabien Royer

Fabien Royer

    Advanced Member

  • Members
  • PipPipPip
  • 406 posts
  • LocationRedmond, WA

Posted 03 December 2011 - 08:34 AM

Please create a pull-request in CodePlex for your changes. If they're acceptable, I'll integrate them. Thanks.

#5 Eric Falsken

Eric Falsken

    Member

  • Members
  • PipPip
  • 25 posts
  • LocationSan Francisco, CA, USA

Posted 03 December 2011 - 05:21 PM

Please create a pull-request in CodePlex for your changes. If they're acceptable, I'll integrate them.


I've never worked with Mercurial and don't really want to. If it was SVN or TFS, no problem. Or if it was on Git, I could understand. The code is here if you wish to reincorporate it.

#6 Fabien Royer

Fabien Royer

    Advanced Member

  • Members
  • PipPipPip
  • 406 posts
  • LocationRedmond, WA

Posted 03 December 2011 - 08:41 PM

I recommend Tortoise HG to work with Mercurial should you change your mind.
So you know, I won't integrate code that doesn't have unit tests.
Cheers,
-Fabien.

#7 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 05 December 2011 - 01:08 AM

Hi Eric,

Thanks for using community code and for adding improvements (esp. the 12/24 flag).

I understand your pain in downloading another source control client. I personally really like Mercurial and how well it integrates on Windows (in contrast to Git), but there are quite a few systems out there these days...

If you ever have time to upload the enhancements to NetduinoHelpers, that would be awesome. [I know that Fabien has unit testing requirements there too...which is a good thing, albeit again...a bit more work that provides lots of benefits.]

Chris

#8 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 05 December 2011 - 06:36 AM

Pardon me, but I'd to know a clarification... Fabien, I mean your idea about a good protocol of development and I agree you viewpoint, but...how do you manage the unit testing? I understand the unit test against the "pure" software, but what's the sense against a mixed hadrware/software. I really think that's not possible, or it will be always limited to the software section. In other words, there's no guarantee about the correct/reliable implementation of the physical interface. This is an interesting argument, but -IMHO- is rather a too much complex task to ask to the typical Netduino user. BTW, I agree about the Tortoise HG. Cheers
Biggest fault of Netduino? It runs by electricity.

#9 Fabien Royer

Fabien Royer

    Advanced Member

  • Members
  • PipPipPip
  • 406 posts
  • LocationRedmond, WA

Posted 07 December 2011 - 08:37 AM

Hi Mario,

By unit-test I mean this:
  • basic schematics / pin-out to reproduce a given hardware setup
  • basic code exercising the functions of the driver driving the hardware setup. In this case, that would mean adding to the existing test code I already wrote. If something was 'refactored', I would expect the test code to be as well.
Basically, a reasonable means of reproducing an experiment, ensuring that things work end-to-end before integrating some unknown code into a source tree that many others will download.
This is also required to detect regressions when seemingly innocuous changes may have introduced.

Cheers,
-Fabien.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.