Multi-tasking and Dynamic C


Dynamic C support three different styles of multi-tasking. The most powerful multi-tasking environment is provided by the µC/OS-II operating system. This is only available with the Premier version of the Dynamic C compiler. Instead we will discuss the other two types of multi-tasking supported by Dynamic C. Two keywords have been added to the compiler in order to support both cooperative and preemptive multi-tasking.


Cooperative Multi-tasking

In this type of multi-tasking environment, each running task controls the CPU until it voluntarily relinquishing control. In reality, this type of multi-tasking is driven as a large state machine. You can surround any code with the special costate statement. Inside the same loop, multiple costate statements are treated as separate tasks, which will run in sequence, one after the other. When one of the tasks uses the yield, abort, waitfor, or waitfordone statements, the task will relinquish the CPU and allow other costate blocks to run.


The yield statement is used to immediately give up the CPU to other tasks. Once all other tasks have been given the opportunity to utilize the CPU, the task that called yield will be activated again and will continue processing at the statement immediately following the yield statement.


Example #1:

main()
{
 int loop_count;
 int time_count;

 loop_count = 0;
 time_count = 0;

 costate {	// Start of the first cooperative task

   loop_count++;
   printf( "Loop count is now: %d\n" );
   yield;

 }

 costate {	// Start of the second cooperative task

   time_count++;
   printf( "Time count is now: %d\n" );
   waitfor( DelaySec( 1 ) );

 }
}

In this example, the first task uses the yield command at the end of the loop to relinquish control of the CPU. Dynamic C will then switch to the second task, which delays for 1 second each time through the loop. The waitfor statement allows the first task to run again while the second task is waiting for the timeout of 1 second to elapse. In this sample program, the first task will usually run 10 times faster than the second task.


Example #2:

main()
{
  int task1_count;
  int task2_count;

  task1_count = 0;
  task2_count = 0;

  while( 1 )        // Must be an endless loop
  {
    costate         // Start of the first cooperative task
    {
      task1_count++;
      printf( "Task1 count = %d\n", task1_count );
      waitfor( DelaySec( 5 ) );
    }

    costate         // Start of the second cooperative task
    {
      task2_count++;
      printf( "Task2 count = %d\n", task2_count );
      waitfor( DelaySec( 1 ) );
    }
  }
}

In this example, both tasks use the waitfor statement to pause. The first task pauses for 5 seconds each time through the loop, while the second pauses for one second. In this program, the second task will run 5 times faster than the first. Due to slight inaccuracies in the delaying code, occasionally the second task will run 4 times or maybe even 6 times before the first task wakes up and runs again.


Example #3:

main()
{
	int task1_count;
	int task2_count;

	task1_count = 0;
	task2_count = 0;

	while( 1 )		// Must be an endless loop
	{
		costate		// Start of the first cooperative task
		{
			task1_count++;
			printf( "Task1 count =  %d\n", task1_count );
			if( task1_count >= 10 )
				abort;
			else
				waitfor( DelaySec( 5 ) );
		}

		costate		// Start of the second cooperative task
		{
			task2_count++;
			printf( "Task2 count = %d\n", task2_count );
			waitfor( DelaySec( 1 ) );
		}
	}
}

In this example, the first task will pause 5 seconds each time through the loop, until the count reaches 10. From that point on, the first task uses the abort command to exit its task loop immediately. When running this program, the second task will run 5 times for each time the first task runs. Once the first task reaches a count of 10 however, it will then begin running much faster than the second task. The first task should run about 10 times faster than the second after it reaches a count of 10.


Example #4:

main()
{
	long task1_count;
	long task2_count;

	task1_count = 0;
	task2_count = 0;

	while( 1 )		// Must be an endless loop
	{
		slice(100,20)			// Start of the first preemptive task (stack = 100 bytes, 20 tick slices)
		{
			for(;;)
				task1_count++;
		}

		slice(100,40)			// Start of the second preemptive task (stack = 100 bytes, 40 tick slice)
		{
			for(;;)
				task2_count++;
		}

		printf( "Task1 count = %ld\n", task1_count );
		printf( "Task2 count = %ld\n", task2_count );
	}
}

