7. The Virtual Driver

Virtual Driver is the name given to some initialization services and a group of services performed by a periodic interrupt. These services are:

Initialization Services

Periodic Interrupt Services

7.1 Default Operation

The user should be aware that by default the Virtual Driver starts and runs in a Dynamic C program with­out the user doing anything. This happens because before main() is called, a function called premain() is called by the Rabbit kernel (BIOS) that actually calls main(). Before premain() calls main(), it calls a function named VdInit() that performs the initialization services, including start­ing the periodic interrupt. If the user were to disable the Virtual Driver by commenting out the call to VdInit() in premain(), then none of the services performed by the periodic interrupt would be available. Unless the Virtual Driver is incompatible with some very tight timing requirements of a program and none of the services performed by the Virtual Driver are needed, it is recommended that the user not disable it.

7.2 Calling _GLOBAL_INIT()

VdInit() calls the function chain _GLOBAL_INIT() which runs all #GLOBAL_INIT sections in a program. _GLOBAL_INIT() also initializes all of the CoData structures needed by costatements and cofunctions. If VdInit() is not called, users could still use costatements and cofunctions if the call to VdInit() was replaced by a call to _GLOBAL_INIT(), but the DelaySec() and DelayMs() functions often used with costatements and cofunctions in waitfor statements would not work because those functions depend on timer variables which are maintained by the periodic interrupt.

7.3 Global Timer Variables

SEC_TIMER, MS_TIMER and TICK_TIMER are global variables defined as shared unsigned long. These variables should never be changed by an application program. Among other things, the TCP/IP stack depends on the validity of the timer variables.

On initialization, SEC_TIMER is synchronized with the real-time clock. The date and time can be accessed more quickly by reading SEC_TIMER than by reading the real-time clock.

The periodic interrupt updates SEC_TIMER every second, MS_TIMER every millisecond, and TICK_TIMER 1024 times per second (the frequency of the periodic interrupt). These variables are used by the DelaySec, DelayMS and DelayTicks functions, but are also convenient for application pro­grams to use for timing purposes.

7.3.1  Example: Timing Loop

The following sample shows the use of MS_TIMER to measure the execution time in microseconds of a Dynamic C integer add. The work is done in a nodebug function so that debugging does not affect timing.

#define N 10000

main(){ timeit(); }

nodebug timeit(){
unsigned long int T0;
float T2,T1;
int x,y;
int i;

T0 = MS_TIMER;
for(i=0;i<N;i++) { }

// T1 gives empty loop time
T1=(MS_TIMER-T0);

T0 = MS_TIMER;
for(i=0;i<N;i++){ x+y;}

// T2 gives test code execution time 
T2=(MS_TIMER-T0);

// subtract empty loop time and convert to time for single pass
T2=(T2-T1)/(float)N;

// multiply by 1000 to convert milliseconds to microseconds.
printf("time to execute test code = %f us\n",T2*1000.0);
}

7.3.2  Example: Delay Loop

An important detail about MS_TIMER is that it overflows (“rolls over”) approximately every 49 days, 17 hours. This behavior causes the following delay loop code to fail:

/* THIS CODE WILL FAIL!! */
endtime = MS_TIMER + delay;
while (MS_TIMER < endtime) {
   //do something
}

If “MS_TIMER + delay” overflows, this returns immediately. The correct way to code the delay loop so that an overflow of MS_TIMER does not break it, is this:

endtime = MS_TIMER + delay;
while ((long)MS_TIMER - endtime < 0) {
   //do something
}

The interval defined by the subtraction is always correct. This is true because the value of the interval is based on the values of MS_TIMER and “endtime” relative to one another, so the actual value of these vari­able does not matter.

One way to conceptualize why the second code snippet is always correct is to consider a number circle like the one in Figure 7.1. In this example, delay=5. Notice that the value chosen for MS_TIMER will “roll over” but that it is only when MS_TIMER equals or is greater than “endtime” that the while loop will eval­uate to false.

Figure 7.1  delay=5

mstimer.png

Another important point to consider is that the interval is cast to a signed number, which means that any number with the high bit set is negative. This is necessary in order for the interval to be less than zero when MS_TIMER is a large number.

7.4 Watchdog Timers

Watchdog timers limit the amount of time your system will be in an unknown state.

7.4.1  Hardware Watchdog

The Rabbit CPU has two built-in hardware watchdog timers, called the watchdog timer (WDT) and the secondary watchdog timer (SWDT). The Virtual Driver hits the watchdog timer (WDT) periodically. The following code fragment could be used to disable this WDT:

#asm
    ld a,0x51
ioi ld (WDTTR),a
    ld a,0x54
ioi ld (WDTTR),a
#endasm

However, it is recommended that the watchdog not be disabled. The watchdog prevents the target from entering an endless loop in software due to coding errors or hardware problems. If the Virtual Driver is not used, the user code should periodically call hitwd().

When debugging a program, if the program is stopped at a breakpoint because the breakpoint was explic­itly set, or because the user is single stepping, then the debug kernel hits the hardware watchdog periodi­cally.

The secondary watchdog timer defaults to disabled. For more information on the hardware watchdogs, please see the user’s manual for your Rabbit processor.

7.4.2  Virtual Watchdogs

There are 10 virtual WDTs available; they are maintained by the Virtual Driver. Virtual watchdogs, like the hardware watchdog, limit the amount of time a system is in an unknown state. They also narrow down the problem area to assist in debugging.

The function VdGetFreeWd(count) allocates and initializes a virtual watchdog. The return value of this function is the ID of the virtual watchdog. If an attempt is made to allocate more than 10 virtual WDTs, a fatal error occurs. In debug mode, this fatal error will cause the program to return with error code 250. The default run-time error behavior is to reset the board.

The ID returned by VdGetFreeWd() is used as the argument when calling VdHitWd(ID) to hit a vir­tual watchdog or VdReleaseWd(ID) to deallocate it.

The Virtual Driver counts down watchdogs every 62.5 ms. If a virtual watchdog reaches 0, this is fatal error code 247. Once a virtual watchdog is active, it should be reset periodically with a call to VdHitWd(ID) to prevent this. If count = 2 for a particular WDT, then VdHitWd(ID) will need to be called within 62.5 ms for that WDT. If count = 255, VdHitWd(ID) will need to be called within 15.94 seconds.

The Virtual Driver does not count down any virtual WDTs if the user is debugging with Dynamic C and stopped at a breakpoint.

7.5 Preemptive Multitasking Drivers

A simple scheduler for Dynamic C’s preemptive slice statement is serviced by the Virtual Driver. The scheduling for µC/OS-II, a more traditional full-featured real-time kernel, is also done by the Virtual Driver.

These two scheduling methods are mutually exclusive—slicing and µC/OS-II must not be used in the same program.