1 はじめに
1.1 装置開発経緯と概容
太陽光発電装置のモニター(一部未実装)を RaspberryPi と言う MPU を搭載したシングルボードコンピュータ(SBC)で作成している。
このモニターに、外温・室温・インバータ・バッテリー×2台で5個の温度測定を追加したい。
温度検出器にはデジタルで温度値を出力するもの、アナログで出力し別置きでデジタルに変換する装置を設置するものがあるが、 今回は安価なものとして DS18B20(以下スレーブと呼ぶ)と言う名前の検出素子を使用する。
これはデジタルの温度値を出力し 1-Wire と言う信号伝送方法の温度検出器を使用する。
今回必要なスレーブは5個だが、温度情報を受け取る場合は下記プログラムが必要となる。
初期:スレーブのIDを取得
①温度変換指示を出す。
②温度変換するまで待つ
③温度を読み出すスレーブのIDを送信し、
次に温度変換後の値を読み出し
(今回は)5回繰り返し 終われば ①へ
ところでMODBUSで使用している通信デバイスにはUSARTなどがあるが、これにはハード的なドライバーにより処理する機能があるため、Raspberry Pi はそのドライバーに Byte信号だけを受け渡しすれば良いが、 上記 初期や①、③で使用する 1-Wire と言う通信方法では ハード的ドライバーがなく通信信号をソフトで数十(us) 単位で制御する必要がある。
Raspberry Pi の標準 OS は特定の処理をミリ秒・マイクロ秒単位の精度で必ず実行する(リアルタイム性)機能は持たないので、 処理負荷によって応答時間が変動する非リアルタイム(汎用)OSなため スレーブとの通信でタイミング信号(μs単位)がずれて通信エラーになる可能性がある。
そこでRaspberry Pi に直接組み込むことをやめ、安価な MCU で温度検出部を集約しそこで①~③のプログラムに加え、 RaspberryPi に温度情報を渡すプログラムを動作させることにした。
MCU と RaspberryPiの情報を渡す方法は、現状でRaspberryPi から電力関係の情報を MODBUS プロトコルで収集しているので、 今回作成するMCUも MODBUS を使用して情報を伝送することにした。
MCUはできるだけ安価な003系を使用する。
想定するスレーブの最大数は8個とした。
※003系は割り込みが2系×2しか無いので、どうなるか・・
このモニターに、外温・室温・インバータ・バッテリー×2台で5個の温度測定を追加したい。
温度検出器にはデジタルで温度値を出力するもの、アナログで出力し別置きでデジタルに変換する装置を設置するものがあるが、 今回は安価なものとして DS18B20(以下スレーブと呼ぶ)と言う名前の検出素子を使用する。
これはデジタルの温度値を出力し 1-Wire と言う信号伝送方法の温度検出器を使用する。今回必要なスレーブは5個だが、温度情報を受け取る場合は下記プログラムが必要となる。
初期:スレーブのIDを取得
①温度変換指示を出す。
②温度変換するまで待つ
③温度を読み出すスレーブのIDを送信し、
次に温度変換後の値を読み出し
(今回は)5回繰り返し 終われば ①へ
ところでMODBUSで使用している通信デバイスにはUSARTなどがあるが、これにはハード的なドライバーにより処理する機能があるため、Raspberry Pi はそのドライバーに Byte信号だけを受け渡しすれば良いが、 上記 初期や①、③で使用する 1-Wire と言う通信方法では ハード的ドライバーがなく通信信号をソフトで数十(us) 単位で制御する必要がある。
Raspberry Pi の標準 OS は特定の処理をミリ秒・マイクロ秒単位の精度で必ず実行する(リアルタイム性)機能は持たないので、 処理負荷によって応答時間が変動する非リアルタイム(汎用)OSなため スレーブとの通信でタイミング信号(μs単位)がずれて通信エラーになる可能性がある。
そこでRaspberry Pi に直接組み込むことをやめ、安価な MCU で温度検出部を集約しそこで①~③のプログラムに加え、 RaspberryPi に温度情報を渡すプログラムを動作させることにした。
MCU と RaspberryPiの情報を渡す方法は、現状でRaspberryPi から電力関係の情報を MODBUS プロトコルで収集しているので、 今回作成するMCUも MODBUS を使用して情報を伝送することにした。
MCUはできるだけ安価な003系を使用する。
想定するスレーブの最大数は8個とした。
※003系は割り込みが2系×2しか無いので、どうなるか・・
1.1.1 制御・設定内容
CH32V003J4m6による試作機
温度分解能は12bit固定とする。
(1) DeviceAddress:
0x00(初期)〜0xF7(247)まで設定可能
(2) 通信速度:
(3) 変換待ち時間:
(4) 温度変換指令:
スレーブ登録が完了し、自動に設定した場合は次を繰り返す。
・全スレーブに対し温度変換指示。
・設定した変換待ち時間を待つ。
・全スレーブのScratchpadを読み出し温度値16(bit)を記録する。
・手動変換指示の場合は温度変換結果をアンサーする。
(5) 接続しているスレーブを登録する。
(1) DeviceAddress:
0x00(初期)〜0xF7(247)まで設定可能
(2) 通信速度:
| 0x00 | 9600(標準) |
| 0x01 | 19200 |
| 0x02 | 38400 |
| 0x03 | 57600 |
| 0x04 | 119200 |
| 0x05 | 230400 |
| 0x06 | 460800 |
| 0x07 | 576000 |
| 0x00 | 分解能変換時間 12bitの場合は 800(ms) |
| 0x01〜0x41 | 1〜65秒で設定 |
| 0x00 | off(標準) |
| 0x01 | 自動繰り返し |
| 0x10 | 手動温度変換指示 |
・全スレーブに対し温度変換指示。
・設定した変換待ち時間を待つ。
・全スレーブのScratchpadを読み出し温度値16(bit)を記録する。
・手動変換指示の場合は温度変換結果をアンサーする。
接続されているスレーブを検索し関係する情報を記録する。
最大スレーブ数は8個とする。
・検出されたスレーブの数を記録する。
・RomCodeを記録する。
・Scratchpadを読み出し、設定値(分解能)を記録する
なお標準の分解能(12bit)と違う場合は、1回のみScratchpadとEEPROMに記録指示を出す。
指定不能な場合はそのままの分解能とする。
最大スレーブ数は8個とする。
・検出されたスレーブの数を記録する。
・RomCodeを記録する。
・Scratchpadを読み出し、設定値(分解能)を記録する
なお標準の分解能(12bit)と違う場合は、1回のみScratchpadとEEPROMに記録指示を出す。
指定不能な場合はそのままの分解能とする。
1.1.2 情報の取得
(1) 記憶アドレスを指定してスレーブのRomCodeを読み出し
(2) 記録アドレスを指定してスレーブの設定(分解能)を読み出し
(3) 記録アドレスを指定してスレーブの温度(16bit)を読み出し
(2) 記録アドレスを指定してスレーブの設定(分解能)を読み出し
(3) 記録アドレスを指定してスレーブの温度(16bit)を読み出し
1.1.3 電源を入れた場合の処理
以下の初期処理を行う
(1) GPIO初期化
(2) TIM2で1000(ms)タイマー動作
(3) UART1初期化/割り込み初期化
(4) SW の割り込みを有効化
(5) スレーブ登録処理
(6) 分解能適正確認 12bitが悪ければ、11,10,9bitの動作確認
(7) 温度変換待ち時間計算
(1) GPIO初期化
(2) TIM2で1000(ms)タイマー動作
(3) UART1初期化/割り込み初期化
(4) SW の割り込みを有効化
(5) スレーブ登録処理
(6) 分解能適正確認 12bitが悪ければ、11,10,9bitの動作確認
(7) 温度変換待ち時間計算
1.1.4 主な処理
プログラムは大きく別けて1つの割り込み方式の処理と1つのポーリング方式の処理を行っている。
(1)割り込み処理部
(2)ポーリング処理部 メイン関数部の繰り返し部の説明をする。
MODBUSの信号解析はFunctionにより、DataAddresデータ、Registerデータ を解析してその処理を行うが、DeviceAddressやCRCが合ってない場合は、エラーメッセージをマスターへ返す。
(1)割り込み処理部
MODBUSの信号をUSART1で受信している。
この受信処理を割り込み処理としている。
スレーブ側で受信するbyte数は8Byteなので、その数でポーリング処理部にMODBUS信号を受信した事を示すフラグ変数(FgIntMBA)をセットします。
マスターから送る信号は他のスレーブとの通信も有ることから通信長は 8Byteとは限らないので、この信号をキャンセルするためにTIME1を使用している。
詳細は 3.2.1 マスターメッセージ長の確認処理 を参照
この受信処理を割り込み処理としている。
スレーブ側で受信するbyte数は8Byteなので、その数でポーリング処理部にMODBUS信号を受信した事を示すフラグ変数(FgIntMBA)をセットします。
マスターから送る信号は他のスレーブとの通信も有ることから通信長は 8Byteとは限らないので、この信号をキャンセルするためにTIME1を使用している。
詳細は 3.2.1 マスターメッセージ長の確認処理 を参照
(2)ポーリング処理部 メイン関数部の繰り返し部の説明をする。
(1)温度変換が自動設定でないなら(9)へ
(2)温度検出素子が登録されていないなら(9)へ
(3)全温度検出素子に温度変換指示
(4)変換待時間になったか:なったら(8)へ
(5)フラグ変数(FgIntMBA)がセットさてないなら(4)へ
(6)MODBUSの信号解析(ModBusAnalyze)へ
(7)フラグ変数をリセットする (8)全温度検出器の温度データを読み込んで(1)へ
(9)フラグ変数(FgIntMBA)がセットさてないなら(1)へ
(10)MODBUSの信号解析(ModBusAnalyze)へ
(11)フラグ変数をリセットして(1)へ
(2)温度検出素子が登録されていないなら(9)へ
(3)全温度検出素子に温度変換指示
(4)変換待時間になったか:なったら(8)へ
(5)フラグ変数(FgIntMBA)がセットさてないなら(4)へ
(6)MODBUSの信号解析(ModBusAnalyze)へ
(7)フラグ変数をリセットする (8)全温度検出器の温度データを読み込んで(1)へ
(9)フラグ変数(FgIntMBA)がセットさてないなら(1)へ
(10)MODBUSの信号解析(ModBusAnalyze)へ
(11)フラグ変数をリセットして(1)へ
MODBUSの信号解析はFunctionにより、DataAddresデータ、Registerデータ を解析してその処理を行うが、DeviceAddressやCRCが合ってない場合は、エラーメッセージをマスターへ返す。
1.1.5 処理速度を早くする処理
(1)USART送信にDMA転送処理
(2)CRC生成処理 444(us)→118(us)
(3)メモリー転送 43.5(us)→28(us)
(4)温度だけの読み込み処理 177(ms)→120(ms)
(5)USART受信を割り込み処理 1Byte= (us) 最後のByte処理= (us)
(2)CRC生成処理 444(us)→118(us)
(3)メモリー転送 43.5(us)→28(us)
(4)温度だけの読み込み処理 177(ms)→120(ms)
(5)USART受信を割り込み処理 1Byte= (us) 最後のByte処理= (us)
1.1.6 エラー処理
マスターがスレーブに対しエラーコードを指示した場合は、スレーブからマスターに対し下記応答をする。
0:Device Address = スレーブ側のアドレス
1:Function = マスターのコード + 0x80
2:Data数 = 2
3:Hi Register = ModBusErrCode0
4:Lo Register = ModBusErrCode1
なお温度検出器のエラーコードは ホールディングレジスタ の 0x0007〜0x000E に格納されている。
格納しているエラーコードを読み出した場合は、そのレジスターは初期化(0x00)される。
(1) スレーブが無い
(2) ファミリーコード0x28のスレーブが無い
(3) CRCエラー
(4) 分解能設定変更ができない
(5) EEPROMに設定変更の記録ができない
0:Device Address = スレーブ側のアドレス
1:Function = マスターのコード + 0x80
2:Data数 = 2
3:Hi Register = ModBusErrCode0
4:Lo Register = ModBusErrCode1
なお温度検出器のエラーコードは ホールディングレジスタ の 0x0007〜0x000E に格納されている。
格納しているエラーコードを読み出した場合は、そのレジスターは初期化(0x00)される。
(1) スレーブが無い
(2) ファミリーコード0x28のスレーブが無い
(3) CRCエラー
(4) 分解能設定変更ができない
(5) EEPROMに設定変更の記録ができない
1.2 回路
スイッチを動作させることにより、DeviceAddressと通信速度をリセットできるようにする。
リセットでは
・DeviceAddressは 0x00
・通信速度は 9600(bps)
1.2.1 開発時の回路
1.2.2 開発後の確認用回路
開発時はH32V003J4m6を使用して確認する。


