「MSP430G2553 - MODBUS」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

編集の要約なし
 
(同じ利用者による、間の10版が非表示)
155行目: 155行目:
MSP430G2553でMODBUSを使用する場合、物理層としてRS-485を使用することが一般的である。<br>
MSP430G2553でMODBUSを使用する場合、物理層としてRS-485を使用することが一般的である。<br>
RS-485は、差動信号方式を採用しており、<u>長距離通信 (最大1200[m])</u> と <u>ノイズ耐性</u> に優れている。<br>
RS-485は、差動信号方式を採用しており、<u>長距離通信 (最大1200[m])</u> と <u>ノイズ耐性</u> に優れている。<br>
<br>
[[ファイル:MSP430G2553 MAX3485 1.png|中央|725x260px]]
<center><big>図. MAX3485 (3.3[V]) を使用する場合</big></center>
<br>
[[ファイル:MSP430G2553 MAX485 1.png|中央|725x398px]]
<center><big>図. MAX485 (5[V]) を使用する場合</big></center>
<br>
<br>
==== 必要なハードウェア ====
==== 必要なハードウェア ====
171行目: 177行目:
| RS-485トランシーバ || ADM485、SN75176 || MAX485互換の代替品
| RS-485トランシーバ || ADM485、SN75176 || MAX485互換の代替品
|-
|-
| 終端抵抗 || 120[Ω] × 2 || バスの両端に配置
| 終端抵抗 || 120[Ω] || バスの両端に配置
|-
|-
| バイアス抵抗 || 560[Ω] × 2 || フェイルセーフバイアス用
| バイアス抵抗 || 560[Ω]〜20[kΩ] × 2 || フェイルセーフバイアス用
|-
|-
| 保護素子 || TVSダイオード || サージ保護用 (オプション)
| 保護素子 || TVSダイオード || サージ保護用 (オプション)
179行目: 185行目:
</center>
</center>
<br>
<br>
==== RS-485トランシーバとの接続 ====
==== RS-485トランシーバとの接続 ====
下表に、MSP430G2553とMAX485を使用したピン接続例を示す。<br>
下表に、MSP430G2553とMAX3485を使用したピン接続例を示す。<br>
<br>
<br>
<center>
<center>
186行目: 193行目:
|+ MSP430G2553 と MAX485 の接続
|+ MSP430G2553 と MAX485 の接続
|-
|-
! MSP430G2553 !! 機能 !! MAX485 !! ピン番号
! MSP430G2553 !! 機能 !! MAX3485 !! ピン番号
|-
|-
| P1.1 || UART TXD (送信) || DI (Driver Input) || Pin 4
| P1.1 || UART TXD (送信) || DI (Driver Input) || Pin 4
198行目: 205行目:
| GND || グラウンド || GND || Pin 5
| GND || グラウンド || GND || Pin 5
|-
|-
| VCC (3.3V) || 電源 || VCC || Pin 8
| - || 電源 || VCC 5[V] || Pin 8
|-
|-
| - || - || A (非反転出力) || RS-485バス
| - || - || A (非反転出力) || RS-485バス
212行目: 219行目:
バスの両端には、120[Ω]の終端抵抗を配置して、信号の反射を防止する必要がある。<br>
バスの両端には、120[Ω]の終端抵抗を配置して、信号の反射を防止する必要がある。<br>
<br>
<br>
==== バイアス抵抗とフェイルセーフ ====
==== バイアス抵抗とフェイルセーフ ====
RS-485バスには、通信していない時の不定状態を防ぐために、バイアス抵抗を追加することが推奨される。<br>
RS-485バスには、通信していない時の不定状態を防ぐために、バイアス抵抗を追加することが推奨される。<br>
227行目: 235行目:
|-
|-
| 560[Ω] || B端子 → GND (プルダウン) || アイドル時にB端子を低電位に保持
| 560[Ω] || B端子 → GND (プルダウン) || アイドル時にB端子を低電位に保持
|}
</center>
<br>
==== 保護ダイオード ====
保護ダイオード (上図のD1〜D3) においては、過酷な環境や信頼性を重視する場合は実装することを推奨する。<br>
<br>
* 保護ダイオードを実装すべき場合
** 屋外や工場等、ノイズや静電気が多い環境
** 長距離配線 (ケーブル長が長い)
** サージや雷サージのリスクがある場所
<br>
下表に、保護ダイオードの選定基準を示す。<br>
<center>
{| class="wikitable"
|+ 保護ダイオードの仕様
|-
! 項目 !! 仕様値
|-
| Working Voltage (VWM) || 5[V]
|-
| Breakdown Voltage (VBR) || 6.4〜7.0[V]程度
|-
| Clamping Voltage (VC) || 9.2〜9.9[V]程度
|-
| Peak Pulse Power || 400[W]以上
|-
| パッケージ || SMA (DO-214AC)
|}
</center>
<br>
<center>
{| class="wikitable"
|+ 保護ダイオード  メーカー別比較表
|-
! メーカー !! 型番 !! 備考
|-
| Vishay || SMAJ5.0A-E3/61<br>SMAJ5.0A-E3/5A || 最も入手しやすい代替品の一つ<br>電気的特性がほぼ同等
|-
| Bourns || SMAJ5.0A || Littelfuseとピンコンパチブル
|-
| Diodes Incorporated || SMAJ5.0A-13-F || コストパフォーマンスが良い
|-
| Taiwan Semiconductor || SMAJ5.0A || 低コストオプション
|-
| ON Semiconductor (onsemi) || SMBJ5.0A || SMBパッケージ(SMAより少し大きい)<br>600Wと高容量が必要な場合に有効
|-
| Lite-On(台湾) || LSMBJ5.0A || SMBパッケージ(SMAより少し大きい)<br>チップワンストップ、RSコンポーネンツで常時在庫あり
|-
| Micro Commercial Components (MCC) || SMBJ5.0A-TP || Digi-Key、Mouserで安定供給
|-
| Comchip(台湾) || CDSOD323-T05C<br>SMBJ5.0CA || SOD-323小型パッケージ<br>SMBパッケージ
|-
| Rohm(ローム) || RB168MM-20TRシリーズ<br>RB521S-30シリーズ || 国内の主要代理店(チップワンストップ、マルツなど)で入手可能<br>高品質で信頼性が高い
|-
| Toshiba(東芝デバイス&ストレージ) || 1.5KE5.0Aシリーズ || DO-201パッケージ<br>SMDタイプも各種ラインナップあり<br>国内流通が豊富
|-
| Panasonic || - || ESD保護デバイスのラインナップあり<br>産業機器向けに実績多数
|}
|}
</center>
</center>

