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=0Discussion.
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] RETURNThese 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] RETURNRAM 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 DONEIntermediate 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 RETURNDiscussion.
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 NEXTBut, 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 wordNote 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.