1.3 通信速度
これは単純に速度を8種類として
0:9800 1:19200 2:38400 3:57600 4:119200 5:230400 6:460800 7:576000
としただけで、大した根拠はない。
576(kbps)で動作できることを確認した。
右図のオシロ黄色線はマスター(PC)の送信信号だが、速度が早いた方形波が三角波になっており USB-RS23変換器の性能が危ない事がわかる。
青線はCH32V003の送信出力でまだ大丈夫そうだ。
1.4 動作確認

スレーブ(DS18B20)が3個接続しているが、矢印のモノは分解能 12bit しか反応が無い偽物ですが、 今回作成したDS18B20-MODBUSプログラムは、各bitで全てが動作し、大きい分解能のbitで測定を実施するようになっており 下記温度は12bitの分解能の値になっている。
Pythonでテスト用のMODBUSプログラムでスレーブの温度情報を読み取ったデータを下に示す。 アドレス8にスレーブを検出した台数が入っている。

2 MODBUSの仕様
2.1 マスター側
マスターとはCH32V003に指令を出す側を言います。
プログラムの開発中はWCH-LinkEを接続するPC側です。
プログラムの開発中はWCH-LinkEを接続するPC側です。
2.1.1 フレーム構造
8Byte固定です。
| NoBytrs | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 項目 | Device Address | Function | DataAddres | Register | CRC | |||
| Top | Lo | Top | Lo | Top | Lo | |||
2.1.2 DeviceAddress
スレーブ側のアドレスを入力する。
最初は0x00になっているが、MODBUSを通して 0x01〜0xF7(10進数 247) まで設定が出来る。
最初は0x00になっているが、MODBUSを通して 0x01〜0xF7(10進数 247) まで設定が出来る。
2.1.3 Function
今回作成するスレーブ Dataには
・読み書きできる2Byteデータ
・読込専用の2Byteデータ
の2種類がある。
このデータをどうするかこのFunctionで指示する。
・読み書きできる2Byteデータ
・読込専用の2Byteデータ
の2種類がある。
このデータをどうするかこのFunctionで指示する。
| Function Code | 指示・レジスタ | 概 容 |
|---|---|---|
| 0x03 | Read Holding Registers | ホールディングレジスタ(16ビットの値)の読み出し。 |
| 0x06 | Write Holding Register | ホールディングレジスタ(16ビットの値)の書き込み。 |
| 0x04 | Read Input Registers | インプットレジスタ(16ビットの値、読み出し専用)の読み出し。 |
| 0xFF | ----------ーーーーーーーーーー | スレーブからマスターへエラーメッセージ |
2.1.4 DataAddres
| レジスタ名 | 開始アドレス | 終了アドレス | 概 容 |
|---|---|---|---|
| Holding Registers | 0x0000 | 0x003F | アドレスなど設定・エラー・ROM*1・閾値*1・分解能*1 |
| Input Registers | 0x0000 | 0x0010 | 測定した温度、エラーコード |
2.1.5 Register
ここはファンクションにより違う内容になります。
ただしスレーブ側の応答の Data数 = N は Registerの数なので、上記レジスターの内容に出てくる nと違い、
n = N*2 となるので注意すること。
| Function Code | レジスターの内容 |
|---|---|
| 0x03 | 読み出すDevice Addressの数 n = (Hi Register +Lo Register)*n |
| 0x04 | |
| 0x06 | 書き込む レジスタの値 |
n = N*2 となるので注意すること。
2.1.6 CRC
CRCについては『Software Protocol MODBUS 全般』のCRC参照下さい
2.2 スレーブ側
2.2.1フレーム構造
| No Byte | 0 | 1 | 2 | 3~(N*2)+1 | 4~(N*2)+2 | N*2+3 | N*2+4 |
|---|---|---|---|---|---|---|---|
| 項目 | Device Address | Function | Data数 = N | Hi Register | Lo Register | Hi CRC | Lo CRC |
スレーブの送信バッファーは 最大 133Byte必要 理由は以下による。
スレーブからマスターへ送るデータは
1Byte(Device Address) +
1Byte(Function) +
1Byte(Data数) +
128Byt(最大レジスター数 64×2) +
2Byte(CRC) = 133Byte
バッファーの数を確保できない場合は、プログラムで制限するとともに取り扱い説明書にData数の最大値を表記しておく必要がある。
なお
Data数 n = 単位は1Byteです。
ModBusのレジスターのデータは通常2Byteなので通常最低値は n = 2 となります。
1Byte(Device Address) +
1Byte(Function) +
1Byte(Data数) +
128Byt(最大レジスター数 64×2) +
2Byte(CRC) = 133Byte
バッファーの数を確保できない場合は、プログラムで制限するとともに取り扱い説明書にData数の最大値を表記しておく必要がある。
なお
Data数 n = 単位は1Byteです。
ModBusのレジスターのデータは通常2Byteなので通常最低値は n = 2 となります。
2.2.2 DeviceAddress
ブロードキャストアドレスは0x00です。
ブロードキャストアドレスでは全スレーブで
保持レジスターの書き込みに使用される。
マスターへ返答はしません。
設定できるアドレスの範囲は0x01〜0xF7です。
初期設定は0x01です。
ブロードキャストアドレスでは全スレーブで
保持レジスターの書き込みに使用される。
マスターへ返答はしません。
設定できるアドレスの範囲は0x01〜0xF7です。
初期設定は0x01です。
2.2.3 Function
| Function Code | 内 容 |
|---|---|
| 0x03 | Read Holding Registers :ホールディングレジスタ(16ビットの値、読み書き可能)の読み出し。 |
| 0x06 | Write Holding Register :ホールディングレジスタへの書き込み。 |
| 0x04 | Read Input Registers :インプットレジスタ(16ビットの値、読み出し専用)の読み出し。 |
2.2.4 DataAddress & Register
(1) ホールディングレジスタ
| Data Address | 項目 | 内容 | ||
|---|---|---|---|---|
| Start | End | Hi Register | Lo Register | |
| 0x0000 | ---- | DeviceAddress | -ーーー | 初期値は0x00 設定範囲は0x01〜0xF7 |
| 0x0001 | ---- | 通信スピード | ---- | 初期値は0:9600bps ※1参照 |
| 0x0002 | ---- | 変換待ち時間 | ---- | 初期値は0で分解能の応じた時間 1~255:単位は1(sec) |
| 0x0003 | ---- | 温度指令 | ---- | 初期値は0x00:off 0x01:自動(0x0002で設定した時間で計測開始) 0x10で変換指示 |
| 0x0004 | ---- | スレーブの登録 | ーーーー | 初期値0:完了 1:指示 |
| 0x0005 | ---- | 温度素子全体 | Hi温度閾値 | Lo温度閾値 |
| 0x0006 | ---- | 温度素子全体 | 0x00 | 変換分解bitなど |
| 0x0007 | ---- | スレーブ0 | ErrCode 01※2 | ErrCode 02※3 |
| 0x0008 | ---- | スレーブ1 | ErrCode 01※2 | ErrCode 02※3 |
| 0x0009 | ---- | スレーブ2 | ErrCode 01※2 | ErrCode 02※3 |
| 0x000A | ---- | スレーブ3 | ErrCode 01※2 | ErrCode 02※3 |
| 0x000B | ---- | スレーブ4 | ErrCode 01※2 | ErrCode 02※3 |
| 0x000C | ---- | スレーブ5 | ErrCode 01※2 | ErrCode 02※3 |
| 0x000D | ---- | スレーブ6 | ErrCode 01※2 | ErrCode 02※3 |
| 0x000E | ---- | スレーブ7 | ErrCode 01※2 | ErrCode 02※3 |
| 0x000F | ---- | MODBUS | ErrCode 01※4 | ErrCode 02※5 |
| 0x0010 | 0x0013 | スレーブ0 | 64bit RomCode 上位〜下位 | |
| 0x0014 | 0x0017 | スレーブ1 | 64bit RomCode 上位〜下位 | |
| 0x0018 | 0x001B | スレーブ2 | 64bit RomCode 上位〜下位 | |
| 0x001C | 0x001F | スレーブ3 | 64bit RomCode 上位〜下位 | |
| 0x0020 | 0x0014 | スレーブ4 | 64bit RomCode 上位〜下位 | |
| 0x0024 | 0x0017 | スレーブ5 | 64bit RomCode 上位〜下位 | |
| 0x0028 | 0x001B | スレーブ6 | 64bit RomCode 上位〜下位 | |
| 0x002C | 0x001F | スレーブ7 | 64bit RomCode 上位〜下位 | |
| 0x0030 | ---- | スレーブ0 | Hi温度閾値 | Lo温度閾値 |
| 0x0031 | ---- | スレーブ1 | Hi温度閾値 | Lo温度閾値 |
| 0x0032 | ---- | スレーブ2 | Hi温度閾値 | Lo温度閾値 |
| 0x0033 | ---- | スレーブ3 | Hi温度閾値 | Lo温度閾値 |
| 0x0034 | ---- | スレーブ4 | Hi温度閾値 | Lo温度閾値 |
| 0x0035 | ---- | スレーブ5 | Hi温度閾値 | Lo温度閾値 |
| 0x0036 | ---- | スレーブ6 | Hi温度閾値 | Lo温度閾値 |
| 0x0037 | ---- | スレーブ7 | Hi温度閾値 | Lo温度閾値 |
| 0x0038 | ---- | スレーブ0 | ---- | 分解能bit |
| 0x0039 | ---- | スレーブ1 | ---- | 分解能bit |
| 0x003A | ---- | スレーブ2 | ---- | 分解能bit |
| 0x003B | ---- | スレーブ3 | ---- | 分解能bit |
| 0x003C | ---- | スレーブ4 | ---- | 分解能bit |
| 0x003D | ---- | スレーブ5 | ---- | 分解能bit |
| 0x003E | ---- | スレーブ6 | ---- | 分解能bit |
| 0x003F | ---- | スレーブ7 | ---- | 分解能bit |
※1 通信スピード枠の値と通信スピード(bps)
| 値 | dec(bps) | 8byte(us) | PassTime(us) |
|---|---|---|---|
| 0x00 | 9600 | 8333 | 10000 |
| 0x01 | 19200 | 4167 | 5000 |
| 0x02 | 38400 | 2083 | 2500 |
| 0x03 | 57600 | 1389 | 1670 |
| 0x04 | 115200 | 694 | 830 |
| 0x05 | 230400 | 347 | 420 |
| 0x06 | 460800 | 174 | 210 |
| 0x07 | 576000 | 139 | 170 |
(2) インプットレジスタ
| Register | 項目 | 備考 |
|---|---|---|
| 0x0000 | 温度計接続数 | HiByteは用途無しLoByteに温度計接続数 |
| 0x0001 | 温度計エラー | ※1 |
| 0x0002 | 温度 温度0 | ※1値は符号付きで1/16にすること。 |
| 0x0003 | 温度 温度1 | ※1 |
| 0x0004 | 温度 温度2 | ※1 |
| 0x0005 | 温度 温度3 | ※1 |
| 0x0006 | 温度 温度4 | ※1 |
| 0x0007 | 温度 温度5 | ※1 |
| 0x0008 | 温度 温度6 | ※1 |
| 0x0009 | 温度 温度7 | ※1 |
3 プログラムの設定と確認
3.1 割り込み順位
| ディバイス | Preemption | Sub | 備考 |
|---|---|---|---|
| USART1 Rx | 0 | 1 | |
| USART1 Tx DMA | 1 | 1 | |
| TIM1 | 0 | 0 | |
| TIM2 | 1 | 0 |
3.2 エラー処理
3.2.1 マスターメッセージ長の確認処理
マスターメッセージ長の確認が必要な理由
上記に多少関係することでマスターから次のデータが送らてくるまでの時間をGoogleのAIで確認した。
このアプリて使用するマスターのメッセージ長は8Byte。
プログラムではメッセージを1Byte受信するたびに配列に入れる。
8Byte受信すると配列のデータを解析してコードが正しければ応答メッセージを返すようにしている。
しかしマスターのメッセージが8Byteでない場合に次に送られてくるメッセージの受信に対して不具合が出てきます。
例えば本装置以外へのマスターからのメッセージで6Byteしか来ない場合、次のマスターからのメッセージの先頭2Byteを加えて、 一つのメッセージとして処理することになるため以降、常に間違ったメッセージになってしまう。
これを防止するためプログラムではメッセージを1Byte受信するたびに配列に入れる。
8Byte受信すると配列のデータを解析してコードが正しければ応答メッセージを返すようにしている。
しかしマスターのメッセージが8Byteでない場合に次に送られてくるメッセージの受信に対して不具合が出てきます。
例えば本装置以外へのマスターからのメッセージで6Byteしか来ない場合、次のマスターからのメッセージの先頭2Byteを加えて、 一つのメッセージとして処理することになるため以降、常に間違ったメッセージになってしまう。
8Byteより短いデータであることを検出し、受信データの配列を最初に戻すようにTIM1を使用する。
このTIM1の時間は8Byteより多少長い時間を設定する。
USART1の速度を変更する時にTIM1の時間も変更する。
もう少し具体的にこのTIM1の時間は8Byteより多少長い時間を設定する。
USART1の速度を変更する時にTIM1の時間も変更する。
メッセージの1byte目でTIM1の割り込み処理を使用する。
メッセージの8byte目でTIM1の割り込み処理を停止、受信データを解析して処理、受信データの配列を最初に戻す。
メッセージが8Byte以下の場合はTIM1の割り込み処理により、受信データの配列を最初に戻す。
メッセージの8byte目でTIM1の割り込み処理を停止、受信データを解析して処理、受信データの配列を最初に戻す。
メッセージが8Byte以下の場合はTIM1の割り込み処理により、受信データの配列を最初に戻す。
TIM1:タイマー値確認
| 通信速度(bps) | 設定値(us) | 測定結果(us) |
|---|---|---|
| 9600 | 9170 | 9160 |
| 19200 | 4580 | 4580 |
| 38400 | 2290 | 2290 |
| 57600 | 1530 | 1530 |
| 115200 | 760 | 760 |
| 230400 | 380 | 380 |
| 460800 | 190 | 190 |
| 576000 | 150 | 150 |
| 921600 | 100 | 100 |
MODBUSのメッセージで次のメッセージを送るまでの間隔(時間)について規定はありますか
Modbusプロトコルでは、次のメッセージを送るまでの間隔(時間)について、使用する
伝送モード(主にRTUモードとASCIIモード)によって具体的な規定があります。
RTUモードの場合
RTUモードでは、メッセージの開始と終了を識別するために無通信時間が重要です。
・メッセージ間の間隔: 1つのメッセージの終了から次のメッセージの開始までは、
最低3.5文字時間(3.5キャラクタータイム)以上の無通信時間が必要です。
・メッセージ内のバイト間隔: 1つのメッセージ内の各バイト間隔は、
最大1.5文字時間以内である必要があります。これを超えると、
受信側デバイスはメッセージが不完全であると判断し、破棄します。
・通信速度と時間: この「文字時間」はボーレート(通信速度)によって変化します。
例えば、9600bpsでは約4.0ms、19200bpsでは約2.0msが3.5文字時間に相当します。
ただし、19200bpsを超えるボーレートでは、3.5ms(または1.75ms)といった固定値が
使用されることもあります。
ASCIIモードの場合
ASCIIモードでは、メッセージの開始と終了が特定の文字(開始はコロン『:』、
終了はCR/LF)で区切られるため、メッセージ内のバイト間隔の規定はRTUモードほど厳密ではありません。
メッセージ内のバイト間隔: メッセージ内の文字間隔は最大1秒まで許容されます。
補足事項
・マスタとスレーブ: 通常、マスタ側は次の要求メッセージを送信する前に、
スレーブからの応答を待つか、タイムアウトするまで待機します。
スレーブは応答メッセージの送信後、次のメッセージを受信する前に3.5文字時間以上の
無通信時間を確保する必要があります。
・機器固有の仕様: 多くの機器では標準仕様に準拠していますが、一部の機器(スレーブ)によっては、
応答遅延時間の設定(0~1000msなど)ができる場合や、特定の推奨値が設定されている場合もあります。
したがって、Modbusのメッセージ間隔は、使用するモードとボーレートに依存し、
特にRTUモードでは厳密なタイミング規定が存在します。機器の仕様書を確認することをお勧めします
3.2.2 マスターメッセージの確認処理
表面上には出てこないが
プログラムを作成していく中で動作テストをするが、MODBUSコードが合っていあに場合にUSART1にエラーコードを送信するようにしている。
そこで使用しているエラーコードを記載する。(通常使用時はコメントアウトしている。)
変数名 ErrCode
下図のオシロ波形説明
黄色線:マスターの要求メッセージ 速度は115200(bps)
水色線:スレーブで処理したエラーコード CRC異常(テスト用エラー表示、通常はコメントアウトしている)
赤色線:USARTのRx割り込み処理を示す。
青色線:TIM1の動作を示す。

