AKI-232C を Android で使う (2018/04/01)
秋月電子のRS232Cラインモニタキット AKI-232C (リンクはこちら)用のモニタツールの Android 版です。
※ラインモニタキットに付属しているモニタツールは、Windows 2000 / XP 用ですが、
こちらの環境では、Windows 8.1 (64bit) でも動いています。
・OTG (USB-Host) に対応した Android 端末でのみ使用できます。
・タブレット端末 Nexus 7 での動作を確認しています。
・7 インチを基準にしているため、スマホ端末では、画面全体が縮小されます。
・通信速度が速い場合、データの取りこぼしが発生する場合があります。
・データ表示はリアルタイムではありません。1024バイト以上受信、または [STOP] ボタンをタップ後に表示されます。
■ダウンロード
APK本体のみ(ダウンロードは、こちら)
※USB - RS232C 通信には、CSD 社の Uni232C コンポーネントを使用しています。(リンクはこちら)
AKI-232C 添付ツールのソースコードを参考にしています。
■ソースコード
Delphi 10.2 Tokyo + Uni23C コンポーネント
unit Unit2; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts, Uni232C, FMX.Edit, FMX.Controls.Presentation, FMX.StdCtrls, FMX.ListBox, FMX.ScrollBox, FMX.Memo, System.Rtti, FMX.Grid.Style, FMX.Grid, System.UIConsts, Androidapi.JNIBridge, AndroidApi.JNI.Media, System.IOUtils, System.IniFiles; type TRxData = record Tick : Byte; Data : Byte; end; TComReadThread = class(TThread) private { Private 宣言 } procedure ComRead; protected procedure Execute; override; public constructor Create; virtual; end; TForm2 = class(TForm) ScaledLayout1: TScaledLayout; Uni232C1: TUni232C; Button1: TButton; ComboBox1: TComboBox; ComboBox2: TComboBox; Button2: TButton; Label1: TLabel; Label2: TLabel; StringGrid1: TStringGrid; StringColumn1: TStringColumn; StringColumn2: TStringColumn; StringColumn3: TStringColumn; StringColumn4: TStringColumn; StringColumn5: TStringColumn; StringColumn6: TStringColumn; StringColumn7: TStringColumn; StringColumn8: TStringColumn; StringColumn9: TStringColumn; StringColumn10: TStringColumn; StringColumn11: TStringColumn; StringColumn12: TStringColumn; StringColumn13: TStringColumn; StringColumn14: TStringColumn; StringColumn15: TStringColumn; StringColumn16: TStringColumn; StringColumn17: TStringColumn; StringColumn18: TStringColumn; StringColumn19: TStringColumn; StringColumn20: TStringColumn; StringColumn21: TStringColumn; StringColumn22: TStringColumn; StringColumn23: TStringColumn; StringColumn24: TStringColumn; StringColumn25: TStringColumn; StringColumn26: TStringColumn; StringColumn27: TStringColumn; StringColumn28: TStringColumn; StringColumn29: TStringColumn; StringColumn30: TStringColumn; StringColumn31: TStringColumn; StringColumn32: TStringColumn; StringColumn33: TStringColumn; Button3: TButton; Button4: TButton; Button5: TButton; GroupBox1: TGroupBox; CheckBox1: TCheckBox; CheckBox2: TCheckBox; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure StringGrid1DrawColumnCell(Sender: TObject; const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF; const Row: Integer; const Value: TValue; const State: TGridDrawStates); procedure Button5Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure CheckBox1Change(Sender: TObject); procedure CheckBox2Change(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { private 宣言 } public { public 宣言 } procedure DispRxData; end; var Form2: TForm2; // モニタバイト数の2倍 RxDataAry : array [0..2047] of TRxData; RxDataIndex : integer; GTicks : Cardinal; Gloopflag : boolean; GStartIndex{, GEndIndex} : integer; GCmdMode : integer; ThComRead : TComReadThread; ControlCode : array of string = [ 'NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL', 'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI', 'DLE', 'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB', 'CAN', 'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US', 'DEL']; //DEL = $7E implementation {$R *.fmx} procedure Beep(typ : integer); // ブザー音 // uses ... Androidapi.JNIBridge, AndroidApi.JNI.Media; var ToneGenerator: JToneGenerator; toneType : integer; begin ToneGenerator := TJToneGenerator.JavaClass.init( TJAudioManager.JavaClass.STREAM_ALARM, TJToneGenerator.JavaClass.MAX_VOLUME); //https://developer.android.com/reference/android/media/ToneGenerator.html if typ = 1 then toneType := TJToneGenerator.JavaClass.TONE_PROP_BEEP else if typ = 2 then toneType := TJToneGenerator.JavaClass.TONE_PROP_BEEP2 else toneType := TJToneGenerator.JavaClass.TONE_PROP_ACK; ToneGenerator.startTone(toneType);//, 1000); end; function CheckRxData:string; var i, k: integer; temp : array [0 ..2047] of TRxData;//Byte; a, b : Byte; ngcnt : integer; res : string; begin res := ''; for i := 0 to 2047 do begin temp[i].Data := 0; temp[i].Tick := 0; end; i := 0; k := 0; ngcnt := 0; while true do begin a := RxDataAry[i].Data shr 4; b := RxDataAry[i + 1].Data shr 4; if ((a = $8) and (b = $9)) or ((a = $A) and (b = $B)) then begin temp[k] := RxDataAry[i]; temp[k + 1] := RxDataAry[i + 1]; k := k + 2; i := i + 2; end else begin temp[k] := RxDataAry[i]; temp[k + 1] := RxDataAry[i]; k := k + 2; Inc(i); Inc(ngcnt); res := res + ((k + 1) div 2).ToString + ','; end; if k > 2047 then break; if i > 2047 then break; if i >= RxDataIndex then break; end; for i := 0 to 2047 do RxDataAry[i] := temp[i]; if ngcnt > 0 then result := 'NG = '+ ngcnt.ToString + ' : '+ Copy(res, 1, res.Length -1); end; function RateMesure: string; // ボーレート計測 var DataCount : integer; Baud : array [0..99] of integer; m, n : integer; BaudRate : integer; bps : string; begin result := '計測失敗'; if RxDataIndex > 100 then begin DataCount := RxDataIndex; m := 0; for n := 0 to DataCount div 2 - 1 do begin Baud[m] := RxDataAry[n * 2 + 1].Data; Baud[m] := Baud[m] * 256; Baud[m] := Baud[m] + RxDataAry[n * 2].Data + 15; Baud[m] := Trunc(Baud[m] * 108.459); // nsec 108.459=1/9.22MHz Inc(m); if m = 100 then Break; end; BaudRate := 5000000; // nsec for n := 0 to 50 -1 do begin if Baud[n] <> 0 then begin if Baud[n] < BaudRate then BaudRate := Baud[n]; end; end; if (3333332 * 0.85 < BaudRate) and (BaudRate < 3333332 * 1.15) then bps := '300bps' else if (1666666 * 0.85 < BaudRate) and (BaudRate < 1666666 * 1.15) then bps := '600bps' else if ( 833333 * 0.85 < BaudRate) and (BaudRate < 833333 * 1.15) then bps := '1200bps' else if ( 416666 * 0.85 < BaudRate) and (BaudRate < 416666 * 1.15) then bps := '2400bps' else if ( 208332 * 0.85 < BaudRate) and (BaudRate < 208332 * 1.15) then bps := '4800bps' else if ( 104166 * 0.85 < BaudRate) and (BaudRate < 104166 * 1.15) then bps := '9600bps' else if ( 69444 * 0.85 < BaudRate) and (BaudRate < 69444 * 1.15) then bps := '14400bps' else if ( 52083 * 0.85 < BaudRate) and (BaudRate < 52083 * 1.15) then bps := '19200bps' else if ( 34722 * 0.85 < BaudRate) and (BaudRate < 34722 * 1.15) then bps := '28800ps' else if ( 26041 * 0.85 < BaudRate) and (BaudRate < 26041 * 1.15) then bps := '38400bps' else if ( 17361 * 0.85 < BaudRate) and (BaudRate < 17361 * 1.15) then bps := '57600bps' else if ( 8680 * 0.85 < BaudRate) and (BaudRate < 8680 * 1.15) then bps := '115200bps' else if ( 4340 * 0.85 < BaudRate) and (BaudRate < 4340 * 1.15) then bps := '230400bps' else if ( 2170 * 0.85 < BaudRate) and (BaudRate < 2170 * 1.15) then bps := '460800bps' else if ( 1085 * 0.85 < BaudRate) and (BaudRate < 1085 * 1.15) then bps := '921600bps'; result := bps; end; end; procedure TForm2.DispRxData; var i, j: integer; a, b, c, d: Byte; cnt : integer; nth, idx, k : integer; begin if RxDataIndex >= 2 then begin idx := 0; cnt := 0; nth := 0; with StringGrid1 do begin BeginUpDate; for i := GStartIndex div 2 to RxDataIndex div 2 - 1 do begin k := nth * 6; a := RxDataAry[i * 2].Data shl 4; b := RxDataAry[i * 2 + 1].Data and $0F; c := RxDataAry[i * 2].Data shr 4; d := RxDataAry[i * 2 + 1].Data shr 4; if (c = $A) and (d = $B) then begin Cells[1 + idx, k + 2] := IntToHex(a + b, 2); Cells[1 + idx, k + 3] := Char(a + b); if Cells[1 + idx, k + 4] <> '' then Cells[1 + idx, k + 4] := ''; if Cells[1 + idx, k + 5] <> '' then Cells[1 + idx, k + 5] := ''; end else if (c = 8) and (d = 9) then begin if Cells[1 + idx, k + 2] <> '' then Cells[1 + idx, k + 2] := ''; if Cells[1 + idx, k + 3] <> '' then Cells[1 + idx, k + 3] := ''; Cells[1 + idx, k + 4] := IntToHex(a + b, 2); Cells[1 + idx, k + 5] := Char(a + b); end else begin for j := 2 to 5 do if Cells[1 + idx, k + j] <> '' then Cells[1 + idx, k + j] := ''; end; // アイドルタイム if RxDataAry[i * 2].Tick > 0 then Cells[1 + idx, k + 1] := RxDataAry[i * 2].Tick.ToString else if RxDataAry[i * 2 + 1].Tick > 0 then Cells[1 + idx, k + 1] := RxDataAry[i * 2 + 1].Tick.ToString else if Cells[1 + idx, k + 1] <> '' then Cells[1 + idx, k + 1] := ''; Inc(idx); Inc(cnt); if idx >= 32 then begin idx := 0; Inc(nth); end; if cnt >= 64 then Break; end; // 余白を消す if cnt < 64 then begin for i := cnt to 64 - 1 do begin if i < 32 then begin k := 0; idx := i; end else begin k := 6; idx := i - 32; end; for j := 1 to 5 do if Cells[1 + idx, k + j] <> '' then Cells[1 + idx, k + j] := ''; end; end; EndUpDate; end; end; end; procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); var IniFile: TMemIniFile; begin IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine( System.IOUtils.TPath.GetDocumentsPath, 'AKI232AND.ini'), TEncoding.UTF8); with IniFile do begin try WriteInteger('ComboBox', 'BaudRate', ComboBox1.ItemIndex); WriteInteger('ComboBox', 'J1<->J2', ComboBox2.ItemIndex); WriteBool('CheckBox', 'Bit8', CheckBox1.IsChecked); WriteBool('CheckBox', 'Bit8+1', CheckBox2.IsChecked); finally Free; end; end; end; procedure TForm2.FormCreate(Sender: TObject); var IniFile: TMemIniFile; begin Label1.Text := ''; Label2.Text := ''; // 横画面に固定 Application.FormFactor.Orientations := [TFormOrientation.Landscape, TFormOrientation.InvertedLandscape]; IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine( System.IOUtils.TPath.GetDocumentsPath, 'AKI232AND.ini'), TEncoding.UTF8); with IniFile do begin try ComboBox1.ItemIndex := ReadInteger('ComboBox', 'BaudRate', 6); ComboBox2.ItemIndex := ReadInteger('ComboBox', 'J1<->J2', 0); CheckBox1.IsChecked := ReadBool('CheckBox', 'Bit8', True); CheckBox2.IsChecked := ReadBool('CheckBox', 'Bit8+1', False); finally Free; end; end; end; procedure TForm2.StringGrid1DrawColumnCell(Sender: TObject; const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF; const Row: Integer; const Value: TValue; const State: TGridDrawStates); // セル描画 var s: string; c: Cardinal; ARect : TRectF; begin if Column.Index = 0 then begin end else begin // データ番号 if (Row = 0) or (Row = 6) then begin if Column.Index > 0 then begin ARect := Bounds; ARect.Left := Bounds.Left - 2; ARect.Right := Bounds.Right + 2; if (StringGrid1.Cells[Column.Index, Row + 2] = '') and (StringGrid1.Cells[Column.Index, Row + 4] = '') then Canvas.Fill.Color := TAlphaColorRec.Grey else Canvas.Fill.Color := TAlphaColorRec.Silver;//.Darkcyan; Canvas.FillRect(ARect, 0, 0, AllCorners, 1, TCornerType.Round ); Canvas.DrawRect(Bounds, 0, 0, AllCorners, 1); if Column.Index + GStartIndex div 2 <= RxDataIndex div 2 then begin if Row = 0 then s := IntToStr(Column.Index + GStartIndex div 2) else s := IntToStr(32 + Column.Index + GStartIndex div 2); Canvas.Font.Size := 11; Canvas.Fill.Color := TAlphaColorRec.White; Canvas.FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); end; end; end // アイドル時間(100ms単位) else if (Row = 1) or (Row = 7) then begin s := StringGrid1.Cells[Column.Index, Row]; if s <> '' then begin Canvas.Fill.Color := claOrange; Canvas.FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Canvas.Fill.Color := claBlack; Canvas.Font.Size := 12; Canvas.FillText(Bounds, s, False, 1.0, [], TTextAlign.Leading); end; end // 16進表示を色分け else if Row mod 2 = 0 then begin if not Value.IsEmpty then s := Value.ToString else s := ''; if s <> '' then begin c := StrToInt('$' + s); if (c < $20) or (c = $7E) then Canvas.Fill.Color := claRed else begin if (Row = 2) or (Row = 8) then Canvas.Fill.Color := claLime else Canvas.Fill.Color := claCyan; end; // 透過で塗りつぶし Canvas.FillRect( Bounds, 0, 0, AllCorners, 0.4, TCornerType.Round ); end; end // 制御コードを表示 else begin s := StringGrid1.Cells[Column.Index, Row - 1]; if s <> '' then begin c := StrToInt('$' + s); if (c < $20) or (c = $7E) then begin Canvas.Fill.Color := claYellow; Canvas.FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Canvas.Fill.Color := claBlack; if c < $20 then s := ControlCode[c] else s := ControlCode[$20]; Canvas.Font.Size := 11; Canvas.FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); end; end; end end; end; // ----------------------------------------------------------------------------- procedure TComReadThread.ComRead; var ret : integer; AData : TBytes; i : integer; Ticks : Cardinal; idl : integer; res : string; begin GloopFlag := True; // ボーレート計測 if GCmdMode = 101 then begin while not Terminated and GloopFlag do begin if Form2.Uni232C1.Connect then begin SetLength(AData, 64); ret := Form2.Uni232C1.Read(64, @AData[0]); if ret > 0 then begin for i := 0 to ret -1 do begin RxDataAry[RxDataIndex].Data := AData[i]; Inc(RxDataIndex); end; { Synchronize(procedure begin Form2.Label2.Text := 'Read Data = ' + RxDataIndex.ToString; end); } if RxDataIndex > 100 then GLoopFlag := False; end else begin //Sleep(1); Synchronize(procedure begin if Form2.Label2.Text <> (RxDataIndex div 2).ToString then Form2.Label2.Text := (RxDataIndex div 2).ToString; end); end; end else GloopFlag := False; end; end // モニタリング else begin while not Terminated and GloopFlag do begin if Form2.Uni232C1.Connect then begin SetLength(AData, 64); ret := Form2.Uni232C1.Read(64, @AData[0]); if ret > 0 then begin Ticks := TThread.GetTickCount; if RxDataIndex > 0 then begin idl := (Ticks - GTicks) div 100; if idl > 255 then idl := 255; RxDataAry[RxDataIndex].Tick := idl; end; GTicks := Ticks; for i := 0 to ret - 1 do begin RxDataAry[RxDataIndex].Data := AData[i]; Inc(RxDataIndex); if RxDataIndex >= 2048 then begin GloopFlag := False; break; end; end; { Synchronize(procedure begin Form2.Label2.Text := (RxDataIndex div 2).ToString; end); } end else begin //Sleep(1); Synchronize(procedure begin if Form2.Label2.Text <> (RxDataIndex div 2).ToString then Form2.Label2.Text := (RxDataIndex div 2).ToString; end); end; end else GloopFlag := False; end; end; Synchronize(procedure begin if Form2.Label2.Text <> (RxDataIndex div 2).ToString then Form2.Label2.Text := (RxDataIndex div 2).ToString; end); // モニタリング、ボーレート計測終了 if Form2.Uni232C1.Connect then begin SetLength(AData, 64); AData := TEncoding.ANSI.GetBytes('E'); Form2.Uni232C1.Write(1, @AData[0]); Sleep(100); Form2.Uni232C1.Close; if GCmdMode = 101 then begin if RxDataIndex > 100 then begin Synchronize(procedure begin ShowMessage(RateMesure); end); end; end; end; if GCmdMode = 101 then begin Synchronize(procedure begin Form2.Button5.Text := 'ボーレート計測'; end); end else begin res := CheckRxData; if res <> ''then Synchronize(procedure begin Form2.Label2.Text := Form2.Label2.Text + ' (' + res + ')'; end); end; Synchronize(procedure begin Form2.DispRxData; Form2.Button1.Enabled := True; Form2.Button5.Enabled := True; end); GCmdMode := 0; Beep(3); end; constructor TComReadThread.Create; begin // スレッドを生成、直ちに実行 inherited Create(False); // スレッド終了時、スレッドオブジェクトを破棄 FreeOnTerminate := True; end; procedure TComReadThread.Execute; begin ComRead; end; procedure TForm2.Button1Click(Sender: TObject); // [START] var AData : TBytes; ret : integer; i: Integer; begin GCmdMode := 0; Label1.Text := ''; SetLength(AData, 64); Uni232C1.BaudRate := 115200; Uni232C1.ByteSize := Bit8; Uni232C1.StopBits := StopBit1; Uni232C1.ParityBits := ParityNone; Uni232C1.Open ; Sleep(100); if Uni232C1.Connect then begin Button1.Enabled := False; Button5.Enabled := False; Beep(1); { [ BaudRate ]; '0' 600bps '1' 1200bps '2' 2400bps '3' 4800bps '4' 9600bps '5' 14400bps '6' 19200bps '7' 28800bps '8' 38400bps '9' 57600bps } // ボーレート AData := TEncoding.ANSI.GetBytes(ComboBox1.ItemIndex.ToString); Uni232C1.Write(1, @AData[0]); Sleep(100); SetLength(AData, 64); Uni232C1.Read(64, @AData[0]); // 'V' ファームウエアのバージョンデータ要求 SetLength(AData, 64); AData := TEncoding.ANSI.GetBytes('V'); Uni232C1.Write(1, @AData[0]); Sleep(100); SetLength(AData, 64); ret := Uni232C1.Read(64, @AData[0]); if ret > 0 then begin SetLength(AData, ret); Label1.Text := TEncoding.ANSI.GetString(AData); end; SetLength(AData, 64); // 8Bit / 8+1Bit (Parity) if CheckBox1.IsChecked then AData := TEncoding.ANSI.GetBytes('d') else AData := TEncoding.ANSI.GetBytes('D'); Uni232C1.Write(1, @AData[0]); Sleep(100); SetLength(AData, 64); Uni232C1.Read(64, @AData[0]); // モニタリング開始 case ComboBox2.ItemIndex of 0 : AData := TEncoding.ANSI.GetBytes('G'); // 双方向 1 : AData := TEncoding.ANSI.GetBytes('A'); // J2->J1 2 : AData := TEncoding.ANSI.GetBytes('B'); // J1->J2 end; Uni232C1.Write(1, @AData[0]); Sleep(100); SetLength(AData, 64); Uni232C1.Read(64, @AData[0]); RxDataIndex := 0; // データ表示の開始データ位置 GStartIndex := 0; for i := 0 to 2047 do RxDataAry[i].Tick := 0; ThComRead := TComReadThread.Create; end; end; procedure TForm2.Button2Click(Sender: TObject); // [STOP] begin GLoopFlag := False; end; procedure TForm2.Button3Click(Sender: TObject); // [<]ボタン begin Button3.Enabled := False; if GStartIndex > 0 then begin GStartIndex := GStartIndex - 64; if GSTartIndex < 0 then GStartIndex := 0; DispRxData; end; Button3.Enabled := True; end; procedure TForm2.Button4Click(Sender: TObject); // [>] ボタン begin Button4.Enabled := False; if GStartIndex < RxDataIndex - 64 then begin GStartIndex := GStartIndex + 64; DispRxData; end; Button4.Enabled := True; end; procedure TForm2.Button5Click(Sender: TObject); // ボーレート計測 var AData : TBytes; ret : integer; i : integer; begin if Button5.Text <> '計測中止' then begin Label1.Text := ''; if Button1.Enabled then begin Uni232C1.BaudRate := 115200; Uni232C1.ByteSize := Bit8; Uni232C1.StopBits := StopBit1; Uni232C1.ParityBits := ParityNone; Uni232C1.Open; Sleep(100); if Uni232C1.Connect then begin Beep(1); Button1.Enabled := False; Button5.Text := '計測中止'; // 'V' ファームウエアのバージョンデータ要求 SetLength(AData, 64); AData := TEncoding.ANSI.GetBytes('V'); Uni232C1.Write(1, @AData[0]); Sleep(100); SetLength(AData, 64); ret := Uni232C1.Read(64, @AData[0]); if ret > 0 then begin SetLength(AData, ret); Label1.Text := TEncoding.ANSI.GetString(AData); end; RxDataIndex := 0; GStartIndex := 0; SetLength(AData, 64); AData := TEncoding.ANSI.GetBytes('M'); Uni232C1.Write(1, @AData[0]); Sleep(100); for i := 0 to 2047 do RxDataAry[i].Tick := 0; GCmdMode := 101; ThComRead := TComReadThread.Create; end; end; end else begin if Uni232C1.Connect then begin GloopFlag := False; end; end; end; procedure TForm2.CheckBox1Change(Sender: TObject); begin if CheckBox1.IsChecked and CheckBox2.IsChecked then CheckBox2.IsChecked := False else if not CheckBox1.IsChecked and not CheckBox2.IsChecked then CheckBox2.IsChecked := True; end; procedure TForm2.CheckBox2Change(Sender: TObject); begin if CheckBox1.IsChecked and CheckBox2.IsChecked then CheckBox1.IsChecked := False else if not CheckBox1.IsChecked and not CheckBox2.IsChecked then CheckBox1.IsChecked := True; end; end.