This example uses slice statements to setup a preemptive multi-tasking program. The first task is allocated 20 ticks (tick = 1/2048th second) to run, while the second task is allocated 40 ticks to run, per loop. The printf statements are not inside the tasks, because when placed inside the task loops, Dynamic C is unable to accurate send data to the host computer. The time-slicing has a negative effect on serial communications between the Rabbit-system and the host computer.


Example #5:

main()
{
	long task1_count;
	long task2_count;

	task1_count = 0;
	task2_count = 0;

	while( 1 )		// Must be an endless loop
	{
		slice(100,20)			// Start of the first preemptive task (stack = 100 bytes, 20 tick slices)
		{
			for(;;)
			{
				task1_count++;
				yield;
			}
		}

		slice(100,40)			// Start of the second preemptive task (stack = 100 bytes, 40 tick slice)
		{
			for(;;)
				task2_count++;
		}

		printf( "Task1 count = %ld\n", task1_count );
		printf( "Task2 count = %ld\n", task2_count );
	}
}

The yield, waitfor, and waitfordone statements can be used inside of slices to voluntarily relinquish control of the slice at any time. In this example, the first task will always yield its CPU time after in increments the counter. The second task is allowed to increment the counter as many times as possible in its allocated 40 time slices.


Example #6:

main()
{
	long task1_count;
	long task2_count;
	long task3_count;
	long bgtimer, timeleft;
	long looptime;

	looptime = 100;
	task1_count = 0;
	task2_count = 0;
	task3_count = 0;

	while( 1 )		// Must be an endless loop
	{
		bgtimer = TICK_TIMER + looptime;
		
		slice(200,20)			// Start of the first preemptive task (stack = 100 bytes, 20 tick slices)
		{
			for(;;)
			{
				task1_count++;
				if( task1_count < 10 )
				{
					yield;
				}
				else
				{
					if( task1_count < 20 )
						waitfor( DelaySec( 2 ) );
				}
			}
		}

		slice(200,40)			// Start of the second preemptive task (stack = 100 bytes, 40 tick slice)
		{
			for(;;)
			{
				task2_count++;
				if( task2_count < 5 )
				{
					yield;
				}
			}
		}

		timeleft = bgtimer - TICK_TIMER;
			
		if(timeleft>=0)
		{
			slice(200,(int)timeleft)	// Run third task with remain ticks
			{
				for(;;)
					task3_count++;
			}
		}

		printf( "Task1 count = %ld\n", task1_count );
		printf( "Task2 count = %ld\n", task2_count );
		printf( "Task3 count = %ld\n", task3_count );
		printf( "timeleft was %ld\n", timeleft );
	}
}

This example adjusts the amount of time a third task is allowed to run on the fly. The first two tasks use the yield and waitfor statements to relinquish control of the CPU differently depending on the value of their counters. Task 3 is given a variable amount of ticks to run, by subtracting the number of ticks the first two tasks used from a total of 100 ticks. This loop repeats itself every 100 ticks (approximately), dividing slice times amongst the three tasks as needed.


Costate Functions

The waitfordone statements are used only when calling special functions that are marked with the cofunc, or scofunc attributes. Basically a cofunc is run as a separate task, which causes the calling function to be suspended until the cofunc completes. The difference between a cofunc and a scofunc is that a cofunc cannot be called multiple times within the same waitfordone statement. This is because Dynamic C would attempt to run both functions simultaneously, which is not allowed. A single-user cofunction can be used instead, since they will be called in sequence.


Example #1:

int bFireAlarm;
int bBreakinAlarm;

int tick_count[2];

