[net.micro.atari16] System+ikbd time, battery backup etc. LONG

mugc@utecfa.UUCP (ModemUserGroupChairman) (08/20/86)

	I have been hacking away at trying to discover the workings
keyboard and system clocks since I was interested in battery-backing
the keyboard and having it maintain my time. Even though most of the
facts in this article may not be new, I thought I would summarize
what I have discovered since others seem to be interested in doing this
as well.

	The clocks:

		There are two clocks in the ST. One of them is the
intelligent keyboard (ikbd) clock which is maintained in hardware by
the Hitachi HD6301 microcomputer which is the keyboard chip. The other
is the system clock which, I believe, is maintained in RAM by the
operating system. The ikbd clock can be read by the _xbios_ call
Gettime(). This function returns both the date and the time as a
single longword result. The time and the date are packed in the low
and high words respectively in the format described below. The system
clock (the one maintained by TOS) can be read by the _gemdos_ calls
Tgettime() and Tgetdate(). Each of these calls return a word (16 bits)
and each word is packed in the same format as for Gettime() which is
described below:

	TIME
bits    0-4 (inclusive) :	seconds divided by 2
,,	5-8 (  , ,    ) :	minutes
,,      9-15(  , ,    ) :	hours

	DATE
bits    0-4 (inclusive) :	day
,,      5-8 (  , ,    ) :	month
,,      9-15(  , ,    ) :	year  offset from 1980

	Since the longword result from Gettime() contains both
time and the date, the longword can be broken into 2 16-bit words
and analysed as above.

	For example, the word (in binary)
	  %0000110100001110
	  represents August 14, 1986

	and
	  %0101000001110100
	  represents 10:03:40

	  (Note the date is offset from 1980 and the seconds are
	  divided by two)


	The system and keyboard clocks can be set by the corresponding
functions Tsetdate() and Tsettime() for the system, and Settime() for
the ikbd clock. The arguments must be passed with the date and time
compressed as described above.

	Since the clocks run quite independently of each other,