続いて正常なマスターからのメッセージを送信: 0x2 0x3 0x0 0x0 0x0 0x1 0x84 0x39
スレーブ側では正常に応答メッセージを送信(自分のDeviceAddressを返答)

プログラムを作成していく中で動作テストをするが、MODBUSコードが合っていあに場合にUSART1にエラーコードを送信するようにしている。
そこで使用しているエラーコードを記載する。(通常使用時はコメントアウトしている。)
変数名 ErrCode
| CodeNo | 使用関数 | |||
|---|---|---|---|---|
| bit | Hex | ModBusAnalyze | ModBus0* | USART1_SET |
| 0b00000001 | 0x01 | -- | Register 0 | --- |
| 0b00000010 | 0x02 | -- | Register > Max | --- |
| 0b00000100 | 0x04 | -- | DataAddres > Max | --- |
| 0b00001000 | 0x08 | -- | DataAddres+Register > Max | --- |
| 0b00010000 | 0x10 | DeviceAddress | --- | |
| 0b00100000 | 0x20 | CRC | --- | |
| 0b01000000 | 0x40 | FunctionNo | --- | |
| 0b10000000 | 0x80 | 通信速度No | ||
テスト例(TIM1の設定も含む確認する)
マスターからのメッセージ: 0x02,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x53 0x41下図のオシロ波形説明
黄色線:マスターの要求メッセージ 速度は115200(bps)
水色線:スレーブで処理したエラーコード CRC異常(テスト用エラー表示、通常はコメントアウトしている)
赤色線:USARTのRx割り込み処理を示す。
青色線:TIM1の動作を示す。
1.マスターの要求メッセージ8Btye(0x02,0x03,0x00,0x00,0x00,0x00,0x00)を受信。
2.TIM1をリセットして
3.メッセージ最後の2ByteはCRC値になるが、CRCを計算すると0x45 0xF9になり、違うためエラー出力
続いて
4.TIM1を動作させ残り6Byteを受信開始(図の[AX]部)
5.メッセージが8ByteにならないのでTIM1の設定値 760(us)経過(図の[BX]部)で受信データの配列を最初に戻す。
[AX]〜[BX]の時間は Cursors の ΔX: 760(us)
以上により次のマスター要求データを異常なく受信できるようになる。
2.TIM1をリセットして
3.メッセージ最後の2ByteはCRC値になるが、CRCを計算すると0x45 0xF9になり、違うためエラー出力
続いて
4.TIM1を動作させ残り6Byteを受信開始(図の[AX]部)
5.メッセージが8ByteにならないのでTIM1の設定値 760(us)経過(図の[BX]部)で受信データの配列を最初に戻す。
[AX]〜[BX]の時間は Cursors の ΔX: 760(us)
以上により次のマスター要求データを異常なく受信できるようになる。

