Paul E. Schoen
2010-01-04 11:05:33 UTC
Some time ago I came up with a simple way to implement delays by using a
loop as follows:
type
TDelayObject = class(TObject)
Timer1: TTimer;
private
procedure Timer1Timer(Sender: TObject);
public
Timeout: Integer;
end;
var
DelayObject: TDelayObject;
procedure Delay( const Msec: Integer );
var fTimerEvent: TNotifyEvent;
{$J+}
const DelayInProgress: Boolean = False;
{$J-}
begin
If DelayInProgress = True then
exit;
DelayInProgress := True;
DelayObject := TDelayObject.Create;
DelayObject.Timeout := mSec;
DelayObject.Timer1 := TTimer.Create(nil);
DelayObject.Timer1.Interval := 100;
fTimerEvent := DelayObject.Timer1Timer;
DelayObject.Timer1.OnTimer := fTimerEvent;
while DelayObject.Timeout > 0 do
Application.ProcessMessages;
DelayObject.Timer1.Free;
DelayObject.Timer1 := nil;
DelayObject.Free;
DelayObject := nil;
DelayInProgress := False;
end;
procedure TDelayObject.Timer1Timer(Sender: TObject);
begin
if fmDelay <> nil then
fmDelay.Edit1.Text := FloatToStr( Timeout/1000 );
If DelayObject.TimeOut > 0 then
DelayObject.TimeOut := DelayObject.TimeOut - 100;
end;
This has worked, or at least seemed to work, in previous applications where
I was using a Serial Port component, which had threads and required delays
in the main application to allow for certain operations to occur and
results returned on the serial port, which was actually a virtual port
using the usbser.sys driver.
Now, however, I am trying to implement a direct "Generic" USB connection
using the Microchip mchpusb.sys driver which allows direct writing and
reading on the USB pipes for a custom USB device (which uses the same
hardware but different PIC code). It has worked reasonably well but I had
problems where I needed to use delays. The Delay function above worked well
most of the time but at other times it seemed to "short-circuit", with no
appreciable delay, and I finally discovered that this was only while a
thread was running. The thread loops on a call to ReadUSB and transfers any
characters to a buffer for later processing. It is implemented as follows:
type
TUSBThread = class(TThread)
private
procedure UpdateMemo;
procedure SetNil(Sender: TObject);
protected
constructor Create(CreateSuspended: Boolean);
procedure Execute; override;
end;
var thUSBread: TUSBThread;
fThreadTerminate: TNotifyEvent;
constructor TUSBThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := True;
end;
procedure TUSBthread.UpdateMemo;
begin
if fmOrtUSB.Memo1 <> nil then
fmOrtUSB.Memo1.Lines.Add(ThreadStr);
end;
procedure TUSBThread.SetNil(Sender: TObject);
begin
thUSBread := nil;
if fmOrtUSB.Memo1 <> nil then
fmOrtUSB.Memo1.Lines.Add('Thread terminated and nil');
end;
procedure TUSBthread.Execute;
var i: Integer;
Data: Byte;
begin
ThreadStr := 'Thread Running';
fmOrtUSB.USBThreadRunning := True;
Synchronize(UpdateMemo);
while fmOrtUSB.Tmo > 0 do begin //1
If Terminated then
exit;
Inc(LoopCount);
// This function has a timeout of 100 mSec
if(_MPUSBRead(myInPipe,receive_buf,64,RecvLength,100)<>0) then begin
//2
// USB Read Successful
for i := 0 to RecvLength-3 do begin //3
Inc(TotalData);
Data := receive_buf[i+2];
fmOrtUSB.WriteBuffer(Data,fmOrtUSB.USBCommBuffer);
Inc(DataCount); end;//-3
fmOrtUSB.Tmo := TMO_VAL;
end //-2
else // USB Read timed out
Dec(fmOrtUSB.Tmo);
end; //-1
ThreadStr := 'Thread Timeout';
Synchronize(UpdateMemo);
fmOrtUSB.USBThreadRunning := False;
end;
When I send a character to the USB device I typically await a response so I
make sure the thread is running. If there is nothing received in 25 loops
at 100 mSec, the thread terminates and is made free and nil.
procedure TfmOrtUSB.USBSendChar( ch: char );
var SentDataLength:DWORD ;
SendLength: Integer;
begin
Tmo := TMO_VAL; // I use 25 which is 2.5 seconds with 100 mSec USBRead
Timeout
if thUSBread = nil then begin
thUSBread := TUSBThread.Create(True);
fThreadTerminate := thUSBread.SetNil;
thUSBread.OnTerminate := fThreadTerminate;
thUSBread.Priority := MyPriority; end; // I'm using tpTimeCritical =
highest available
send_buf[0]:= $80;
send_buf[1]:= 3;
send_buf[2]:=BYTE(ch);
SendLength := 3;
thUSBread.Resume;
if(_MPUSBWrite(myOutPipe,send_buf,SendLength,SentDataLength,100)<>0) then
begin
Tmo := TMO_VAL;
thUSBread.Resume; end;
end;
Admittedly this may not be the best way to do this, but it seems to work
except for the problem of the delays, which are necessary. And the timeout
and termination of the thread is a way that I can sense if the connection
has failed (such as removal of the USB cable) and alert the user of a
problem.
I only just now discovered the problem with the delay function, and I have
some ideas to try. But perhaps there is a better way to perform delays. And
I still do not understand why the delay function does not seem to work. It
is as if the DelayInProgress Boolean gets set and not reset. And I tried
commenting out those parts and I got an AV where the DelayObject was nil on
the Timer tick.
Time for some sleep and a fresh start tomorrow.
Thanks,
Paul
loop as follows:
type
TDelayObject = class(TObject)
Timer1: TTimer;
private
procedure Timer1Timer(Sender: TObject);
public
Timeout: Integer;
end;
var
DelayObject: TDelayObject;
procedure Delay( const Msec: Integer );
var fTimerEvent: TNotifyEvent;
{$J+}
const DelayInProgress: Boolean = False;
{$J-}
begin
If DelayInProgress = True then
exit;
DelayInProgress := True;
DelayObject := TDelayObject.Create;
DelayObject.Timeout := mSec;
DelayObject.Timer1 := TTimer.Create(nil);
DelayObject.Timer1.Interval := 100;
fTimerEvent := DelayObject.Timer1Timer;
DelayObject.Timer1.OnTimer := fTimerEvent;
while DelayObject.Timeout > 0 do
Application.ProcessMessages;
DelayObject.Timer1.Free;
DelayObject.Timer1 := nil;
DelayObject.Free;
DelayObject := nil;
DelayInProgress := False;
end;
procedure TDelayObject.Timer1Timer(Sender: TObject);
begin
if fmDelay <> nil then
fmDelay.Edit1.Text := FloatToStr( Timeout/1000 );
If DelayObject.TimeOut > 0 then
DelayObject.TimeOut := DelayObject.TimeOut - 100;
end;
This has worked, or at least seemed to work, in previous applications where
I was using a Serial Port component, which had threads and required delays
in the main application to allow for certain operations to occur and
results returned on the serial port, which was actually a virtual port
using the usbser.sys driver.
Now, however, I am trying to implement a direct "Generic" USB connection
using the Microchip mchpusb.sys driver which allows direct writing and
reading on the USB pipes for a custom USB device (which uses the same
hardware but different PIC code). It has worked reasonably well but I had
problems where I needed to use delays. The Delay function above worked well
most of the time but at other times it seemed to "short-circuit", with no
appreciable delay, and I finally discovered that this was only while a
thread was running. The thread loops on a call to ReadUSB and transfers any
characters to a buffer for later processing. It is implemented as follows:
type
TUSBThread = class(TThread)
private
procedure UpdateMemo;
procedure SetNil(Sender: TObject);
protected
constructor Create(CreateSuspended: Boolean);
procedure Execute; override;
end;
var thUSBread: TUSBThread;
fThreadTerminate: TNotifyEvent;
constructor TUSBThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := True;
end;
procedure TUSBthread.UpdateMemo;
begin
if fmOrtUSB.Memo1 <> nil then
fmOrtUSB.Memo1.Lines.Add(ThreadStr);
end;
procedure TUSBThread.SetNil(Sender: TObject);
begin
thUSBread := nil;
if fmOrtUSB.Memo1 <> nil then
fmOrtUSB.Memo1.Lines.Add('Thread terminated and nil');
end;
procedure TUSBthread.Execute;
var i: Integer;
Data: Byte;
begin
ThreadStr := 'Thread Running';
fmOrtUSB.USBThreadRunning := True;
Synchronize(UpdateMemo);
while fmOrtUSB.Tmo > 0 do begin //1
If Terminated then
exit;
Inc(LoopCount);
// This function has a timeout of 100 mSec
if(_MPUSBRead(myInPipe,receive_buf,64,RecvLength,100)<>0) then begin
//2
// USB Read Successful
for i := 0 to RecvLength-3 do begin //3
Inc(TotalData);
Data := receive_buf[i+2];
fmOrtUSB.WriteBuffer(Data,fmOrtUSB.USBCommBuffer);
Inc(DataCount); end;//-3
fmOrtUSB.Tmo := TMO_VAL;
end //-2
else // USB Read timed out
Dec(fmOrtUSB.Tmo);
end; //-1
ThreadStr := 'Thread Timeout';
Synchronize(UpdateMemo);
fmOrtUSB.USBThreadRunning := False;
end;
When I send a character to the USB device I typically await a response so I
make sure the thread is running. If there is nothing received in 25 loops
at 100 mSec, the thread terminates and is made free and nil.
procedure TfmOrtUSB.USBSendChar( ch: char );
var SentDataLength:DWORD ;
SendLength: Integer;
begin
Tmo := TMO_VAL; // I use 25 which is 2.5 seconds with 100 mSec USBRead
Timeout
if thUSBread = nil then begin
thUSBread := TUSBThread.Create(True);
fThreadTerminate := thUSBread.SetNil;
thUSBread.OnTerminate := fThreadTerminate;
thUSBread.Priority := MyPriority; end; // I'm using tpTimeCritical =
highest available
send_buf[0]:= $80;
send_buf[1]:= 3;
send_buf[2]:=BYTE(ch);
SendLength := 3;
thUSBread.Resume;
if(_MPUSBWrite(myOutPipe,send_buf,SendLength,SentDataLength,100)<>0) then
begin
Tmo := TMO_VAL;
thUSBread.Resume; end;
end;
Admittedly this may not be the best way to do this, but it seems to work
except for the problem of the delays, which are necessary. And the timeout
and termination of the thread is a way that I can sense if the connection
has failed (such as removal of the USB cable) and alert the user of a
problem.
I only just now discovered the problem with the delay function, and I have
some ideas to try. But perhaps there is a better way to perform delays. And
I still do not understand why the delay function does not seem to work. It
is as if the DelayInProgress Boolean gets set and not reset. And I tried
commenting out those parts and I got an AV where the DelayObject was nil on
the Timer tick.
Time for some sleep and a fresh start tomorrow.
Thanks,
Paul