2026年1月8日 (木) 14:51時点における最新版

概要

MODBUS (Modbus Protocol) は、産業用シリアル通信プロトコルであり、PLCや計測機器、センサ等の制御機器間でデータを送受信するために広く使用されている。
1979年にModicon社 (現在のSchneider Electric社) により開発されたオープンな通信プロトコルであり、産業用ネットワークにおけるデファクトスタンダードとして世界中で採用されている。

下表に、MODBUSプロトコルの特徴を示す。

MODBUSプロトコルの特徴
項目 説明
マスタ・スレーブ方式 1台のマスタ (親機) が複数のスレーブ (子機) を制御する通信方式
アドレス指定 各スレーブ機器に1~247のアドレスを割り当てて個別に通信
ファンクションコード 読み取り、書き込み等の命令を定義 (01h~17h等)
オープン仕様 仕様が公開されており、誰でも実装可能
物理層の選択肢 RS-232、RS-485、TCP/IP等の物理層に対応
2つのモード ASCII モードとRTU モードの2種類の伝送モードを提供


MODBUS RTU は、バイナリ形式で効率的な通信が可能であり、産業用途で広く使用されている。
MODBUS ASCII は、可読性が高く、デバッグが容易であるが、伝送効率は低い。

RS-485トランシーバを使用することにより、長距離通信とノイズ耐性を実現できる。
終端抵抗とバイアス抵抗を適切に配置して、信号品質を確保することが重要である。

MODBUSは、単純で実装しやすい構造を持つため、組み込みシステムやマイコンでの実装に適しており、 MSP430G2553マイコンでは、内蔵のUSCI_A0 UARTモジュールを使用して、MODBUSプロトコルを設計することができる。


MODBUSプロトコル

MODBUSプロトコルには、伝送モードとして、ASCIIモード と RTUモードの2種類がある。
どちらのモードも同じファンクションコードと通信手順を使用するが、データの符号化方式とフレーム構造が異なる。

MODBUS RTU

MODBUS RTU (Remote Terminal Unit) は、バイナリ形式でデータを送信するモードである。
データ伝送効率が高いため、産業用途で最も広く使用されている。

下表に、MODBUS RTUの特徴を示す。

MODBUS RTU フレーム構造
フィールド バイト数 説明
スレーブアドレス 1 通信先スレーブ機器のアドレス (1~247)
ファンクションコード 1 実行する命令コード (01h~17h等)
データ N 送受信するデータ (可変長)
CRC16 2 巡回冗長検査 (CRC-16/MODBUS) チェックサム


MODBUS RTUでは、3.5文字時間以上の無通信状態をフレームの区切りとして認識する。
通信速度が9600[bps]、8N1の場合、3.5文字時間は約3.64[ms]となる。

エラー検出には、CRC-16/MODBUS アルゴリズムが使用される。
CRC16は、多項式 を使用して、初期値 から計算される。

例 :
スレーブアドレス : 
ファンクションコード : 
開始アドレス : 
レジスタ数 :  のフレーム 

CRC16は、 となる。

計算過程 :
初期値  から開始して、各バイトとXORを取り、8回のシフトと条件付きXOR () を繰り返すことにより、
最終的なCRC値  が得られる。
これをリトルエンディアン形式で、 として送信する。


MODBUS ASCII

MODBUS ASCII は、データをASCII文字列として送信するモードである。
可読性が高く、デバッグが容易であるため、開発時やトラブルシューティングで有用である。

下表に、MODBUS ASCIIの特徴を示す。

MODBUS ASCII フレーム構造
フィールド 文字数 説明
スタート文字 1 ':' (コロン、0x3A) でフレーム開始を示す
スレーブアドレス 2 アドレスを2桁のASCII 16進数で表現 ('01'~'F7')
ファンクションコード 2 命令コードを2桁のASCII 16進数で表現
データ 2N データを2桁ずつのASCII 16進数で表現 (可変長)
LRC 2 縦方向冗長検査 (LRC) チェックサム
エンド文字 2 CR (0x0D) とLF (0x0A) でフレーム終了を示す


MODBUS ASCIIでは、各バイトのデータを2文字のASCII 16進数文字 ('0'~'9'、'A'~'F') に変換して送信する。
例えば、0x5Aは、'5' と 'A' の2文字として送信される。

エラー検出には、LRC (Longitudinal Redundancy Check) が使用される。
LRCは、スレーブアドレスからデータまでの全バイトを加算して、2の補数を取ることにより計算される。
計算式は、 であり、8ビット演算では、 となる。

例 :
スレーブアドレス : 
ファンクションコード : 
開始アドレス : 
レジスタ数 :  のフレーム

合計は、 となる。

この合計値の2の補数を取ると、 となり、
最終的なLRC値は、 である。


MODBUS RTU と MODBUS ASCII の比較

MODBUS RTUの方が伝送効率とエラー検出精度の点で優れているため、産業用途で広く使用されている。

MODBUS ASCIIは、開発初期段階やデバッグ時に有用であるが、実運用ではRTUモードを使用することが推奨される。

