Armadilloフォーラム

USB to RS485双方向コンバータを使用した通信について

rikuya-h

2025年3月28日 10時02分

お世話になっております。

Armadillo-IoT G4 にて開発を進めておりますが
RS-485通信を行う必要が出てきたため、以下のUSBとのコンバータを使用
https://www.switch-science.com/products/8426

RS-485通信にあまり知見が無く...試しに
PCとArmadillo-IoT G4間で通信させたく思うのですが
上手く通信させる事が出来ておりません。

ご教授頂けると大変助かります。

※PC(ドライバーインストール済み)及びArmadillo側でのコンバータ認識は出来ております。

コメント

at_shota.shimoyama

2025年3月28日 12時04分

アットマークテクノの下山です。

まず、お互いのコンバータのA+・B+・GNDを接続した状態であることを確認してください。
PC側のコンバータをTeratermなどで表示している場合は、改行コードを送信・受信ともにLFに設定してください。

■通信設定
G4の通信設定を以下のように設定します
ここでは、baudrateを115200として設定してます、PC側も同じbaudrateに設定してください

armadillo:~# stty -F /dev/ttyACM0 speed 115200 parenb -parodd brkint ignpar -icrnl -ixon -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

■G4から送信する場合
以下のコマンドでPC側に文字は表示されますでしょうか?

armadillo:~# echo "hello" > /dev/ttyACM0

■G4が受信する場合
以下のコマンドを実行してから、PC側で文字入力+EnterでG4側のコンソールに文字は表示されますでしょうか?

armadillo:~# while read var ; do echo $var ; done < /dev/ttyACM0

よろしくお願いします

> アットマークテクノの下山です。
>
> まず、お互いのコンバータのA+・B+・GNDを接続した状態であることを確認してください。
> PC側のコンバータをTeratermなどで表示している場合は、改行コードを送信・受信ともにLFに設定してください。
>
> ■通信設定
> G4の通信設定を以下のように設定します
> ここでは、baudrateを115200として設定してます、PC側も同じbaudrateに設定してください
>

> armadillo:~# stty -F /dev/ttyACM0 speed 115200 parenb -parodd brkint ignpar -icrnl -ixon -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke
> 

>
> ■G4から送信する場合
> 以下のコマンドでPC側に文字は表示されますでしょうか?
>

> armadillo:~# echo "hello" > /dev/ttyACM0
> 

>
> ■G4が受信する場合
> 以下のコマンドを実行してから、PC側で文字入力+EnterでG4側のコンソールに文字は表示されますでしょうか?
>

> armadillo:~# while read var ; do echo $var ; done < /dev/ttyACM0
> 

>
> よろしくお願いします
上記手順でPC側及びG4側で文字出力を確認出来ました!
ありがとうございます。

続けてで恐縮ですが、
これらで実験的にModbus Protocolを用いた通信を行いたいのですが、
どの様に設定し、操作すれば良いかもご教授頂けると大変助かります。

at_shota.shimoyama

2025年3月28日 20時25分

rikuya-h 様

Modbus/RTUをシェルスクリプトで行うのは結構大変なので、PythonのPyModbusなどを使用するのがいいんじゃないかと思います。

Modbus/RTUで通信する際、G4はマスター側でしょうか?

> rikuya-h 様
>
> Modbus/RTUをシェルスクリプトで行うのは結構大変なので、PythonのPyModbusなどを使用するのがいいんじゃないかと思います。
>
> Modbus/RTUで通信する際、G4はマスター側でしょうか?
実運用でもPythonを使用しようと考えていました。
G4がマスター側になります。

at_shota.shimoyama

2025年3月31日 10時41分

> 実運用でもPythonを使用しようと考えていました。
> G4がマスター側になります。

本来はABOSDEでPythonプロジェクトから実行環境などを用意するのが望ましいのですが、
実験的に手っ取り早く確かめるなら、ABOS上に直接インストールしてスクリプトを実行することもできます。

■パッケージ等のインストール

armadillo:~# apk update
armadillo:~# apk add python3 py3-pip
armadillo:~# pip3 install pymodbus pyserial --break-system-packages

■スクリプト
例えばスレーブIDが1のデバイスの入力レジスタ0x07d0から10ワード読み取る(read_input_registers)処理は次のようになります。
そのあたりやbaudrateについては適宜変更してください。

from pymodbus.client import ModbusSerialClient
 
client = ModbusSerialClient(
    port='/dev/ttyACM0',
    baudrate=115200,
    parity='N',
    stopbits=1,
    bytesize=8,
    timeout=3,
)
 
