packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/timeofda.mod

IMPLEMENTATION MODULE TimeOfDay;

	(****************************************************************)
	(*								*)
	(*			Time-of-day Module			*)
	(*								*)
	(*  This module keeps a time-of-day record, to the nearest 16th	*)
	(*  of a second, by using the CMOS clock hardware.		*)
	(*								*)
	(*  Programmer:		P. Moylan				*)
	(*  Last edited:	22 March 1995				*)
	(*  Status:		Working					*)
	(*								*)
	(****************************************************************)

FROM MiscPMOS IMPORT
    (* proc *)	ReadCMOS, WriteCMOS, EnterCriticalSection, LeaveCriticalSection;

FROM LowLevel IMPORT
    (* proc *)	IORB, IAND, IANDB, LS, RS, OutByte, InByte;

FROM Windows IMPORT
    (* type *)	Window, Colour, FrameType, DividerType,
    (* proc *)	OpenWindow, WriteString, WriteLn;

FROM NumericIO IMPORT
    (* proc *)	WriteHexByte;

FROM TaskControl IMPORT
    (* proc *)	CreateInterruptTask, WaitForInterrupt;

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure;

(************************************************************************)
(*									*)
(*  The first 14 bytes of CMOS are used for the real-time clock:	*)
(*									*)
(*	00	seconds			01	second alarm		*)
(*	02	minutes			03	minute alarm		*)
(*	04	hours			05	hour alarm		*)
(*	06	day of week		07	date of month		*)
(*	08	month			09	year			*)
(*	0A	status register A	0B	status register B	*)
(*	0C	status register C	0D	status register D	*)
(*									*)
(*  Byte number 50 (decimal) holds the century.  This is maintained	*)
(*  by software, not hardware.						*)
(*									*)
(*  Times and dates are normally stored in BCD format, although there	*)
(*  is an option to use binary.  The four status registers are for	*)
(*  various options and flags; for the details, the Technical Reference	*)
(*  Manual should be consulted.						*)
(*									*)
(************************************************************************)

CONST
    InterruptNo = 70H;		(* hardware interrupt number *)
    StatusA = 10;		(* status register A	*)
    StatusB = 11;		(* status register B	*)
    StatusC = 12;		(* status register C	*)

VAR
    MasterTime: TimeRecord;

    (* BinaryClock keeps track of whether we are currently running in	*)
    (* binary mode.  (If FALSE, we are running in BCD mode.)		*)

    BinaryClock: BOOLEAN;

    (* WrapPending should logically be a local static variable of the	*)
    (* interrupt handler, but Modula-2 has no local static variables.	*)

    WrapPending: BOOLEAN;

(************************************************************************)
(*			    THE INTERRUPT TASK				*)
(************************************************************************)

PROCEDURE InterruptHandler;

    CONST UF = 010H;	(* update-ended flag in Status Register C *)
	testing = FALSE;

    VAR flags: BYTE;
	w: Window;

    BEGIN
	IF testing THEN
	    OpenWindow (w, blue, cyan, 16, 24, 0, 79, simpleframe, nodivider);
	END (*IF*);

	LOOP (*FOREVER*)
	    WaitForInterrupt;
	    flags := ReadCMOS (StatusC);

	    IF testing THEN
		WriteHexByte (w, flags);  WriteString (w, "  ");
	    END (*IF*);

	    IF ORD(IANDB (flags, UF)) <> 0 THEN
		WITH MasterTime DO
		    ticks := 0;
		    seconds := ReadCMOS (0);
		    minutes := ReadCMOS (2);
		    hours := ReadCMOS (4);
		    dayofweek := ReadCMOS (6);
		    dayofmonth := ReadCMOS (7);
		    month := ReadCMOS (8);
		    year := ReadCMOS (9);
		    century := ReadCMOS (50);
		    IF ((year=99) AND BinaryClock)
		    		OR ((year=99H) AND NOT BinaryClock) THEN
			WrapPending := TRUE;
		    ELSIF WrapPending AND (year = 0) THEN
			WrapPending := FALSE;
			INC (century);
			IF NOT BinaryClock
				AND (ORD(IANDB(century,0FH)) > 9) THEN
			    INC (century, 6);
			END (*IF*);
			WriteCMOS (50, century);
		    END (*IF*);
		END (*WITH*);

		IF testing THEN WriteLn(w) END(*IF*);

	    ELSE
		INC(MasterTime.ticks);
	    END (*IF*);

	END (*LOOP*);
    END InterruptHandler;

(************************************************************************)
(*			READING AND SETTING THE CLOCK			*)
(************************************************************************)

PROCEDURE ReadClock (VAR (*OUT*) result: TimeRecord);

    (* Returns the current time and date.	*)

    BEGIN

	(* Take a copy of the master time record, disabling clock	*)
	(* interrupts via the slave interrupt mask register.		*)

	OutByte (0A1H, IORB(InByte(0A1H), 1));
	result := MasterTime;
	OutByte (0A1H, IANDB(InByte(0A1H), 0FEH));
    END ReadClock;

(************************************************************************)

