Multi task on PIC16Fxx |Naming Symbols | Traffic Generation 2 | Traffic Generation 2 |

Multi Process code on a Pic - Naming Symbols to Structure the code.

It may seem that running multiple processes or tasks on such a small chip as a PIC16F628 is a bit of an ordeal.

Why split up the problem into separate processes? What is a process?

Process B has to listen to Process A, Process C, the Hardware and respond and report to management.

To aid documentation of the code, prefix each symbol, name, variable, function associated with each process with a two letter code. You can then quickly understand which process it belongs to.


    while(true){
	// loop around each process in turn.
	// These must not block and should return as fast as possible
        // They may run code for any pending events and progress a state machine.        
	PAtimeslice()
	PBtimeslice()
	PCtimeslice()
	MMtimeslice()
    }

Multi Process Code on a PIC

Processes run concurrently, at the same time. They send messages between each other.

They react to incoming messages and process them and maybe send a message to another process.

The processes essentially run concurrently, but on a PIC they cannot. However, the processing required on each event should not take too long and each process can be run in turn and then only if they have a message to process.

If a process executes a delay loop, then it cannot the yield processing power of the chip to another process.

My shipping forecast timer beeps out the time when reviewing the time. This uses a process with a state machine as it would take too long to run and would affect the main loop timing.

Naming of Functions and variables.

It is possible to have the same code that compiles to identical .hex file to be loaded into a PIC chip. However one source code can be impossible to read, and the other easy to understand.

How you name functions variables and lay out the code, is vitally import.

You could write your code and name your variables and functions as you think up the names, but if you have set up processes, give each process or block of functionality a prefix. What I found was that the prefix means that the function or variable belongs to that bit of functionality, and prevents somebody trying to understand your code spending time reading and trying to understanding the wrong bit of code.

I split my shipping forecast timer up into the the following modular blocks:

	CL	- Clock chain functions.
	ST	- State machine temporary variables
	LS	- Loud Speaker driver
	IP	- Input Polling
	SR 	- Serial Out
	OP	- Button Open Logger
	Sound	- Sound process

So I could group the variables as below:

;----------------------------------------------------------------
; Sec 2.0 Variables
;----------------------------------------------------------------
; Variables start 0x0C
;
; Variables for FlashLED ( FL ) process 
;

		;	DoTimeSlice Bits, to schedule, set bit
		;	0	run 	onQtrSecondTick
		;	1	run	onSecondTick
		;	2	run	onMinuteTick
		;	3	run	onHourTick
		;	4	not used
		;	5	run	Serial Port readout
		;	6	BeepPause
		;	7	run	BeepMachine

DoTimeSlice	EQU	0x0C 

STstate		EQU	0x0E	

CLtmr4sec	EQU	0x0F	; counts quarter seconds

CLnbcdS 	EQU	0x10	; stores current time
CLnbcdM 	EQU	0x11
CLnbcdH 	EQU	0x12

CLdbcdM 	EQU	0x15	; stores the time being input
CLdbcdH 	EQU	0x16

CLbbcdM 	EQU	0x18	; stores the time being beeped out
CLbbcdH 	EQU	0x19
CLTNum		EQU	0x1A	; Index into EEPROM when updating 
an ON/OFF time

CLTNumChk	EQU	0x1C	; EEPROM index when testing for ON/OFF time

LScnt		EQU	0x1F	; For Loudspeaker beep generation
LScnt2		EQU	0x20

IPlast		EQU	0x21	; Input variables
IPnew		EQU	0x22
IPbuttonEvent	EQU	0x23
IPsetState	EQU	0x24

SRout 		EQU	0x25	; for RS232 routines
SRcnt		EQU	0x27

SoundCnt	EQU	0x28	; Counts beeps
Sound5Cnt	EQU	0x2A	; count for 5th beep

DispState	EQU	0x2B	; Display State