続いて正常なマスターからのメッセージを送信: 0x2 0x3 0x0 0x0 0x0 0x1 0x84 0x39
スレーブ側では正常に応答メッセージを送信(自分のDeviceAddressを返答)

3.3 動作確認
3.3.1 通常動作
UART1速度115200(bps)
マスターからのメッセージ : 0x02 0x03 0x00 0x00 0x00 0x02 0xC4 0x38
下図のオシロ波形説明
黄色線:マスターの要求メッセージ
水色線:スレーブの返答メッセージ
赤色線:USARTのRx割り込み処理を示す。
青色線:TIM1の動作を示す。

応答メッセージは DeviceAddress = 2 通信速度 = 4 = 115200(bps)を表している。
マスターからのメッセージ : 0x02 0x03 0x00 0x00 0x00 0x02 0xC4 0x38
下図のオシロ波形説明
黄色線:マスターの要求メッセージ
水色線:スレーブの返答メッセージ
赤色線:USARTのRx割り込み処理を示す。
青色線:TIM1の動作を示す。

応答メッセージは DeviceAddress = 2 通信速度 = 4 = 115200(bps)を表している。
3.3.2 DeviceAddress変更
DeviceAddressを2➡1へ変更
UART1速度115200(bps)
マスターからのメッセージ : 0x02 0x06 0x00 0x00 0x00 0x01 0x48 0x39
下図のオシロ波形説明
黄色線:マスターの要求メッセージ
水色線:スレーブの返答メッセージ
赤色線:USARTのRx割り込み処理を示す。
青色線:TIM1の動作を示す。