#asm
timer_tick::

	push		af
	push		hl

	ioi ld	a, (TBCSR)				; Read TimerB Control/Status Register (Resets Interrupt)

	ld			a, 00h					; Setup lower part of match register
	ioi ld	(TBM1R), a				; Setup upper part of match register
	ioi ld	(TBL1R), a

	ipres
	
	ld			a, (bFireAlarm)		; Is alarm flag already set?
	and		1
	jr			nz, btn1_up

	ioi ld	a, (PBDR)				; Read Port B (switches)

	and		a, 04h					; Is S2 down?
	jr			nz, btn1_up				; No, skip

	ld			hl, (tick_count)		; Add 1 to Btn 1 counter
	inc		hl
	ld			(tick_count), hl

	ld			a, h						; 1024 ticks with button down?
	sub		4
	jr			nz, btn2_check

	ld			a, 1						; Btn has been down long enough, set flag
	ld			(bFireAlarm), a
	jr			btn2_check

btn1_up:
	ld			a, 0						; Reset tick count to 0
	ld			(tick_count), a
	ld			(tick_count+1), a

btn2_check:

	ld			a, (bBreakinAlarm)	; Is Break-in Alarm set?
	and		1
	jr			nz, btn2_up

	ioi ld	a, (PBDR)				; Reread Port B (switches)

	and		a, 08h					; Is S3 down?
	jr			nz, btn2_up				; No, skip

	ld			hl, (tick_count+2)	; Add 1 to Btn 2 counter
	inc		hl
	ld			(tick_count+2), hl

	ld			a, h						; 1024 ticks with button down?
	sub		4
	jr			nz, all_done

	ld			a, 1						; Btn has been down long enough, set flag
	ld			(bBreakinAlarm), a
	jr			all_done

btn2_up:
	ld			a, 0						; Reset tick count to 0
	ld			(tick_count+2), a
	ld			(tick_count+3), a

all_done:

	pop		hl
	pop		af

	ret

#endasm

void setupISR()
{
#asm
	ld			a, iir			; get internal interrupt register
	ld			h, a				; copy to high nibble of HL
	ld			l, 0B0h			; We want Timer B interrupts
	ld			iy, hl			; IY now points to the interrupt table for Timer B
	ld			hl, timer_tick	; Load address of ISR
	ld			(iy), 0C3h		; Put JUMP command (0C3h opcode) into interrupt table
	ld			(iy+1), hl		; Follow JUMP command with address of our ISR
#endasm
}

cofunc void call_police()
{
  printf( "Calling police...");
  BitWrPortI( PADR, &PADRShadow, 0x00, 0 );

  waitfor( DelaySec( 10 ) );  

  BitWrPortI( PADR, &PADRShadow, 0xff, 0);
  printf( "done.\n" );
}

cofunc call_fire_department()
{
  printf( "Calling fire department...");
  BitWrPortI( PADR, &PADRShadow, 0x00, 1 );

  waitfor( DelaySec( 10 ) );

  BitWrPortI( PADR, &PADRShadow, 0xff, 1 );
  printf( "done.\n" );
}

main()
{
	//
	// GLOBAL_INIT sections are run before main()
	//
	#GLOBAL_INIT
	{
		bFireAlarm = 0;
		bBreakinAlarm = 0;
		tick_count[0] = 1;
		tick_count[1] = 2;
	}

	//
	// Setup Port A to drive LEDs, then turn off LEDs
	//
	WrPortI( SPCR, &SPCRShadow, 0x84 );
	WrPortI( PADR, &PADRShadow, 0xff );

	// setupISR();
	SetVectIntern(0x0B, timer_tick );

	//
	// Setup Timer B to run from PCLK/2
	//
	WrPortI( TBCR, &TBCRShadow, 0x01 );
	WrPortI( TBM1R, NULL, 0x00 );
	WrPortI( TBL1R, NULL, 0x00 );
	WrPortI( TBCSR, NULL, 0x03 );

	while( 1 )
	{
		costate
		{
			if( bFireAlarm )
			{
				waitfordone call_fire_department();
				bFireAlarm = 0;
			}

			if( bBreakinAlarm )
			{
				waitfordone call_police();
				bBreakinAlarm = 0;
			}

			waitfor( DelayMs( 200 ) );
		}

		costate
		{
		/*
			printf( "Alarms: %s %s\ntick_count[0] = %d\ntick_count[1] = %d\n",
					bFireAlarm ? "Fire" : " ",
					bBreakinAlarm ? "Police" : " ",
					tick_count[0], tick_count[1] );
		*/

			waitfor( DelayMs( 500 ) );
		}
	}
}