MODBUS RTU と MODBUS ASCIIの比較
項目 MODBUS RTU MODBUS ASCII
データ符号化 バイナリ形式 ASCII 16進数文字列
伝送効率 高い (バイナリ) 低い (2倍のデータ量)
エラー検出 CRC-16 (高精度) LRC (低精度)
フレーム区切り 3.5文字時間の無通信 スタート文字 ':' とエンド文字 'CR+LF'
可読性 低い 高い (デバッグが容易)
使用頻度 非常に高い 限定的
通信速度 9600~115200[bps]が一般的 9600~19200[bps]が一般的



ハードウェア構成

MSP430G2553でMODBUSを使用する場合、物理層としてRS-485を使用することが一般的である。
RS-485は、差動信号方式を採用しており、長距離通信 (最大1200[m])ノイズ耐性 に優れている。

図. MAX3485 (3.3[V]) を使用する場合


図. MAX485 (5[V]) を使用する場合


必要なハードウェア

下表に、MSP430G2553でMODBUS通信を行うために必要なハードウェアコンポーネントを示す。

ハードウェアコンポーネント
コンポーネント 型番例 説明
マイコン MSP430G2553 TI社 16ビットRISCマイコン
RS-485トランシーバ MAX485、MAX3485 半二重通信用トランシーバIC
RS-485トランシーバ ADM485、SN75176 MAX485互換の代替品
終端抵抗 120[Ω] バスの両端に配置
バイアス抵抗 560[Ω]〜20[kΩ] × 2 フェイルセーフバイアス用
保護素子 TVSダイオード サージ保護用 (オプション)


RS-485トランシーバとの接続

下表に、MSP430G2553とMAX3485を使用したピン接続例を示す。

MSP430G2553 と MAX485 の接続
MSP430G2553 機能 MAX3485 ピン番号
P1.1 UART TXD (送信) DI (Driver Input) Pin 4
P1.2 UART RXD (受信) RO (Receiver Output) Pin 1
P1.3 GPIO (方向制御) DE (Driver Enable) Pin 3
P1.3 GPIO (方向制御) /RE (Receiver Enable) Pin 2
GND グラウンド GND Pin 5
- 電源 VCC 5[V] Pin 8
- - A (非反転出力) RS-485バス
- - B (反転出力) RS-485バス


MAX485のDE (Driver Enable) と /RE (Receiver Enable) ピンは、共通の制御線 (P1.3) に接続する。
P1.3をHIGHにすると送信モードとなり、LOWにすると受信モードとなる。

RS-485バスには、通信線AとBの2本の差動信号線を使用する。
バスの両端には、120[Ω]の終端抵抗を配置して、信号の反射を防止する必要がある。

バイアス抵抗とフェイルセーフ

RS-485バスには、通信していない時の不定状態を防ぐために、バイアス抵抗を追加することが推奨される。

バイアス抵抗により、通信していない時でも差動電圧が発生して、受信側が確定した論理レベルを認識できるようになる。
これにより、誤動作を防止することができる。

バイアス抵抗の接続
抵抗値 接続先 目的
560[Ω] VCC → A端子 (プルアップ) アイドル時にA端子を高電位に保持
560[Ω] B端子 → GND (プルダウン) アイドル時にB端子を低電位に保持


保護ダイオード

保護ダイオード (上図のD1〜D3) においては、過酷な環境や信頼性を重視する場合は実装することを推奨する。

  • 保護ダイオードを実装すべき場合
    • 屋外や工場等、ノイズや静電気が多い環境
    • 長距離配線 (ケーブル長が長い)
    • サージや雷サージのリスクがある場所


下表に、保護ダイオードの選定基準を示す。

保護ダイオードの仕様
項目 仕様値
Working Voltage (VWM) 5[V]
Breakdown Voltage (VBR) 6.4〜7.0[V]程度
Clamping Voltage (VC) 9.2〜9.9[V]程度
Peak Pulse Power 400[W]以上
パッケージ SMA (DO-214AC)


保護ダイオード メーカー別比較表
メーカー 型番 備考
Vishay SMAJ5.0A-E3/61
SMAJ5.0A-E3/5A
最も入手しやすい代替品の一つ
電気的特性がほぼ同等
Bourns SMAJ5.0A Littelfuseとピンコンパチブル
Diodes Incorporated SMAJ5.0A-13-F コストパフォーマンスが良い
Taiwan Semiconductor SMAJ5.0A 低コストオプション
ON Semiconductor (onsemi) SMBJ5.0A SMBパッケージ(SMAより少し大きい)
600Wと高容量が必要な場合に有効
Lite-On(台湾) LSMBJ5.0A SMBパッケージ(SMAより少し大きい)
チップワンストップ、RSコンポーネンツで常時在庫あり
Micro Commercial Components (MCC) SMBJ5.0A-TP Digi-Key、Mouserで安定供給
Comchip(台湾) CDSOD323-T05C
SMBJ5.0CA
SOD-323小型パッケージ
SMBパッケージ
Rohm(ローム) RB168MM-20TRシリーズ
RB521S-30シリーズ
国内の主要代理店(チップワンストップ、マルツなど)で入手可能
高品質で信頼性が高い
Toshiba(東芝デバイス&ストレージ) 1.5KE5.0Aシリーズ DO-201パッケージ
SMDタイプも各種ラインナップあり
国内流通が豊富
Panasonic - ESD保護デバイスのラインナップあり
産業機器向けに実績多数



UART設定

MSP430G2553のUSCI_A0 UARTモジュールを使用して、MODBUS通信を行うための設定手順を示す。

クロック設定

UARTの動作には、安定したクロックソースが必要である。
通常は、SMCLK (Sub-Main Clock) をクロックソースとして使用する。

以下の例では、MSP430G2553のキャリブレーション済みDCOを使用して、1[MHz]のクロックを生成している。

 // クロック設定 (1[MHz])
 DCOCTL = 0;             // DCOをリセット
 BCSCTL1 = CALBC1_1MHZ;  // 1[MHz]キャリブレーション値を設定
 DCOCTL = CALDCO_1MHZ;   // 1[MHz]キャリブレーション値を設定


