Attempting To Build A SimpleIDE Library For The DS1302 RTC - Could Use A Little Help :)

idbruceidbruce Posts: 5,903
edited 2020-03-08 - 08:31:51 in Propeller 1
I recently purchased a DS1302 module from Parallax for my current project. Of course I am a C and C++ guy, and the documentation for this module and language is somewhat lacking my needs, so I decided to write my own library. I initially started the groundwork for this library, loosely based upon a post by Rsadeika, which can be found here: https://forums.parallax.com/discussion/168949/ds1302-module-c-code

His or her code has resulted in the following:

Hearder file (.h):
#include "simpletools.h"
#include <time.h> //Included for the get_unix_time function

//DS1302 Pin Usage
#define DS1302_CS 1
#define DS1302_SCK 2
#define DS1302_IO 3

//DS1302 Time Registers
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84

//DS1302 Date Registers
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_YEAR 0x8C

//DS1302 Day Register
#define DS1302_DAY 0x8A

//Read an individual DS1302 register value
int read_ds1302(int ds1302_register);

//Write an individual DS1302 register value
void write_ds1302(int ds1302_register, int ds1302_value);

//Set the DS1302 Hour register value
void set_ds1302_hour(int hour_register_value);

//Set the DS1302 Minute register value
void set_ds1302_minute(int minute_register_value);

//Set the DS1302 Second register value
void set_ds1302_second(int second_register_value);

//Set the DS1302 Date register value
void set_ds1302_date(int date_register_value);

//Set the DS1302 Month register value
void set_ds1302_month(int month_register_value);

//Set the DS1302 Year register value
void set_ds1302_year(int year_register_value);

//Set the DS1302 Day register value
void set_ds1302_day(int day_register_value);

//Get the DS1302 Hour register value
int get_ds1302_hour();

//Get the DS1302 Minute register value
int get_ds1302_minute();

//Get the DS1302 Second register value
int get_ds1302_second();

//Get the DS1302 Date register value
int get_ds1302_date();

//Get the DS1302 Month register value
int get_ds1302_month();

//Get the DS1302 Year register value
int get_ds1302_year();

//Get the DS1302 Day register value
int get_ds1302_day();

//Get Unix time - The header file "time.h" has been included
//specifically for this function
//
//Please note that in order to obtain fairly accurate
//Unix time, the DS1302 registers must be set according
//Coordinated Universal Time (or UTC), otherwise programming
//must compensate for time zone differences.  For further
//information, please research Unix Time
int get_unix_time();

And the C file (.c)
#include "simpletools.h"

int read_ds1302(int ds1302_register)
{
  int read_register_value;
  int result;
  
  read_register_value = ds1302_register + 1;
  
  high(DS1302_CS);
  shift_out(DS1302_IO, DS1302_SCK, LSBFIRST, 8, read_register_value);
  
  result = shift_in(DS1302_IO, DS1302_SCK, LSBPRE, 8);
  
  low(DS1302_CS);
  
  return result;
}

void write_ds1302(int ds1302_register, int ds1302_value)
{
  high(DS1302_CS);
  shift_out(DS1302_IO, DS1302_SCK, LSBFIRST, 8, ds1302_register);
  shift_out(DS1302_IO, DS1302_SCK, LSBFIRST, 8, ds1302_value);
  low(DS1302_CS);
}

void set_ds1302_hour(int hour_register_value)
{
  write_ds1302(DS1302_HOUR, hour_register_value);
}

void set_ds1302_minute(int minute_register_value)
{
  write_ds1302(DS1302_MINUTE, minute_register_value);
}

void set_ds1302_second(int second_register_value)
{
  write_ds1302(DS1302_SECOND, second_register_value);
}

void set_ds1302_date(int date_register_value)
{
  write_ds1302(DS1302_DATE, date_register_value);
}

void set_ds1302_month(int month_register_value)
{
  write_ds1302(DS1302_MONTH, month_register_value);
}

void set_ds1302_year(int year_register_value)
{
  write_ds1302(DS1302_YEAR, year_register_value);
}

void set_ds1302_day(int day_register_value)
{
  write_ds1302(DS1302_DAY, day_register_value);
}  

int get_ds1302_hour()
{
  return read_ds1302(DS1302_HOUR);
}

int get_ds1302_minute()
{
  return read_ds1302(DS1302_MINUTE);
}