OPcnt		EQU	0x2C	; Count 
OPopcnt		EQU	0x2D	; Count 

OPbufferStart	EQU	0x30	; 32 bytes of ram that can store Dates

The function names are also grouped by and prefixed with the two letter variable names.

Using Section Headers

I also tried splitting the source code into sections like any good document. I tried putting a comment like:

;----------------------------------------------------------------
; Sec 2.0 Variables
;----------------------------------------------------------------

You could even include a contents comment block, but a grep of "Sec" would produce a contents page.

Apend the structure type - A tautology in context, a reminder out of context

I once worked on a project that had 100,000 symbols. They say that most educated people have a vocabulary of 10,000 words. Being expected to understand the purpose of 100,000 symbols is obviously beyond the educated person. Some structure to the names is required so that you can work out that you can ignore the symbol as irrelevant to what you are working on.

Adding a type coding to the symbol name also tells you how to treat it.

e.g.

Suppose I have a structure to the leakyBucketData.

I could have an array of leakyBucketData, and in my code, I could have a pointer to an instance of leakyBucketData.

If you add postfixs to define the type you end up with symbols:

  
    leakyBucketDataT //- type definition 
    leakyBucketData  //- Instance of leakyBucketDataT
    leakyBucketDataA //- Array of instances of leakyBucketDataT 
    leakyBucketDataP //- Pointer to instance of leakyBucketDataT 

or
 
    struct leakyBucketDataS { 
	int   	fill;
	int 	leak;
    }; 				//- structure definition 

    typedef struct leakyBucketDataS leakyBucketDataT; - type definition 

    leakyBucketDataT lb; 	    //- instance of leakyBucketData 
    leakyBucketDataT lbArray[100];  //- Array of instances of leakyBucketData 
    leakyBucketDataT *lbPointer;    //- Pointer to instance of leakyBucketData

or
 
    struct leakyBucketDataS { 
	int   	fill;
	int 	leak;
    }; 				//- structure definition 

    typedef struct leakyBucketDataS leakyBucketDataT; - type definition 

    leakyBucketDataT lb; 	//- instance of leakyBucketData 
    leakyBucketDataT lbA[100];  //- Array of instances of leakyBucketData 
    leakyBucketDataT *lbP; 	//- Pointer to instance of leakyBucketData

This is much better than just adding 1,2 to the end of the symbol to make it unique:
 
    struct leakyBucketData { 
      int fill;
      int leak;
    };

    struct leakyBucketData lb; 		//- instance of leakyBucketData 
    struct leakyBucketData lb1[100];  	//- Array of instances of leakyBucketData 
    struct leakyBucketData *lb2; 	//- Pointer to instance of leakyBucketData

This is much easier to read and maintain.
  
    leakyBucketDataT             //- type 
    leakyBucketDataT lb;         //- instance
    leakyBucketDataT lbA[100];   //- array  
    leakyBucketDataT *lbP;       //- pointer  

You could argue that this seems to be tautological as the postfix duplicates the context. lbA is a an array as it has [100] after the definition, lbP is a pointer as it has a * in front of it. However, this means you need to constantly refer to the variable declarations to know the type if you do not postfix the symbol names and use them out of context. This wastes time and adds complexity. What is easier to read and understand
 
    lb  = ...
    lb1 = ... 
    lb2 =  ...

or
 
    lb        = ... 
    lbArray   = ...
    lbPointer = ...
or
 
    lb  = ... 
    lbA = ...
    lbP = ...
This is not a novel idea, but does not seem to be a subject taught on computer science courses. I have seen _ and __ prefix symbolds to do the same thing.

Here is the program with pointers dhr.c

Here is some Dephi ( note Delphi puts the type after the variable name )