DeviceAddressを1以外はスレーブから返答がなかった

マスターからのメッセージ : 0x02 0x06 0x00 0x00 0x00 0x01 0x48 0x39
下図のオシロ波形説明
黄色線:マスターの要求メッセージ
水色線:スレーブの返答メッセージ
赤色線:USARTのRx割り込み処理を示す。
青色線:TIM1の動作を示す。

DeviceAddressを1以外はスレーブから返答がなかった

3.3.3 USART1速度変更
速度を230400(bps)➡115200(bps)へ変更する。
マスターからのメッセージ : 0x2 0x6 0x0 0x1 0x0 0x4 0xd9 0xfa
下図のオシロ波形説明
黄色線:マスターの要求メッセージ 速度は230400(bps)
水色線:スレーブの返答メッセージ 速度は115200(bps)
赤色線:速度変更の動作を示す。
青色線:TIM1の動作を示す。

マスターからのメッセージ : 0x2 0x6 0x0 0x1 0x0 0x4 0xd9 0xfa
下図のオシロ波形説明
黄色線:マスターの要求メッセージ 速度は230400(bps)
水色線:スレーブの返答メッセージ 速度は115200(bps)
赤色線:速度変更の動作を示す。
青色線:TIM1の動作を示す。

3.3.4 CRC処理方法の比較
(1) CRCの計算は2箇所あり
マスターからの要求フォーマットのCRC計算スレーブからの応答フォーマットのCRC計算
(2) ModBusのCRC計算方法として今回は2種類を比較する。
①純計算方式②配列を組合せた計算方式
(3) CRCの対象Byte数
マスターからの要求フォーマットは6Byte固定スレーブの応答ファーマットは 最大131Byte 〜 最小5Byte
処理時間の測定は、マスターからの8Byte目の受信〜スレーブの応答データをTx用のDMAへ送るまでの時間を測定する。
| 計算方法 | 対象Byte | 実行時間(us) |
|---|---|---|
| ① | 6+131 | 444 |
| 6+5 | 46 | |
| ② | 6+131 | 118 |
| 6+5 | 19 |
(4) 結果
配列を組合せた計算方式の方が4倍早い事が判った。
今回のこの方式を採用する。
今回の時間データから
A + B × Byte数 の式で 固定時間(A)とByte変動時間(B) を出したのが下表。
計算方式 A(us) B(us)
① 11.25 3.16
② 10.35 0.79
①/② 1.087 4
今回のこの方式を採用する。
今回の時間データから
A + B × Byte数 の式で 固定時間(A)とByte変動時間(B) を出したのが下表。
A + B × Byte数 の式で 固定時間(A)とByte変動時間(B) を出したのが下表。
| 計算方式 | A(us) | B(us) |
|---|---|---|
| ① | 11.25 | 3.16 |
| ② | 10.35 | 0.79 |
| ①/② | 1.087 | 4 |
3.3.5 コピー方法の比較
| ① | 6+131 | 118 |
|---|---|---|
| 6+5 | 19 | |
| ② | 6+131 | 111 |
| 6+5 | 18 |
3.3.6 メインプログラム
プログラム概容
初期・各部の初期化
・スレーブ登録
・スレーブがあれば温度測定 停止→自動
①自動になっているか no → ⑩ → ①へ
↓
②スレーブ台数が有る場合は no → ⑩ → ①へ
↓
③温度変換指令
↓
④-1 ⑩
④-2 待時間が超えたか no→④-1へ
↓
⑤各スレーブの温度を読込、配列へ記録
↓
①へ
⑩ModBusの信号が来たか(ディバイス番号が合っているかまでを割り込み処理で判定している。) no→ 戻る
↓
⑳
↓
戻る
⑳ ファンクション他解析、実行
↓
戻る
メインプログラムの動作例
(1)ModBusで変換指示 0x(00,06,00,03,00,10,79,d7)(2)全スレーブに温度変換指示を出す
(3)変換待ち時間待機
(4)各スレーブの温度を取得してModBusで使用している配列にデータを格納する。
(5)ModBusで変換したデータを受信 0x(00,04,12,01,37,01,3a,01,33,01,3f,01,3f,01,3b,01,4a,01,3c,00,08,fa,92)
時間をオシロで観測 黄線 CH32V003のRX、水線 TX、赤線 1-Wire動作 青線 1-Wire
(1)〜(5)の全体波形![]() (4) 処理時間は121(ms) (5) 24(ms) ![]() |
(1) 8.22(ms) (2)3.2(ms) ![]() 信号内容について (1) 黄線したのHex値の通り (1)~(2)初め 8.25(ms) (2) 0011001100100010→11001100(0xCC)01000100(0x44) |
4 プログラム
プログラムはMounRiver Studio Ⅱで作成しました。
コメントや無駄なプログラムは消したつもりです。
プログラムとしては 1000行程度になりました。
無駄なものや改善点がございましたら、お教え下さい。
プログラムの説明用ページは後日になります。
コメントや無駄なプログラムは消したつもりです。
プログラムとしては 1000行程度になりました。
無駄なものや改善点がございましたら、お教え下さい。
プログラムの説明用ページは後日になります。