In the example above, a timer tick interrupt service routine is used to monitor the two switches on the Rabbit development board. When the ISR detects that one of the switches has been pressed, it increments a counter. If the switch is released in less than 1024 timer ticks, the count is reset to zero. If the counter reaches 1024, the ISR sets a flag to notify other parts of the application that the switch was pressed. The reason for the counting loop is to debounce the switch press. When switches are pushed, they don't go from open to closed as smoothly as is desired. Instead, as the switch is being pressed, it temporarily makes and breaks the contacts, which results in noise being generated temporarily. This must be taken into account by the software when reading switches directly.


Example #2:

int bFireAlarm;
int bBreakinAlarm;

int tick_count[2];

root interrupt timer_tick()
{
	int	reg;

	//
	// Reset interrupt flag and reload match registers
	//
	reg = RdPortI( TBCSR );
	WrPortI( TBM1R, NULL, 0 );
	WrPortI( TBL1R, NULL, 0 );

	if( !bFireAlarm )
	{
		//
		// Read switch states from port B
		//
		reg = RdPortI( PBDR );		// Read switches

		//
		// Is switch S2 up or down?
		//
		if( !(reg & 0x04) )
		{
			//
			// Increment count if down
			//
			tick_count[0]++;

			//
			// Is debounce delay over?
			//
			if( tick_count[0] >= 1024 )
				bFireAlarm = 1;
		}
		else
		{
			tick_count[0] = 0;
		}
	}
	else
	{
		tick_count[0] = 0;
	}

	if( !bBreakinAlarm )
	{
		//
		// Read switch states from port B
		//
		reg = RdPortI( PBDR );

		//
		// Is switch S3 up or down?
		//
		if( !(reg & 0x08) )
		{
			//
			// Increment count if down
			//
			tick_count[1]++;

			//
			// Is debounce delay over?
			//
			if( tick_count[1] >= 1024 )
				bBreakinAlarm = 1;
		}
		else
		{
			tick_count[1] = 0;
		}
	}
	else
	{
		tick_count[1] = 0;
	}
}		

void setupISR()
{
#asm
	ld			a, iir			; get internal interrupt register
	ld			h, a				; copy to high nibble of HL
	ld			l, 0B0h			; We want Timer B interrupts
	ld			iy, hl			; IY now points to the interrupt table for Timer B
	ld			hl, timer_tick	; Load address of ISR
	ld			(iy), 0C3h		; Put JUMP command (0C3h opcode) into interrupt table
	ld			(iy+1), hl		; Follow JUMP command with address of our ISR
#endasm
}

cofunc void call_police()
{
  printf( "Calling police...");
  BitWrPortI( PADR, &PADRShadow, 0x00, 0 );

  waitfor( DelaySec( 10 ) );  

  BitWrPortI( PADR, &PADRShadow, 0xff, 0);
  printf( "done.\n" );
}

cofunc call_fire_department()
{
  printf( "Calling fire department...");
  BitWrPortI( PADR, &PADRShadow, 0x00, 1 );

  waitfor( DelaySec( 10 ) );

  BitWrPortI( PADR, &PADRShadow, 0xff, 1 );
  printf( "done.\n" );
}

