Introduction
This discussion focuses on the Dallas DS1302 Timekeeping Chip. A data sheet is available at http://www.dalsemi.com. We have the DS1302 in an 8-pin DIP for $3.00 and the 32.768 kHz crystal for $0.50.
This effort was initiated by undergraduate student LaVerne Ervin as her capstone design project (2 credits). Her work was very well done and enabled me to quickly make minor revisions and build on her work.
The DS1302 performs two functions; a 31 byte RAM and a real time clock. Clearly, most people probably use the real time clock feature, but the RAM might also be of value. For example; the Parallax BASIC Stamp 2 provides 26 RAM locations. The DS1302 might be used to add another 31 much like the BS2SX adds 63 RAM bytes
This discussion attempts to discuss all of the features of the DS1302 in the context of interfacing with the Parallax BASIC Stamp 2. However, BASIC is pretty "basic" and hopefully this discussion proves of value to people using other target processors.
This was written over the Christmas, '98 break. All of the routines except the last routines related to computing the Julian date and the number of seconds since the beginning of the year were tested on a BASIC Stamp 2. The last material relating to Julian date was not tested.
Overview of the Operation.
The interface consists of three wires; /RST, SCLK and I/O. /RST and SCLK are inputs to the DS1302 (outputs as viewed by the controlling processor) and I/O is bi-directional.
Normally, /RST is low. All communication sequences are intitiated by driving /RST high. A communications sequence is terminated by bringing /RST low. SCLK must be low when /RST is brought to a logic one to initiate a sequence.
Data, including commands is sent to the DS1302 by first bringing I/O to the appropriate state while SCLK is low. The data bit is then clocked into the DS1302 on the rising edge of the SCLK. All data, including commands is sent as a byte, least significant bit first.
Data is read from the DS1302 on lead I/O. Each bit is read on the falling edge of SCLK. Here again, data is read beginning with the least significant bit.
Note that /RST is high throughout the entire interchange.
As with all intelligent serial devices, a data interchange begins with a command byte of the form;
7 6 5 4 3 2 1 0 1 RAM/CLK A4 A3 A2 A1 A0 RD/WRNote that RAM is selected by making bit 6 a 1. A read or write operation is indicated by bit 0 being either a logic 0 or logic 1, respectively.
Thus if one desires to write to RAM location %0 1100, the command byte is;
7 6 5 4 3 2 1 0 1 RAM/CLK A4 A3 A2 A1 A0 RD/WR 1 1 0 1 1 0 0 0Note that a general expression for writing to RAM memory is;
%1100 0000 | (ADR << 1) or $C0 | (ADR << 1)Similarly, a general expression for reading from RAM memory is;
%1100 0001 | (ADR << 1) or $C1 | (ADR << 1)Writing and reading from the CLK is similar, except that bit 6 is a logic zero.
Thus, writing to CLK address %0 0011, which is the date within a month, the command byte is;
7 6 5 4 3 2 1 0 1 RAM/CLK A4 A3 A2 A1 A0 RD/WR 1 0 0 0 0 1 1 0Thus, general expressions for writing and reading to the clock;
$80 | (ADR << 1) ' write to clock $81 | (ADR << 1) ' read from clockBefore, we get anymore confused, lets take a look at a simple routine to write to and read from RAM memory.
Circuit Connection.
In all of the following discussions, connections between the BS2 and the DS1302 are;
BS2 DS1302 P2 (term 7) <-----------10K R---------------> I/O (term 6) P1 (term 6) --------------------------------> SCLK (term 7) P0 (term 5) --------------------------------> /RST (term 5)Writing to and Reading from RAM.
In program PUT_GET, two subroutines; _PUT and _GET are presented. These are quite like the PUT and GET commands associated with the BS2SX.
' Program PUT_GET.BS2
'
' Illustrates how to write to and read from the DS1302 RAM.
'
' copyright, LaVerne Ervin, Baltimore, MD, Dec, '98
VAL VAR BYTE
ADDR VAR BYTE
O_BYTE VAR BYTE
N VAR BYTE
MAIN:
DIRS = $0007 ' /RST, SCLK and I/O are outputs
ADR=3
VAL=125
GOSUB _PUT ' write 125 to RAM address 3
ADR=3 ' read from address 3 and display
GOSUB _GET
DEBUG ? VAL
DONE:
GOTO DONE
_PUT: ' writes VAL to ADR. Uses bytes O_BYTE and N
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
O_BYTE=$C0|(ADR<<1) ; write RAM command
GOSUB OUT_BYTE
O_BYTE = VAL
GOSUB OUT_BYTE
OUT0=0 ' /RST brought low to terminate the sequence
RETURN
_GET: ' reads from ADR into VAL. Uses bytes O_BYTE and N
DIR2=1
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
O_BYTE=$C1|(ADR<<1) ' read RAM command
GOSUB OUT_BYTE
DIR2=0
GOSUB IN_VAL
OUT0=0 ' /RST low to terminate the sequence
RETURN
OUT_BYTE: ' shift out O_BYTE on I/O, least sig bit first
FOR N=0 TO 7
OUT2= O_BYTE.BIT0
GOSUB CLK_PULSE
O_BYTE = O_BYTE >>1
NEXT
RETURN
IN_VAL: ' shift in on I/O, least sig bit first
FOR N=0 TO 7
GOSUB CLK_PULSE
VAL = VAL >>1
VAL.BIT7 =IN2
NEXT
RETURN
CLK_PULSE:
OUT1=1
OUT1=0
Discussion.
In subroutine OUT_BYTE, variable O_BYTE is shifted out to the DS1302, beginning with the least significant bit. Note that OUT2 (I/O) is set to the value of .BIT0 followed by a clock pulse. O_BYTE is then shifted to the right such that the next bit is now in the least significant bit position.
In subroutine IN_VAL, a clock pulse occurs. VAL is shifted to the right and the data bit which is read is placed in the most significant bit position.
These routines might also be implemented using the SHIFTOUT and SHIFTIN commands. I am not a big fan of these commands during the intitial development as I tend to lose sight of exactly what I am doing.
However, I have to admit that once you are beyond the initial development, the SHIFTOUT and SHIFTIN are a whole lot more efficient
OUT_BYTE:
SHIFTOUT 2, 1, LSBFIRST, [O_BYTE\8]
RETURN
IN_VAL:
SHIFTIN 2, 1, LSBPOST, [IN_VAL\8]
RETURN
These later implementations will be used in all subsequent discussions.
In higher level routine, _PUT, the /RST terminal is brought high, the command byte is sent followed by the data byte and /RST is then brought low.
In _GET, the /RST terminal is brought high, the command byte to read is sent, the data is read and the sequence is terminated by bringing /RST low.
Arrays.
' ARRAY0.BS2
'
' Illustrates how to use DS1302 memory to implement an array.
' Array is dummied up. Average, MAX and MIN are calculated and
' displayed.
'
' LaVerne Ervin, Baltimore, MD, Dec, '98
SUM VAR WORD
AVG_10 VAR WORD ' ten times the average
_MAX VAR BYTE
_MIN VAR BYTE
VAL VAR BYTE
ADDR VAR BYTE
O_BYTE VAR BYTE
FOR ADR=0 TO 29
VAL = ADR//3
GOSUB _PUT ' put some numbers into the DS1302
NEXT
' Compute the average
SUM = 0
FOR ADR=0 TO 29
GOSUB _GET
SUM = SUM + VAL
NEXT
AVG_10 = SUM * 10 / 50
DEBUG "AVG = ", DEC AVG_10/10, ".", DEC AVG_10//10, CR
' Now for the MIN
_MIN = 255
FOR ADR=0 TO 29
GOSUB _GET
IF (VAL>=_MIN) THEN SKIP_1
_MIN = VAL ' VAL is less than _MIN
SKIP_1:
NEXT
DEBUG "MIN = ", DEC _MIN, CR
' Now for the MAX
_MAX = 0
FOR ADR=0 TO 29
GOSUB _GET
IF (VAL<=_MAX) THEN SKIP_2
_MAX = VAL ' VAL is greater than MAX
SKIP_2:
NEXT
DEBUG "MAX = ", DEC _MAX, CR
DONE:
GOTO DONE
'''''''''
_PUT: ' writes VAL to ADR. Uses byte O_BYTE
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
O_BYTE=$C0|(ADR<<1) ; write RAM command
GOSUB OUT_BYTE
O_BYTE = VAL
GOSUB OUT_BYTE
OUT0=0 ' /RST brought low to terminate the sequence
RETURN
_GET: ' reads from ADR into VAL. Uses bytes O_BYTE and N
DIR2=1
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
O_BYTE=$C1|(ADR<<1) ' read RAM command
GOSUB OUT_BYTE
DIR2=0
GOSUB IN_VAL
OUT0=0 ' /RST low to terminate the sequence
RETURN
OUT_BYTE:
SHIFTOUT 2, 1, LSBFIRST, [O_BYTE\8]
RETURN
IN_VAL:
SHIFTIN 2, 1, LSBPOST, [VAL\8]
RETURN
RAM Burst Mode.
Note that only RAM locations 0-30 (31 bytes) are available for general purpose RAM.
RAM location 31 is reserved for placing the DS1302 in a burst mode where multiple values are written to or read from the DS1302 in a single data exchange. Thus, $C0 | ($1F << 1) or $FE places the DS1302 in a RAM burst write mode and $C1 | ($1F << 1) or $FF places it in the RAM burst read mode.
' ARRAY1.BS2
'
' Illustrates how to use DS1302 memory to implement an array.
' Array is dummied up. The average is calculated and displayed.
'
' Illustrates the use of RAM burst write and RAM burst read modes.
'
' copyright, LaVerne Ervin, Baltimore, MD, Dec, '98
SUM VAR WORD
AVG_10 VAR WORD ' ten times the average
VAL VAR BYTE
N VAR BYTE
DIRS=$0007
' write 30 values to RAM in burst mode
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
SHIFTOUT 2, 1, LSBFIRST, [$FE\8]
' RAM burst write command byte
FOR N=0 TO 29
VAL = N//3
SHIFTOUT 2, 1, LSBFIRST, [VAL\8] ' send 30 bytes
NEXT
OUT0=0 ' terminate the data sequence
' Compute the average
SUM = 0
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
SHIFTOUT 2, 1, LSBFIRST, [$FE\8] ' RAM burst read
DIR2=0 ' be sure I/O is an input
FOR N=0 TO 29
SHIFTIN 2, 1, LSBPOST, [VAL\8] ' fetch each byte
SUM = SUM + VAL ' and add to the sum
NEXT
OUT0=0 ' terminate the data sequence
AVG_10 = SUM * 10 / 30
DEBUG "AVG = ", DEC AVG_10/10, ".", DEC AVG_10//10, CR
DONE:
GOTO DONE
Intermediate Summary.
Thus far, I have focused on the RAM portion of the DS1302.
Note that very little code is required; bring /RST high, send the command byte, either $C0 | (ADR << 1) for reading or $C1 | (ADR << 1) followed by either writing or reading a byte and then bring /RST low. The burst mode is simply a matter of sending $FE and then sequentially writing byte by byte or, when reading, sending command byte $FF and then sequentially reading byte by byte.
The BS2SX adds 63 bytes of scratchpad RAM which are fetched and stored using the new commands GET and PUT. The DS1302 may be a somewhat less expensive alternative, particularly if you need the real time clock capability.
Real Time Clock.
Operation is similar to that discussed above except that the command byte addresses the clock portion of the DS1302.
7 6 5 4 3 2 1 0 1 RAM/CLK A4 A3 A2 A1 A0 RD/WRNote that bit 6 is set to a zero to address the clock. Thus, the general forms of the command byte are;
$80 | (ADR <<1) ' write to CLK ADR $81 | (ADR <<1) ' read from CLK ADREach address corresponds to an element of the date and time or control as summarized in the following constant table;
SEC CON 0 MIN CON 1 HR CON 2 DATE CON 3 MONTH CON 4 DAY CON 5 YEAR CON 6 CONTROL CON 7 TRICKLE CON 8 BURST CON 9Note that addresses 10-31 are not used.
Thus, to write the "seconds" which might be in Stamp variable VAL
' bring /RST high ADR = SECS O_BYTE = $81 | (ADR << 1) GOSUB OUT_BYTE O_BYTE = VAL GOSUB OUT_BYTE ' bring /RST lowPrior to forging into a simple routine that illustrates how to write the time and then periodically read the time, the are a few bookkeeping matters.
The control byte consists of a single bit, bit 7, which is Write Protect. On powerup, the state of this bit is not defined. Thus, prior to writing a date and time to the DS1302, the normal procedure would be to write a zero to bit 7 of address CONTROL, write to the time and date addresses and then write a logic 1 to the write protect bit.
' CLK_1.BS2 ' ' Initializes DS1302 40th sec, 59th min, 11th hour PM, 31st day, ' 12th month, day 5 (Friday), 99th year. (20 secs short of the ' millenium) ' ' Continually reads clock and displays using the debug command. ' ' copyright, LaVerne Ervin, Baltimore, MD, Dec, '98 C_SEC CON 0 C_MINUTE CON 1 C_HOUR CON 2 C_DATE CON 3 C_MONTH CON 4 C_DAY CON 5 C_YEAR CON 6 CONTROL CON 7 TRICKLE CON 8 BURST CON 31 DATE_TIME DATA $40, $59, $23, $31, $12, $5, $00 ' secs DIRS=$0007 ADR VAR BYTE VAL VAR BYTE DIRS=$0007 ' clear the write protect bit DIR2=1 ' be sure I/O is an output OUT0=0 OUT1=0 OUT0=1 ' bring /RST high while SCLK is low SHIFTOUT 2, 1, LSBFIRST, [$80 | (CONTROL<<1) \8] SHIFTOUT 2, 1, LSBFIRST, [$00\8] OUT0=1 ' bring /RST low to terminate ' Now write the date and time, one byte at a time. FOR ADR=0 TO 6 READ DATE_TIME+ADR, VAL ' fetch value from EEPROM GOSUB _PUT_TIME ' and write it to the clock NEXT ' set the write protect bit DIR2=1 ' be sure I/O is an output OUT0=0 OUT1=0 OUT0=1 ' bring /RST high while SCLK is low SHIFTOUT 2, 1, LSBFIRST, [$80 | (CONTROL<<1) \8] SHIFTOUT 2, 1, LSBFIRST, [$80\8] ' 1 in most sign bit OUT0=1 ' bring /RST low to terminate ' Now, display the date and time in YY/MM/DD HH:MM:SS format DISPLAY_DATE_TIME: ADR = C_YEAR GOSUB _GET_TIME DEBUG HEX2 VAL, "/" ADR = C_MONTH GOSUB _GET_TIME DEBUG HEX2 VAL, "/" ADR = C_DATE GOSUB _GET_TIME DEBUG HEX2 VAL, " " ADR = C_HOUR GOSUB _GET_TIME DEBUG HEX2 VAL, ":" ADR = C_MINUTE GOSUB _GET_TIME DEBUG HEX2 VAL, ":" ADR = C_SEC GOSUB _GET_TIME DEBUG HEX2 VAL, CR PAUSE 1000 ' wait for a second GOTO DISPLAY_DATE_TIME ''''' _PUT_TIME: DIR2=1 ' be sure I/O is an output OUT0=0 OUT1=0 OUT0=1 ' bring /RST high while SCLK is low SHIFTOUT 2, 1, LSBFIRST, [$80 | (ADR<<1)\8] ' command byte SHIFTOUT 2, 1, LSBFIRST, [VAL\8] OUT0=0 ' terminate the sequence RETURN _GET_TIME: DIR2=1 ' be sure I/O is an output OUT0=0 OUT1=0 OUT0=1 ' bring /RST high while SCLK is low SHIFTOUT 2, 1, LSBFIRST, [$81 | (ADR<<1)\8] ' command byte DIR2=0 SHIFTIN 2, 1, LSBPRE, [VAL\8] OUT0=0 RETURNDiscussion.
In the above, the write protect bit is set to zero. The time and date info is sequentially read from the Stamp's EEPROM and written to the DS1302. The write protect bit is then set to a logic one to protect against accidental writes. The date and time are then read every second and displayed using the DEBUG command.
I happened to use a time which is 20 seconds short of the millenium and indeed, it did roll over to the new century. However, there is another distinction between the year 2000 and 1900 in that the year 2000 is a leap year, 1900 wasn't. (A leap year occurs every four years, except at the turn of the century. However, every 400 years, the turn of the century is a leap year. Thus, the Y2K problem is a bit more complex than at first glance.) I tried Feb 28, 00 at 23:59:40 and indeed it rolled over to Feb 29.
Note that the date and time quantities are in binary coded decimal (BCD). That is, 32 seconds is saved in the DS1302 as %0011 0010, not as %0010 0000. Thus, note that the values in EEPROM are saved in hexadecimal format and the results are displayed using the HEX2 modifier.
However, if you plan to be doing calculations, the quantities must be converted from BCD to natural binary. For example, assume the quantities are in variables SECS, MINS, DATE, MONTHS and YEARS. Note that HOURS is discussed later.
SECS = SECS & $7F ' take only the lower 7 bits SECS = SECS.NIB1 * 10 + SECS.NIB0 MINS = MINS & $7F MINS = MINS.NIB1 * 10 + MINS.NIB0 HOURS = HOURS & $3F HOURS = HOURS.NIB1 * 10 + HOURS.NIB0 DATE = DATE & $3F ' take only the lower 6 bits DATE = DATE.NIB1 * 10 + DATE.NIB0 MONTHS = MONTHS & $1F MONTHS = MONTHS.NIB1 * 10 + MONTHS.NIB0 YEARS = YEARS.NIB1 * 10 + YEARS.NIB0The hours is a bit more complex.
When writing to C_HRS, bit 7 indicates whether the DS1302 is to operated in 12-hour (logic 1) or 24 hour (logic zero) mode. In the above program, the number of hours was programmed as $23. Note that bit 7 is a zero, and thus the clock was in a 24 hour mode. Thus, in this case, the number of hours as shown above.
Note that the DS1302 may be placed in a 12-hour mode by setting bit 7 to a 1 and using bit 5 to indicate either AM (logic 1) or PM (logic 0). This discussion does not include any examples of the 12 hour mode.
Program CLK_2.BS2 performs a similar function as CLK_1, except the burst mode is used to write the data and the date and time are read into variables. This enables you to use the DS1302 to time between events or to take an action at a particular time. In addition, the program illustrates how the date may be expressed in the form;
Friday Dec 31, '99, 23:59:40
' CLK_2.BS2
'
' Initializes DS1302 40th sec, 59th min, 11th hour PM, 31st day,
' 12th month, day 5 (Friday), 99th year. (20 secs short of the
' millenium) using the burst mode.
'
' Continually reads clock and displays using the debug command.
'
' Illustrates how to display strings.
'
' Note that date and time is saved in variables.
'
' copyright, Peter H. Anderson, Baltimore, MD, Dec, '98
C_SEC CON 0
C_MINUTE CON 1
C_HOUR CON 2
C_DATE CON 3
C_MONTH CON 4
C_DAY CON 5
C_YEAR CON 6
CONTROL CON 7
TRICKLE CON 8
BURST CON 31
DATE_TIME DATA $40, $59, $23, $31, $12, $5, $00
JAN DATA "Jan", 0
FEB DATA "Feb", 0
MAR DATA "Mar", 0
APR DATA "Apr", 0
MAY DATA "May", 0
JUN DATA "Jun", 0
JUL DATA "Jul", 0
AUG DATA "Aug", 0
SEP DATA "Sep", 0
OCT DATA "Oct", 0
NOV DATA "Nov", 0
DEC DATA "Dec", 0
SUN DATA "Sunday", 0
MON DATA "Monday", 0
TUE DATA "Tuesday", 0
WED DATA "Wednesday", 0
THU DATA "Thursday", 0
FRI DATA "Friday", 0
SAT DATA "Saturday", 0
' the following is a table of the days of the week
WEEK_DAYS DATA WORD SUN, WORD MON, WORD TUE, WORD WED
DATA WORD THU, WORD FRI, WORD SAT
DIRS=$0007
ADR VAR BYTE
VAL VAR BYTE
YEARS VAR BYTE
MONTHS VAR BYTE
MO_DAYS VAR BYTE
HOURS VAR BYTE
MINS VAR BYTE
SECS VAR BYTE
WK_DAY VAR BYTE
PTR VAR WORD
DIRS=$0007
' clear the write protect bit
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
SHIFTOUT 2, 1, LSBFIRST, [$80 | (CONTROL<<1) \8]
SHIFTOUT 2, 1, LSBFIRST, [$00\8]
OUT0=1 ' bring /RST low to terminate
' Now write the date and time, in the burst mode.
' Note that eight bytes are written
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
SHIFTOUT 2, 1, LSBFIRST, [$80 | (BURST<<1) \8]
FOR ADR=0 TO 6
READ DATE_TIME+ADR, VAL ' fetch value from EEPROM
SHIFTOUT 2, 1, LSBFIRST, [VAL\0] ' and write it to the clock
NEXT
' set the write protect bit
SHIFTOUT 2, 1, LSBFIRST, [$80\8] ' 1 in most sign bit
OUT0=1 ' bring /RST low to terminate
' Now, fetch the date and time
GET_DATE_TIME:
ADR = C_YEAR
GOSUB _GET_TIME
YEARS = VAL.NIB1*10 + VAL.NIB0 ' convert BCD to natural binary
ADR = C_MONTH
GOSUB _GET_TIME
VAL = VAL & $1F
MONTHS = VAL.NIB1*10 + VAL.NIB0
ADR = C_DATE
GOSUB _GET_TIME
VAL = VAL & $3F
MO_DAYS = VAL.NIB1*10 + VAL.NIB0
ADR = C_HOUR
GOSUB _GET_TIME
VAL = VAL & 3F
HOURS = VAL.NIB1*10 + VAL.NIB0
ADR = C_MINUTE
GOSUB _GET_TIME
VAL = VAL & $7F
MINS = VAL.NIB1*10 + VAL.NIB0
ADR = C_SEC
GOSUB _GET_TIME
VAL = VAL & $7F
SECS = VAL.NIB1*10 + VAL.NIB0
ADR = C_DAY ' day of the week
GOSUB _GET_TIME
WK_DAY = VAL & $07
DISPLAY:
' Now display the day of the week
' map the week day into a pointer that points to beginning of desired
' string
READ WEEK_DAYS + (2*WK_DAY), PTR.BYTE0
READ WEEK_DAYS + (2*WK_DAY)+1, PTR.BYTE1
GOSUB DISPLAY_STR
DEBUG CR
' Now, the month
PTR = JAN + 4*(MONTHS-1)
GOSUB DISPLAY_STR
' Now the date and time
DEBUG " ", DEC MO_DAYS, ", '", DEC2 YEARS, " "
DEBUG DEC2 HOURS, ":", DEC2 MINS, ":", DEC2 SECS, CR
PAUSE 1000
GOTO GET_DATE_TIME ' continual loop
DISPLAY_STR:
READ PTR, X
IF X=0 THEN STR_OUT_DONE
DEBUG X
P=PTR+1
GOTO DISPLAY_STR
RETURN
_GET_TIME:
DIR2=1 ' be sure I/O is an output
OUT0=0
OUT1=0
OUT0=1 ' bring /RST high while SCLK is low
SHIFTOUT 2, 1, LSBFIRST, [$81 | (ADR<<1)\8] ' command byte
DIR2=0
SHIFTIN 2, 1, LSBPRE, [VAL\8]
OUT0=0
RETURN
Discussion.
In CLK2.BS2, the "write protect" bit on the Control Address is first set to zero.
The "burst" mode is then used to transmit all eight bytes of data are transmitted in a single data exchange. Note that the the command byte, $80 | ($1F) is followed by eight bytes for locations 0 through 7. Addresses 0 through 6 correspond to the time and date and finally $80 is written to the Control register (address 7) to again place the clock in the write protect mode.
Note that with the RAM burst, the sending of consecutive bytes may be interrupted at any time. That is, not all 31 bytes need be transferred.
However, with the CLK burst, all eight bytes (0 - 7) must be transmitted. Note that this includes the Control register.
In CLK2.BS2, the time and date are periodically read and displayed.
The day of the week is first read. This is then mapped into an PTR address of the beginning of the associated string as follows;
READ WEEK_DAYS + (2*(WK_DAY-1)), PTR.BYTE0 READ WEEK_DAYS + (2*(WK_DAY-1)+1, PTR.BYTE1For example, if the week day is Sunday, WK_DAY is 1. Thus, the pointer PTR is read from WEEK_DAYS + 0 (low byte) and WEEK_DAYS + 1 (high byte).
Computing the Julian Date.
Note that the following material was developed some time ago and unfortunately I put it away in favor of something more interesting. Thus, this has not been tested nor is it complete. I hesitate to simply delete it as it may be of use to someone.
The Julian Date is simply the day of the year. Thus, Jan 31 is day 31. Feb 28 is day 59. If the year is not a leap year, Dec 31 is day 365.
The importance of the Julian date is that it enables one to calculate the number of seconds between two times. For example, it is mighty hard to calculate the number of days between Mar 1 and Nov 25, but mighty simple to calculate the number of days between Julian day 61 and 325. (Note that I don't really know if Mar 1 and Nov 25 days 61 and 325.)
In the following code fragment, an array of the days in each month is implemented in EEPROM. Note that there are 13 entries, with element 0 being any value. That is, it really isn't used. Thus, the number of days in month N month may be read into TMP;
READ DAYS_IN_MONTH+N, TMPLets avoid the leap year problem for the moment. Assume the date is Jan 7 (MONTH=1, DAY=7). You really don't care how many days there are in January. The Julian date is simply the DAY. However, if the day is March 7, the Julian date is the number of days in Jan (310) plus the number of days in Feb (28), plus DAY.
Thus, in the following, the Julian date is initialized to DAY. If, the MONTH is 1 (Jan), we are done. Otherwise, we must also add the number of days in each month up through MONTHS-1. That is, in the case of March 7, we have 7 + 31 + 28.
DAYS_IN_MONTH DATA 00, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
' 1 2 3 4 5 6 7 8 9 10 11 12
JULIAN_DATE = DAY
IF MONTH=1 THEN JULIAN_DONE
FOR N=1 TO MONTH-1
READ DAYS_IN_MONTH+N, TMP
JULIAN_DATE = JULIAN_DATE + TMP
NEXT
But, we do have the added confusion of the leap year and it is a bit more complex than many people realize. A leap year occurs every four years, but there is no leap year at the turn of the century, but every 400 years, there is. Thus, the year 2000 is a leap year and a leap year occurs every four years thereafter. The year 2100 will not be a leap year, but I doubt any reader will have to reckon with this. Thus, for now until 2099, a leap year occurs every four years.
Clearly, if the MONTH is less than 3 (March), we need not be concerned with whether it is a leap year or not and we are done. Otherwise, if the year is not equally divisible by 4, we are done. But if MONTH >= 3 and it is a leap year, we add 1 to calculate the Julian date.
IF (MONTH < 3) THEN JULIAN_DONE
IF (YEAR//4 !=0) THEN JULIAN_DONE ' not a leap year
JULIAN = JULIAN+1
JULIAN_DONE:
By knowing the Julian date, we can calculate the number of seconds that have elapsed since the beginning of the year. Note that the following statements involve 32 bit arithmetic and will not work on a BS2 as they are written.
ELAPSED_SECS = JULIAN_DATE * (24 * 3600) + (HOURS * 3600) + (MINS * 60) + SECSThere is one problem here in that 24 * 3600 = 86,400 which is too large to be accommodated in a word. However, we can multiply the Julian date by 2;
ELAPSED_SECS = (JULIAN_DATE * 2) * (12 * 3600) + (HOURS * 3600) + (MINS * 60) + SECSNote that ELAPSED_SECS is a 32-bit quantity which cannot be directly handled as it is written above. Rather, we must define two 16-bit words.
ES1_H = ((JULIAN_DATE * 2) ** (12*3600))
+ (HOURS ** 3600) ' calculate the high word
Note that the operator ** returns the high 16 bits of the operation.
Now, for the lower 16 bits;
ES1_L = (MINS * 60) + SECS TMP = ES1_L + (HOURS * 3600) IF (ES1_L >= TMP) THEN EL_TIME_1 ' no carry occurred ES1_H = ES1_H + 1 EL_TIME_1: ES1_L = TMP TMP = ES1_L + (JULIAN_DATE * 2) * (12*3600) IF (ES1_L >= TMP) THEN EL_TIME_2 ' no carry occurred ES1_H = ES1_H + 1 EL_TIME_2: ES1_L = TMPNote that in calculating the lower 16 bits, a summing of four terms is required. Clearly, (MINS * 60) + SECS will not cause an overflow. However, in summing the other two terms, a check is required to ascertain if an overflow occurred and if so, ES1_H is incremented.
Note that in the above, ES1_H and ES1_L uniquely identify what might be termed the current "Julian second". That is, the number of seconds since the beginning of the year. This might be useful in causing a periodic event to happen or in efficiently logging time data to EEPROM. That is, log the elapsed time since the beginning of the year and then after dumping the data to a PC convert this back to a date and time.