type
  POFNotifyA = ^TOFNotifyA;
  POFNotifyW = ^TOFNotifyW;
  POFNotify = POFNotifyA;
  TOFNotifyA = packed record
    hdr: TNMHdr;
    lpOFN: POpenFilenameA;
    pszFile: PAnsiChar;
  end;
  TOFNotifyW = packed record
    hdr: TNMHdr;
    lpOFN: POpenFilenameW;
    pszFile: PWideChar;
  end;
  TOFNotify = TOFNotifyA;

Example C program


/* --- The following code comes from c:\lcc\lib\wizard\textmode.tpl. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// ============================
struct leakyBucketData {
	int 	fill;
	int 	leak;
};


/*
*  old format variable names lb0,lb1,lb2
* use no postfix. I have run out of names so use lb1,lb2
*/

struct leakyBucketData lb0; 		//- instance of leakyBucketData
struct leakyBucketData lb1[100]; //- Array of instances of leakyBucketData
struct leakyBucketData *lb2; 	//- Pointer to instance of leakyBucketData


/*
* Use Type Defs to simplify
* use a postfix T for Typedef
*/
typedef struct leakyBucketData leakyBucketDataT; //- type definition

leakyBucketDataT lb; 		//- instance of leakyBucketData

/*
* new format variable names lb,lbArray,lbPtr
* use a postfix Array for arrays and Ptr for Pointers
* This may be a bit verbose
*/
leakyBucketDataT lbArray[100];  //- Array of instances of leakyBucketData
leakyBucketDataT *lbPtr; 		//- Pointer to instance of leakyBucketData

/*
* new format variable names lb,lbA,lbP
* use a postfix A for Array and P for Pointer
*/
leakyBucketDataT lbA[100];  //- Array of instances of leakyBucketData
leakyBucketDataT *lbP; 		//- Pointer to instance of leakyBucketData

// ============================
void init( void ){
	int cnt;

	for ( cnt = 0 ; cnt < 100 ; cnt++ ){
		lb1[ cnt ].fill = cnt;
		lb1[ cnt ].leak = 50-cnt;
	}


	for ( cnt = 0 ; cnt < 100 ; cnt++ ){
		lbArray[ cnt ].fill = cnt;
		lbArray[ cnt ].leak = 50-cnt;
	}

	for ( cnt = 0 ; cnt < 100 ; cnt++ ){
		lbA[ cnt ].fill = cnt;
		lbA[ cnt ].leak = 50-cnt;
	}
}