int get_ds1302_second()
{
  return read_ds1302(DS1302_SECOND);
}  

int get_ds1302_date()
{
  return read_ds1302(DS1302_DATE);
}  

int get_ds1302_month()
{
  return read_ds1302(DS1302_MONTH);
}  

int get_ds1302_year()
{
  return read_ds1302(DS1302_YEAR);
}

int get_ds1302_day()
{
  return read_ds1302(DS1302_DAY);
}

long get_unix_time()
{
  struct tm t;
  time_t unix_time;

  t.tm_year = get_ds1302_year() - 1900;
  t.tm_mon = get_ds1302_month();
  t.tm_mday = get_ds1302_day();
  t.tm_hour = 16;
  t.tm_min = get_ds1302_minute();
  t.tm_sec = get_ds1302_second();
  
  //Is Daylight Saving Time in effect? -1 = Unknown, 0 = No, 1 = Yes
  t.tm_isdst = -1;
  
  unix_time = mktime(&t);
  
  return (long)unix_time;
}  

Please note that none of this code has been tested yet :)

I am currently working on the get_unix_time function. As the self describing function clearly states, I am attempting to obtain Unix time. In order to obtain a fairly accurate Unix time (meanwhile ensuring that this library is useful to others), I must determine whether the RTC is in 12 or 24 hour mode. There is other significant code and efforts that are associated with the DS1302 RTC chip, by the greatly missed Chris Savage, but they are for the BASIC Stamp, which can be downloaded from here (https://parallax.com/downloads/ds1302-basic-stamp-demo-code.

A little help writing this last function would surely be appreciated :)