UART初期化

USCI_A0 UARTモジュールの初期化手順を示す。
MODBUSでは、8N1 (8ビットデータ、パリティなし、1ストップビット) が標準的な設定である。

 // UART初期化 (9600[bps]、8N1)
 void UART_Init(void)
 {
    // GPIO設定: P1.1とP1.2をUART機能に割り当て
    P1SEL |= BIT1 + BIT2;            // P1.1 = TXD, P1.2 = RXD
    P1SEL2 |= BIT1 + BIT2;
 
    // USCI_A0をリセット状態にする
    UCA0CTL1 |= UCSWRST;
 
    // UART設定
    UCA0CTL1 |= UCSSEL_2;            // SMCLK (1[MHz]) をクロックソースに選択
 
    // ボーレート設定 (9600[bps])
    // N = SMCLK / Baudrate = 1000000 / 9600 = 104.166
    UCA0BR0 = 104;                   // 下位バイト
    UCA0BR1 = 0;                     // 上位バイト
    UCA0MCTL = UCBRS0;               // モジュレーションステージ (0x01)
 
    // データフォーマット設定 (8N1)
    UCA0CTL0 &= ~UCPEN;              // パリティなし
    UCA0CTL0 &= ~UC7BIT;             // 8ビットデータ
    UCA0CTL0 &= ~UCSPB;              // 1ストップビット
 
    // USCI_A0を動作状態にする
    UCA0CTL1 &= ~UCSWRST;
 
    // 受信割り込み有効化
    IE2 |= UCA0RXIE;
 }


ボーレート設定

下表に、MODBUSで使用される一般的なボーレートと、1[MHz] SMCLK時の設定値を示す。

ボーレート設定の計算式は、 である。
UCAxBR0とUCAxBR1は、Nの整数部を格納して、UCAxMCTLは、小数部の補正を行う。

ボーレート設定値 (SMCLK = 1[MHz])
ボーレート UCAxBR0 UCAxBR1 UCAxMCTL 備考
9600[bps] 104 0 UCBRS0 (0x01) 標準的な設定
19200[bps] 52 0 UCBRS0 (0x01) 2倍速
38400[bps] 26 0 UCBRS0 (0x01) 4倍速
115200[bps] 8 0 UCBRS7+UCBRS6 (0xC0) 高速通信


RS-485方向制御

RS-485は半二重通信であるため、送信と受信を切り替える方向制御が必要である。

以下の例では、P1.3を使用して、MAX485のDE/REピンを制御している。

 // RS-485方向制御用GPIO設定
 #define RS485_DIR_BIT   BIT3              // P1.3を方向制御に使用
 #define RS485_TX_MODE() (P1OUT |= RS485_DIR_BIT)   // 送信モード (DE=1, /RE=1)
 #define RS485_RX_MODE() (P1OUT &= ~RS485_DIR_BIT)  // 受信モード (DE=0, /RE=0)
 
 void RS485_GPIO_Init(void)
 {
    P1DIR |= RS485_DIR_BIT;            // P1.3を出力に設定
    RS485_RX_MODE();                   // 初期状態は受信モード
 }


送信前にRS485_TX_MODE()を呼び出して送信モードに切り替えて、送信完了後にRS485_RX_MODE()を呼び出して受信モードに戻す必要がある。


MODBUS RTU実装

MODBUS RTUの実装では、バイナリ形式のデータ送受信とCRC-16エラー検出が必要である。

CRC-16/MODBUS計算

MODBUS RTUでは、CRC-16/MODBUSアルゴリズムを使用してエラー検出を行う。
CRC-16は、多項式 を使用して計算される。

CRC-16計算の実装例を以下に示す。

 /*
  * CRC-16/MODBUS計算関数
  * 入力: data - データ配列, length - データ長
  * 出力: CRC-16値 (16ビット)
  */
 unsigned int CRC16_MODBUS(unsigned char *data, unsigned int length)
 {
    unsigned int crc = 0xFFFF;        // 初期値は0xFFFF
    unsigned int i, j;
 
    for (i = 0; i < length; i++) {
       crc ^= (unsigned int)data[i]; // データとXOR
 
       for (j = 0; j < 8; j++) {
          if (crc & 0x0001) {        // 最下位ビットが1の場合
             crc >>= 1;              // 右シフト
             crc ^= 0xA001;          // 多項式とXOR (ビット反転版)
          }
          else {
             crc >>= 1;              // 右シフトのみ
          }
       }
    }
 
    return crc;
 }


計算されたCRC-16値は、下位バイトを先に送信するリトルエンディアン形式でフレームに付加する。

MODBUS RTUフレーム送信

MODBUS RTUフレームの送信関数の実装例を以下に示す。

 /*
  * 1バイト送信関数
  */
 void UART_SendByte(unsigned char data)
 {
    while (!(IFG2 & UCA0TXIFG));      // 送信バッファが空になるまで待機
    UCA0TXBUF = data;                 // データを送信
 }
 
 /*
  * MODBUS RTUフレーム送信関数
  * 入力 : slave_addr - スレーブアドレス
  *        func_code - ファンクションコード
  *        data - データ配列
  *        data_length - データ長
  */
 void MODBUS_RTU_SendFrame(unsigned char slave_addr, unsigned char func_code, unsigned char *data, unsigned int data_length)
 {
    unsigned char frame[256];
    unsigned int frame_length = 0;
    unsigned int crc;
    unsigned int i;
 
    // フレーム構築
    frame[frame_length++] = slave_addr;      // スレーブアドレス
    frame[frame_length++] = func_code;       // ファンクションコード
 
    for (i = 0; i < data_length; i++) {
       frame[frame_length++] = data[i];      // データ部
    }
 
    // CRC-16計算
    crc = CRC16_MODBUS(frame, frame_length);
    frame[frame_length++] = (unsigned char)(crc & 0xFF);         // CRC下位バイト
    frame[frame_length++] = (unsigned char)((crc >> 8) & 0xFF);  // CRC上位バイト
 
    // 送信モードに切替
    RS485_TX_MODE();
    __delay_cycles(100);                     // 切替待ち時間
 
    // フレーム送信
    for (i = 0; i < frame_length; i++) {
       UART_SendByte(frame[i]);
    }
 
    // 送信完了待ち
    while (UCA0STAT & UCBUSY);               // UART送信完了待ち
    __delay_cycles(1000);                    // 最終ビット送信完了待ち
 
    // 受信モードに戻す
    RS485_RX_MODE();
 }