they do not have to be in sync, and unless are made so, are
guaranteed to by out of sync. In fact, this feature could be used to maintain
dual-time :-). The system time, by virtue of the fact that it
sits in RAM and is maintained by TOS, is not maintained across resets
(because my version of TOS clobbers it and sets it to some magic date in
1985) and cannot be maintained on power-down. The intelligent keyboard
contains its own on-chip RAM, which is not erased when the system is
reset.  By keeping the keyboard powered up even when the system is
down, it is possible to keep the time and date alive in the machine.
Since many programs read the system clock for their time and date
information, BOTH clocks must be maintained on time. However, because my
system time is clobbered across resets, I have a small startup program
(posted after this article) that checks to see if the keyboard
time is valid (if the date <> 1980 and month <> 0 it is considered
to be valid) and if it is, it copies it into the system time. If it
is not valid, it will prompt the user to enter the time and date. Calling
the program with a -f argument will force it to ask you for the
date and time and it will set both clocks with this date and time.

	Finally some comments:

	No, the HD6301 has not been through any revisions in
	the recent past.

	However, Atari may have had the software modified 
	in the on-chip ROM, or perhaps the earlier versions
	were shipped with EPROM-med software. (Comments Neil?)

	If after a cold-start your keyboard clock reads
	00:00:00 00/00/80 or some date like this, it will NOT
	TICK (mine doesn't). To get it to start ticking, put a valid
	time in it (run the program given below). Your chip may
	be different of course, but my computer is a VERY early
	machine so it should have a very early chip!


Hardware:

	The following little modification can be used to battery
backup the keyboard clock. Bear in mind though that this mod will
void your warranty and probably will require you to put the Nicad
rechargeable cells in some very cramped quarters in the ST case.


	****
	Note, in a subsequent posting, I have sent the same circuit
	diagram as a squeezed an uuencoded Degas picture which is
	far more legible than the one below.
   
   
     ^ Vcc from main unit (5 +/- 0.1 V)
     |
     +------+--[>|-----+----------->--------> to Vcc of 6301
            |     D1   |                                   to sys  ^
            _          |                      to ikbd         reset|
            V D3       |                     reset^          +-|<]-+
            -          |                          |          |
            |          |                          |          |
       +----+--[>|-----+----/\/\/\----------------+----------+----+
 +   __|__        D2           4Kohm                         |    |
       _                                            0.1uF  -----  |
 Vb  _____                                                 -----  |
       _     Vb approx= 4.5V under load (3 NiCad cells)      | S1|0
     _____                                                   |   |0
 -     _                                                     |    |
       |                                                     |    |
       L_ _ _GND (common with main unit)---------------------+----+
   
   
     Diodes D1 and D2 are germanium diodes, while D3 is a 1N914 
switching diode. I'm not sure if 1N914s can be used throughout, but 
going by the Hitachi specs I have here, it can be flakey!
   
     The current drawn from the Ni-Cad cells is not too much 
because the HD6301 has a maximum operating current of 10mA, and 
together with the little LED provided and some of the other 
discretes, the current consumption goes up to about 15mA (guess) 
which will still give you a battery life of 3*450/15 = 90 hours (my 
el-cheapo Radio Shack AA nicads are rated at 450mAh).

     
     Theory of operation: When the main unit is powered up, Diode D3  
provides current to the Nicad cells to keep them charged, and D1 
provides power to the keyboard, crystal-oscillator, LED and other 
things. Because the Ge diodes have a small Vd, diode D2 will be 
nearly reverse-biased and hence the Nicads will not source any 
current. When the main unit is powered off, diodes D1 and D3 will 
prevent the Ni-cads from driving the main-unit, while D2 will 
provide the current to the keyboard, clock and LED.
   

      To do the mod, get hold of one male and one female header
plug, some wire-wrap wire, the diodes, the resistor and capacitor
shown above, an SPST switch to make an secondary reset button, and
a battery holder for the Ni-cads. Make direct connections from
the male to the female header plug for all except the following pins:

	13	(Vcc line from main-unit)
	16	(reset line)
	 1	(GND)

	 (count pins such that the key (missing pin) is pin-2)

   
   
	---




	The following is a little program that you can stick in
your auto folder and forget:

------------SNIP-------CUT------------in your mother tongue now------


/* This program was written by: Bob Ritter - January 18, 1986      */
/*    Link with: gemstart,osbind,gemlib,libf  --- in that order.   */
/*       Put on startup disk in the folder named "auto".           */

/*
 *	clock.c version 1.0 August 18, 1986 Anees Munshi
 *
 *	Original program was modified to
 *	1. set ikbd time.
 *	2. copy ikbd time into system time if system time is valid.
 *	3. accept command line argument to force time set.
 *
 *	Also, generally cleaned up and linted the code.
 */

#include <stdio.h>
#include <osbind.h>

#define TRUE 	1
#define FALSE	0

/* some character constants */
#define BS 	(char) (8)
#define LF	(char) (10)
#define CR	(char) (13)
#define SP 	(char) (32)
#define DEL	(char) (127)


/* these definitions are not really required, but just in case  */
/* your osbind.h is damaged ...					*/
#define settime(x) xbios(0x16,(long)x)
#define gettime()  (long)xbios(0x17)


/* this could be done by massaging Cconrs() */
read_line (buffer)
char buffer[];
{
	char c;
	int i = 0;
	do
	    {
		c = Cconin();
		if ( (c != BS) && (c != DEL) )  /* if not oops */
		{
			buffer[i] = c;
			++i;
		}
		else /* erase a mistake */
		{
			--i;
			Cconout(SP);  /* blank out previous character */
			Cconout(BS);   /* backspace */
		}
	}
	while ( (c != CR) && (c != LF) );
	buffer[i-1] = (char)0; /* throw in the null-termination */
	
	printf("\n"); /* echo an LF */

}



/* for those systems which lack a decent atoi() */
int stoi(string)
char string[];
{
	int i, int_val, result=0;
	for (i=0; string[i] >= '0' && string[i] <= '9'; ++i){
		int_val = string[i] - '0';
		result = result * 10 + int_val;
	}
	return(result);
}




main(argc, argv)
int argc;
char *argv[];
{
	int x;
	int i, j, k;
	char  cc[20], xx[20];
	unsigned hrs, mins, secs, yy, mm, dd;
	unsigned sys_date, sys_time;		/* date and time words */
	unsigned long date_time;		/* date and time longword */
	int counter;
	int valid = TRUE;	/* time and date in keyboard default to TRUE */

	if (argc > 2) {
		fprintf(stderr, "Clock: usage: No arguments, normal use.\n");
		fprintf(stderr, "              `-f' to force set time.\n");
		exit(0);
	}
	if (argc == 2) valid = FALSE;	/* force set time, not -f, I know */

	Cconws("\033E\033e");	/* clear screen, turn on the cursor */
	sys_time = (unsigned)gettime();		/* read ikbd clock, set time */
	sys_date = (unsigned)(gettime() >> 16); /* set date variable */
	yy = (sys_date >> 9 & 0x7F) + 80;	/* calculate yr (in 2 dig.) */
	mm = sys_date >> 5 & 0xF;		/* calculate month */
	dd = sys_date & 0x1F;			/* calculate day */
	hrs = sys_time >> 11 & 0x1F;		/* calculate hour */
	mins = sys_time >> 5 & 0x3F;		/* calculate minute */
	secs = (sys_time & 0x1F) << 1;		/* calculate seconds */

	/* decide if date is valid based on month and year entry */
	printf("The current date is: ");
	if (mm == 0 || dd == 0 || yy == 80){
		printf("INVALID\n\n\n");
		valid = FALSE;
	}
	else {	/* print date in keyboard clock */
		switch(mm) {
		case  0: 
			printf("Wow! Nonsense!\n"); 
			break;
		case  1: 
			printf("January "); 
			break;
		case  2: 
			printf("February "); 
			break;
		case  3: 
			printf("March "); 
			break;
		case  4: 
			printf("April "); 
			break;
		case  5: 
			printf("May "); 
			break;
		case  6: 
			printf("June "); 
			break;
		case  7: 
			printf("July "); 
			break;
		case  8: 
			printf("August "); 
			break;
		case  9: 
			printf("September "); 
			break;
		case 10: 
			printf("October "); 
			break;
		case 11: 
			printf("November "); 
			break;
		case 12: 
			printf("December "); 
			break;
		}
		printf("%d, %d\n", dd, yy + 1900);
		fflush(stdout);
	}

	if (valid == FALSE) {
		printf("Enter the new date (dd/mm/yy): ");
		fflush(stdout);

		read_line(cc);
		i = strlen(cc);

		for (j=k=counter=0 ; j<=i ; ++j){
			if (cc[j] >= '0' && cc[j] <= '9'){ /* while numeric */
				xx[k] = cc[j];
				++k;
			}
			else {
				xx[k] = '\0';
				x = stoi(xx);
				k = 0;
				++counter;
				switch(counter){
				case 1: /* set day */
					if (x <= 31 && x > 0) dd = x;
					break;
				case 2: /* month */
					if (x <= 12 && x > 0) mm = x;
					break;
				case 3: /* year */
					if (x >= 80) yy = x;
					break;
				} /* switch */
			} /* else */
		} /* for */
	} /* if */

	cc[0] = (hrs/10) + '0';
	cc[1] = (hrs%10) + '0';
	cc[2] = ':';
	cc[3] = (mins/10) + '0';
	cc[4] = (mins%10) + '0';
	cc[5] = ':';
	cc[6] = (secs/10) + '0';
	cc[7] = (secs%10) + '0';
	cc[8] = (char)0;

	printf("The current time is: ");
	printf("%s\n",cc);
	fflush(stdout);

	if (valid == FALSE) {
		printf("Enter the new time: ");
		fflush(stdout);

		read_line(cc);
		i = strlen(cc);

		counter = 0;
		k = 0;
		for (j=0; j<=i; j++){
			if (cc[j] >= '0' && cc[j] <= '9'){
				xx[k] = cc[j];
				++k;
			}
			else {
				xx[k] = '\0';
				x = stoi(xx);
				k = 0;
				++counter;
				switch(counter){
				case 1: 
					if (i!= 0 && x <= 23 && x >= 0) hrs = x;
					break;
				case 2: 
					if (x <= 59 && x >= 0) mins = x;
					break;
				case 3: 
					if (x <= 59 && x >= 0) secs = x;
					break;
				}
			}
		}

	}

	sys_time = (hrs << 11) + (mins << 5) + (secs >> 1);
	sys_date = ((yy - 80) << 9) + (mm << 5) + dd;

	if (valid == FALSE) {
		date_time = (((long)sys_date) << 16) + (long)sys_time;
		settime(date_time);
	}

	Tsettime(sys_time);	/* set the gemdos time as well */
	Tsetdate(sys_date);	/* set the gemdos date as well */


}

--------------------------Again, please--------------------------------


	Regards,
	 anees

-- 
	Anees Munshi
	@ University of Toronto Engineering Comp. Facility :A
	{allegra,ihnp4,linus,decvax}!utzoo!utcsri!utecfa!mugc
	{allegra,ihnp4,linus,decvax}!utzoo!utcsri!utecfb!munshi