I was hit with a problem recently. I had a very time critical interrupt. but I also needed several timers. I found that the time spent in the timer interrupt with SysTimers produced too much jitter in my interrupt.
So I designed this routine, which might be useful to others.
It is quite specific; At 16Mhz it produces a 'tick' of 1.024 ms. That was good enough for me, although it could be made more accurate.. Hence I called it 'crude' timers. You will need to modify CrudeTimersInit if your crystal is not 16MHz.
The 'timers' are defined as an enum, so you could in theory have up to 255. That is one downside. - all timers have to be defined in one place.
Usage can be very similar to SysTimers, i.e. SetCrudeTimer, IsCrudeTimerZero in place of SetSysTimer and IsSysTimerZero, but also you can register a call-back for each timer if you wish, but if you do that and don't is IsCrudeTimerZero, you will need to call CrudeTimerIdleAction regularly.
Anyway here it is.
I hope someone finds it useful.
So I designed this routine, which might be useful to others.
It is quite specific; At 16Mhz it produces a 'tick' of 1.024 ms. That was good enough for me, although it could be made more accurate.. Hence I called it 'crude' timers. You will need to modify CrudeTimersInit if your crystal is not 16MHz.
The 'timers' are defined as an enum, so you could in theory have up to 255. That is one downside. - all timers have to be defined in one place.
Usage can be very similar to SysTimers, i.e. SetCrudeTimer, IsCrudeTimerZero in place of SetSysTimer and IsSysTimerZero, but also you can register a call-back for each timer if you wish, but if you do that and don't is IsCrudeTimerZero, you will need to call CrudeTimerIdleAction regularly.
Anyway here it is.
Code
Unit CrudeTimers;
(*******************************************************************************
* *
* The idea behind this is to spend as little time in the timer interrupt as *
* possible, for applications where other interrupts are critical, but timers *
* are still needed. *
* *
* The timers themselves are defined as an enum of type TCrudeTimer. *
* *
* An optional callback of type procedure may be exectuted when the timer *
* reaches zero - for efficiency of code writing. *
* *
* However it is not a requirement. IsCrudeTimerZero can be used instead *
* *
*******************************************************************************)
interface
// global part
{ $W+} // enable/disable warnings for this unit
uses
;
{--------------------------------------------------------------}
{ Const Declarations }
const
{$IDATA}
{--------------------------------------------------------------}
{ Type Declarations }
type
TCrudeTimer = (TimerFred, TimerBill, TimerEtc); // Can be defined externally
TOnTimerZero = procedure;
{--------------------------------------------------------------}
{ Var Declarations }
var
{--------------------------------------------------------------}
{ functions }
// can use like normal timers
procedure CrudeTimersInit;
procedure CrudeTimersIdleAction;
procedure SetCrudeTimer( pTimer : TCrudeTimer; pDelay : word ); // delay in ms
function IsCrudeTimerZero( pTimer : TCrudeTimer ) : boolean;
// Advanced options
procedure RegisterCallback( pTimer : TCrudeTimer; pCallback : TOnTimerZero; pRepeat : word );
// repeat of 0 means no repeat. Otherwise = repeat in ms
// If repeat is 0, a call to SetCrudeTimer is required. Otherwise not.
// If no calls to IsCrudeTimerZero are made, CrudeTimerIdleACtion must be called regularly
procedure UnRegisterCallback( pTimer : TCrudeTimer );
implementation
// local part
{--------------------------------------------------------------}
{ Type Declarations }
type
{--------------------------------------------------------------}
{ Const Declarations }
const
{--------------------------------------------------------------}
{ Var Declarations }
{$DATA}
var
fPulse : byte;
{$IDATA}
var
fTimers : array[ TCrudeTimer ] of word;
fTimerCallBack : array[ TCrudeTimer ] of TOnTimerZero;
fTimersRepeat: array[ TCrudeTimer ] of word;
{--------------------------------------------------------------}
{ functions }
procedure RegisterCallback( pTimer : TCrudeTimer; pCallback : TOnTimerZero; pRepeat : word );
// repeat of 0 means no repeat. Otherwise = repeat in ms
// If repeat is 0, a call to SetCrudeTimer is required. Otherwise not.
// If no calls to IsCrudeTimerZero are made, CrudeTimerIdleACtion must be called regularly
begin
fTimerCallBack[ pTimer ] := pCallback;
fTimersRepeat[ pTimer ] := pRepeat;
if pRepeat <> 0 then
fTimers[ pTimer ] := pRepeat;
endif;
end;
procedure UnRegisterCallback( pTimer : TCrudeTimer );
begin
fTimerCallBack[ pTimer ] := nil;
end;
procedure SetCrudeTimer( pTimer : TCrudeTimer; pDelay : word ); // delay in ms
begin
fTimers[ pTimer ] := pDelay;
end;
function IsCrudeTimerZero( pTimer : TCrudeTimer ) : boolean;
begin
CrudeTimersIdleAction;
return( fTimers[ pTimer ] = 0);
// Note - this is valid because fTimers is NOT updated in an interrupt!
end;
interrupt TIMER0;
begin
asm;
INC CrudeTimers.fPulse;
endasm;
end;
procedure CrudeTimersInit;
begin
// 16 MHz crystal
asm;
LDI _ACCA, 000h
OUT tcnt0, _ACCA
LDI _ACCA, 3
OUT tccr0, _ACCA ; internal clock / 64
LDI _ACCA, 001h
STS timsk0, _ACCA
endasm;
end;
procedure CrudeTimersIdleAction;
var
i : TCrudeTimer;
begin
asm;
CTAStart:
TST CrudeTimers.fPulse
BRNE CTAContinue
JMP CTADone
CTAContinue:
DEC CrudeTimers.fPulse
endasm;
for i := MIN( TCrudeTimer ) to MAX(TCrudeTimer) do
if fTimers[ i ] > 0 then
dec( fTimers[ i ] );
if fTimers[ i ] = 0 then
if fTimerCallBack[ i ] <> nil then
fTimers[ i ] := fTimersRepeat[ i ];
fTimerCallBack[ i ]();
endif;
endif;
endif;
endfor;
asm;
JMP CTAStart
CTADone:
endasm;
end;
initialization
// at StartUp
CrudeTimersInit;
// finalization // optional
// at System_ShutDown
end CrudeTimers.
(*******************************************************************************
* *
* The idea behind this is to spend as little time in the timer interrupt as *
* possible, for applications where other interrupts are critical, but timers *
* are still needed. *
* *
* The timers themselves are defined as an enum of type TCrudeTimer. *
* *
* An optional callback of type procedure may be exectuted when the timer *
* reaches zero - for efficiency of code writing. *
* *
* However it is not a requirement. IsCrudeTimerZero can be used instead *
* *
*******************************************************************************)
interface
// global part
{ $W+} // enable/disable warnings for this unit
uses
;
{--------------------------------------------------------------}
{ Const Declarations }
const
{$IDATA}
{--------------------------------------------------------------}
{ Type Declarations }
type
TCrudeTimer = (TimerFred, TimerBill, TimerEtc); // Can be defined externally
TOnTimerZero = procedure;
{--------------------------------------------------------------}
{ Var Declarations }
var
{--------------------------------------------------------------}
{ functions }
// can use like normal timers
procedure CrudeTimersInit;
procedure CrudeTimersIdleAction;
procedure SetCrudeTimer( pTimer : TCrudeTimer; pDelay : word ); // delay in ms
function IsCrudeTimerZero( pTimer : TCrudeTimer ) : boolean;
// Advanced options
procedure RegisterCallback( pTimer : TCrudeTimer; pCallback : TOnTimerZero; pRepeat : word );
// repeat of 0 means no repeat. Otherwise = repeat in ms
// If repeat is 0, a call to SetCrudeTimer is required. Otherwise not.
// If no calls to IsCrudeTimerZero are made, CrudeTimerIdleACtion must be called regularly
procedure UnRegisterCallback( pTimer : TCrudeTimer );
implementation
// local part
{--------------------------------------------------------------}
{ Type Declarations }
type
{--------------------------------------------------------------}
{ Const Declarations }
const
{--------------------------------------------------------------}
{ Var Declarations }
{$DATA}
var
fPulse : byte;
{$IDATA}
var
fTimers : array[ TCrudeTimer ] of word;
fTimerCallBack : array[ TCrudeTimer ] of TOnTimerZero;
fTimersRepeat: array[ TCrudeTimer ] of word;
{--------------------------------------------------------------}
{ functions }
procedure RegisterCallback( pTimer : TCrudeTimer; pCallback : TOnTimerZero; pRepeat : word );
// repeat of 0 means no repeat. Otherwise = repeat in ms
// If repeat is 0, a call to SetCrudeTimer is required. Otherwise not.
// If no calls to IsCrudeTimerZero are made, CrudeTimerIdleACtion must be called regularly
begin
fTimerCallBack[ pTimer ] := pCallback;
fTimersRepeat[ pTimer ] := pRepeat;
if pRepeat <> 0 then
fTimers[ pTimer ] := pRepeat;
endif;
end;
procedure UnRegisterCallback( pTimer : TCrudeTimer );
begin
fTimerCallBack[ pTimer ] := nil;
end;
procedure SetCrudeTimer( pTimer : TCrudeTimer; pDelay : word ); // delay in ms
begin
fTimers[ pTimer ] := pDelay;
end;
function IsCrudeTimerZero( pTimer : TCrudeTimer ) : boolean;
begin
CrudeTimersIdleAction;
return( fTimers[ pTimer ] = 0);
// Note - this is valid because fTimers is NOT updated in an interrupt!
end;
interrupt TIMER0;
begin
asm;
INC CrudeTimers.fPulse;
endasm;
end;
procedure CrudeTimersInit;
begin
// 16 MHz crystal
asm;
LDI _ACCA, 000h
OUT tcnt0, _ACCA
LDI _ACCA, 3
OUT tccr0, _ACCA ; internal clock / 64
LDI _ACCA, 001h
STS timsk0, _ACCA
endasm;
end;
procedure CrudeTimersIdleAction;
var
i : TCrudeTimer;
begin
asm;
CTAStart:
TST CrudeTimers.fPulse
BRNE CTAContinue
JMP CTADone
CTAContinue:
DEC CrudeTimers.fPulse
endasm;
for i := MIN( TCrudeTimer ) to MAX(TCrudeTimer) do
if fTimers[ i ] > 0 then
dec( fTimers[ i ] );
if fTimers[ i ] = 0 then
if fTimerCallBack[ i ] <> nil then
fTimers[ i ] := fTimersRepeat[ i ];
fTimerCallBack[ i ]();
endif;
endif;
endif;
endfor;
asm;
JMP CTAStart
CTADone:
endasm;
end;
initialization
// at StartUp
CrudeTimersInit;
// finalization // optional
// at System_ShutDown
end CrudeTimers.
I hope someone finds it useful.