MODBUS RTUフレーム受信

MODBUS RTUフレームの受信には、3.5文字時間のタイムアウト検出が必要である。

以下の例では、タイマを使用して、フレーム区切りを判定している。

 // 受信バッファと変数
 #define RX_BUFFER_SIZE  256
 
 unsigned char rx_buffer[RX_BUFFER_SIZE];
 unsigned int rx_index = 0;
 volatile unsigned char frame_complete = 0;
 
 /*
  * Timer_A0初期化 (3.5文字時間タイムアウト検出用)
  * 9600[bps]、8N1の場合、1文字時間 = 1.04[ms]
  * 3.5文字時間 = 3.64[ms]
  */
 void Timer_Init(void)
 {
    TA0CTL = TASSEL_2 + MC_0 + TACLR;       // SMCLK、停止モード、クリア
    TA0CCR0 = 3640;                         // 3.64[ms] (1[MHz] SMCLK時)
    TA0CCTL0 = CCIE;                        // タイマ割り込み有効
 }
 
 /*
  * UART受信割り込みハンドラ
  */
 #pragma vector=USCIAB0RX_VECTOR
 __interrupt void USCI0RX_ISR(void)
 {
    unsigned char rx_data = UCA0RXBUF;      // 受信データを読み込み
 
    if (rx_index < RX_BUFFER_SIZE) {
       rx_buffer[rx_index++] = rx_data;     // バッファに格納
    }
 
    // タイマをリスタート (3.5文字時間タイムアウトをリセット)
    TA0CTL |= TACLR;                        // タイマクリア
    TA0CTL |= MC_1;                         // アップモードで開始
 }
 
 /*
  * Timer_A0割り込みハンドラ (3.5文字時間タイムアウト)
  */
 #pragma vector=TIMER0_A0_VECTOR
 __interrupt void Timer0_A0_ISR(void)
 {
    TA0CTL &= ~MC_3;                        // タイマ停止
 
    if (rx_index > 0) {
       frame_complete = 1;                  // フレーム受信完了フラグをセット
    }
 }
 
 /*
  * MODBUS RTUフレーム受信処理
  */
 void MODBUS_RTU_ProcessFrame(void)
 {
    unsigned int crc_calculated;
    unsigned int crc_received;
    unsigned char slave_addr;
    unsigned char func_code;
 
    if (!frame_complete || rx_index < 4) {
       return;                              // フレーム未完了または短すぎる
    }
 
    // CRC検証
    crc_calculated = CRC16_MODBUS(rx_buffer, rx_index - 2);
    crc_received = (unsigned int)rx_buffer[rx_index - 2] | 
                   ((unsigned int)rx_buffer[rx_index - 1] << 8);
 
    if (crc_calculated != crc_received) {
       // CRCエラー
       rx_index = 0;
       frame_complete = 0;
       return;
    }
 
    // フレーム解析
    slave_addr = rx_buffer[0];
    func_code = rx_buffer[1];
 
    // 自分宛のフレームかチェック
    if (slave_addr == MY_SLAVE_ADDRESS) {
       // ファンクションコードに応じた処理
       switch (func_code) {
          case 0x03:  // Read Holding Registers
             // 処理定義
             break;
          case 0x06:  // Write Single Register
             // 処理定義
             break;
          // その他のファンクションコード
       }
    }
 
    // バッファクリア
    rx_index = 0;
    frame_complete = 0;
 }


ファンクションコード例

下表に、MODBUSで使用される主なファンクションコードを示す。

主要なMODBUSファンクションコード
コード 名称 説明
0x01 Read Coils コイル (ビット単位) の読み取り
0x02 Read Discrete Inputs 離散入力 (ビット単位) の読み取り
0x03 Read Holding Registers 保持レジスタ (16ビット単位) の読み取り
0x04 Read Input Registers 入力レジスタ (16ビット単位) の読み取り
0x05 Write Single Coil 単一コイルへの書き込み
0x06 Write Single Register 単一レジスタへの書き込み
0x0F Write Multiple Coils 複数コイルへの書き込み
0x10 Write Multiple Registers 複数レジスタへの書き込み



MODBUS ASCIIの使用

MODBUS ASCIIを使用した通信制御では、ASCII文字列への変換とLRCエラー検出が必要である。

LRC (縦方向冗長検査) 計算

MODBUS ASCIIでは、LRC (Longitudinal Redundancy Check) を使用してエラー検出を行う。
LRCは、全バイトを加算して、2の補数を取ることにより計算される。

以下の例では、LRC計算を定義している。

 /*
  * LRC計算関数
  * 入力 : data - データ配列, length - データ長
  * 出力 : LRC値 (8ビット)
  */
 unsigned char LRC_Calculate(unsigned char *data, unsigned int length)
 {
    unsigned char lrc = 0;
    unsigned int i;
 
    for (i = 0; i < length; i++) {
       lrc += data[i];                     // 全バイトを加算
    }
 
    lrc = (~lrc) + 1;                      // 2の補数を取る
 
    return lrc;
 }


16進数からASCII変換

MODBUS ASCIIでは、1バイトのデータを2文字のASCII 16進数文字に変換する必要がある。

