Blink two LEDs with non-blocking delays

1. Description

For this example I ported the timer0_isr_interval.jal library of Joep from Jallib team, being helped by Philippe Paternotte which, obviously, knows all the tricks of the language. The library offer sloted non-blocking delays using timer0 and an array to keep track of your delays. The library is composed by two files, the library itself, and an additional library which will include user defined constants and an user interrupt procedure (if required). The program will use this library to simultaneously blink D13 LED at 1000 milliseconds and Boot LED at 250 milliseconds.

2. The library (two files)

Put these two libraries on "Common" sub-folder of "Examples" folder.

a. The user modifiable library (Tmr0_isr_interval_setup.pas):

UPDATE: Here was an error in setup - DELAY_SLOTS had to have value 2 because we use two delays. My apologies! However, the file included at attachments have the right value.

unit tmr0_isr_interval_setup;
interface
const DELAY_SLOTS: byte = 2; // how many delays to use - user modifiable
const timer0_isr_rate: dword = 1000; //1 kHz isr rate - user modifiable
procedure timer0_isr_wedge; // other processes you need to do on timer0 overflow
                            // - user defined
implementation
procedure timer0_isr_wedge;
begin
  // your tasks here - leave it empty if you don't have other interrupts
end;
initialization
end.

b. The library itself (Tmr0_isr_interval.pas):

{ ========================================================== }
{ Unit title / purpose                                       }
{ Fixed interval isr with support for non-blocking delays.   }
{    - can be used for multitasking simulation               }
{ Author: Joep Jsuijs                                        }
{ Translated by: funlw65 and PPA                             }
{ ========================================================== }
{
-- Description:
--
-- The setup of this library is straight-forward.
-- First (inside Tmr0_isr_interval_setup.pas), you define the interval
-- of the interrupt you want. With a higher value, more interrupts are generated.
-- This gives a higher resolution of your delay, but also puts a high background
-- load on your PIC. Be carefull if you go beyond 1000 (1kHz). The lowest possible
-- rate depends on the clock frequency you use and is 77 with an 16f877 on 20 MHz.
--
-- >>> const timer0_isr_rate: word = 1000; //-- 1 kHz isr rate
--
-- Next, you need to specify the number of slots. A slot is used to store the
-- end-time of a delay-period so you need one slot for each concurrent delay.
--
-- >>> const DELAY_SLOTS: byte = 2; //-- support 2 delays at the same time
--
-- Now, include the library and call it's init function:
--
-- >>> uses timer0_isr_interval;
-- and, inside your main begin, include the next line:
-- >>> timer0_isr_init; //-- init timer0 isr
--
-- Now we are ready to use the delay functions. To demonstrate it's use, we take
-- two LEDs and let them blink at their own interval:
--
-- >> forever loop
-- >> if (check_delay(0)) then
-- >> set_delay(0, 409) -- 409 ticks on delay-slot 0
-- >> led = !led
-- >> end if
-- >> if (check_delay(1)) then
-- >> set_delay(1, 619) -- 619 ticks on delay-slot 1
-- >> led2 = !led2
-- >> end if
-- >> end loop
--}
unit tmr0_isr_interval;
interface
uses
  tmr0_isr_interval_setup; // user defined constants
{$IF defined('PIC18*') and not declared(TMR0)}
var TMR0: volatile byte @ TMR0L;
{$endif}
{$if declared(OPTION_REG)} // for PIC16 MCUs with an accessible OPTION_REG
type
// Fake a T0CON into OPTION_REG
  tT0CON = record
    T0PS : BITS[3]; // Note : 3 is the number of bits
    PSA : boolean;
    T0SE : Boolean;
    T0CS : Boolean;
    INTEGD : Boolean;
    NOT_RAB_PU : Boolean;
  end;
var
  T0CON: volatile tT0CON absolute OPTION_REG; // Define a T0CON for PIC16
{$ifend}
procedure set_delay(slot: byte; ticks: word);
function  check_delay(slot: byte): boolean;
procedure timer0_isr_init; // use it once to initialize the library
implementation
var
  internal_isr_counter: word;
  isr_countdown: array[0..DELAY_SLOTS - 1] of word;
  timer0_load: byte;
procedure set_delay(slot: byte; ticks: word);
begin
  if (slot > high(isr_countdown)) then exit; //protection against wrong param.
  INTCON.TMR0IE := 0;
  isr_countdown[slot] := ticks;
  INTCON.TMR0IE := 1;
end;
function check_delay(slot: byte): boolean;
begin
  Result := false;
  if (slot > high(isr_countdown)) then exit; //protection against wrong param.
  if (isr_countdown[slot] <> 0) then exit;
  if (isr_countdown[slot] <> 0) then exit;
  //-- note: double checking is done to cope with the isr
  //-- decrementing from 0x100 to 0x0ff without disabling the isr.
  Result := true; //    -- delay passed
end;
procedure timer0_isr_init;
const
  timer0_div: dword  = (FREQUENCY div 4 div timer0_isr_rate) - 1;
