CommX・WindowsXPで通信不可

解決


daiya  URL  2004-08-31 20:38:09  No: 10739

Delphi5でCommXを使い、電子天秤と通信するフリーソフトを製作公開しています。
今回、東芝のノートブック(WindowsXP)で、ハイパーターミナルでは問題なく受信出来るのですが、CommXだと全く受信できないという不具合に遭遇しました。
結局OSをwindows98にして送受信は出来るようになったのですが、何か情報がありましたら教えて下さい。
WindowsXpで正常に送受信出来ているユーザーも居り、原因が分からず対処できずにおります。


take  2004-09-01 20:24:57  No: 10740

レスが付かないようなので
まずは必要最低限のソース
TCommXクラス生成からPortOpenまでに行っているProperty設定
の提示をお願いします。
次に電子天秤とのやりとりがわかりませんが
PC->電子天秤(データ要求
PC<-電子天秤(取得したいデータ
といやりとりなのでしょうか?

可能であれば
東芝<-クロスケーブル->他のPC
と環境を用意して正常にやりとりできるのか確認するのが一番だと思います。

ただハイパーターミナルで動いてTCommXで動かないという現象はこちらでは
起きたことが無いです。


daiya  2004-09-02 05:18:46  No: 10741

takeさん  こん**は

電子天秤の連続送出モードでは1秒間に4−8回程度の垂れ流し状態のg数データを受信することになります。
データフォーマットは      P1D1D2D3D4D5D6D7U1U2S1S2CRLF     デリミタ(CRLF)を含めて14文字でデータブロックが構成されている。
P1:              +か−(マイナス)
D1−D7:        _または数字(0−9)            _はスペース
U1U2:           _  G
S1S2:            _  (データ安定の意)      
と言う構成になっています。
具体的に、123.45gの計量を行った場合、データは「+_123.45_G_SCRLF」という14バイトの文字列となっている。
また0.0gの時のデータは「+____0.0_G_SCRLF」となる。

14バイトのg数データを2ブロック分(14*2=28バイト)をまとめて1回で受信すれば、受信データの先頭で+を受信できなくとも、受信文字列の中に有効なg数データが入っているはずである。
この28バイトの受信文字列からLF(復帰コード#10)の場所を基に有効g数データを切り出す事で連続送出モードに対応しています。

通信部分のソースは下記の通りです。

/電子天秤からのシリアル信号受信処理ブロック/
procedure TFrmMain.CommX1Receive(Sender: TObject; ReceiveSize:Integer);
var
buf : PChar;
weightbuf : string;
weightbufA : string;
weight : string;
weight_B : string;
weight_CK : string;
wflg_A: string;
wflg_H : Currency;
wflg_I : Integer;
i : Integer;
j : Integer;
k : Integer;
begin
buf:=AllocMem(28);
try
CommX1.ReceiveBlock(buf,28);
for i := 0 to 27 do begin
weightbufA := weightbufA + Buf[i];
end;
finally
FreeMem(Buf, 28);
end;

j := Pos(#10,weightbufA); //有効g数の切り出し #10=LFの場所を得る

if j<14 then begin //#10が13以下
weightbuf := copy(weightbufA,j+1,14);
end else begin //#10が14
weightbuf := copy(weightbufA,j-13,14);
end;

//切り出し後のg数データのチェック
weight_CK := copy(weightbuf,13,2) ;
if (weight_CK <> #13#10) then begin
EXIT; //入力エラー時パス
end;

wflg_A := copy(weightbuf,1,1); //+-
weight_B := copy(trim(weightbuf),2,7); //+__***.*     g数  文字列
wflg_I := 0; //+の時用に初期化
weightbufA := '';

wflg_H := StrToCurr(weight_B); //  g数  絶対値

if wflg_A = '-' then begin //-
if wflg_H > 0.09 then begin //マイナス0.1g以上で記録  ブレ対策  
wflg_I :=-1;
end else begin
wflg_I :=0;
end;
end else begin //+
if wflg_H > 0 then begin
wflg_I := 1;
end else begin
wflg_I := 0;
end;
end;

case wflg_I of
1: begin
if MstDsp.Visible then begin                         //薬品選択時
weight := copy(trim(weight_B),1,5);
D_Weight.Value := StrToFloat(weight);
wflg_B.Value := StrToFloat(weight);

上記ソースでWindowsXpの一部機種で受信ができないという不具合が発生しています。ポート関係のエラー表示もなく、WindowsXp付属のハイパーターミナルでは正常に受信しております。
またWindowXp搭載の他のPCで正常稼働しているモノも多々あり、原因が全くわかりません。


take  2004-09-02 18:01:31  No: 10742

まずハッキリさせておきたいのですが受信できない環境というのは
手元にあるのでしょうか?

手元にある場合はOnReceive内にブレイクポイントを設けて受信でき
るか調べることでTCommXが悪いのか使い方の問題か切り分けられる
と思います。

手元にない場合は受信したデータをひたすらTListBoxなどに送る
だけのプログラムを作成しそれを受信できない環境をお持ちの人に
配布しどのようなデータが送られてくるのかを確認することで解決
につながるかと思います。

次にソースを拝見しましたが、どちらかというとTCommXのプロパティ
に代入している部分つまり初期設定を見たかったのですが無いよう
なのでなんとも言えませんが

まずCommX1Receiveイベント内で
CommX1.ReceiveBlock(buf,28);
for i := 0 to 27 do begin
weightbufA := weightbufA + Buf[i];
end;
と28バイトのデータがあることを期待して処理しておられるようですが
Receiveイベントは最初の1バイト目でも発生することも十分考えられる
ので、この処理方法は問題があると思います。
また何かの拍子にデータの受信に失敗し1バイトずれただけでもその後
同期がとれなくなると思います。

CRLFがデリミタということで似たような物を過去に作ったことがありま
すので、そのときのサンプルソースを提示します。

受信クラス
interface

uses CommX,ExtCtrls,Classes,SysUtils;

type TCommInReceiveEvent = procedure(Sender: TObject; ReceiveStr: string) of Object;

//--------------------------------------------------------------------------//
//  プリンターからの受信クラス                                              //
//--------------------------------------------------------------------------//
type
  TCommIn = class(TPersistent)
  private
    { Private 宣言 }
    FComm : TCommX;
    FRecStr : string;
    FOnReceive: TCommInReceiveEvent;
    procedure DoReceive(str : string);
    procedure OnCommReceive(Sender: TObject; ReceiveSize: Integer);
  public
    { Public 宣言 }
    constructor Create;
    destructor Destroy;override;

    procedure PortOpen();
    procedure PortClose();

    property OnReceive : TCommInReceiveEvent read FOnReceive write FOnReceive;
  end;

implementation

{ TRdSchedulelItem }

constructor TCommIn.Create;
begin
  FComm := TCommX.Create(nil);
  FComm.OnReceive := OnCommReceive;
end;

destructor TCommIn.Destroy;
begin
  FComm.Free;
  inherited;
end;

procedure TCommIn.DoReceive(str: string);
begin
  if Assigned(FOnReceive) then FOnReceive(Self,str);
end;

procedure TCommIn.OnCommReceive(Sender: TObject;
  ReceiveSize: Integer);
var
  i : integer;
  d : integer;
  Buf :array[0..2047] of char;             // 受信バッファ
begin
  FComm.ReceiveBlock(Buf,ReceiveSize);     // 受信バイト数分まとめて取得
  for i := 0 to ReceiveSize-1 do begin     // 受信データバイト分ループ
    d := Integer(Buf[i]);                  // 1バイトずつ処理
    case d of
      $0a:  begin
              FRecStr := FRecStr + Char(d);
              DoReceive(FRecStr);
              FRecStr := '';
            end;
    else    begin
              FRecStr := FRecStr + Char(d);
            end;
    end;

  end;
end;

procedure TCommIn.PortClose;
begin
  FComm.PortClose;
end;

procedure TCommIn.PortOpen;
begin
  FComm.PortNo       := 1;                    // ポート番号
  FComm.BitRate      := 115200;               // 通信速度
  FComm.CharSize     := 8;                    // データ長
  FComm.ParityBit    := cpbEven;              // パリティチェック
  FComm.StopBit      := csb1;                 // ストップビット
  FComm.FlowCtrl     := cfcRtsCts;            // RtsCtsフロー制御
  FComm.TimeOutTrans := 1000;                 // タイムアウトを1秒に設定
  FComm.PortOpen;
end;

というクラスを作成しておき

FormのPrivateに
    FCommIn : TCommIn;
    procedure OnCommReceive(Sender: TObject; ReceiveStr: string) ;

という具合に定義した後、Formにはイベントとして
FormCreate、FormDestroy、FormShow
を作ります。

FormCreate内に
  FCommIn := TCommIn.Create;
  FCommIn.OnReceive := OnCommReceive;

FormDestroy内に
  FCommIn.PortClose;
  FCommIn.Free;

FormShow内に
  FCommIn.PortOpen;

と定義すれば1データ受信の度に
procedure TForm1.OnCommReceive(Sender: TObject; ReceiveStr: string);
内に処理がくるかと思います。

こういう使い方をしておけば先ほど言ったような不具合は起きないと思われます。


daiya  2004-09-05 03:50:28  No: 10743

takeさん  詳細な御指導ありがとうございます。
やっと電子天秤が空いたのでtakeさんのソースを参考に書き換えてトライしてみます。

不具合の発生したPCは遠く四国の地にあるので、当方で動作確認できたらコンパイルして送付・試してもらうしかないですね。
しばらくお時間を下さい。


daiya  2004-09-06 07:27:15  No: 10744

takeさん  こん**は
雨模様だったので電子天秤のデータと格闘してみました。
OSはwindows2000で受信エラーは出ていないPCです。

1:bufサイズを固定でなく受信バイト数にしてみた。
CommX1Receiveイベント内で
CommX1.ReceiveBlock(buf,ReceiveSize);
for i := 0 to ReceiveSize-1 do begin
weightbufA := weightbufA + Buf[i];
end;
とすると、確かにReceiveイベントは最初の1バイト目でも発生するようで、ReceiveSizeが2−22と不定でした。

2:CommXのヘルプ  
制御機器などとの通信の項目に次のようなことが書いてあります。

procedure TForm1.Button1Click(Sender: TObject);
var
  buf : array[0..255] of Char;

  len : Integer;
begin
  CommX1.TransString('00RD0001');

  len := CommX1.ReceiveBlock(buf, 10);
  if len = 10 then begin
    ShowMessage('正常終了');
  end
  else begin
    ShowMessage('異常終了');
  end;
end;

  ボタンをクリックするとコマンドを送信し、10文字受信するまで受信手続きの中で待機します。
−−−−−−−−−−−−−−−−−−−−−−−
電子天秤のデータは、同期が取れないのでCommX1.ReceiveBlock(buf, 28)とサイズを指定すれば、28バイト受信するまで待機してくれるものと思いますが、違うのでしょうか?


take  2004-09-06 18:14:43  No: 10745

作者に問い合わせたわけではないので推測に過ぎませんがおそらくTCommXは
受信まで待機する方法とイベントによる方法を両立せるため、このような方
式になっているのだと思います。

サンプルの方法では確かに10バイト受信するまで待機するかと思います。
ただしOnReceiveイベントの中ではReceiveSize以上のデータを処理しては
いけないとヘルプに書いてあったと思います。
ですので正常な受信を行うにはサンプルのように送信直後にReceiveBlockを
使って受信するか(受信中は他の処理は停止)提示したサンプルを使用した
方法(受信中も他の処理が可能)のどちらかをしてください。

なお受信バッファとして Buf :array[0..2047] of char;と2048バイト固定
になっていますが、これはTCommXの受信バッファ初期値が2048バイトのため
ReceiveSizeは必ず2048以下になるのでバッファがあふれることはありません。

今回の説明でわかりにくかった所がありましたら詳しく説明しますので指摘
してください。
では健闘を祈ります。


take  2004-09-06 18:43:46  No: 10746

訂正です。
TCommXの受信バッファは2048なのでオーバーしないと書きましたが
実はXPを含むNT系のOSではOS側に蓄積された受信データが一気に来るため
この制限を超えた量が来ることもあるようです。

ですのでReceiveBlockで読み込む前に使用するバッファ以上が来るようであれば
読み込む量をバッファ以下にするなどの工夫も必要のようです。


daiya  2004-09-08 03:21:42  No: 10747

takeさん  こん**は
いろいろ過去の掲示板を当たってみたところ
niftyのDelphi会議室にそれらしき書き込みがありました。
実機が手元にないので、すぐに試験は出来ませんが、この線でソースを変更してみます。

−−−−−−−−−−−−−−−−−−
Nifty  FDELPHI
12番会議室の古い(01/01/31)発言(3821-)にある
Q.
>ちなみに COMMX で、同じ方法(CommX1.Receiveblock を使用)で動作は
>するのですが、不思議なことに、Windows NT や Windows 2000 では、
>旨く動かないのです。
>最初の3バイトの受信は、成功してるようなのですが、その後の
>データーが全く違う物が表示されてしまいます。
>何か原因が思い当たりましたら、教えてください。
A.
>過去にも同じような問題が #3495 でありましたが、その後連絡がなかったん
>でほったらかしにしていました。(^^;
>CommX のソースの SetTimeOut のところを ReadIntervalTimeout := 0;
>にするとどうでしょうか。

RE.
>これで、完璧に動作するようになりました。
>いままで、Windows2000 で CommX を使用すると先の不都合の他に
>CommX をクローズすると、「応答がない・・」等の状態になっていましたが、
>これも完全に直っています
−−−−−−−−−−−−−−−−−−−−−−−−−


daiya  2004-09-08 03:59:09  No: 10748

書き忘れましたが、Aを書いておられるのは、DR−X(旧ハンドル名X)さん)です。
残念なことに、ソース(下記)を覗いて、何を設定しているのか理解できないのです。
MAXDWORDとはなんですか??なんでゼロにするのか?

//  〜 送受信タイムアウト時間の設定 
//--------------------------------------------------------------------
procedure TCustomCommX.SetTimeOut;
var
  CommTimeOut : TCommTimeouts;    // タイムアウトパラメータ
begin
  if FHandle <> INVALID_HANDLE_VALUE then begin
    GetCommTimeouts(FHandle, CommTimeOut);
    with CommTimeOut do begin
      ReadIntervalTimeout         := MAXDWORD;
      ReadTotalTimeoutMultiplier  := 0;
      ReadTotalTimeoutConstant    := DWORD(FTimeOutReceive);
      WriteTotalTimeoutMultiplier := 0;
      WriteTotalTimeoutConstant   := DWORD(FTimeOutTrans);
    end;
    if not SetCommTimeouts(FHandle, CommTimeOut) then begin
      raise ECommError.Create('通信タイムアウト時間の設定で失敗しました');
    end;
  end;
end;


daiya  2004-09-12 06:35:30  No: 10749

Windows2000でも同様の不具合が発生するPCがやっと見つかりました。なんと4台目でした。
CommXの上記設定を変更したところ、無事に通信が出来るようになりました。
お騒がせいたしました。


daiya  2004-09-12 06:38:37  No: 10750

[[解決]]
ありがとうございました。


daiya  2004-09-12 06:40:47  No: 10751

[[解決]]
ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加