以下の例では、変換関数を定義している。

 /*
  * 16進数1桁をASCII文字に変換
  */
 unsigned char HexToASCII(unsigned char hex)
 {
    if (hex <= 9) {
       return '0' + hex;                   // '0'~'9'
    }
    else {
       return 'A' + (hex - 10);            // 'A'~'F'
    }
 }
 
 /*
  * ASCII文字を16進数1桁に変換
  */
 unsigned char ASCIIToHex(unsigned char ascii)
 {
    if (ascii >= '0' && ascii <= '9') {
       return ascii - '0';                 // '0'~'9'
    }
    else if (ascii >= 'A' && ascii <= 'F') {
       return ascii - 'A' + 10;            // 'A'~'F'
    }
    else if (ascii >= 'a' && ascii <= 'f') {
       return ascii - 'a' + 10;            // 'a'~'f'
    }
    else {
       return 0;                           // 無効な文字
    }
 }


MODBUS ASCIIフレーム送信

以下の例では、MODBUS ASCIIフレームの送信関数を定義している。

 /*
  * MODBUS ASCIIフレーム送信関数
  */
 void MODBUS_ASCII_SendFrame(unsigned char slave_addr, unsigned char func_code, unsigned char *data, unsigned int data_length)
 {
    unsigned char binary_frame[128];
    unsigned int binary_length = 0;
    unsigned char lrc;
    unsigned int i;
 
    // バイナリフレーム構築
    binary_frame[binary_length++] = slave_addr;
    binary_frame[binary_length++] = func_code;
 
    for (i = 0; i < data_length; i++) {
       binary_frame[binary_length++] = data[i];
    }
 
    // LRC計算
    lrc = LRC_Calculate(binary_frame, binary_length);
    binary_frame[binary_length++] = lrc;
 
    // 送信モードに切替
    RS485_TX_MODE();
    __delay_cycles(100);
 
    // スタート文字 ':' を送信
    UART_SendByte(':');
 
    // バイナリデータをASCII 16進数に変換して送信
    for (i = 0; i < binary_length; i++) {
       UART_SendByte(HexToASCII((binary_frame[i] >> 4) & 0x0F));   // 上位4ビット
       UART_SendByte(HexToASCII(binary_frame[i] & 0x0F));          // 下位4ビット
    }
 
    // エンド文字 'CR + LF' を送信
    UART_SendByte(0x0D);                   // CR
    UART_SendByte(0x0A);                   // LF
 
    // 送信完了待ち
    while (UCA0STAT & UCBUSY);
    __delay_cycles(1000);
 
    // 受信モードに戻す
    RS485_RX_MODE();
 }


MODBUS ASCIIフレーム受信

以下の例では、MODBUS ASCIIフレームの受信処理を定義している。

 // ASCII受信バッファと変数
 unsigned char ascii_rx_buffer[512];
 unsigned int ascii_rx_index = 0;
 volatile unsigned char ascii_frame_start = 0;
 volatile unsigned char ascii_frame_complete = 0;
 
 /*
  * UART受信割り込みハンドラ (ASCII用)
  */
 #pragma vector=USCIAB0RX_VECTOR
 __interrupt void USCI0RX_ISR_ASCII(void)
 {
    unsigned char rx_data = UCA0RXBUF;
 
    if (rx_data == ':') {
       // スタート文字検出
       ascii_frame_start = 1;
       ascii_rx_index = 0;
    }
    else if (ascii_frame_start) {
       if (rx_data == 0x0A) {
          // LF検出
          ascii_frame_complete = 1;
          ascii_frame_start = 0;
       }
       else if (rx_data != 0x0D && ascii_rx_index < 512) {
          // CR以外
          ascii_rx_buffer[ascii_rx_index++] = rx_data;
       }
    }
 }
 
 /*
  * MODBUS ASCIIフレーム処理
  */
 void MODBUS_ASCII_ProcessFrame(void)
 {
    unsigned char binary_data[256];
    unsigned int binary_length = 0;
    unsigned char lrc_calculated;
    unsigned char lrc_received;
    unsigned int i;
 
    if (!ascii_frame_complete || ascii_rx_index < 6) {
       return;
    }
 
    // ASCII 16進数からバイナリに変換
    for (i = 0; i < ascii_rx_index; i += 2) {
       binary_data[binary_length] = (ASCIIToHex(ascii_rx_buffer[i]) << 4) | ASCIIToHex(ascii_rx_buffer[i + 1]);
       binary_length++;
    }
 
    // LRC検証
    lrc_received = binary_data[binary_length - 1];
    lrc_calculated = LRC_Calculate(binary_data, binary_length - 1);
 
    if (lrc_calculated != lrc_received) {
       // LRCエラー
       ascii_rx_index = 0;
       ascii_frame_complete = 0;
       return;
    }
 
    // フレーム解析
    unsigned char slave_addr = binary_data[0];
    unsigned char func_code = binary_data[1];
 
    // なんらかの処理
    // ...略
 
    // バッファクリア
    ascii_rx_index = 0;
    ascii_frame_complete = 0;
 }



サンプルコード

以下の例では、MSP430G2553でMODBUS RTUスレーブを使用して通信制御している。

