Modbus RTUモジュール、IO拡張ボード（N4D3E16）

## modbus式のIO拡張ボード


こちらで売っているのですが、詳しい情報がありませんのでプログラムを作ってみました。
modbus自体あまり良くわかりませんのでプログラムを作りながらの手探りです。

https://www.amazon.co.jp/gp/product/B0BP14RMFK/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1

N4D3E16で検索すると製作者？のyoutubeはあるようです。

https://www.youtube.com/watch?v=QpPSA8jL0qI

PC側との通信にはUSBをRS485に変換します。

https://www.amazon.co.jp/dp/B016IG6X7I?ref=ppx_yo2ov_dt_b_fed_asin_title


## Linux でmodbus通信

libmodbusのインストールします（Ubuntuの場合）
```
sudo apt-get install libmodbus-dev
```

コンパイルは
```
gcc modbus.cpp  -lmodbus
```
modbus.hが読み込まれなかった場合は-Iで指定
```
gcc modbus.cpp  -lmodbus -I/usr/include/modbus
```

## modbus送受信のサンプルプログラム
``` c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <modbus.h>
#include <cerrno>



void
print_bits (int value, int num_bits)
{
  for (int i = (num_bits) - 1; i >= 0; i--)
    {
      printf ("%u", ((value) >> i) & 1);
    }
  printf ("\n");
}



int
main (int argc, char *argv[])
{
  modbus_t *ctx;
  uint16_t tab_reg[32];
  uint16_t read_value = 0;
  int rc;


  // シリアルポートと通信設定
  const char *device = "/dev/ttyUSB0";	// 使用するシリアルポートを指定
  int baud = 9600;
  char parity = 'N';
  int data_bit = 8;
  int stop_bit = 1;

  // Modbusコンテキストの作成と設定
  ctx = modbus_new_rtu (device, baud, parity, data_bit, stop_bit);
  if (ctx == NULL)
    {
      fprintf (stderr, "Unable to create the libmodbus context\n");
      return -1;
    }

  // デバッグ情報の出力有効化
  modbus_set_debug (ctx, TRUE);

  // スレーブIDの設定（ここではスレーブID 1）
  if (modbus_set_slave (ctx, 1) == -1)
    {
      fprintf (stderr, "Failed to set slave ID\n");
      modbus_free (ctx);
      return -1;
    }

  // シリアルポートを開く
  if (modbus_connect (ctx) == -1)
    {
      fprintf (stderr, "Connection failed: %s\n", modbus_strerror (errno));
      modbus_free (ctx);
      return -1;
    }



  //============  OUTPUT側の処理 ===============
  // 6つのコマンド
  //
  //1.0x100:open(個別に保持)
  //2.0x200:close(個別に保持) 
  //3.0x300:toggle （個別にトグル）
  //4.0x400:latch
  //5.0x500:momentary
  //6.0x1060A:10秒間open保持
  //  0x10605:5秒間openを保持


  //下の例ではOUTPUTの２番でLEDが点灯
  uint32_t write_value = 0x100;
  rc = modbus_write_register (ctx, 2, write_value);



  if (rc == -1)
    {
      // 書き込みエラーのチェック
      int expected_function_code = 6;
      uint8_t response[MODBUS_RTU_MAX_ADU_LENGTH];
      modbus_receive (ctx, response);

      // 応答が03（読み取り）である場合を処理
      if (response[1] == 3)
	{
	  fprintf (stderr,
		   "Device returned unexpected read response for write command\n");
	  // ここで、受け取ったデータをさらに処理するか、または何らかの対応を行う
	}
      else
	{
	  fprintf (stderr, "Write register failed: %s\n",
		   modbus_strerror (errno));
	}
    }
  else
    {
      printf ("Written value: %d\n", write_value);
    }




  //============  INPUT側の処理 ===============
  //レジスタ番号0x1C0を読み込む
  //（資料がないのでビットの結果を見ながら探し、0x1C0と判断した）
  rc = modbus_read_registers (ctx, 0x1C0, 1, &read_value);

  if (rc == -1)
    {
      fprintf (stderr, "Failed to read registers: %s\n",
	       modbus_strerror (errno));
    }
  print_bits (read_value, 16);	// 16ビットで表示





  // クリーンアップ
  modbus_close (ctx);
  modbus_free (ctx);

  return 0;
}
```

## 実行結果
```
Opening /dev/ttyUSB0 at 9600 bauds (N, 8, 1)
[01][06][00][02][01][00][29][9A]
Waiting for a confirmation...
<01><06><00><02><01><00><29><9A>
Written value: 256
[01][03][01][C0][00][01][85][CA]
Waiting for a confirmation...
<01><03><02><80><02><58><45>
1000000000000010
```

デバッグモードなので詳しい情報が出ています。
#### 前半部分は書き込みのための送信、書き込みのコマンドが06です。
[01][06][00][02][01][00][29][9A]

01: スレーブアドレス

06: 関数コード。06は「Write Single Register」を意味します。
    これは、1つのレジスタに値を書き込むコマンドです。

00 02: レジスタアドレス
       IOボードのOUTPUT側でON/OFFさせたい端子の番号です。
    
01 00: 書き込むデータ。16ビットの値で、ここでは0x0100（10進数の256）
       とりあえず個別にON/OFFする場合は下記のコマンド
       0x100:open
       0x200:close 
       ※送信コマンドによっては他の端子番号の保持も失われるので扱いには注意が必要
       
29 9A: CRCチェックサム



#### 後半部分は読み込みのための送信、読み込みのコマンドが03です。
[01][03][01][C0][00][01][85][CA]
01: スレーブアドレス
03: 関数コード。03は「Read Holding Registers」を意味します。
    これは、保持レジスタから値を読み込むコマンドで、
    ここでは1つのレジスタを読み取ることを指定しています。
01 C0:レジスタアドレス
00 01:読み取るレジスタの数
85 CA:CRCチェックサム

一番下に読み込んだ値を16ビットで表示しています(配線の状況によって変わります)。
1000000000000010