if client.connect():
    print("client.connect() OK")
else:
    print("client.connect() Failed")
    exit()
 
reg = client.read_input_registers(0x07d0, count=10, slave=1)
if not reg.isError():
    r = reg.registers
    print(r)
else:
    print("Error read_input_registers 0x07d0")
 
client.close()

また、/dev/ttyACM0がデバイスファイル名になるだけで、RS-485やModbus/RTUの使用方法においてArmadilloに固有の操作などは特にありませんので、インターネットから得られる一般的な情報を参考にできます。
PyModbusの使用方法などについては
https://pymodbus.readthedocs.io/en/v3.8.6/
などを参照してください。

ご無沙汰しております。
頂いたコメントにて参考にArmadilloを"マスター"にした際の方法で開発を進めています。

ただ、最近Armadilloを"スレーブ"にした際の簡単な検証をしたところレジスタの初期化が上手くいかず悩んでいます。
Armadillo G4 Slave side

import asyncio
import pymodbus
from pymodbus.server import StartAsyncSerialServer
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
 
async def run():
    store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [18] * 100),ir=ModbusSequentialDataBlock(0, [19] * 100))
    slaves = {
        0x01: store,
    }
    context = ModbusServerContext(slaves=slaves, single=False)
    await StartAsyncSerialServer(context=context, port="/dev/ttyACM0", baudrate=115200, stopbits=1, bytesize=8, parity="N")
 
if __name__ == "__main__":
    pymodbus.pymodbus_apply_logging_config("DEBUG")
    asyncio.run(
        run(), debug=True
    )

PC Master side

from pymodbus.client import ModbusSerialClient
import pymodbus
def run():
    client = ModbusSerialClient(
        port='COM15',  
        baudrate=115200,
        parity='N',
        stopbits=1,
        bytesize=8,
        timeout=3,
    )
 
    if client.connect():
        print("client.connect() OK")
    else:
        print("client.connect() Failed")
        exit()
 
    # ホールディングレジスタを読み取る
    reg = client.read_holding_registers(0x00, count=10, slave=1)
    if not reg.isError():
        r = reg.registers
        print("HR読み取ったレジスタ:", r)
    else:
        print("Error read_holding_registers 0x00")
    # インプットレジスタを読み取る
    reg = client.read_input_registers(0x00, count=10, slave=1)
    if not reg.isError():
        r = reg.registers
        print("IR読み取ったレジスタ:", r)
    else:
        print("Error read_input_registers 0x00")
 
    client.close()
 
if __name__ == "__main__":
    pymodbus.pymodbus_apply_logging_config("DEBUG")
    run()

Armadillo G4 側出力

2025-06-20 12:06:28,510 DEBUG transport:256 Awaiting connections server_listener
2025-06-20 12:06:28,516 INFO  base:85 Server listening.
2025-06-20 12:06:28,518 DEBUG transport:277 Connected to server
2025-06-20 12:06:32,706 DEBUG transport:329 recv: 0x1 0x3 0x0 0x0 0x0 0xa 0xc5 0xcd old_data:  addr=None
2025-06-20 12:06:32,706 DEBUG base:91 Processing: 0x1 0x3 0x0 0x0 0x0 0xa 0xc5 0xcd
2025-06-20 12:06:32,707 DEBUG decoders:113 decoded PDU function_code(3 sub -1) -> ReadHoldingRegistersRequest(dev_id=0, transaction_id=0, address=0, count=10, bits=[], registers=[], status=1)
2025-06-20 12:06:32,707 DEBUG base:101 Frame advanced, resetting header!!
2025-06-20 12:06:32,711 DEBUG context:121 getValues: fc-[3] address-1: count-10
2025-06-20 12:06:32,712 DEBUG transport:386 send: 0x1 0x3 0x14 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xa3 0x67
2025-06-20 12:06:32,723 DEBUG transport:329 recv: 0x1 0x4 0x0 0x0 0x0 0xa 0x70 0xd old_data:  addr=None
2025-06-20 12:06:32,724 DEBUG base:91 Processing: 0x1 0x4 0x0 0x0 0x0 0xa 0x70 0xd
2025-06-20 12:06:32,724 DEBUG decoders:113 decoded PDU function_code(4 sub -1) -> ReadInputRegistersRequest(dev_id=0, transaction_id=0, address=0, count=10, bits=[], registers=[], status=1)
2025-06-20 12:06:32,724 DEBUG base:101 Frame advanced, resetting header!!
2025-06-20 12:06:32,726 DEBUG context:121 getValues: fc-[4] address-1: count-10
2025-06-20 12:06:32,727 DEBUG transport:386 send: 0x1 0x4 0x14 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x95 0x81