MODBUS RTU スレーブとして動作して、ファンクションコード 0x03 (Read Holding Registers) と 0x06 (Write Single Register) に対応している。
10個の保持レジスタを持ち、マスタからの要求に応答する。

 /*
  * MSP430G2553 MODBUS RTU スレーブ実装例
  * 
  * ハードウェア接続:
  *   P1.1 (TXD) -> MAX485 DI
  *   P1.2 (RXD) <- MAX485 RO
  *   P1.3 (GPIO) -> MAX485 DE, /RE
  * 
  * ボーレート: 9600[bps], 8N1
  * スレーブアドレス: 0x01
  */
 
 #include <msp430g2553.h>
 
 // 定数
 #define MY_SLAVE_ADDRESS    0x01
 #define RS485_DIR_BIT       BIT3
 #define RX_BUFFER_SIZE      256
 
 // マクロ定義
 #define RS485_TX_MODE()     (P1OUT |= RS485_DIR_BIT)
 #define RS485_RX_MODE()     (P1OUT &= ~RS485_DIR_BIT)
 
 // グローバル変数
 unsigned char rx_buffer[RX_BUFFER_SIZE];
 unsigned int rx_index = 0;
 volatile unsigned char frame_complete = 0;
 
 // 保持レジスタ (例 : 10個)
 unsigned int holding_registers[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
 
 // 関数プロトタイプ宣言
 void Clock_Init(void);
 void GPIO_Init(void);
 void UART_Init(void);
 void Timer_Init(void);
 void UART_SendByte(unsigned char data);
 unsigned int CRC16_MODBUS(unsigned char *data, unsigned int length);
 void MODBUS_SendResponse(unsigned char *response, unsigned int length);
 void MODBUS_ProcessFrame(void);
 
 /*
  * メイン関数
  */
 int main(void)
 {
    WDTCTL = WDTPW + WDTHOLD;          // ウォッチドッグタイマ停止
 
    Clock_Init();                      // クロック初期化
    GPIO_Init();                       // GPIO初期化
    UART_Init();                       // UART初期化
    Timer_Init();                      // タイマ初期化
 
    __bis_SR_register(GIE);            // 全体割り込み有効
 
    // メインループ
    while (1) {
       if (frame_complete) {
          MODBUS_ProcessFrame();       // MODBUSフレーム処理
       }
 
       __bis_SR_register(LPM0_bits);   // 低消費電力モード0
    }
 }
 
 /*
  * クロック初期化
  */
 void Clock_Init(void)
 {
    DCOCTL = 0;
    BCSCTL1 = CALBC1_1MHZ;             // 1[MHz]キャリブレーション
    DCOCTL = CALDCO_1MHZ;
 }
 
 /*
  * GPIO初期化
  */
 void GPIO_Init(void)
 {
    // UART端子設定
    P1SEL |= BIT1 + BIT2;
    P1SEL2 |= BIT1 + BIT2;
 
    // RS-485方向制御端子
    P1DIR |= RS485_DIR_BIT;
    RS485_RX_MODE();                   // 初期状態は受信モード
 }
 
 /*
  * UART初期化
  */
 void UART_Init(void)
 {
    UCA0CTL1 |= UCSWRST;               // リセット状態に
    UCA0CTL1 |= UCSSEL_2;              // SMCLK選択
    UCA0BR0 = 104;                     // 9600[bps] (1[MHz])
    UCA0BR1 = 0;
    UCA0MCTL = UCBRS0;                 // モジュレーション
    UCA0CTL0 &= ~UCPEN;                // パリティなし
    UCA0CTL0 &= ~UC7BIT;               // 8ビットデータ
    UCA0CTL0 &= ~UCSPB;                // 1ストップビット
    UCA0CTL1 &= ~UCSWRST;              // リセット解除
    IE2 |= UCA0RXIE;                   // 受信割り込み有効
 }
 
 /*
  * タイマ初期化 (3.5文字時間タイムアウト検出)
  */
 void Timer_Init(void)
 {
    TA0CTL = TASSEL_2 + MC_0 + TACLR;  // SMCLK、停止モード
    TA0CCR0 = 3640;                    // 3.64[ms] @ 1[MHz]
    TA0CCTL0 = CCIE;                   // 割り込み有効
 }
 
 /*
  * 1バイト送信
  */
 void UART_SendByte(unsigned char data)
 {
    while (!(IFG2 & UCA0TXIFG));
    UCA0TXBUF = data;
 }
 
 /*
  * CRC-16/MODBUS計算
  */
 unsigned int CRC16_MODBUS(unsigned char *data, unsigned int length)
 {
    unsigned int crc = 0xFFFF;
    unsigned int i, j;
 
    for (i = 0; i < length; i++) {
       crc ^= (unsigned int)data[i];
       for (j = 0; j < 8; j++) {
          if (crc & 0x0001) {
             crc >>= 1;
             crc ^= 0xA001;
          }
          else {
             crc >>= 1;
          }
       }
    }
    return crc;
 }
 
 /*
  * MODBUS応答送信
  */
 void MODBUS_SendResponse(unsigned char *response, unsigned int length)
 {
    unsigned int crc;
    unsigned int i;
 
    // CRC計算
    crc = CRC16_MODBUS(response, length);
 
    // 送信モードに切替
    RS485_TX_MODE();
    __delay_cycles(100);
 
    // レスポンス送信
    for (i = 0; i < length; i++) {
       UART_SendByte(response[i]);
    }
 
    // CRC送信 (リトルエンディアン)
    UART_SendByte((unsigned char)(crc & 0xFF));
    UART_SendByte((unsigned char)((crc >> 8) & 0xFF));
 
    // 送信完了待ち
    while (UCA0STAT & UCBUSY);
    __delay_cycles(1000);
 
    // 受信モードに戻す
    RS485_RX_MODE();
 }
 
 /*
  * MODBUSフレーム処理
  */
 void MODBUS_ProcessFrame(void)
 {
    unsigned int crc_calculated, crc_received;
    unsigned char slave_addr, func_code;
    unsigned char response[256];
    unsigned int response_length = 0;
 
    // CRC検証
    if (rx_index < 4) {
       goto cleanup;                   // フレームが短すぎる
    }
 
    crc_calculated = CRC16_MODBUS(rx_buffer, rx_index - 2);
    crc_received = (unsigned int)rx_buffer[rx_index - 2] | ((unsigned int)rx_buffer[rx_index - 1] << 8);
 
    if (crc_calculated != crc_received) {
       goto cleanup;                   // CRCエラー
    }
 
    // フレーム解析
    slave_addr = rx_buffer[0];
    func_code = rx_buffer[1];
 
    // 自分宛かチェック
    if (slave_addr != MY_SLAVE_ADDRESS) {
       goto cleanup;
    }
 
    // ファンクションコード処理
    switch (func_code) {
       case 0x03:  // Read Holding Registers
       {
          unsigned int start_addr = ((unsigned int)rx_buffer[2] << 8) | rx_buffer[3];
          unsigned int num_regs = ((unsigned int)rx_buffer[4] << 8) | rx_buffer[5];
          unsigned int i;
 
          // 範囲チェック
          if (start_addr + num_regs > 10) {
             // エラー応答 (例外コード 0x02: 不正なデータアドレス)
             response[response_length++] = slave_addr;
             response[response_length++] = func_code | 0x80;
             response[response_length++] = 0x02;
             break;
          }
 
          // 正常応答
          response[response_length++] = slave_addr;
          response[response_length++] = func_code;
          response[response_length++] = (unsigned char)(num_regs * 2);  // バイト数
 
          for (i = 0; i < num_regs; i++) {
             response[response_length++] = (unsigned char)((holding_registers[start_addr + i] >> 8) & 0xFF);
             response[response_length++] = (unsigned char)(holding_registers[start_addr + i] & 0xFF);
          }
          break;
       }
 
       case 0x06:  // Write Single Register
       {
          unsigned int reg_addr = ((unsigned int)rx_buffer[2] << 8) | rx_buffer[3];
          unsigned int reg_value = ((unsigned int)rx_buffer[4] << 8) | rx_buffer[5];
 
          // 範囲チェック
          if (reg_addr >= 10) {
             // エラー応答
             response[response_length++] = slave_addr;
             response[response_length++] = func_code | 0x80;
             response[response_length++] = 0x02;
             break;
          }
 
          // レジスタ書き込み
          holding_registers[reg_addr] = reg_value;
 
          // 正常応答 (エコーバック)
          response[response_length++] = slave_addr;
          response[response_length++] = func_code;
          response[response_length++] = rx_buffer[2];
          response[response_length++] = rx_buffer[3];
          response[response_length++] = rx_buffer[4];
          response[response_length++] = rx_buffer[5];
          break;
       }
 
       default:
          // サポートされていないファンクションコード
          response[response_length++] = slave_addr;
          response[response_length++] = func_code | 0x80;
          response[response_length++] = 0x01;  // 例外コード: 不正なファンクション
          break;
    }
 
    // 応答送信
    if (response_length > 0) {
       MODBUS_SendResponse(response, response_length);
    }
 
 cleanup:
    rx_index = 0;
    frame_complete = 0;
 }
 
 /*
  * UART受信割り込みハンドラ
  */
 #pragma vector=USCIAB0RX_VECTOR
 __interrupt void USCI0RX_ISR(void)
 {
    unsigned char rx_data = UCA0RXBUF;
 
    if (rx_index < RX_BUFFER_SIZE) {
       rx_buffer[rx_index++] = rx_data;
    }
 
    // タイマリスタート
    TA0CTL |= TACLR;
    TA0CTL |= MC_1;                    // アップモードで開始
 
    __bic_SR_register_on_exit(LPM0_bits);  // 低消費電力モード解除
 }
 
 /*
  * Timer_A0割り込みハンドラ (3.5文字時間タイムアウト)
  */
 #pragma vector=TIMER0_A0_VECTOR
 __interrupt void Timer0_A0_ISR(void)
 {
    TA0CTL &= ~MC_3;                   // タイマ停止
 
    if (rx_index > 0) {
       frame_complete = 1;
    }
 
    __bic_SR_register_on_exit(LPM0_bits);  // 低消費電力モード解除
 }



トラブルシューティング

通信エラーの診断

よくある通信エラーと対処方法
症状 原因 対処方法
応答がない ボーレート不一致 マスタとスレーブのボーレート設定を確認
応答がない スレーブアドレス不一致 スレーブアドレスの設定を確認
CRCエラー頻発 ノイズの影響 シールドケーブルの使用
終端抵抗の確認
CRCエラー頻発 配線の問題 A/B線の接続を確認
グラウンドを共通化
データ化け RS-485方向制御の不良 DE/RE制御タイミングを見直す
文字落ち 受信バッファオーバーフロー バッファサイズを増やす
割り込み優先度を上げる
断続的なエラー 終端抵抗なし バスの両端に120[Ω]の終端抵抗を配置
通信開始時のエラー バイアス抵抗なし フェイルセーフバイアス抵抗を追加


デバッグ方法

MODBUS通信のデバッグには、以下に示す方法が有効である。

デバッグ方法
方法 説明
LEDインジケータ 送受信状態をLEDで表示して、動作を目視確認
ロジックアナライザ TXD、RXD、DE/RE信号をキャプチャして、タイミングを確認
USB-RS485変換器 PCとRS-485バス間でデータをモニタ
MODBUS ASCIIモード デバッグ時はASCIIモードで通信内容を可視化
CRCチェック無効化 開発初期はCRC検証を一時的にスキップして動作確認


RS-485バスの配線ガイドライン

RS-485バスの配線では、以下に示す事柄に注意する必要がある。

RS-485配線ガイドライン
項目 推奨値 説明
最大通信距離 1200[m] ボーレートとケーブルの品質に依存
最大ノード数 32台 標準ドライバの場合
(最大247台まで拡張可能)
ケーブル特性インピーダンス 120[Ω] ツイストペアケーブルを使用
終端抵抗 120[Ω] バスの両端に配置
バイアス抵抗 (プルアップ) 470~680[Ω] A端子をVCCに接続
バイアス抵抗 (プルダウン) 470~680[Ω] B端子をGNDに接続
推奨ケーブル ツイストペアシールド 外部ノイズ対策


省電力モードの注意点

MSP430G2553の省電力モードを使用する場合、UART受信時にマイコンを確実に起動させる必要がある。
そのため、受信割り込みで低消費電力モードを解除するようにしたほうがよい。

 // 受信割り込み内で低消費電力モード解除
 __bic_SR_register_on_exit(LPM0_bits);