My wife and I are in the process of building a new house. Well, actually, the contractor is doing the building, we are mostly just watching in awe as the framing goes together. My wife, being the gardening fanatic of our little family, is quite concerned with the outside temperature differences between our existing house and the new one. She is concerned that the growing season is going to be shorter. The new house is at a higher elevation (650 feet) and is farther east into the mountains than our existing house (380 feet). I don't think there is much of a temperature difference, but the only way I am going to prove that is to have the data to back it up.
The key to doing this is to take measurements in both places at roughly the same time. Since neither of us is dedicated enough to actually implement the plan manually, a computer based project seems in order.
Of course, me thinking robots, I decided to build a pair of robots that would be able to test my theories. My plan is to have one at our existing house, and one at the new house, logging temperature and lighting levels. I thought about just using a data logging device, but being able to sample different areas of the yard seemed like a great use for a robot. So, the current plan is to build a small robot(s) that can handle traveling between the front and backs of the house(s).
To insure we are taking measurements at the same time, I needed to add a clock to the project. Looking around on the internet, I found just what I think I need. The DS1305 Timekeeper!
This article sets out to show two useful things. First is how to connect and use a DS1305 Timekeeper device. Second is how to manage multiple devices on a Single Master SPI bus.
The DS1305 is a member of the Dallas Semiconductor timekeeper family. It is a 16-pin device that implements a real time clock with dual alarms, 96 bytes of non-volatile RAM, a trickle charger for your battery, and runs on the Motorola SPI bus (or optionally on a 3 wire interface). As such, you can connect this device to most processors that support the SPI bus. Specifically, the 68HC11 and 68HC12. Other parts also support the SPI bus.
To show you this project, I am going to implement a simple data logger device using a BotBoard Plus. The BotBoard Plus (available at www.nwlink.com/~kevinro/products.html ) is a simple 68HC811E2 based board that includes a serial EEPROM device. This serial EEPROM device is also connected to the SPI bus. I use a 3 volt battery to keep the time while the unit is powered down.
The basic function of these board is to collect a sample of the A/D port every hour, and record that value to the serial EEPROM. Then, when requested, the software on the BotBoard Plus will dump the logged data to the serial port. This is an intermediate step to building a robot that can keep track of atmospheric conditions as it wanders around. Along with the clock, I had also ordered a temperature conversion chip that I will describe in another article.
The DS1305 chip is a fairly cool device that is simple to connect electrically and relatively simple to configure via software. The DS1305 is an SPI compatible device. Dallas actually makes a number of different timekeeping devices. Some are SPI compatible, some aren't. Most all have the same clock/calendar engine, but different bus interfaces and extra stuff, like the power charger. Almost all of them implement the same basic software requirements.
I have placed a copy of the DS1305 Data Sheet in PDF format on our website.
As always, Engineers just love to see a block diagram. Here, ruthlessly swiped from the DS1305 data sheet .PDF file, is the block diagram of the DS1305.
A quick tour of the interesting parts. First of all, in the upper left, you can see the connections that are related to the power management functions. This device will run on your main power source (Vcc1), a rechargeable battery source (Vcc2), and a backup battery (Vbat). This allows you quite a bit of flexibility as far as making sure your real time clock is always powered. There are a number of different wiring options which are explained in the text of the .PDF file.
On the bottom left, it shows the serial interface block. There are two protocols supported. The first is the Motorola SPI, and the second is the 3-wire interface. We are going to focus primarily on the SPI interface for now. I will be doing an article on the 3-wire interface in the near future.
On the right, you see the clock / control register / RAM portion. Connected to this is the all important clock calendar logic. This is the logic that actually performs the clock functions. There is a crystal oscillator connected to this part. It is a 32.768khz crystal, which is a pretty standard part. More about that later.
Two outputs from the section on the right are interesting. There are two alarm clock settings on the part. When the alarm goes off, it will drive these pins low. That is pretty useful for something like a data logging device. For example, a board may enter a sleep mode to conserve battery power. Since the DS1305 consumes very little power, we can leave it powered up. By connecting the alarm clock to the IRQ line of the BotBoard Plus, we would be able to wake the CPU up so it can do something useful.
All of the control registers, RAM, and clock data variables are stored in a single address space. The address map is shown below.
The maximum address on the part is 0xFF, which is handy since all addressing is handled by a single byte address. Note that the address map is roughly split into two halves. The first range, 0x00-0x7F, represents the read addresses. If you send an address byte in this range, the part assumes that you wish to read the contents of that address. The second range, 0x80-0xFF, are the write addresses. In reality, there are only addresses 0x00 - 0x7F. The upper addresses map to the same memory space, only the operation changes. You can also say that the high bit of the address determines if the operation is a Read (0) or a Write (1). More on this later.
The Clock Calendar addresses are mapped as follows.
This is really the meat of the information you need. As you can see, the memory map for the Clock/Calendar addresses is broken into 4 major areas. The first being the current clock. The second is for Alarm 0, the third for Alarm 1, and the fourth belongs to the control and status registers. This takes you from addresses 0x00-0x1f. The rest of the map (0x20-0x7F) is all non-volatile RAM.
The data is stored in these registers in Binary Coded Decimal (BCD). If you know about BCD numbers, you may skip this next section.
Confused by this BCD term? Let me give you a quick run down. BCD is another one of those very old, very crusty, and occasionally useful formats that just won't go away! A BCD value is stored in a single byte, and consists of two decimal numbers.
A normal byte has 8 bits of data. A normal byte can represent a value between 0 and 255 (decimal) which we usually write as 0x00 through 0xFF which is the hexadecimal (base16). Each group of 4 bits is called a nibble (or sometimes nybble). Each normal nibble containing 4 bits can represent a value between 0x0 and 0xF (hexadecimal). When you are dealing with hexadecimal, the low 4 bits are represented on the right and the high four bits are on the left. A value of 0x53, for example, has the high 4 bits containing 0101 (0x5) and the low four bits containing 0011 (0x3). You can combine (concatenate) these two nibbles to get the 0x53. We computer geeks just love hexadecimal because it is a great shorthand that shows us which bits in the byte are on or off.
Binary Coded Decimal is also represented as a pair of nibbles. The catch is that each nibble's 4 bits is limited to hexadecimal values 0x0 through 0x9. That means that some combinations of the bits in a nibble are not used. This seems a little strange to think about, so I will provide you a table of possible nibbles below.
|Binary||Hex||Dec||BCD||BCD as Binary||BCD as Hex|
*** are invalid nibbles.
Looking down the Binary column, you can see that the binary numbers progress like expected. Their Hex counterparts also look like they should. In DECimal, you also see the numbers progress like they should. Looking down the BCD column, you can see that a single nibble can only represent 0 through 9. That means that any value over 9 doesn't work in a BCD nibble. To make it work, you need to shift to a BCD Byte. The BCD as Binary column shows how the transition from 9 to 10 decimal is encoded into a single BCD Byte. Since a BCD nibble can only represent 0-9, we need to carry the value over into the next nibble. Therefore, representing 10 or above requires 2 nibbles, or an entire byte.
The last column is called BCD as Hex. If I were to dump a BCD byte in Hexadecimal format, I just happen to get the decimal number displayed. Taking a look, for example, at the Hex value 0x0C. It translates into 0x13 in BCD format.
Why did they do this? Well, it turns out that BCD can actually be useful in some cases. Take, for example, a Clock. Assume I was storing the time data in binary format. A standard second of time could take the value 0x00 through 0x3B ( 0 through 59 decimal). If I were to store the values as standard hex values, then need to actually display these values, I might need a fairly complex bit of hardware or at least a microcontroller to translate between the byte value and its display value. Take, for instance, 14 seconds. In hex format, I need to translate 0x0E into the numbers 1 and 4.
However, if I stored these values in another form, say BCD, then the digits I need for these values are represented in each nibble. As the clock counts up in BCD, the display data is always available. Look at the line above for 0x0E. The BCD value is 0x14, which are the digits I need to print.
In the crusty past, there were an entire set of parts created to deal with BCD values. The 7447 LED Segment driver, for example, accepts BCD input to generate the appropriate signals for a 7 segment LED to display the correct number. Now, or should I say 'then', you would be able to shift the contents of the clock registers out to a series of these LED drivers, and latch them in to display the current clock. No fancy pants microcontroller required.
Ok, so that is a brief explanation of BCD values. These were/are so common that most microcontrollers have at least a function or two to help you deal with BCD. On the 68HC11, the DAA (Decimal Adjust ACCA) is the instruction to use. Rather than spend too much time on the DAA instruction, suffice it to say that I usually only use the BCD numbers for display purposes. Doing math operations in BCD isn't trivial. I highly recommend converting to normal binary, doing your math, then converting back.
Here is a partial schematic showing how I connected this part up to the 68HC811E2 based BotBoard Plus ( www.nwlink.com/~kevinro/products.html ). The BotBoard Plus already has an onboard a 25LC640 SPI based serial EEPROM.
As you can see, the connections aren't very complicated for the DS1305. There are 4 SPI bus wires, plus some power connections. Note that a typical SPI device uses the SS line from the SPI port as the chip select. If you look at U3, the serial EEPROM, its Chip Select (CS pin 1) is already connected to the SS pin on the HC11. To allow for independent control of the two devices, we need an additional control line. For this implementation, I selected PORTB:0 which is a decent choice since PORTB is an output only port. If your system has an expanded memory bus, you will need to select a different port, probably one of the PORTA pins.
The battery B1 connected to the Vbat and GND pins will supply the part with enough power to keep time while the main supply is off.
Here is a rundown of the connections to the DS1305:
|01: Vcc2||Supply Voltage number 2. This pin connects to a rechargeable battery device. If you set some configuration bits, the part will keep this battery fully charged.||For my purposes, I didn't install a rechargeable battery. According to the docs, I grounded this pin|
|02: Vbat||Backup battery connection. This pin connects to a backup battery source. This can be as little as a 3 volt lithium cell. This battery is used to keep the clock running when power isn't available on Vcc1 or Vcc2.||Here I installed a 3volt Lithium battery. The + side goes to this pin, the - side to the BotBoard Plus GND pin. You can also use a couple of AA cells|
|03: X1||This is one leg of the Crystal Connection. This part is setup for a 32.768khz crystal||This and Pin 04 are connected to either side of a crystal|
|05: NC||No Connection||No Connection|
|06: INT0||Interrupt Source Zero signals that the alarm clock zero has been triggered. You can setup an alarm in software||Didn't connect this yet|
|07: INT1||Interrupt Source 1 signals that the alarm clock one has been triggered. You can setup an alarm in software||Didn't connect this yet|
|08: GND||Ground for part.||Connected this to the BotBoard Plus ground|
|09: SERMOD||This selects SPI or the 3-wire interface.||Pulled high to set the part for SPI.|
|10: CE||Chip Enable. When high, the chip listens to the bus. If low, it will ignore it.||On a single device SPI bus, I might have connected this to the SS pin of the SPI port. Since the SS pin is already in use, I used one of the output pins, namely PORTB:0|
|11: SCLK||Serial Clock. As each bit is put on the data bus, the Serial Clock is toggled, shifting data into the part.||Connects to the SCLK pin on the SPI bus.|
|12: SDI||Serial Data In. In SPI mode, this pin is where serial data is shifted in.||Connects to the Master Out / Slave In (MOSI) pin of the SPI bus.|
|13: SDO||Serial Data Out. In SPI mode, this pin shifts data out to the SPI bus.||Connects to the Master In / Slave Out (MISO) pin of the SPI bus.|
|14: Vccif||Interface Logic Power Supply Input. It is possible to run the DS1305 as a 3 volt device. To make the logic driver compatible with a 5 volt device, you can use this pin to supply the correct voltage.||Connected to Vcc to insure we have the correct voltage for our interface|
|15: PF||Power Fail. Active low, this pin can be used to alert the CPU that a power failure has been detected, and that the DS1305 is running on battery power. You could also connect this to an LED to indicate a power fail condition.||I didn't use this, so it isn't connected|
|16: Vcc1||Primary Power Supply. Under normal operating conditions, this is the power supply that the chip uses for power.||Connected to Vcc|
Electrically, this was a pretty straight forward job. I just placed the part out on the bread boarding area, leaving room on the pin 3/4 side to fit a crystal, and point to point soldered the 12 connections using wire wrap wire. I also made room for the 3 volt battery.
The last step was to connect up a temperature sensor. For the first stab, I am using an LM34DZ, which outputs a signal proportional to the temperature. It works fine for this first stab, but I already have an eye on the Dallas 1620, which converts temperatures into a digital format which is much more accurate than the LM34. I will explain that part in an upcoming article. For now, the LM34DZ just connects to Power, Ground, and PORTE:0. Nothing to it!
I am going run forward a little, and tell you about an issue I am running into. That 32.768khz crystal seems to be fairly accurate, but not accurate enough. I am finding that over 4 full days, the clock is drifting approximately 30 seconds. That means it will be about a minute a week off time. This might be just fine for what your needs are. Actually, it is probably fine for what I am doing, but the lack of precision bothered me just a little! I also found it to be temperature dependent, which means even worse accuracy in the fluctuating outdoor environment.
After looking around, I found the DS32KHz Temperature-Compensated Crystal Oscillator, which according to the spec, should keep time to within +-4 minutes a year. I am anxious to get one of these, but unfortunately, they won't be shipping me one until June 6th, which isn't in time for this article.
If you don't care about that level of accuracy, the $0.89 crystal works just great. If you really need the accuracy, the $7.20 DS32KHZ might be something to look into. I decided to punt and just rely on the cheap crystal for now. I am sure I can convince my wife even if we are off by a minute or two a week!
I was able to order this part directly from Dallas Semiconductor ( http://www.dalsemi.com ) using their credit card ordering hotline.
The software has been an interesting project. At first, I went for a strictly SBASIC implementation of a driver file. SBASIC is a fine language, and seems to work pretty well for most tasks. The only real issue I ran into with the SBASIC implementation was I started to run pretty tight on memory.
The 811E2 has 2k of EEPROM for use as code space. This is a decent amount, but it turned out that my little test program consumed quite a significant amount of it!
Thinking that another language would work better, I decided to try doing an implementation in Imagecraft C. I had a small battle with the compilers default libraries, since the implementation of printf provided with the compiler uses the LONG libraries. After a few iterations, I tossed the printf function and used puts and some basic character I/O instead. That made the basic driver fit, but without much in the way of a test driver program. SBASIC and Imagecraft C seem to produce similar sized programs.
As such, I am going to provide you with both a C implementation and an SBASIC implementation of the drivers for the ds1305. Its pretty simple code.
If you find yourself really tight on space, you could also reimplement this code in assembler or Tiny4th. In an upcoming article, I will use Tiny4th to squeeze yet another Dallas part into the mix.
To kick off the software, allow me to show you the required steps to get the SPI port setup on a BotBoard Plus.
' ' ds1305_Init sets up the SPI port, and makes sure the part is ' deselected. ' ds1305_Init: ' Setup PORTB:0 to be low. Going high selects the DS1305 gosub ds1305_spi_deselect ' Set the DDRD5 bit so that SS is an output pin. This is an ' important step because it disables the mode fault detection ' feature of the SPI port. ' pokeb PORTD, peekb(PORTD) OR $38 pokeb DDRD, peek(DDRD) OR $38 ' Setup the SPI register to be MASTER, CPOL=0, CPHA=1 pokeb SPCR, $54 ' On a 68HC11, ECLOCK is 2mhz (assume 8mhz clock) which is OK ' On a 68HC12, ECLOCK is 8mhz, which is too fast. A prescale ' may be required on a 68HC12 return ' ' ds1305_spi_select raises the chip select line, and also ' sets the SPI control register to operate the SPI port in ' CPOL = 0 CPHA = 1 mode ' ' The Chip Select is set to be on PORTB:0 and is active high ' for the DS1305. ' ds1305_spi_select: pokeb SPCR, peekb(SPCR) OR $04 pokeb PORTB, peekb(PORTB) OR $01 return ds1305_spi_deselect: pokeb PORTB, peekb(PORTB) AND $FE return
The ds1305_Init routine calls the deselect routine to insure that the CE line is lowered. This prevents changes in the SPI configuration from sending any data to the part.
The next step, which is required when your part is trying to be an SPI master, is to set the Data Direction Register for PORTD. You will want to disable mode fault detection, or you will find that you will be unable to send data to your device. Mode Fault Detection is a feature of the chip used in multi-master SPI busses. Thats another subject, so just disable it for now.
The ds1305 prefers to use CPHA == 1. This bit controls how the leading edge of the signals are defined. With CPHA == 1, the data is valid on the falling edge of the SCK signal. With CPHA == 0, the data is valid on the rising edge of SCK. There is a decent diagram of this in the Pink book, pages 8-2 and 8-3.
If you have multiple SPI devices, it is a good idea to check to insure that the CPHA bit is set correctly for each one. In my example, using a BotBoard Plus, the Serial EEPROM likes CPHA==0, while the ds1305 would rather have CPHA==1. Thus, in the chip select routine for each part, I have put in an instruction to insure that the SPI port has the correct phase.
Since everything on the ds1305 is memory mapped, there are really only two operations required. These are the ds1305_Read_Block and ds1305_Write_Block routines.
' ' ds1305_Read_Block ' ' invoke using gosub ds1305_Read_Block <A>, <P>, <N> ' ' Reads N bytes from address A into buffer P ' ds1305_Read_Block: ' Select the ds1305 for data operations gosub ds1305_spi_select ' Set the starting address on the ds1305. It is always ' the first byte sent gosub ds1305_spi_xfer,pick(2) ' Loop through the buffer reading the data in do while pick(0) <> 0 pokeb pick(1), usr(ds1305_spi_xfer,0) place 1, pick(1) + 1 place 0, pick(0) - 1 loop ' We are now done with the ds1305 gosub ds1305_spi_deselect ' In SBASIC, we remove our own arguments from the stack drop 3 return
Stepping through this routine, we first select our SPI bus device. All operations on the ds1305 start out with an address. In this case, we send the address of the start of the read. Its a byte sized address that was passed into this routine on the data stack. The pick(2) instruction gets the value from the stack. N is at pick(0), P is at pick(1), and A is at pick(2).
We then go into a loop to read each individual byte from the ds1305 and place them into the buffer passed into this routine. Remember that the buffer is really a pointer, so to call this routine, you should be using the addr() function on your array. You will see an example of that in the code.
Once we have finished reading, we deselect the ds1305 SPI bus device, drop the 3 arguments that were passed in, and return. Not too bad!
' ' ds1305_Write_Block ' invoke using gosub ds1305_Write_Block <A>, <P>, <N> ' ' Writes N bytes from buffer P to address A ' ds1305_Write_Block: ' Select the ds1305 for data operations gosub ds1305_spi_select ' An address with the high bit on is considered a ' write operation on the ds1305. gosub ds1305_spi_xfer,pick(2)+$80 ' Once the address has been sent, it will be automatically ' incremented after each operation ' Loop through the buffer sending the data to the chip do while pick(0) <> 0 gosub ds1305_spi_xfer,peekb(pick(1)) place 1,pick(1)+1 place 0,pick(0)-1 loop ' We are now done with the ds1305 gosub ds1305_spi_deselect ' In SBASIC, we remove our own arguments from the stack drop 3 return
The ds1305_Write_Block routine is pretty much the same as the read routine, except for two things.
First is the address sent. We have added $80 to the address. This indicates to the part that we want to do a write operation instead of a read operation. If you recall in the memory map, there are two major areas in the address map. The first section were all read addresses. The second section are write addresses. The memory areas overlap meaning that writing to address $A0 ($80 + $20) will write to the memory address that can be read back from address $20.
The second is that we are sending bytes instead of reading them. The SPI bus actually does a 2 way transfer. You send a byte and read a byte during the same 8 bit transfer. In the Read_Block, we sent out a 0 byte. It doesn't matter since we were only interested in the inbound data. During this write operation, we send the byte we want to write, and ignore whatever was shifted back in.
The following three files are needed to duplicate exactly what I have done in SBASIC.
Click here to download the SBASIC version of the ds1305 driver file.
Click here to download the SBASIC version of the serial EEPROM driver file
Click here to download the SBASIC version of the test program
I also have a C version that implements the same basic interface to the ds1305, but I have not done the same test driver file. Its pretty easy to put together. These are done using the Imagecraft C compiler.
Click here to download the C version of ds1305.c, plus you need to download header file ds1305.h , and here is a small test driver that just displays the current time.
So, we now have the ability to read and write memory from the ds1305 part. That is good. Now I will walk you through the test driver program I wrote.
This SBASIC program acts as a data logger for the ds1305. When connected to a BotBoard Plus, it will allow you to monitor the value on A/D port E0, and log it once per minute to the Serial EEPROM on the back of the BotBoard Plus.
I have written an article about Serial EEPROM last year. Though the article was written for the HC12, the function of the chip on the SPI bus is identical. The only difference is a slight change in the names of the ports involved to move it to the HC11. An SBASIC file for the HC11 serial eeprom driver is found here.
Note: I had to modify the file sermem.bas recently to make this work. This version is slightly different than the version that had previously shipped on the software diskette for the BotBoard Plus. I put in an additional line to reset the CPHA == 0 bit in the sermem_Select routine. You will need to do this as well, or download the new version.
To do this, I ended up having to write a fair amount of code for mangling the data. There are routines for outputting BCD and Hex numbers. This isn't my proudest bit of code, but it does the trick.
To make the test program work, you need to first compile it using SBASIC. If you check in the ds1305t.bas file, you will find that both ds1305.bas and sermem.bas are included
sbasic ds1305t.bas /cf800 /v0000 /s00ff > ds1305t.asc
Assuming there are no errors, download the .S19 file to the HC11 using DL11 or PCBUG11, or whatever other file you have.
There are three commands you can issue to the test program. These are case sensitive!
To set the clock, you will need to type in all of the digits in the following stream
There are no spaces, and you must enter each digit, including leading zeros. If you enter garbage, the resulting time/date may end up being rather bizarre!
Each second, the program will output the time, date, and the current value of A/D port 0.
At the top of each minute, the program as written will take a reading of A/D channel 0, and store it to the EEPROM. It will also print out a W to show you that a value was written.
When you do a Dump, each line will be prefaced by a # symbol, followed by the month/day Hour:Minute:Second Data
The reason I selected this order is the slowest moving data is first. Therefore, if you sort the output, it comes out in chronological order.
If you look at the source for the test driver, you can see the initialization and basic operation of both the Serial EEPROM and the DS1305. In particular, you might like to check out the use of the battery backed RAM on the DS1305. I store the next available EEPROM location at address $20. This is a great place to write data that you need to survive across boots of your controller. Note that this isn't EEPROM that you are writing to. It is RAM backed up with the same battery that runs the clock.
As a side note, if I were so inclined, I should install a large capacity capacitor across Vbat and GND to hold Vbat in place while I change its batteries. That would give me additional protection against loosing the time/data. However, it wasn't important on my project, so I didn't. I would imagine a 100uF cap would run this DS1305 for at least 20-50 seconds or longer. I should try it to be sure, but that should be more than enough to swap battery packs.
There are two other functions that I didn't touch on with regard to this part. The first is the battery trickle charger. I have not had time to experiment with it yet, but perhaps in an upcoming project I will have a chance to do so.
The other thing I didn't do was to use the two built in alarms. These are really cool, since you can set them up, then put your CPU to sleep to conserve battery power. With the clock running on its own battery power, it can generate a wakeup signal to the CPU by triggering an interrupt. This is what most data logging devices do in the field. I haven't played with this yet, but I plan to and you will hear about this in a later article.
The third function (ok, I said two, but I lied!) is the Power Failure detect. There is a pin on the DS1305 that tells you when the main power has been removed, and you are running on the battery. This could be useful for you if you would like to know that you have dropped to the backup battery. You might change the way in which you handle your data and devices, like entering a low energy state, shutting down motors, lights, etc.
The driver code works pretty good, and I am pretty happy with it. It is currently about $FA bytes long, which is roughly 250 bytes. If I was so inclined, I could probably shrink that down to about 150 bytes or less by rewriting it in assembler.
The driver for the serial EEPROM is quite a bit larger, and could also be reduced in size somewhat.
What I was really thinking of doing was implementing both in Tiny4th, which is a language that Karl Lunt wrote. Speed on a data logging device isn't usually very critical, so Tiny4th is handy since the code is tokenized and makes for some pretty compact programs. My hope was that I would be able to pack more functionality into the same 2k of code space.
This article explained what the DS1305 looks like, how to connect it electrically, and how to write software to drive it. It also pointed out that you can have multiple SPI devices on a single bus as long as you have a good way to select each pin. You also need to determine which parameters are important, such as the clock polarity and phase. A little planning allows you to mix and match SPI interface components.