begin
  if (timer0_div > ((256 * 256) - 1)) then begin
     // {$ERROR 'requested ISR rate is too low'}
  end
  else if (timer0_div > ((128 * 256) - 1)) then begin
    T0CON.T0PS := 7; //prescaler 256
    timer0_load := 255 - byte(timer0_div div 256);
  end
  else if (timer0_div > ((64 * 256) - 1)) then begin
    T0CON.T0PS := 6; //prescaler 128
    timer0_load := 255 - byte(timer0_div div 128);
  end
  else if (timer0_div > ((32 * 256) - 1)) then begin
    T0CON.T0PS := 5; //prescaler 64
    timer0_load := 255 - byte(timer0_div div 64);
  end
  else if (timer0_div > ((16 * 256) - 1)) then begin
    T0CON.T0PS := 4; //prescaler 32
    timer0_load := 255 - byte(timer0_div div 32);
  end
  else if (timer0_div > ((8 * 256) - 1)) then begin
    T0CON.T0PS := 3; //prescaler 16
    timer0_load := 255 - byte(timer0_div div 16);
  end
  else if (timer0_div > ((4 * 256) - 1)) then begin
    T0CON.T0PS := 2; //prescaler 8
    timer0_load := 255 - byte(timer0_div div 8);
  end
  else if (timer0_div > ((2 * 256) - 1)) then begin
    T0CON.T0PS := 1; //prescaler 4
    timer0_load := 255 - byte(timer0_div div 4);
  end
  else begin
    T0CON.T0PS := 0; //prescaler 2
    timer0_load := 255 - byte(timer0_div div 2);
  end;// if
  T0CON.T0CS   := 0; //internal clock
  T0CON.PSA    := 0; //assign prescaler to timer0
  clr(isr_countdown); // initialize the array for non-bloking delays
  INTCON.TMR0IF := 0;
  INTCON.TMR0IE := 1;
  INTCON.GIE  := 1; //enable global interrupts
  INTCON.PEIE := 1;
end;
{$if defined('PIC18*')}
procedure ISR; INTERRUPT; High;
{$else}
procedure ISR; INTERRUPT;
{$ifend}
var
  index: byte;
begin
  //pragma interrupt
  //{$OPTIMIZE SPEED}
  if INTCON.TMR0IF = true then begin
    TMR0 := timer0_load;
    //-- counters
    internal_isr_counter := internal_isr_counter + 1;
    for index := 0 to DELAY_SLOTS - 1 do
      if (isr_countdown[index] <> 0) then
        isr_countdown[index] := isr_countdown[index] - 1;
    //if user defined wedge procedure, call it.
    {$if defined('timer0_isr_wedge')}
      timer0_isr_wedge;
    {$ifend}
    INTCON.TMR0IF := 0;
  end;// if
end; //procedure
initialization
end.

3. The program

Program tmr0_no_boot;
{$PROCESSOR PIC18F2550 }
{$FREQUENCY 48 MHZ }
{$OPTIMIZE SPEED }
{$CONFIG PLLDIV = 5 }
{$CONFIG CPUDIV = OSC1_PLL2 }
{$CONFIG USBDIV = 2}
{$CONFIG FOSC = HSPLL_HS}
{$CONFIG FCMEN = OFF}
{$CONFIG IESO = OFF}
{$CONFIG PWRT = OFF}
{$CONFIG VREGEN = ON}
{$CONFIG BORV = 3}
{$CONFIG BOR = OFF}
{$CONFIG WDTPS = 32768}
{$CONFIG WDT = OFF}
{$CONFIG CCP2MX = ON}
{$CONFIG PBADEN = OFF}
{$CONFIG LPT1OSC = ON}
{$CONFIG MCLRE = ON}
{$CONFIG STVREN = OFF}
{$CONFIG LVP = OFF}
{$CONFIG XINST = OFF}
{$CONFIG DEBUG = OFF}
{$CONFIG CP0 = OFF}
{$CONFIG CP1 = OFF}
{$CONFIG CP2 = OFF}
{$CONFIG CP3 = OFF}
{$CONFIG CPB = OFF}
{$CONFIG CPD = OFF}
{$CONFIG WRT0 = OFF}
{$CONFIG WRT1 = OFF}
{$CONFIG WRT2 = OFF}
{$CONFIG WRT3 = OFF}
{$CONFIG WRTB = OFF}
{$CONFIG WRTD = OFF}
{$CONFIG WRTC = OFF}
{$CONFIG EBTR0 = OFF}
{$CONFIG EBTR1 = OFF}
{$CONFIG EBTR2 = OFF}
{$CONFIG EBTR3 = OFF}
{$CONFIG EBTRB = OFF}
Uses Tmr0_isr_interval;// don't forget to set Tmr0_isr_interval_setup.pas unit.
begin
  // all_pins_digital;
  ADCON0 := 0b00000000; //         -- disable ADC
  ADCON1 := 0b00001111; //         -- digital I/O
  ADCON2 := 0b00000000; //
  CMCON  := 0b00000111; //         -- disable comparator
  //
  timer0_isr_init;
  TRISC.TRISC2 := 0; // set D13 as output
  TRISA.TRISA4 := 0; // set Boot LED as output
  set_delay(0, 1000); // set delay 1000ms at slot nr. 0
  LATC.LATC2 := 1;
  set_delay(1, 250); // set delay 250ms at slot nr. 1
  LATA.LATA4 := 1;
  While 1 do begin
    if (check_delay(0)) then begin
      set_delay(0, 1000); //-- 1000 ticks on delay-slot 0
      LATC.LATC2 := not LATC.LATC2;
    end;
    if (check_delay(1)) then begin
      set_delay(1, 250); //-- 250 ticks on delay-slot 1
      LATA.LATA4 := not LATA.LATA4;
    end;
  end;
end.

4. The result

This is a movie for an older version of the program, which blinked only one LED but, it was tested on FreeJALduino (PIC18F2550) and on Low Pin Count Demo Board with PIC16F690 uC and later, with PIC12F675 and PIC12F629 uCs:

And this is how your program must behave:

5. Conclusions

For me, it is a very useful library which helps me a lot in making multitasking applications. Many thanks to Philippe Paternotte which helped me to translate the library from JALv2 language and for amending his compiler to make things work!

6. Updates

May 05, 2012: Updated for the last version at this date - v1.6.2.96 (the name of some registers were modified).