PROCEDURE SetClock (VAR (*IN*) newtime: TimeRecord);

    (* Modifies the current time and date.  Note that newtime.ticks is	*)
    (* in effect ignored.  To take that into account, and thereby	*)
    (* achieve greater accuracy, we would need to use the more complex	*)
    (* approach of delaying for a few ticks before resetting.		*)

    VAR PSW: CARDINAL;

    BEGIN
	(* Disable clock interrupts by setting a bit in the mask	*)
	(* register of the slave interrupt controller.  Also prevent	*)
	(* any clock updates by setting the high-order bit of Status	*)
	(* Register B.  For extra safety (just in case other tasks are	*)
	(* accessing CMOS), disable processor interrupts as well.	*)

	PSW := EnterCriticalSection();
	OutByte (0A1H, IORB(InByte(0A1H), 1));
	WriteCMOS (StatusB, IORB(ReadCMOS(StatusB), 80H));

	MasterTime := newtime;
	WITH newtime DO
	    WriteCMOS (0, seconds);
	    WriteCMOS (2, minutes);
	    WriteCMOS (4, hours);
	    WriteCMOS (6, dayofweek);
	    WriteCMOS (7, dayofmonth);
	    WriteCMOS (8, month);
	    WriteCMOS (9, year);
	    WriteCMOS (50, century);
	END (*WITH*);

	(* Re-enable the clock.	*)

	OutByte (0A1H, IANDB(InByte(0A1H), 0FEH));
	WriteCMOS (StatusB, IANDB(ReadCMOS(StatusB), 7FH));
	LeaveCriticalSection (PSW);

    END SetClock;

(************************************************************************)
(*		     SETTING CLOCK MODE TO BINARY OR BCD		*)
(************************************************************************)

PROCEDURE Convert (j: CARDINAL;  SetToBinary: BOOLEAN);

    (* Converts the value in CMOS register j from BCD to binary if	*)
    (* SetToBinary is TRUE, and from binary to BCD otherwise.		*)

    VAR value: CARDINAL;

    BEGIN
	value := ORD(ReadCMOS(j));
	IF SetToBinary THEN
	    value := 10*ORD(RS(value, 4)) + ORD(IAND(value, 0FH));
	ELSE
	    value := ORD(LS(value DIV 10, 4)) + (value MOD 10);
	END (*IF*);
	WriteCMOS (j, BYTE(value));
    END Convert;

(************************************************************************)

PROCEDURE SetBinaryMode (NewMode: BOOLEAN);

    (* Makes the clock run in BCD if NewMode is FALSE, or in binary if	*)
    (* NewMode is TRUE.  If this requires a mode change, adjusts the	*)
    (* current date/time values held by the hardware.			*)

    VAR j: CARDINAL;  StatusBvalue: BYTE;

    BEGIN
	StatusBvalue := ReadCMOS (StatusB);
	IF (BinaryClock <> NewMode) THEN

	    (* Modify our copy of the date mode bit. *)

	    StatusBvalue := IANDB (StatusBvalue, 07BH);
	    IF NewMode THEN
		INC (StatusBvalue, 4);
	    END (*IF*);

	    (* Disable clock interrupts and clock updating.  See	*)
	    (* procedure SetClock for explanation.			*)

	    OutByte (0A1H, IORB (InByte (0A1H), 1));
	    WriteCMOS (StatusB, IORB (StatusBvalue, 080H));

	    (* Modify all the time and date values. *)

	    FOR j := 0 TO 9 DO
		Convert(j, NewMode);
	    END (*FOR*);
	    Convert (50, NewMode);	(* Don't forget the century *)

	    (* Re-enable the clock in the new mode.	*)

	    BinaryClock := NewMode;
	    WriteCMOS (StatusB, StatusBvalue);
	    OutByte (0A1H, IANDB(InByte(0A1H), 0FEH));

	END (*IF*);
    END SetBinaryMode;

(************************************************************************)
(*			PROGRAM TERMINATION HANDLER			*)
(************************************************************************)

PROCEDURE Cleanup;

    (* Disables CMOS interrupts, and sets the CMOS clock back to its	*)
    (* standard MS-DOS setup condition.					*)

    VAR PSW: CARDINAL;

    BEGIN
	PSW := EnterCriticalSection();
	SetBinaryMode (FALSE);
	WriteCMOS (StatusA, 26H);
	WriteCMOS (StatusB, 2);
	LeaveCriticalSection (PSW);
    END Cleanup;

(************************************************************************)
(*				INITIALISATION				*)
(************************************************************************)

PROCEDURE HardwareSetup;

    CONST
	PIenable = 40H;		(* periodic interrupt enable	*)
	UIenable = 10H;		(* update-ended interrupt enable *)
	mode24 = 2;		(* 24-hour mode *)

    VAR PSW: CARDINAL;

    BEGIN
	PSW := EnterCriticalSection();
	BinaryClock := ORD(IANDB(ReadCMOS (StatusB), 4)) <> 0;
	SetBinaryMode (FALSE);

	(* Select a periodic clock rate of 16 Hz.	*)

	WriteCMOS (StatusA, 2CH);

	(* Set the desired interrupt options in status register B, and	*)
	(* install the interrupt handler.				*)

	WriteCMOS (StatusB, PIenable+mode24);
	CreateInterruptTask (InterruptNo, InterruptHandler, "CMOS interrupt");

	(* Clear a bit in the interrupt mask register.  The mask	*)
	(* register address is 21H for the master interrupt controller,	*)
	(* 0A1H for the slave.						*)

	OutByte (0A1H, IANDB(InByte(0A1H), 0FEH));
	OutByte (021H, IANDB(InByte(021H), 0FBH));
	LeaveCriticalSection (PSW);

    END HardwareSetup;

(************************************************************************)

BEGIN
    WrapPending := FALSE;
    SetTerminationProcedure (Cleanup);
    HardwareSetup;
END TimeOfDay.