file : /kb/timerdsgn.text date : 29-MAR-1982 kb This is the design document for the TIMER driver. The Timer driver performs 3 services. First, it maintains a table of user timer service routines which on a 50 millisecond clock interrupt it invokes. Second, it uses the VIA timer number 2 to produce a Bell sound (e.g. control G). Last, the driver will read and write the time and date clock. The first service, 50 milliseconds timer call, is setup by the Timer unitstatus routine. The serviced routines are invoked during the VIA timer #1 interrupt service procedure. This service call is not a precise 50 ms. interval call. Users who have very time critical processes must setup VIA timer #2 and monitor its activity. Timer #1 is for the exclusive use of the Timer driver. Therefore, do not use it for user controlled timing activities. The Timer driver unitstatus performs 4 table manipulation actions depending on the value of the input parameters. OS defined parameters for the unitstatus procedure are : unit number (word), buffer address (long word), and control (word) . The Timer driver's 4 actions are Create, Enable, Disable and Delete table entry. The Timer driver selects the correct action by the value passed as the control parameter. The control parameter values are 1 for Create, 2 for Delete, 3 for Disable and 4 for Enable. The remaining parameters to the procedure are passed in a parameter block, whose address is passed as the unitstatus parameter buffer address. Space in the parameter block must be made for Output values as well as Input values. The following is a description of the parameters in each of the table manipulation procedure's parameter blocks. Create table entry function takes as input an address of the user service routine (long word), the count of 50 ms. periods before interrupt service routine call (word) and a word of Flags and returns a table entry ID (word) of the entry made. The count ranges from 1 to 65,535, where a 1 value means make a call to the user service routine every 50 millisecond interval timer interrupt. Currently 2 bit flags are defined in the word of Flags. The first is the Continuous/One-shot flag. When it is clear the table entry is used until it is removed by a Delete call. Consequently, everytime the entry's down counter is less than 0 (zero) the associated user's service routine is called. When the flag is set the entry is used in a one-shot mode. Therefore, when the user service routine returns the associated table entry in this mode is automatically Deleted. The second is Skip First Call flag. This flag tells the Timer driver to ignore the first call to be made to the corresponding user service routine when the down counter reaches less than 0 (zero). Since the Create operation is performed asynchronously to the interrupts generated by the free running interval timer (VIA timer #2), it is likely that the first user service routine call for this entry with this flag clear will occur in less time than the requested interval specified by the Count. Therefore, the user should use this flag when a minimum delay time of the count of interrupt periods passed in the Create function call is needed. The Create function has 3 input parameters and 1 output parameter for a total of 10 bytes, which must start on a even word boundary. Create Parameter block in Pascal fragment : CONST OneShot = 2; SkipFirst = 4; TYPE record UserServiceRtn : pointer; {Input parameter} Count : integer; {Input parameter} Flags : integer; {Input parameter} TableID : integer; {Output parameter} end;{record} Delete table entry procedure takes the table entry ID (word) for the entry desired and deletes that entry from the table. This table entry ID is the same value returned by the Create function. The Delete procedure has 1 input parameter and no (zero,0) output parameters, for a total of 2 bytes. The Disable procedure allows the user to prevent the Timer interrupt routine from calling the user's timer service routine without Deleting the user's table entry. The procedure has one input parameter, the table entry ID of the entry to disable. It has no output parameters. The total number of bytes for this procedure's parameter block is 2. The Enable procedure allows the user to restart a table entry. It clears the Disable condition and resets the down counter to it's starting condition. It can be used for either starting up a Disabled table entry or restarting a running entry before it causes a call to the service routine. This procedure has only 1 parameter, the table entry ID, for a total of 2 bytes. All the actions will place error condition codes within the IORESULT system variable. Only two error codnditions are defined. They are a) table full and b) no such entry (or invalid table entry ID). User timer service routines are called during the interrupt processing. They should take a minimal amount of time; I suggest less than 500 microseconds. Therefore, don't use them for doing any significant processing. They would be best used for setting flags informing blocked processes that an event has occurred. The table operations are not indivisible. Therefore, a user timer service routine may be called while the user is making a Delete or Disable call. Consequently, any operation done by the user service routine must be easily reversable after the table operation call. This only reenforces the need for keeping the user service routines simple with few destructive operations. All user timer service routines must return to the Timer driver interrupt routine via the RTS instruction. Also, no arguement passing is available to the the user's timer service routine. For Pascal programs which want to have a user timer interrupt service routine there are a few restrictions. First, the procedure, NOT a function, to be used as the service routine must be a global procedure. Functions cannot be used as user timer service routines. The procedure must return to the Timer driver and cannot use Global GOTO's. When the table entry is created, the routine that calls the Timer driver Create function must be a resident part of the program that the service routine procedure is a part of. The second service, the Bell, is also performed by the Timer unitstatus function. The unitstatus parameters must be set as follows : Control must be set to 0 and buffer address must point to the Bell parameter block. The parameter block contains 3 parameters, Bell frequency (word), pattern of 8 on and offs of the speaker (byte), a filler byte to keep block to even number of bytes, and the duration in counts of Timer #1 interrupt periods (50 milliseconds), a word. The Bell parameter block is 6 bytes. The pattern of 8 on and offs of the speaker is a byte with a bit equal to 1 for speaker on and 0 for speaker off. The Timer driver uses VIA timer #2 non-exclusively. The third service, the time and date clock, is accessed via the Unitread and Unitwrite driver interfaces. The clock interfaces require a Clock Parameter Block. This is a fixed length, fixed format interface. Unlike other drivers, the timer driver cannot do reads and writes of variable length data strings. The Clock Parameter Block is one field longer for the Unitwrite procedure then for the Unitread procedure. The following is a Pascal fragment which describes the Clock Parameter Block. type clockPB = record DayofWeek : 1..7; Month : 1..12; Day : 1..31; Hour : 0..23; Mins : 0..59; Secs : 0..59; Tenths : 0..9; case INTEGER of 0 : ( {Write only} LeapYear : 0..3); end;{variant} end;{record} Though each field is a sub range of the integer type and could fit in less space then a word each field MUST be a word long. Therefore, declare each field as an integer, but restrict your useage to the valid range. The driver does validate the range in case of a user error. Unfortunately, the Day parameter is not completely verified. If the month specified is Febraury (Month := 2), but the Day is 30, the driver does not report an error. Except for the fields DayofWeek, Tenths, and LeapYear the field meanings are self-explanatory. DayofWeek specifies which day it is, such as Monday, Tuesday, etc. The value 1 specifies Sunday while 7 specifies Saturday. The field Tenths is tenths of a second. Finally, the clock has no register set for years. However, to keep the correct date after a year change the clock tracks if this year is a leap year. The LeapYear field specifies the number of years AFTER the last leap year. Therefore, if this year is leap year then the field should be assigned to 0 (zero). Consequently, if last year was leap year then the field should be assigned to 1 (one), and so forth. The LeapYear field is only used when setting the clock (Unitwrite). It is not returned when reading the clock (Unitread). To set the clock, first fill in all the fields in the Clock Parameter Block, including the LeapYear field. Next, call the timer driver Unitwrite to set the clock. Unitwrite( TimerDriverNumber, WriteParmBlock, LengthWriteParmBlock ); The driver will set the IORESULT variable if the parameter block is in error. To read the clock just call the timer driver Unitread with the Clock Parameter Block. Unitread( TimerDriverNumber, ReadParmBlock, LengthReadParmBlock ); The correct length of the Read version of the Clock Paramter Block must be passed to the driver. If it is to short, an error is returned in IORESULT and the clock is NOT read. The Timer driver unitinstall procedure sets-up the VIA, initializes the timer table to no entries value, starts the 50 ms. interrupt driven interval timer (VIA timer #1), and initializes the clock chip. Clock initialization makes sure the clock is running, the chip is not in test mode, and the interrupt capabilities of the chip are reset. The chip can interrupt, however, it is not connected to the system interrupt system and it's interrupt is ignored. The interrupt is cleared and reset so it does not effect the running of the clock. The Timer driver unitunmount procedure turns off the VIA timer interrupt for Timer #2. It also installs in the timer interrupt vector a pointer to a ReTurn from Exception (RTE) instruction to insure system integrity in case of a spurrious level 5 interrupt. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Internal : 1) Table entry ID is the entry number into the table to the entry defined. 2) The VIA clock (phe 2) works at 1 megaherz, therefore, 1 tick every 1 microsecond. Consequently, the 16 bit counter only counts a maximum of 64K microseconds (approx. 65 milliseconds). 3) MAX_ENTRIES will probably be equal to 10. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Questions : 1) How does the user setup and use VIA timer #2 to get access to the timer without causing Timer interrupts and making the Bell sound? %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Eventually the Timer driver can be used for the time-of-day clock. The clock should be read with unitreads and set with unitwrites. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The Timer table Data structure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The table has 6 fields. They are : 1) Flags - (word) bits defined : D0 = Valid Entry flag D1 = Continuous/One-shot mode flag D2 = Skip First Call flag D3 = Enable/Disable flag 2) Address of user's timer service routine - (long word) 3) Count of interrupt periods - (word) 4) Down Counter - (word) 5) Register A4 save area - (long word) 6) Register A5 save area - (long word) Explanation of flag states : 1) Valid Entry flag - 1 = this table entry is valid. 0 = this table entry is not in use (invalid). 2) Continuous/One-shot mode flag - 1 = use entry in One-shot mode. 0 = use entry in Continuous mode. 3) Skip First Call flag 1 = Skip first call to user's service routine. 0 = Call user's service routine everytime. 4) Enable/Disable flag - 1 = entry Disabled. 0 = entry Enabled. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The Timer Driver pseudo-code design %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The Interrupt Routine : Interrupt routine processes the table entries to determine which user service routine, if any, to call. TIMER_INTERRUPT : for i := 1 to MAX_ENTRIES do {if have valid entry that is not disabled then see if should call} if (Valid Entry flag[i] is set) AND (Enable/Disable flag[i] is clear) then begin Down_Counter[i] := Down_Counter[i] - 1; if (Down_Counter[i] = 0) then begin {call user service routine} Get register A4 and A5 save values from entry and assign them to the registers. (A4) := RegA4[i]; (A5) := RegA5[i]; Call user's service routine at Address[i]. if (Continuous/One-shot mode flag is clear) then Down_Counter[i] := Counter[i]; {Continuous - restart counter} else Valid Entry flag[i] := clear; {One-shot - Delete entry} end; {2nd if} end; {1st if} do RTE. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Since do not guarantee that any of the table operations are indivisible, there is no need to disable the timer interrupt on the 68000. Unit I/O interface : UNITSTATUS : { do table manipulation & Bell procedures } Case (Control) of (0) : BELL; (1) : CREATE; (2) : DELETE; (3) : DISABLE; (4) : ENABLE; Otherwise : ERROR -> Invalid Timer driver function. End; Return; (**********************) CREATE : {create a timer table entry} i := Find table entry that is unused. i := 1; found := false; while ( (i<=MAX_ENTRIES) AND (NOT found) ) do begin found := NOT Valid Entry flag[i]; {find a entry that is invalid} i := i + 1; end; if (NOT found) then ERROR -> Table full. {No entries unused in table} else begin {found a free entry} get buffer_address. paramter_block := buffer_address; Address of user routine[i] := @parameter_block[bytes-0,1,2,3]; Count[i] := @parameter_block[bytes-4,5]; Down_counter[i] := Count[i]; Flags[i] := 0; {clear all flags} pb_flags := @parameter_block[bytes-6,7]; if (pb_flags Continuous/One-shot flag is set) then set Continuous/One-shot flag[i]; {set entry's flag if set} if (pb_flags Skip First Call flag is set) then set Skip First Call flag[i]; {set entry's flag if set} RegA4[i] := (A4); {save register contents, needed for Pascal routines} RegA5[i] := (A5); {A4 - jump table address,A5 - global data area} set Valid Entry flag[i]. @parameter_block[bytes-8,9] := i; {return Table entry ID} end; Return; (**********************) DELETE : {delete a timer table entry} get buffer_address. paramter_block := buffer_address; i := @parameter_block[bytes-0,1]; {get table entry ID} if (i is a valid index into the Timer table) then clear Valid Entry flag[i]. else ERROR -> Invalid table entry ID. Return; (**********************) DISABLE : {disable a timer table entry} get buffer_address. paramter_block := buffer_address; i := @parameter_block[bytes-0,1]; {get table entry ID} if (i is a valid index into the Timer table) then set Enable/Disable flag[i]. else ERROR -> Invalid table entry ID. Return; (**********************) ENABLE : {enable a timer table entry} get buffer_address. paramter_block := buffer_address; i := @parameter_block[bytes-0,1]; {get table entry ID} if (i is a valid index into the Timer table) then begin Down_Counter[i] := Counter[i]; {restart down counter} clear Enable/Disable flag[i]. end else ERROR -> Invalid table entry ID. Return; (**********************) BELL : {passed frequency,pattern and duration make a Bell sound} get buffer_address. paramter_block := buffer_address; Initialize VIA for frequency and pattern without disturbing Timer #1. Turn-off Shift Register by clearing it to all zeros. Set bit D5 of the Auxillary Control Register (ACR) by doing a Logical ORI.B operation with the value $10 to the ACR. Frequency := @parameter_block[bytes-0,1]; Set Timer #2 to Frequency. Call Create to setup a One-shot mode table entry with Count equal to the value of duration(passed in parameter block). Shut_off := false; Have an Internal parameter block for call to Create. It has the format : ADDR DATA.L address of Bell timer service routine CNT DATA.W store duration- duration := @parameter_block[bytes-4,5]; FLG DATA.W all bits clear except set One-shot mode TE DATA.W table entry-ignore Call Create routine directly, not through Unit I/O. Pattern := @parameter_block[byte-2]; Set Shift Register to Pattern. Wait until timer interrupt call. while (not Shut_off) do if (IFR T2 interrupt bit set) then begin clear IFR T2 bit; set Timer #2 to Frequency; end; Shut-off Timer #2 and Shift Register without disturbing Timer #1. Clear Shift Register to shut-off speaker Clear bit D5 of the ACR by doing a Logical ANDI.B operation with the value $EF to the ACR. Return. Bell timer service routine : Shut_off := true; do RTS. &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& UNITINSTALL : {initialize Timer table, setup VIA, turnon VIA interrupts} Disable ALL interrupts on VIA. Initialize Timer table to all unused entries. for i := 1 to MAX_ENTRIES do Valid Entry flag[i] := clear; Setup VIA for Timer #2 to do 50 millisecond interval interrupts. Put address of Timer driver interrupt service routine in Timer interrupt vector. Setup for Timer #2 to run in free-running mode with PB7 disabled. Set timer for 50 milliseconds. Clear Interrupt Status register (IFR). Enable interrupts on VIA for Timer #2 only. Return. --------------------------------------------------------------------------- possible ASSEMBLY language for UNITINSTALL ; ACR EQU $30F77 ACRBYTE EQU $40 ;ACR DATA - T1 FREE RUN DISABLE PB7 IER EQU $30F7D DISABL EQU $7F ;DISABLE ALL INTERRUPTS ENBLT1 EQU $C0 ;ENABLE IRQ FOR T1 IFR EQU $30F7B CLEAR EQU $FF ;CLEAR ALL INT STAT BITS T1LL EQU $30F6D ;TIMER 1 LATCH LOW T1LH EQU $30F6F ;TIMER 1 LATCH HIGH T1CL EQU $30F69 ;TIMER 1 COUNTER LOW - READ ONLY T1CH EQU $30F6B ;TIMER 1 COUNTER HIGH TIML EQU $00 TIMH EQU $20 ; START ; MOVE.B #DISABL,IER.L ;TURN OFF ALL INTERUPTS ON VIA ; ; INITIALIZE TIMER TABLE ; ******* ???? ******* ???? ; ; PUT ADDRESS OF INTERRUPT ROUTINE IN VECTOR ; LEA TIMINT,A0 MOVE.L A0,VECTOR.W ; ; SETUP VIA ; MOVE.B #ACRBYTE,ACR.L ;FREE RUN MODE PB7 OUTPUT DISABLED MOVE.B #TIML,T1LL.L ;TIMER #1 LATCH LOW MOVE.B #TIMH,T1LH.L ;TIMER #1 LATCH HIGH MOVE.B #TIMH,T1CH.L ;TIMER #1 COUNTER HIGH - FORCE LOAD MOVE.B #CLEAR,IFR.L ;CLEAR IFR ; ; ENABLE TIMER #2 ; MOVE.B #ENBLT1,IER.L ;TURN INTERUPTS ON FOR T1 ; ANDI.W #$2BFF,SR <--+ ;ALLOW LEVEL 4 INTERRUPTS ON 68K RTS + END START +____ not sure if necessary &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& UNITUNMOUNT : {turn off VIA timer #2 interrupts} Clear Interrupt Enable Register bit for Timer #2 in the VIA. Return. &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&