main()
{
	//
	// GLOBAL_INIT sections are run before main()
	//
	#GLOBAL_INIT
	{
		bFireAlarm = 0;
		bBreakinAlarm = 0;
		tick_count[0] = 1;
		tick_count[1] = 2;
	}

	//
	// Setup Port A to drive LEDs, then turn off LEDs
	//
	WrPortI( SPCR, &SPCRShadow, 0x84 );
	WrPortI( PADR, &PADRShadow, 0xff );

	// setupISR();
	SetVectIntern(0x0B, timer_tick );

	//
	// Setup Timer B to run from PCLK/2
	//
	WrPortI( TBCR, &TBCRShadow, 0x01 );
	WrPortI( TBM1R, NULL, 0x00 );
	WrPortI( TBL1R, NULL, 0x00 );
	WrPortI( TBCSR, NULL, 0x03 );

	while( 1 )
	{
		costate
		{
			if( bFireAlarm )
			{
				waitfordone call_fire_department();
				bFireAlarm = 0;
			}

			if( bBreakinAlarm )
			{
				waitfordone call_police();
				bBreakinAlarm = 0;
			}

			waitfor( DelayMs( 200 ) );
		}

		costate
		{
		/*
			printf( "Alarms: %s %s\ntick_count[0] = %d\ntick_count[1] = %d\n",
					bFireAlarm ? "Fire" : " ",
					bBreakinAlarm ? "Police" : " ",
					tick_count[0], tick_count[1] );
		*/

			waitfor( DelayMs( 500 ) );
		}
	}
}

This example is the same as the previous one, except that the interrupt service routine (ISR) is coded in C instead of Assembler. If you compare the two versions, you will see that exactly the same steps are performed in each.


In the earlier examples, uou should have noticed that the call_police() and call_fire_department() functions are marked with the cofunc option. This makes it possible to use the waitfordone statement with those functions. In this case, we only have one task that needs to be done when a switch closes, either call the police or call the fire department. It is quite possible that you may need to perform several tasks in response to external events. If the tasks can run simultaneously, all tasks can be placed inside the waitfordone statement. Dynamic C will run all the cofunc functions in parallel and waits until the slowest one completes before allowing the task to continue with the next statement. This can be useful, be aware that all the functions will be run at the same time apparently.


Example #3:

int bFireAlarm;
int bBreakinAlarm;

int tick_count[2];

root interrupt timer_tick()
{
	int	reg;

	//
	// Reset interrupt flag and reload match registers
	//
	reg = RdPortI( TBCSR );
	WrPortI( TBM1R, NULL, 0 );
	WrPortI( TBL1R, NULL, 0 );

	if( !bFireAlarm )
	{
		//
		// Read switch states from port B
		//
		reg = RdPortI( PBDR );		// Read switches

		//
		// Is switch S2 up or down?
		//
		if( !(reg & 0x04) )
		{
			//
			// Increment count if down
			//
			tick_count[0]++;

			//
			// Is debounce delay over?
			//
			if( tick_count[0] >= 1024 )
				bFireAlarm = 1;
		}
		else
		{
			tick_count[0] = 0;
		}
	}
	else
	{
		tick_count[0] = 0;
	}

	if( !bBreakinAlarm )
	{
		//
		// Read switch states from port B
		//
		reg = RdPortI( PBDR );

		//
		// Is switch S3 up or down?
		//
		if( !(reg & 0x08) )
		{
			//
			// Increment count if down
			//
			tick_count[1]++;

			//
			// Is debounce delay over?
			//
			if( tick_count[1] >= 1024 )
				bBreakinAlarm = 1;
		}
		else
		{
			tick_count[1] = 0;
		}
	}
	else
	{
		tick_count[1] = 0;
	}
}		

void setupISR()
{
#asm
	ld			a, iir			; get internal interrupt register
	ld			h, a				; copy to high nibble of HL
	ld			l, 0B0h			; We want Timer B interrupts
	ld			iy, hl			; IY now points to the interrupt table for Timer B
	ld			hl, timer_tick	; Load address of ISR
	ld			(iy), 0C3h		; Put JUMP command (0C3h opcode) into interrupt table
	ld			(iy+1), hl		; Follow JUMP command with address of our ISR
#endasm
}

