Thursday, October 9, 2008

multithreaded approach

I rewrote the first test program with a multithreaded approach. Basically there are three thread involved:

  1. PollThread
  2. LogicThread
  3. MainThread
The Pollthread serve to check the FTDI ADBUS line connected to the interrupt. Whenever an interrupt arrives an Event is set to signal the MainThread that work has to be done. The access to the FDTI is guarded by a mutex. Let me show you the code:

DWORD WINAPI PollThread ( LPVOID lp )
{
UCHAR ad [1];
UCHAR in [5];
UCHAR out [5];

status = FTDI_I2C_STARTUP( _400KHZ );
status = FTDI_I2C_PCA_READN_ALL( PCA9698_INPUT_READ , in );
RefSet.FromByteBuf(in);
RefSet.flip();
RefOut.ToByteBuf(CmdOut);
status = FTDI_I2C_PCA_WRITE_ALL( PCA9698_OUTPUT_WRITE , CmdOut );
while (1)
{
WaitForSingleObject(InterruptAckEvent,INFINITE);
WaitForSingleObject(FtdiMutex,INFINITE);
status = FTDI_2XX_READN_ADBUS(ad);
ReleaseMutex(FtdiMutex);
while ( (ad[0] & INT_MASK_FLAG) != 0 )
{
WaitForSingleObject(FtdiMutex,INFINITE);
status = FTDI_2XX_READN_ADBUS(ad);
ReleaseMutex(FtdiMutex);
}
SetEvent(InterruptEvent);
}
return 0;
}

The second thread is the LogicThread that will be created by the MainThread on event basis. This thread reads the inputs resets the interrupt and sends an acknowledge to the the PollThread. This is needed because otherwise the interrupt event will be send more than ones if it is not reset. The read and writes will be guarded by the same mutex as the PollThread. Here is the code:

DWORD WINAPI LogicThread ( LPVOID lp )
{
UCHAR in [5];
UCHAR out [5];


WaitForSingleObject(FtdiMutex,INFINITE);
status = FTDI_I2C_PCA_READN_ALL( PCA9698_INPUT_READ , in );
ReleaseMutex(FtdiMutex);

SetEvent(InterruptAckEvent);

BitSetExtended<40> InNew(in);
InNew.flip();

for ( int input = 0 ; input <>
{
if ( (RefSet[input] != InNew[input]) && (InNew.test(input)) )
{
RefOut.flip(input);
}
}

RefSet = InNew;
RefOut.ToByteBuf(CmdOut);

WaitForSingleObject(FtdiMutex,INFINITE);
status = FTDI_I2C_PCA_WRITE_ALL( PCA9698_OUTPUT_WRITE , CmdOut );
status = FTDI_I2C_DAC_SET(0,0);
ReleaseMutex(FtdiMutex);

return 0;
}

The last thread is the main and is very small. Basically this thread waits until the interrupt event is set and creates the LogicThread with a priority just a bit higher to force the context switch immediately:

InterruptEvent = CreateEvent(NULL,false,false,L"Local\\INT");
InterruptAckEvent = CreateEvent(NULL,false,true,L"Local\\INTACK");
FtdiMutex = CreateMutex(NULL,false,L"Local\\FTDI");

// start to poll
CreateThread(NULL,0,PollThread,NULL,0,&PollThreadId);

while (1)
{
WaitForSingleObject(InterruptEvent,INFINITE);
hThreadLogic = CreateThread(NULL,0,LogicThread,NULL,CREATE_SUSPENDED,&LogicThreadId);
SetThreadPriority(hThreadLogic,THREAD_PRIORITY_ABOVE_NORMAL);
ResumeThread(hThreadLogic);
CloseHandle(hThreadLogic);
}

The program works very stable. The only thing I noticed was that the process better runs with ABOVE_NORMAL priority as normal on the eee Asus PC. This is due to having only a single core and running quit small with 1GHz. The only improvement I can add is a mutex object on the input BitSet to exclude all possible race conditions possible. But it's only a test of course.