int main(int argc,char *argv[]){


	printf("\n=================!");
	printf("\nHello World!");
	printf("\n=================!");
	printf("\n");

	init();

	printf("\n=================!");
	/*
	*  old format variable names lb0,lb1,lb2
        * use no postfix. I have run out of names so use lb1,lb2
	*/
	printf("\n %d %d ",lb1[3].fill,lb1[50].fill ) ;
	printf("\n %d %d ",lb1[3].leak,lb1[50].leak );

	lb2 = lb1;
	printf("\n %d %d ",(*lb2).fill,(*lb2).leak );

	lb2 = &lb1[89];
	printf("\n %d %d ",lb2->fill,lb2->leak );

	lb2 = &lb1[35];
	printf("\n %d %d ",lb2->fill,lb2->leak );

	lb2 = lb1;	//
	lb2 +=10;   // increment point by 10 elements of the array
	printf("\n %d %d ",lb2->fill,lb2->leak );

	lb2 +=10;   // increment point by 10 elements of the array
	printf("\n %d %d ",lb2->fill,lb2->leak );

	lb2 +=10;   // increment point by 10 elements of the array
	printf("\n %d %d ",lb2->fill,lb2->leak );

	lb2 +=10;   // increment point by 10 elements of the array
	printf("\n %d %d ",lb2->fill,lb2->leak );


	printf("\n=================!");
	/*
	* new format variable names lb,lbArray,lbPtr
        * use a postfix Array for arrays and Ptr for Pointers
	* This may be a bit verbose
 	*/
	printf("\n %d %d ",lbArray[3].fill,lbArray[50].fill ) ;
	printf("\n %d %d ",lbArray[3].leak,lbArray[50].leak );

	lbPrt = &lbArray[89];

	printf("\n %d %d ",(*lbPrt).fill,(*lbPrt).leak );

	lbPrt = &lbArray[35];
	printf("\n %d %d ",lbPrt->fill,lbPrt->leak );

	lbPrt = lbA;	//
	lbPrt +=10;   // increment point by 10 elements of the array
	printf("\n %d %d ",lbPrt->fill,lbPrt->leak );

	lbPrt +=10;
	printf("\n %d %d ",lbPrt->fill,lbPrt->leak );

	lbPrt +=10;
	printf("\n %d %d ",lbPrt->fill,lbPrt->leak );

	lbPrt +=10;
	printf("\n %d %d ",lbPrt->fill,lbPrt->leak );


	printf("\n=================!");
	/*
	* new format variable names lb,lbA,lbP
	* use a postfix A for Array and P for Pointer
	*/
	printf("\n %d %d ",lbA[3].fill,lbA[50].fill ) ;
	printf("\n %d %d ",lbA[3].leak,lbA[50].leak );

	lbP = &lbA[89];
	printf("\n %d %d ",(*lbP).fill,(*lbP).leak );

	lbP = &lbA[35];
	printf("\n %d %d ",lbP->fill,lbP->leak );

	lbP = lbA;	//
	lbP +=10;   // increment point by 10 elements of the array
	printf("\n %d %d ",lbP->fill,lbP->leak );

	lbP +=10;
	printf("\n %d %d ",lbP->fill,lbP->leak );

	lbP +=10;
	printf("\n %d %d ",lbP->fill,lbP->leak );

	lbP +=10;
	printf("\n %d %d ",lbP->fill,lbP->leak );

	printf("\n=================!");

	return 0;
}


Thoughs on Distributed state machines as used in Object Oriented Programming

http://java.sun.com/docs/books/tutorial/java/concepts/ What Is an Object?

An object is a software bundle of related state and behavior. Software objects are often used to model the real-world objects that you find in everyday life. This lesson explains how state and behavior are represented within an object, introduces the concept of data encapsulation, and explains the benefits of designing your software in this manner.

Thoughs on Distributed state machines as used in Object Oriented Programming, and web sites.

Here is combinatory simple logic system.

Here is a simple state machine system, as outputs are fed back to provide history.

Here is a simple state machine system, as outputs are fed back to provide history. A latch is added to clock the state machine.

Note: In software, it is probably impossible to not have a latch. If the state machine is written in a function called ProcessTimeSlice(), then every time the function is called, it is like clocking the state machine latch. In further examples, they will all be latched. There may even be more than one latch, each clocked by their own bit of software.

Here is a state machine where the state information is stored in more than one place. There is an assumption that the state machine is not run continously. Often it is only run when the inputs change or on a regular time tick. Getting the state from a database may require a data base query. Maybe the state is held in a local copy, and either the database is responsible for pushing down updates or the statemachine is responsible for polling for updates.

In the diagram above, any available processor can be given the code for the combinationatory logic and run it. It will need to be given access to the state variables, and to avoid corruption, these will be locked for sole use by the processor. It will need a way of storing the state variables back into any central data store. so:
 
localCopyOfStateVariables =  StateVariables

get from array:
 
localCopyOfStateVariables =  StateVariables[ instanceWanted ]

get from any where:
 
localCopyOfStateVariables =  getStateVariables( instanceWanted )
...
putStateVariables(    instanceWanted )

Here is an outline of running with locks:
 
lockStateVariables(   instanceWanted )
localCopyOfStateVariables = getStateVariables( instanceWanted )
localCopyOfStateVariables = runCombinatoryLogic( localCopyOfStateVariables )
putStateVariables(    instanceWanted )
unlockStateVariables( instanceWanted )

Here is a state machine used to model analogue electronics.

Thats enough for now!