cofunc void show_call( char *pMsg )
{
  printf( "Calling %s...", pMsg );
}

cofunc void show_done()
{
  printf( "done.\n" );
}

cofunc void call_police()
{
  BitWrPortI( PADR, &PADRShadow, 0x00, 0 );

  waitfor( DelaySec( 10 ) );  

  BitWrPortI( PADR, &PADRShadow, 0xff, 0);
}

cofunc call_fire_department()
{
  BitWrPortI( PADR, &PADRShadow, 0x00, 1 );

  waitfor( DelaySec( 10 ) );

  BitWrPortI( PADR, &PADRShadow, 0xff, 1 );
}

main()
{
	//
	// GLOBAL_INIT sections are run before main()
	//
	#GLOBAL_INIT
	{
		bFireAlarm = 0;
		bBreakinAlarm = 0;
		tick_count[0] = 1;
		tick_count[1] = 2;
	}

	//
	// Setup Port A to drive LEDs, then turn off LEDs
	//
	WrPortI( SPCR, &SPCRShadow, 0x84 );
	WrPortI( PADR, &PADRShadow, 0xff );

	// setupISR();
	SetVectIntern(0x0B, timer_tick );

	//
	// Setup Timer B to run from PCLK/2
	//
	WrPortI( TBCR, &TBCRShadow, 0x01 );
	WrPortI( TBM1R, NULL, 0x00 );
	WrPortI( TBL1R, NULL, 0x00 );
	WrPortI( TBCSR, NULL, 0x03 );

	while( 1 )
	{
		costate
		{
			if( bFireAlarm )
			{
				waitfordone
				{
					show_call("Fire Department");
					call_fire_department();
					show_done();
				}
				bFireAlarm = 0;
			}

			if( bBreakinAlarm )
			{
				waitfordone
				{
					show_call("Police");
					call_police();
					show_done();
				}
				bBreakinAlarm = 0;
			}

			waitfor( DelayMs( 200 ) );
		}

		costate
		{
		/*
			printf( "Alarms: %s %s\ntick_count[0] = %d\ntick_count[1] = %d\n",
					bFireAlarm ? "Fire" : " ",
					bBreakinAlarm ? "Police" : " ",
					tick_count[0], tick_count[1] );
		*/

			waitfor( DelayMs( 500 ) );
		}
	}
}

The only change in this version of the application is the fact that a waitfordone block is used to run the show_call(), call_police(), and show_done() functions at the same time. As you can see when you execute the program, all three functions will be called and run at the same time. The end result is that the show_call() and show_done() functions will complete their work and return long before the call_police() function does. So, cofunctions provide another way to do multi-tasking in Dynamic C.


Since a cofunction is normally run in parallel with other confuctions, you cannot call the exact same cofunction more than once inside a waitfordone block. This attempts to run the same function twice, simultaneously. This is not allowed, primarily because each confunction only has a single data structure that tracks its execution state.


A single-user confunction provides a slightly different way to do this. A single-user cofunction can be called multiple times from within the same waitfordone block. In this case, Dynamic C will call the single-user cofunctions listed in sequence. After calling the scofunc for the first time, the task waits for it complete, then calls the function again, sequentially if needed.


Example #4

int bFireAlarm;
int bBreakinAlarm;

int tick_count[2];

