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);
}