IM920 シリアル I/O (その2) 2019/07/25, 26
・2019/07/26 PWM x 2 点を追加。コードを変更。
920MHz 無線モジュール IM920 の「データモード」(シリアル通信)を使っています。
送信側から送られる文字列を受信側の Arduino UNO で受け、その内容により、デジタル入力 4 点 + デジタル出力 2 点 + アナログ入力
4 点を使います。
※ IM920 と Arduino の接続には、レベルシフタが必要です。面倒なので、Arduino 用 シールド (IM315-SHLD-RX-V2) を使いました。
送信側は、USB インターフェイスモジュール (IM315-USB-RX) を使っています。
下記、サンプルでは、500 msec 周期で、デジタル、アナログの入力状態をポーリングしています。
実用の時は、送信側には Android 端末を使う予定です。
OTG (USBホスト)機能 対応であれば 「USBインターフェイスモジュール」 がそのまま使えます。
■サンプルコード
送信側 : Delphi 10.2.3、受信側 : Arduino UNO R3
※データ送信は、"TXDA"コマンドを使用し、入出力設定は、送受信側とも規定値の "DCIO" (HEX
入出力設定) です。
"ECIO" (キャラクタ入出力設定) の場合は、コードの変更が必要です。
詳しくは、「IM920 取扱説明書(ソフトウェア編)」を参照してください。
// 送信側 : Delphi 10.2.3 // 要:ApdComPort コンポーネント { ■コマンド ・SWRD : デジタル入力 4 点読み込み(LOW で ON) >レスポンス : SWRDx (x は 入力の値を 16進表記) SW1 = 1, SW2 = 2, SW3 = 4, SW4 = 8 の合計値。4 点すべて ON では 'F' ・ADRD : アナログ入力 4 点読み込み (基準電圧 1.1V) >レスポンス : ADRDh1h2h3h4 (h1~h4 は A0~A3 の入力の値を 16進表記) ・SARD : デジタル入力 4 点 + アナログ入力 4 点読み込み SWRD + ADRD を 1 つにしたもの >レスポンス : SARDxh1h2h3h4 (x, h1~h4 は 入力の値を 16進表記) ・RY1ON, RY2ON, RY1OF, RY2OF : デジタル出力 2 点 ON または OFF >レスポンス : RYOK01, RYOK02 ・RY1PLS, RY2PLS : デジタル出力 2 点 ON/OFF (500msec ワンパルス) >レスポンス : RYOK01, RYOK02 ・PWM1hh, PWM2hh : PWM 出力 2 点 (hh は 0~5V に対して 0~255 を16進表記) >レスポンス : PWMOK01, RWMOK02 } unit Unit4; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, OoMisc, AdPort, Vcl.ExtCtrls, System.UITypes, AdSelCom, IniFiles, Vcl.Buttons; type TForm4 = class(TForm) ApdComPort1: TApdComPort; Button1: TButton; Button2: TButton; Memo1: TMemo; Timer1: TTimer; GroupBox1: TGroupBox; Button5: TButton; Button6: TButton; Button7: TButton; Button8: TButton; Shape1: TShape; Shape2: TShape; Shape3: TShape; Shape4: TShape; Button9: TButton; Button10: TButton; ComboBox1: TComboBox; Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Edit4: TEdit; Shape5: TShape; Shape6: TShape; CheckBox1: TCheckBox; v: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label1: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; Label8: TLabel; Label9: TLabel; Edit5: TEdit; Label10: TLabel; Label11: TLabel; Edit6: TEdit; Shape7: TShape; Shape8: TShape; SpeedButton1: TSpeedButton; SpeedButton2: TSpeedButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure ApdComPort1TriggerAvail(CP: TObject; Count: Word); procedure Button5Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure CheckBox1Click(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); procedure Edit5KeyPress(Sender: TObject; var Key: Char); procedure Edit6KeyPress(Sender: TObject; var Key: Char); private { Private 宣言 } public { Public 宣言 } resBuf : string; end; var Form4: TForm4; implementation {$R *.dfm} // n の k 乗 (Math ユニット不要) function intPower(n, k : integer):integer; var i : integer; begin result := 1; for i := 1 to k do result := result * n; end; procedure TForm4.ApdComPort1TriggerAvail(CP: TObject; Count: Word); // IM920 から受信文字 var i : Word; ch : AnsiChar; shp : TShape; j : integer; coOn, coOff : TColor; n, v, m : integer; s, cmd, sv : string; edt : TEdit; begin coOn := clLime; coOff := clWhite; for i := 1 to Count do begin ch := ApdComPort1.GetChar; resBuf := resBuf + string(ch); if ch = #10 then begin resBuf := Trim(resBuf); // 前後の制御文字、空白を削除 Memo1.Lines.Add(resBuf); resBuf := StringReplace(resBuf, ',', '', [rfReplaceAll]); n := Pos(':', resBuf); if n > 0 then begin cmd := Copy(resBuf, n + 1, 8); s := Copy(resBuf, n + 8 + 1); Memo1.Lines.Add(cmd +'=' + s); // スイッチ入力(4 点) if cmd = '53575244' then begin // 'SWRD' Memo1.Lines.Add('SWRD'); v := StrToInt('$' + s); for j := 0 to 3 do begin shp := FindComponent('Shape' + IntToStr(j + 1)) as TShape; if shp <> nil then begin if v and IntPower(2, j) > 0 then begin if shp.Brush.Color <> coOn then shp.Brush.Color := coOn; end else begin if shp.Brush.Color <> coOff then shp.Brush.Color := coOff; end; end; end; end // アナログ入力(4 点) else if cmd = '41445244' then begin // 'ADRD' Memo1.Lines.Add('ADRD'); m := Length(s) div 4; v := 0; for j := 0 to m - 1 do begin sv := Copy(s, 1 + j * 4, 4); if sv <> '' then begin v := StrToInt('$' + sv); Memo1.Lines.Add('AD' + IntToStr(j+1) +' = ' + IntToStr(v)); end; edt := FindComponent('Edit' + IntToStr(j + 1)) as TEdit; if edt <> nil then edt.Text := Format('%.2f', [(v / 1023) * 1.1]); end; end // スイッチ入力(4 点) + アナログ入力(4 点) else if cmd = '53415244' then begin // 'SARD' v := StrToInt('$' + Copy(s, 1, 2)); Memo1.Lines.Add(intToStr(v)); for j := 0 to 3 do begin shp := FindComponent('Shape' + IntToStr(j + 1)) as TShape; if shp <> nil then begin if v and IntPower(2, j) > 0 then begin if shp.Brush.Color <> coOn then shp.Brush.Color := coOn; end else begin if shp.Brush.Color <> coOff then shp.Brush.Color := coOff; end; end; end; m := (Length(s) - 2) div 4; v := 0; for j := 0 to m - 1 do begin sv := Copy(s, 3 + j * 4, 4); if sv <> '' then v := StrToInt('$' + sv); edt := FindComponent('Edit' + IntToStr(j + 1)) as TEdit; if edt <> nil then edt.Text := Format('%.2f', [(v / 1023) * 1.1]); end; end // リレー x 2 個 else if cmd = '5259514B' then begin // 'RYOK' v := StrToIntDef(s, 0); if (v = 1) or (v = 2) then begin // アンサーバック if v = 1 then shp := Shape5 else shp := Shape6; with shp do begin Brush.Color := coOn; Repaint; Sleep(500); Brush.Color := coOff; Repaint; end; end; end // PWM x 2 個 else if cmd = '50574D4F' then begin // 'PWMO' // s には 'K' が含まれる v := StrToIntDef(Copy(s, 3), 0); if (v = 1) or (v = 2) then begin // アンサーバック if v = 1 then shp := Shape7 else shp := Shape8; with shp do begin Brush.Color := coOn; Repaint; Sleep(500); Brush.Color := coOff; Repaint; end; end; end; end; resBuf := ''; end; end; end; procedure TForm4.Button1Click(Sender: TObject); // COM ポートオープン var comNo : integer; begin comNo := - 1; with ComboBox1 do begin if ItemIndex >= 0 then begin comNo := StrToIntDef(Copy(Items[ItemIndex], 4), -1); end; end; if comNo >= 0 then begin with ApdComport1 do begin ComNumber := comNo; Baud := 19200; DataBits := 8; StopBits := 1; Parity := TParity.pNone; try Open:= True; if Open then Memo1.Lines.Add('COMPORT OPEN OK') else Memo1.Lines.Add('COMPORT OPEN NG'); except Memo1.Lines.Add('COMPORT ERROR'); end; end; end; end; procedure TForm4.Button2Click(Sender: TObject); // COM ポートクローズ begin with ApdComport1 do begin if Open then begin Open := False; Memo1.Lines.Add('COMPORT CLOSE'); end; end; end; procedure TForm4.Button5Click(Sender: TObject); // RY1,2 On/Off var btn : TButton; cmd : string; s : string; i : integer; begin btn := Sender as TButton; s := btn.Caption; s := StringReplace(s, ' ', '', [rfReplaceAll]); with ApdComport1 do begin if Open then begin Timer1.Enabled := False; Sleep(400); // 400 ~ 500 msec resBuf := ''; cmd := 'TXDA'; for i := 1 to Length(s) do cmd := cmd + IntToHex(Ord(s[i]), 2); PutString(cmd + #13#10); Memo1.Lines.Add('>'+cmd); Timer1.Enabled := CheckBox1.Checked; end; end; end; procedure TForm4.CheckBox1Click(Sender: TObject); begin Timer1.Enabled := CheckBox1.Checked; end; procedure TForm4.Edit5KeyPress(Sender: TObject; var Key: Char); // PWN キー入力 {Enter] でコマンド送信 begin if Key = #13 then begin Key := #00; SpeedButton1Click(SpeedButton1); end; end; procedure TForm4.Edit6KeyPress(Sender: TObject; var Key: Char); begin if Key = #13 then begin SpeedButton1Click(SpeedButton2); end; end; procedure TForm4.FormCreate(Sender: TObject); // フォーム生成 var i : integer; ini : TIniFile; s : string; begin Memo1.Lines.Clear; // COM ポートの列挙 AdSelCom.ShowPortsInUse := False; for i := 0 to 32 do if AdSelCom.IsPortAvailable(i) then ComboBox1.Items.Add (AdPort.ComName(i)); // 前回選択した COM ポート名を読み込み ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); try s := ini.ReadString('Comport', 'ComName', ''); CheckBox1.Checked := ini.ReadBool('CheckBox', 'Auto', True); with ComboBox1 do begin if (s <> '') and (Items.Count > 0) then begin for i := 0 to Items.Count - 1 do begin if Items[i] = s then begin ItemIndex:= i; break; end; end; end; end; finally ini.Free; end; end; procedure TForm4.FormDestroy(Sender: TObject); // フォーム破棄 var ini : TIniFile; begin // 選択した COM ポート名を保存 ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); try ini.WriteString('Comport', 'ComName', ComboBox1.Text); ini.WriteBool('CheckBox', 'Auto', CheckBox1.Checked); finally ini.Free; end; end; procedure TForm4.SpeedButton1Click(Sender: TObject); // PWN // 送信文字列 ex: 'PWM1FF' var pwmNo : integer; cmd, s : string; i : integer; v : Double; m : integer; begin if Sender as TSpeedButton = SpeedButton1 then begin pwmNo := 1; v := StrToFloatDef(Edit5.Text, 0); end else begin pwmNo := 2; v := StrToFloatDef(Edit6.Text, 0); end; m := Trunc(v / 5.0 * 255); if m < 0 then m := 0; if m > 255 then m := 255; with ApdComport1 do begin if Open then begin Timer1.Enabled := False; Sleep(400); s := 'PWM' + pwmNo.ToString + IntToHex(m, 2); resBuf := ''; cmd := 'TXDA'; for i := 1 to Length(s) do cmd := cmd + IntToHex(Ord(s[i]), 2); PutString(cmd + #13#10); Memo1.Lines.Add('>'+cmd); Timer1.Enabled := CheckBox1.Checked; end; end; end; procedure TForm4.Timer1Timer(Sender: TObject); // Interval = 500 msec var s, cmd : string; i : integer; begin with ApdComport1 do begin if Open then begin Timer1.Enabled := False; // DI x 4 点 + AI x 4 点の値を取得 s := 'SARD'; resBuf := ''; cmd := 'TXDA'; for i := 1 to Length(s) do cmd := cmd + IntToHex(Ord(s[i]), 2); PutString(cmd + #13#10); Timer1.Enabled := CheckBox1.Checked; end; end; end; end.
// Arduino UNO R3 + Arduino IDE 1.8.10 //IM920 で Serial I/O // 2019/07/27 by f.izawa #include <SoftwareSerial.h> // Pin 8 ~ 11 は、IM920 にて使用 #define rxPin 8 #define txPin 9 #define busyPin 10 // DI x 4 点 int swPins[] = {2, 3, 4, 7}; // DO x 2 点 int ryPins[] = {12,13}; // PWM x 2 点 int pwmPins[] = {5, 6}; String rcvStr; String cmdStr; int busy; // set up a new serial port SoftwareSerial IM920Serial = SoftwareSerial(rxPin, txPin); // // n の k 乗 int intPow(int n, int k){ int res = 1; for (int i = 0; i < k; i++) res *= n; return res; } // // 桁そろえ(文字列の前に"0"を追加) void strDigits(String &str, int digits){ int len = str.length(); if (len < digits){ for (int i = len; i < digits; i++){ str = "0" + str; } } } // int hexToInt(String hex){ char buf[5]; hex.getBytes(buf, 5); // char[] に return (int)strtol(buf, NULL, 16); // HexToInt } // void setup() { Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only } Serial.println("IM920 Serial!"); // ピンモード設定 pinMode(busyPin, INPUT); for (int i = 0; i < sizeof(swPins); i++) pinMode(swPins[i], INPUT_PULLUP); // プルアップ for (int i = 0; i < sizeof(ryPins); i++) pinMode(ryPins[i], OUTPUT); // アナログ入力基準電圧(内部 1.1V) analogReference(INTERNAL); for (int i = 0; i < 4; i++) analogRead(i); // PWM 初期化 for (int i = 0; i < sizeof(pwmPins); i++) analogWrite(pwmPins[i], 0); // シリアル初期化 IM920Serial.begin(19200); } // // void loop() { // 受信 if (IM920Serial.available()) { rcvStr = IM920Serial.readStringUntil(0x0A); rcvStr.trim(); Serial.println(rcvStr); // 受信文字列をそのまま表示 rcvStr.replace(",", ""); // "," を削除 int idx = rcvStr.indexOf(":"); if (idx >= 0){ rcvStr = rcvStr.substring(idx + 1); // ":"以降の文字列 int n = int(rcvStr.length() / 2); //繰り返し回数 String s, sval; long m; cmdStr = ""; for (int i = 0; i < n; i++){ // 2文字ずつ取り出し数値に変換 m = hexToInt(rcvStr.substring(i * 2, i * 2 + 2)); cmdStr += char(m); // ascii コードを文字に変換 } // 送信側での 16 進変換前のコマンド文字列 Serial.println(cmdStr); // コマンドを表示 if (cmdStr == "SWRD"){ // デジタル 4 点 // busy 解除待ち do { busy = digitalRead(busyPin); } while (busy != 0); // SW1 ~ 4 の状態を集計 int sw = 0; for (int i = 0; i < sizeof(swPins); i++){ if (digitalRead(swPins[i]) == LOW) sw = sw + intPow(2, i); } sval = String(sw, HEX); strDigits(sval, 2); // 桁合わせ s = "TXDA53575244" + sval + "\r\n"; // "SWRD" IM920Serial.print(s); delay(30); } else if (cmdStr == "ADRD"){ // アナログ 4 点 // busy 解除待ち do { busy = digitalRead(busyPin); } while (busy != 0); s = "TXDA41445244"; // "ADRD" // A0 ~ A1 の状態を集計 for (int i = 0; i < 4; i++){ sval = String(analogRead(i), HEX); strDigits(sval, 4); s += sval; } s += "\r\n"; IM920Serial.print(s); delay(30); } else if (cmdStr == "SARD"){ // デジタル 4 点 + アナログ 4 点 // busy 解除待ち do { busy = digitalRead(busyPin); } while (busy != 0); s = "TXDA53415244"; // "SARD" // SW1 ~ 4 の状態を集計 int sw = 0; for (int i = 0; i < sizeof(swPins); i++){ if (digitalRead(swPins[i]) == LOW) sw += intPow(2, i); } sval = String(sw, HEX); strDigits(sval, 2); s += sval; //sval = ""; // A0 ~ A3 の状態を集計 for (int i = 0; i < 4; i++){ sval = String(analogRead(i), HEX); strDigits(sval, 4); s += sval; } s += "\r\n"; IM920Serial.print(s); delay(30); } else { // RY ON/OFF s = cmdStr.substring(0,2); if (s == "RY"){ // ex : "RY1ON", "RY2PLS", "RY2OFF" s = cmdStr.charAt(2); int ryNo = s.toInt(); if (ryNo >= 1 && ryNo <= 2){ if (cmdStr.indexOf("PLS") > 0){ digitalWrite(ryPins[ryNo - 1], HIGH); delay(500); digitalWrite(ryPins[ryNo - 1], LOW); } else { int val = LOW; if (cmdStr.indexOf("ON") > 0) val = HIGH; digitalWrite(ryPins[ryNo - 1], val); } // busy 解除待ち do { busy = digitalRead(busyPin); } while (busy != 0); s = "TXDA5259514B"; // "RYOK"; if (ryNo == 1) s += "01"; else s += "02"; s += "\r\n"; IM920Serial.print(s); delay(30); } } else if (s == "PW"){ // ex "PWM1FF" s = cmdStr.charAt(3); int pwmNo = s.toInt(); // 1 or 2 if (pwmNo == 1 || pwmNo == 2){ m = hexToInt(cmdStr.substring(4)); if (m > 255) m = 255; analogWrite(pwmPins[pwmNo - 1], m); // busy 解除待ち do { busy = digitalRead(busyPin); } while (busy != 0); s = "TXDA50574D4F4B"; // "PWMOK"; if (pwmNo == 1) s += "01"; else s += "02"; s += "\r\n"; IM920Serial.print(s); delay(30); } } } } } delay(1); }