root interrupt timer_tick()
{
	int	reg;

	//
	// Reset interrupt flag and reload match registers
	//
	reg = RdPortI( TBCSR );
	WrPortI( TBM1R, NULL, 0 );
	WrPortI( TBL1R, NULL, 0 );

	if( !bFireAlarm )
	{
		//
		// Read switch states from port B
		//
		reg = RdPortI( PBDR );		// Read switches

		//
		// Is switch S2 up or down?
		//
		if( !(reg & 0x04) )
		{
			//
			// Increment count if down
			//
			tick_count[0]++;

			//
			// Is debounce delay over?
			//
			if( tick_count[0] >= 1024 )
				bFireAlarm = 1;
		}
		else
		{
			tick_count[0] = 0;
		}
	}
	else
	{
		tick_count[0] = 0;
	}

	if( !bBreakinAlarm )
	{
		//
		// Read switch states from port B
		//
		reg = RdPortI( PBDR );

		//
		// Is switch S3 up or down?
		//
		if( !(reg & 0x08) )
		{
			//
			// Increment count if down
			//
			tick_count[1]++;

			//
			// Is debounce delay over?
			//
			if( tick_count[1] >= 1024 )
				bBreakinAlarm = 1;
		}
		else
		{
			tick_count[1] = 0;
		}
	}
	else
	{
		tick_count[1] = 0;
	}
}		

void setupISR()
{
#asm
	ld			a, iir			; get internal interrupt register
	ld			h, a				; copy to high nibble of HL
	ld			l, 0B0h			; We want Timer B interrupts
	ld			iy, hl			; IY now points to the interrupt table for Timer B
	ld			hl, timer_tick	; Load address of ISR
	ld			(iy), 0C3h		; Put JUMP command (0C3h opcode) into interrupt table
	ld			(iy+1), hl		; Follow JUMP command with address of our ISR
#endasm
}

scofunc void show_msg( char *pMsg )
{
  printf( "%s", pMsg );
}


cofunc void call_police()
{
  BitWrPortI( PADR, &PADRShadow, 0x00, 0 );

  waitfor( DelaySec( 10 ) );  

  BitWrPortI( PADR, &PADRShadow, 0xff, 0);
}

cofunc call_fire_department()
{
  BitWrPortI( PADR, &PADRShadow, 0x00, 1 );

  waitfor( DelaySec( 10 ) );

  BitWrPortI( PADR, &PADRShadow, 0xff, 1 );
}

main()
{
	//
	// GLOBAL_INIT sections are run before main()
	//
	#GLOBAL_INIT
	{
		bFireAlarm = 0;
		bBreakinAlarm = 0;
		tick_count[0] = 1;
		tick_count[1] = 2;
	}

	//
	// Setup Port A to drive LEDs, then turn off LEDs
	//
	WrPortI( SPCR, &SPCRShadow, 0x84 );
	WrPortI( PADR, &PADRShadow, 0xff );

	// setupISR();
	SetVectIntern(0x0B, timer_tick );

	//
	// Setup Timer B to run from PCLK/2
	//
	WrPortI( TBCR, &TBCRShadow, 0x01 );
	WrPortI( TBM1R, NULL, 0x00 );
	WrPortI( TBL1R, NULL, 0x00 );
	WrPortI( TBCSR, NULL, 0x03 );

	while( 1 )
	{
		costate
		{
			if( bFireAlarm )
			{
				waitfordone
				{
					show_msg("Calling Fire Department...");
					call_fire_department();
					show_msg("done.\n");
				}
				bFireAlarm = 0;
			}

			if( bBreakinAlarm )
			{
				waitfordone
				{
					show_msg("Calling Police...");
					call_police();
					show_msg("done.\n");
				}
				bBreakinAlarm = 0;
			}

			waitfor( DelayMs( 200 ) );
		}

		costate
		{
		/*
			printf( "Alarms: %s %s\ntick_count[0] = %d\ntick_count[1] = %d\n",
					bFireAlarm ? "Fire" : " ",
					bBreakinAlarm ? "Police" : " ",
					tick_count[0], tick_count[1] );
		*/

			waitfor( DelayMs( 500 ) );
		}
	}
}

In this last example, the show_msg() function has been marked as a single-user cofunction. Therefore when the waitfordone block is encountered, Dynamic C will call the first show_msg() function, passing it the string "Calling Police?" and the call_police() function at the same time. Once the first call to the show_msg() function has completed, show_msg() is called again the second time around. In other words, the show_msg() function must be called sequentially, but the call_police() function can be run in parallel along with the show_msg() function.