Comments

  • idbruceidbruce Posts: 5,903
    edited 2020-03-08 - 08:50:45
    In addition to my previous post, I will be adding one or two other functions, which will be create_timestamp_1 and create_timestamp_2. One or both of these functions, will create strings, for naming files and/or folders :)
  • Here is my library that I created some time ago. It could use better documentation but this is how I create all my libraries for use.

    Mike
  • Mike

    As far as I can tell, your code does not handle the 12 or 24 hour mode, which is the reason for my post. :)
  • We could also use this library in C to add the module to BlocklyProp when it's complete. It's a need from many of our customers.

    Ken Gracey
  • Ken
    We could also use this library in C to add the module to BlocklyProp when it's complete.

    My goal was and still is to write a simple, easy to understand library, which most users could easily modify to suit their needs, and post it here, with appropriate licensing.

    Being "complete" is quite a different issue :) and would require a few more functions to read and write the Burst Mode, Trickle Charge, and Clock Halt registers, and I am sure there is more.

    I am more of a PC programmer than a microcontroller programmer, so we would need a bit banger or two to jump in here.

    However, you are certainly welcome to the end result whatever it may become, "complete" or incomplete :)
  • In case you don't have it, I've attached the DS1302 datasheet which is located at:
    www.maximintegrated.com/en/products/analog/real-time-clocks/DS1302.html
    https://datasheets.maximintegrated.com/en/ds/DS1302.pdf

    Table 3 shows the layout of the registers. The values are in BCD format.

    I have some SPIN code I wrote for a larger project if your interested. It reads and writes most the clock registers. Has a simple terminal interface for setting the date/time interactively and a somewhat unique method for displaying the data/time string.

    It's still a work in progress.
    547 x 648 - 109K
  • Yes, only handles 12 hour mode. Didn't see a need for 24 hour. Actually just use it to set the internal clock of the propeller so the time format is irrelevant. C handles all the time formats including time zone.

    I looked at the burst mode and memory functions that seem nice but offer very little to the product. It's not like it's an IMU and we need to constantly read the values in a timely manner.

    Mike
  • An aside: the DS1302 is a useful chip, but its accuracy suffers. You might look at the DS3231 which is almost completely register compatible with the DS1302 but is a super accurate timekeeper (a couple of seconds per month usually). It also has an internal temp sensor that you can read (it maps into the register space) and I find that data comes in very handy.
  • JRoark

    Thanks for the heads up on the accuracy. I just purchased the DS1302 module from Parallax, so I think I will work on the code and experiment with that a little before swapping it out.

    Ken

    I have been looking very closely at Chris Savage's example code for the DS1302 and I now believe that it should be very easy to port it to the Propeller, in fact I have just started to swap the code. The main difference between his code and what I was attempting is that he used the burst mode for both his read from and writes to the DS1302, which should be perfectly fine for your customers.
  • My experience with CM2302, as JRoark has noted, initial accuracy suffers. I tested the unit outside, and a deep freeze in air temperature made the unit unstable, if not unusable.

    For inside a home, the unit is relatively reliable, after you do the initial recalibration via the program code. For your general testing and code development, it should suffice.

    Ray
  • Note that the standard C library already contains functions like localtime() and gmtime() to convert a struct tm structure to/from a unix time_t value. Filling in the struct tm should be very straightforward. I don't have access to man pages right now but Google should be able to point you in the right direction.
  • The library functions I attached has the code to do that built in. DS1302_setDateTime()

    Also my logging project shows how it can be used.

    Mike
  • Now that you guys mention it, I am certain that it can all be done programmatically with "time.h". However, it still would be nice to be able to set and get the 12 and 24 hour modes for this RTC, which I am sure would also be useful for other RTCs.
  • I have updated the documentation and added the 12 hour functions and also added a method to write and read a message from RAM.

    Mike
  • Hi Bruce,

    Not sure if there are any revelations in the following but I just stumbled across it.
    The code examples are in line-numbered BASIC but easy enough to follow and I'm sure easily downgraded converted to c.
    To set the time and data you first need to set the DS to write enable. Doing this the first time also got it's ticker going also.

    To write a value first look up the address in table 3 (in DS1302 pdf) to write to and work out the 8 bits to send. Data is in 2 x 4bit nibbles.

    So to set the write protect the address is 8Eh or 1000 1110 and the data is 0000 0000 to be able to write and 1000 0000 to write protect

    To set the seconds the address is 80h or 1000 0000 The data, for example, could be 15 or 0001 0101. 2 nibbles of 1 and 5

    Use 24 hour time on the DS1302
    write to 84h. Bit seven must be 1 so write 1xxx xxxx

    The year data is 11 or 0001 0001. When setting the time on the maximite the program converts it to 2011.

    So in my program when it asks for the write address put in the 8 bits without the space and then the same with the data.

    I've put in some more comments in the program so I hope you can follow it. I trust you can also convert hex to bin. The addresss are hex to bin and the data is dec to bin.

    rtc.bas is the read and write program. My autorun has the read portion of the code to set the time and date at startup.

    From a hardware point of view, there are the 3 data lines ce, clk and di/dout. All must have pull up resistors to 5v on them. I think I used 2.2k resistors.
    6 ' Interface to a DS1302 RTC
    10 ce=20 ' clock enable pin
    12 clk=19 ' clock pin
    14 din = 18 ' read pin
    16 dout = 18 ' write pin. normally is same as din
    18 SETPIN ce,9
    20 SETPIN clk,9
    22 ' SETPIN din,2 'only required for different din/dout
    24 SETPIN dout,9
    26 PIN(ce)=0
    28 PIN(clk)=0
    30 PIN(dout)=0
    32 INPUT "(r)ead and set time/date (rb)read byte (w)rite (e)nd";inp$
    34 IF inp$="r" THEN GOSUB 200
    36 IF inp$="w" THEN GOSUB 2000
    38 IF inp$="rb" THEN GOSUB 400
    40 IF inp$="e" THEN END
    42 GOTO 32
    44 '
    200 ' ----- read -------
    202 PRINT "old time ";TIME$
    205 bitdata$="10000101":GOSUB 250: hr$=hi$+lo$
    210 bitdata$="10000011":GOSUB 250: min$=hi$+lo$
    215 bitdata$="10000001":GOSUB 250: sec$=hi$+lo$
    220 TIME$ = hr$+":"+min$+":"+sec$ : PRINT "new time ";TIME$
    225 bitdata$="10000111":GOSUB 250: dat$=hi$+lo$
    230 bitdata$="10001001":GOSUB 250: mnt$=hi$+lo$
    235 bitdata$="10001101":GOSUB 250: year$="20"+hi$+lo$
    240 PRINT dat$;"/";mnt$;"/";year$ : DATE$=dat$+"/"+mnt$+"/"+year$
    245 RETURN
    249 '
    250 'read byte required and process
    251 'data is in 2 x 4 bit nibbles
    254 dataout$ = ""
    255 GOSUB 1000
    260 FOR p = 1 TO 8 STEP 4 'this sets the first bit to read. 1 or 5
    265 REM PRINT MID$(dataout$,p,4);
    270 bitval = VAL(MID$(dataout$,p,1))
    275 bitval=bitval + 2 * (VAL(MID$(dataout$,p+1,1)))
    280 bitval=bitval + 4 * (VAL(MID$(dataout$,p+2,1)))
    285 bitval=bitval + 8 * (VAL(MID$(dataout$,p+3,1)))
    290 REM PRINT bitval;"   ";
    295 IF p=1 THEN lo$=STR$(bitval) ELSE hi$=STR$(bitval)
    300 bitval = 0
    305 NEXT p
    310 RETURN
    400 ' ----- read single byte only ----
    405 INPUT "read byte";bitdata$
    410 GOSUB 250
    415 PRINT hi$;lo$;"  ";
    420 FOR i = 8 TO 1 STEP -1
    425 PRINT MID$(dataout$,i,1);
    430 NEXT i
    435 PRINT
    440 RETURN
    445 END
    1000 ' ------ read from RTC --------
    1005 PIN(ce)=1
    1009 'step 1. write address to read
    1010 FOR i = 8 TO 1 STEP -1
    1015 bit =VAL(MID$(bitdata$,i,1))
    1020 PIN(dout)=bit
    1025 PIN(clk)=1
    1030 IF i>1 THEN PIN(clk)=0 'must leave clk high on transistion from write to r
    1035 NEXT i
    1040 PIN(dout)=1 'must leave dout high to be able to read din
    1044 'step 2. read data coming back
    1045 FOR i = 1 TO 8
    1050 PIN(clk)=0 ' read is on the fall of the clock
    1055 dataout$=dataout$+STR$(PIN(din))
    1060 PIN(clk)=1
    1065 NEXT i
    1070 PIN(ce)=0
    1075 PIN(dout)=0
    1080 PIN(clk)=0
    1085 RETURN
    1090 REM
    2000 REM ------ write to RTC --------
    2005 PIN(ce)=1
    2010 PIN(clk)=0
    2015 INPUT "write address";bitdata$
    2020 INPUT "data";wbitdata$
    2025 FOR c = 1 TO 2 '2 bytes to write
    2030 FOR i = 8 TO 1 STEP -1
    2035 bit=VAL(MID$(bitdata$,i,1))
    2040 PIN(dout)=bit
    2045 PIN(clk)=1
    2050 PIN(clk)=0
    2055 NEXT i
    2060 bitdata$=wbitdata$
    2065 NEXT c
    2070 PAUSE 10
    2075 PIN(ce)=0
    2080 PIN(dout)=0
    2085 PIN(clk)=0
    2090 RETURN
    
    
  • idbruceidbruce Posts: 5,903
    edited 2020-03-16 - 02:19:11
    Mickster

    At this point, I have a fairly decent grasp of what is required, and I have written four functions, burst write, burst read, individual value write, and individual value read. The functions are currently working, except for having the data in the proper format. I know this is true, by testing with other firmware.

    I am currently sidetracked from setting the proper format because I recently learned of the compiler's preprocessor macros for _DATE_ and _TIME_. I want to use these values to set the RTC, so that people will not have to code it manually. In other words, just compile the program, run the program, and the RTC will be set according to the PCs clock. However, before compiling and running the program, the user will have constants that can be modified to meet their particular requirements. Additionally, I am sure there will be a need for a time offset to adjust for the difference between compile and runtime, and this will also be a constant that can be modified. That is providing I find a way to copy these values :neutral: If I use these values outside of a "print" statement, the compiler complains that they are not defined. So I am currently looking into printing them to the terminal and then reading them back. However, it may be possible some other way with a proper include or something.

    Anyhow, as always, I do appreciate your input.

    EDIT: This works and the compiler does not complain :smile: I thought of it earlier, but did not try it because I got sleepy.
      char strDate[12];
      char strTime[9];
      
      strcpy(strDate, __DATE__);
      strcpy(strTime, __TIME__);
      printf("%s\n", strDate);
      printf("%s\n", strTime);
    
  • These compiler macros give the date and time of compilation not the current time.
  • rbehm
    These compiler macros give the date and time of compilation not the current time.
    Additionally, I am sure there will be a need for a time offset to adjust for the difference between compile and runtime, and this will also be a constant that can be modified.

    I believe my previous post indicated this fact. At this point, my "time offset" would need to be about 2~3 seconds, but that will change.
Sign In or Register to comment.