PC 側出力

client.connect() OK
2025-06-20 12:06:32,746 DEBUG base:91 Processing: 0x1 0x3 0x14 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xa3 0x67
2025-06-20 12:06:32,746 DEBUG decoders:113 decoded PDU function_code(3 sub -1) -> ReadHoldingRegistersResponse(dev_id=0, transaction_id=0, address=0, count=0, bits=[], registers=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], status=1)
2025-06-20 12:06:32,747 DEBUG base:101 Frame advanced, resetting header!!
HR読み取ったレジスタ: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
2025-06-20 12:06:32,761 DEBUG base:91 Processing: 0x1 0x4 0x14 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x95 0x81
2025-06-20 12:06:32,761 DEBUG decoders:113 decoded PDU function_code(4 sub -1) -> ReadInputRegistersResponse(dev_id=0, transaction_id=0, address=0, count=0, bits=[], registers=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], status=1)
 
2025-06-20 12:06:32,761 DEBUG base:101 Frame advanced, resetting header!!
IR読み取ったレジスタ: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

以下コードで初期値を設定しているはずですが、"0"が値としては返ってきてしまっています。
store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [18] * 100),ir=ModbusSequentialDataBlock(0, [19] * 100))

"マスター:Armadillo G4","スレーブ:PC"の関係だと正しく動作するのですが、 "スレーブ:Armadillo G4"だと正しく動作しない様に見えます。
私の勉強不足かもしれませんが、何か分かればご教示頂けると助かります。

at_shota.shimoyama

2025年6月20日 16時15分

rikuya-h 様

こちらでも再現できました(マスターがPC・Armadillo G4のどちらであっても起こりました)

原因を調べてみたのですが、pymodbusライブラリ内のバグ(仕様?)でした…
ModbusSlaveContext(...)のところで、di=...の引数も指定しないと、他のco=... , ir=... , hr=...は無効になります

例えば、添付いただいたコードでは、

store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [18] * 100),ir=ModbusSequentialDataBlock(0, [19] * 100))

となっていますが、di=...の指定がないため、hrとirはデフォルトの [0]*65536 になってしまいます。(そのため0しか読み取れない)

ですので、何かテキトーな数値でよいので、以下のようにdi=...も指定してください

store = ModbusSlaveContext(di=ModbusSequentialDataBlock(0, [1]), hr=ModbusSequentialDataBlock(0, [18] * 100),ir=ModbusSequentialDataBlock(0, [19] * 100))

ちなみに、pipでインストールできるpymodbusの最新バージョン(3.9.2)だと上記のバグがありますが、
githubで公開されているレポジトリでは4月末に修正されたようです↓
https://github.com/pymodbus-dev/pymodbus/commit/eb84cfef92ddab8780652bd…

3.9.2より新しいバージョンが公開されたら、もしかしたらこのバグも治るかもしれません

> rikuya-h 様
>
> こちらでも再現できました(マスターがPC・Armadillo G4のどちらであっても起こりました)
>
> 原因を調べてみたのですが、pymodbusライブラリ内のバグ(仕様?)でした…
> ModbusSlaveContext(...)のところで、di=...の引数も指定しないと、他のco=... , ir=... , hr=...は無効になります
>
> 例えば、添付いただいたコードでは、
>

> store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [18] * 100),ir=ModbusSequentialDataBlock(0, [19] * 100))
> 

> となっていますが、di=...の指定がないため、hrとirはデフォルトの [0]*65536 になってしまいます。(そのため0しか読み取れない)
>
> ですので、何かテキトーな数値でよいので、以下のようにdi=...も指定してください
>

> store = ModbusSlaveContext(di=ModbusSequentialDataBlock(0, [1]), hr=ModbusSequentialDataBlock(0, [18] * 100),ir=ModbusSequentialDataBlock(0, [19] * 100))
> 

>
> ちなみに、pipでインストールできるpymodbusの最新バージョン(3.9.2)だと上記のバグがありますが、
> githubで公開されているレポジトリでは4月末に修正されたようです↓
> https://github.com/pymodbus-dev/pymodbus/commit/eb84cfef92ddab8780652bd…
>
> 3.9.2より新しいバージョンが公開されたら、もしかしたらこのバグも治るかもしれません

at_shota.shimoyama様

ご丁寧な回答ありがとうございます!
バグ(仕様)だったとの事で現時点での修正案
diを追加する事で私の環境でも期待通りの動作になりました。

これで開発を進められます。ありがとうございました。