MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
MSP430G2553 - MODBUSのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
MSP430G2553 - MODBUS
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == MODBUS (Modbus Protocol) は、産業用シリアル通信プロトコルであり、PLCや計測機器、センサ等の制御機器間でデータを送受信するために広く使用されている。<br> 1979年にModicon社 (現在のSchneider Electric社) により開発されたオープンな通信プロトコルであり、産業用ネットワークにおけるデファクトスタンダードとして世界中で採用されている。<br> <br> 下表に、MODBUSプロトコルの特徴を示す。<br> <br> <center> {| class="wikitable" |+ MODBUSプロトコルの特徴 |- ! 項目 !! 説明 |- | マスタ・スレーブ方式 || 1台のマスタ (親機) が複数のスレーブ (子機) を制御する通信方式 |- | アドレス指定 || 各スレーブ機器に1~247のアドレスを割り当てて個別に通信 |- | ファンクションコード || 読み取り、書き込み等の命令を定義 (01h~17h等) |- | オープン仕様 || 仕様が公開されており、誰でも実装可能 |- | 物理層の選択肢 || RS-232、RS-485、TCP/IP等の物理層に対応 |- | 2つのモード || ASCII モードとRTU モードの2種類の伝送モードを提供 |} </center> <br> MODBUS RTU は、バイナリ形式で効率的な通信が可能であり、産業用途で広く使用されている。<br> MODBUS ASCII は、可読性が高く、デバッグが容易であるが、伝送効率は低い。<br> <br> RS-485トランシーバを使用することにより、長距離通信とノイズ耐性を実現できる。<br> 終端抵抗とバイアス抵抗を適切に配置して、信号品質を確保することが重要である。<br> <br> MODBUSは、単純で実装しやすい構造を持つため、組み込みシステムやマイコンでの実装に適しており、 MSP430G2553マイコンでは、内蔵のUSCI_A0 UARTモジュールを使用して、MODBUSプロトコルを設計することができる。<br> <br><br> == MODBUSプロトコル == MODBUSプロトコルには、伝送モードとして、ASCIIモード と RTUモードの2種類がある。<br> どちらのモードも同じファンクションコードと通信手順を使用するが、データの符号化方式とフレーム構造が異なる。<br> <br> ==== MODBUS RTU ==== MODBUS RTU (Remote Terminal Unit) は、バイナリ形式でデータを送信するモードである。<br> データ伝送効率が高いため、産業用途で最も広く使用されている。<br> <br> 下表に、MODBUS RTUの特徴を示す。<br> <br> <center> {| class="wikitable" |+ MODBUS RTU フレーム構造 |- ! フィールド !! バイト数 !! 説明 |- | スレーブアドレス || 1 || 通信先スレーブ機器のアドレス (1~247) |- | ファンクションコード || 1 || 実行する命令コード (01h~17h等) |- | データ || N || 送受信するデータ (可変長) |- | CRC16 || 2 || 巡回冗長検査 (CRC-16/MODBUS) チェックサム |} </center> <br> MODBUS RTUでは、3.5文字時間以上の無通信状態をフレームの区切りとして認識する。<br> 通信速度が9600[bps]、8N1の場合、3.5文字時間は約3.64[ms]となる。<br> <br> <u>エラー検出には、<u>CRC-16/MODBUS</u> アルゴリズムが使用される。</u><br> <u>CRC16は、多項式 <math>\mbox{0x8005} (x^{16} + x^{15} + x^{2} + 1)</math> を使用して、初期値 <math>\mbox{0xFFFF}</math> から計算される。</u><br> <br> 例 : スレーブアドレス : <math>\mbox{0x01}</math> ファンクションコード : <math>\mbox{0x03}</math> 開始アドレス : <math>\mbox{0x0000}</math> レジスタ数 : <math>\mbox{0x0001}</math> のフレーム <math>[\mbox{0x01}, \mbox{0x03}, \mbox{0x00}, \mbox{0x00}, \mbox{0x00}, \mbox{0x01}]</math> CRC16は、<math>\mbox{0x84}</math>、<math>\mbox{0x0A}</math> となる。 計算過程 : 初期値 <math>\mbox{0xFFFF}</math> から開始して、各バイトとXORを取り、8回のシフトと条件付きXOR (<math>\mbox{0xA001}</math>) を繰り返すことにより、 最終的なCRC値 <math>\mbox{0x0A84}</math> が得られる。 これをリトルエンディアン形式で、<math>\mbox{0x84}</math>、<math>\mbox{0x0A}</math> として送信する。 <br> ==== MODBUS ASCII ==== MODBUS ASCII は、データをASCII文字列として送信するモードである。<br> 可読性が高く、デバッグが容易であるため、開発時やトラブルシューティングで有用である。<br> <br> 下表に、MODBUS ASCIIの特徴を示す。<br> <br> <center> {| class="wikitable" |+ 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) でフレーム終了を示す |} </center> <br> MODBUS ASCIIでは、各バイトのデータを2文字のASCII 16進数文字 ('0'~'9'、'A'~'F') に変換して送信する。<br> <u>例えば、0x5Aは、'5' と 'A' の2文字として送信される。</u><br> <br> <u>エラー検出には、LRC (Longitudinal Redundancy Check) が使用される。</u><br> <u>LRCは、スレーブアドレスからデータまでの全バイトを加算して、2の補数を取ることにより計算される。</u><br> <u>計算式は、<math>\mbox{LRC} = (- (\mbox{sum} \bmod 256)) \bmod 256</math> であり、8ビット演算では、<math>\mbox{LRC} = (\sim \mbox{sum} + 1) \& \mbox{0xFF}</math> となる。</u><br> <br> 例 : スレーブアドレス : <math>\mbox{0x01}</math> ファンクションコード : <math>\mbox{0x03}</math> 開始アドレス : <math>\mbox{0x00}</math>、<math>\mbox{0x00}</math> レジスタ数 : <math>\mbox{0x00}</math>、<math>\mbox{0x01}</math> のフレーム 合計は、<math>\mbox{0x01} + \mbox{0x03} + \mbox{0x00} + \mbox{0x00} + \mbox{0x00} + \mbox{0x01} = \mbox{0x05}</math> となる。 この合計値の2の補数を取ると、<math>\mbox{LRC} = (\sim\mbox{0x05} + 1) \& \mbox{0xFF} = \mbox{0xFA} + 1 = \mbox{0xFB}</math> となり、 最終的なLRC値は、<math>\mbox{0xFB}</math> である。 <br> ==== MODBUS RTU と MODBUS ASCII の比較 ==== MODBUS RTUの方が伝送効率とエラー検出精度の点で優れているため、産業用途で広く使用されている。<br> <br> MODBUS ASCIIは、開発初期段階やデバッグ時に有用であるが、実運用ではRTUモードを使用することが推奨される。<br> <br> <center> {| class="wikitable" |+ MODBUS RTU と MODBUS ASCIIの比較 |- ! 項目 !! MODBUS RTU !! MODBUS ASCII |- | データ符号化 || バイナリ形式 || ASCII 16進数文字列 |- | 伝送効率 || 高い (バイナリ) || 低い (2倍のデータ量) |- | エラー検出 || CRC-16 (高精度) || LRC (低精度) |- | フレーム区切り || 3.5文字時間の無通信 || スタート文字 ':' とエンド文字 'CR+LF' |- | 可読性 || 低い || 高い (デバッグが容易) |- | 使用頻度 || 非常に高い || 限定的 |- | 通信速度 || 9600~115200[bps]が一般的 || 9600~19200[bps]が一般的 |} </center> <br><br> == ハードウェア構成 == MSP430G2553でMODBUSを使用する場合、物理層としてRS-485を使用することが一般的である。<br> RS-485は、差動信号方式を採用しており、<u>長距離通信 (最大1200[m])</u> と <u>ノイズ耐性</u> に優れている。<br> <br> ==== 必要なハードウェア ==== 下表に、MSP430G2553でMODBUS通信を行うために必要なハードウェアコンポーネントを示す。<br> <br> <center> {| class="wikitable" |+ ハードウェアコンポーネント |- ! コンポーネント !! 型番例 !! 説明 |- | マイコン || MSP430G2553 || TI社 16ビットRISCマイコン |- | RS-485トランシーバ || MAX485、MAX3485 || 半二重通信用トランシーバIC |- | RS-485トランシーバ || ADM485、SN75176 || MAX485互換の代替品 |- | 終端抵抗 || 120[Ω] × 2 || バスの両端に配置 |- | バイアス抵抗 || 560[Ω] × 2 || フェイルセーフバイアス用 |- | 保護素子 || TVSダイオード || サージ保護用 (オプション) |} </center> <br> ==== RS-485トランシーバとの接続 ==== 下表に、MSP430G2553とMAX485を使用したピン接続例を示す。<br> <br> <center> {| class="wikitable" |+ MSP430G2553 と MAX485 の接続 |- ! MSP430G2553 !! 機能 !! MAX485 !! ピン番号 |- | 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 (3.3V) || 電源 || VCC || Pin 8 |- | - || - || A (非反転出力) || RS-485バス |- | - || - || B (反転出力) || RS-485バス |} </center> <br> MAX485のDE (Driver Enable) と /RE (Receiver Enable) ピンは、共通の制御線 (P1.3) に接続する。<br> P1.3をHIGHにすると送信モードとなり、LOWにすると受信モードとなる。<br> <br> RS-485バスには、通信線AとBの2本の差動信号線を使用する。<br> バスの両端には、120[Ω]の終端抵抗を配置して、信号の反射を防止する必要がある。<br> <br> ==== バイアス抵抗とフェイルセーフ ==== RS-485バスには、通信していない時の不定状態を防ぐために、バイアス抵抗を追加することが推奨される。<br> <br> バイアス抵抗により、通信していない時でも差動電圧が発生して、受信側が確定した論理レベルを認識できるようになる。<br> これにより、誤動作を防止することができる。<br> <br> <center> {| class="wikitable" |+ バイアス抵抗の接続 |- ! 抵抗値 !! 接続先 !! 目的 |- | 560[Ω] || VCC → A端子 (プルアップ) || アイドル時にA端子を高電位に保持 |- | 560[Ω] || B端子 → GND (プルダウン) || アイドル時にB端子を低電位に保持 |} </center> <br><br> == UART設定 == MSP430G2553のUSCI_A0 UARTモジュールを使用して、MODBUS通信を行うための設定手順を示す。<br> <br> ==== クロック設定 ==== UARTの動作には、安定したクロックソースが必要である。<br> 通常は、SMCLK (Sub-Main Clock) をクロックソースとして使用する。<br> <br> 以下の例では、MSP430G2553のキャリブレーション済みDCOを使用して、1[MHz]のクロックを生成している。<br> <br> <syntaxhighlight lang="c"> // クロック設定 (1[MHz]) DCOCTL = 0; // DCOをリセット BCSCTL1 = CALBC1_1MHZ; // 1[MHz]キャリブレーション値を設定 DCOCTL = CALDCO_1MHZ; // 1[MHz]キャリブレーション値を設定 </syntaxhighlight> <br> ==== UART初期化 ==== USCI_A0 UARTモジュールの初期化手順を示す。<br> MODBUSでは、8N1 (8ビットデータ、パリティなし、1ストップビット) が標準的な設定である。<br> <br> <syntaxhighlight lang="c"> // 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; } </syntaxhighlight> <br> ==== ボーレート設定 ==== 下表に、MODBUSで使用される一般的なボーレートと、1[MHz] SMCLK時の設定値を示す。<br> <br> ボーレート設定の計算式は、<math>\mbox{N} = \dfrac{\mbox{SMCLK}{\mbox{Baudrate}}</math> である。<br> UCAxBR0とUCAxBR1は、Nの整数部を格納して、UCAxMCTLは、小数部の補正を行う。<br> <br> <center> {| class="wikitable" |+ ボーレート設定値 (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) || 高速通信 |} </center> <br> ==== RS-485方向制御 ==== RS-485は半二重通信であるため、送信と受信を切り替える方向制御が必要である。<br> <br> 以下の例では、P1.3を使用して、MAX485のDE/REピンを制御している。<br> <br> <syntaxhighlight lang="c"> // 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(); // 初期状態は受信モード } </syntaxhighlight> <br> 送信前にRS485_TX_MODE()を呼び出して送信モードに切り替えて、送信完了後にRS485_RX_MODE()を呼び出して受信モードに戻す必要がある。<br> <br><br> == MODBUS RTU実装 == MODBUS RTUの実装では、バイナリ形式のデータ送受信とCRC-16エラー検出が必要である。<br> <br> ==== CRC-16/MODBUS計算 ==== MODBUS RTUでは、CRC-16/MODBUSアルゴリズムを使用してエラー検出を行う。<br> CRC-16は、多項式 <math>\mbox{0x8005} (x^{16} + x^{15} + x^{2} + 1)</math> を使用して計算される。<br> <br> CRC-16計算の実装例を以下に示す。<br> <br> <syntaxhighlight lang="c"> /* * 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; } </syntaxhighlight> <br> 計算されたCRC-16値は、下位バイトを先に送信するリトルエンディアン形式でフレームに付加する。<br> <br> ==== MODBUS RTUフレーム送信 ==== MODBUS RTUフレームの送信関数の実装例を以下に示す。<br> <br> <syntaxhighlight lang="c"> /* * 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(); } </syntaxhighlight> <br> ==== MODBUS RTUフレーム受信 ==== MODBUS RTUフレームの受信には、3.5文字時間のタイムアウト検出が必要である。<br> <br> 以下の例では、タイマを使用して、フレーム区切りを判定している。<br> <br> <syntaxhighlight lang="c"> // 受信バッファと変数 #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; } </syntaxhighlight> <br> ==== ファンクションコード例 ==== 下表に、MODBUSで使用される主なファンクションコードを示す。<br> <br> <center> {| class="wikitable" |+ 主要な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 || 複数レジスタへの書き込み |} </center> <br><br> == MODBUS ASCIIの使用 == MODBUS ASCIIを使用した通信制御では、ASCII文字列への変換とLRCエラー検出が必要である。<br> <br> ==== LRC (縦方向冗長検査) 計算 ==== MODBUS ASCIIでは、LRC (Longitudinal Redundancy Check) を使用してエラー検出を行う。<br> LRCは、全バイトを加算して、2の補数を取ることにより計算される。<br> <br> 以下の例では、LRC計算を定義している。<br> <br> <syntaxhighlight lang="c"> /* * 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; } </syntaxhighlight> <br> ==== 16進数からASCII変換 ==== MODBUS ASCIIでは、1バイトのデータを2文字のASCII 16進数文字に変換する必要がある。<br> <br> 以下の例では、変換関数を定義している。<br> <br> <syntaxhighlight lang="c"> /* * 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; // 無効な文字 } } </syntaxhighlight> <br> ==== MODBUS ASCIIフレーム送信 ==== 以下の例では、MODBUS ASCIIフレームの送信関数を定義している。<br> <br> <syntaxhighlight lang="c"> /* * 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(); } </syntaxhighlight> <br> ==== MODBUS ASCIIフレーム受信 ==== 以下の例では、MODBUS ASCIIフレームの受信処理を定義している。<br> <br> <syntaxhighlight lang="c"> // 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; } </syntaxhighlight> <br><br> == サンプルコード == 以下の例では、MSP430G2553でMODBUS RTUスレーブを使用して通信制御している。<br> <br> MODBUS RTU スレーブとして動作して、ファンクションコード 0x03 (Read Holding Registers) と 0x06 (Write Single Register) に対応している。<br> 10個の保持レジスタを持ち、マスタからの要求に応答する。<br> <br> <syntaxhighlight lang="c"> /* * 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); // 低消費電力モード解除 } </syntaxhighlight> <br><br> == トラブルシューティング == ==== 通信エラーの診断 ==== <center> {| class="wikitable" |+ よくある通信エラーと対処方法 |- ! 症状 !! 原因 !! 対処方法 |- | 応答がない || ボーレート不一致 || マスタとスレーブのボーレート設定を確認 |- | 応答がない || スレーブアドレス不一致 || スレーブアドレスの設定を確認 |- | CRCエラー頻発 || ノイズの影響 || シールドケーブルの使用<br>終端抵抗の確認 |- | CRCエラー頻発 || 配線の問題 || A/B線の接続を確認<br>グラウンドを共通化 |- | データ化け || RS-485方向制御の不良 || DE/RE制御タイミングを見直す |- | 文字落ち || 受信バッファオーバーフロー || バッファサイズを増やす<br>割り込み優先度を上げる |- | 断続的なエラー || 終端抵抗なし || バスの両端に120[Ω]の終端抵抗を配置 |- | 通信開始時のエラー || バイアス抵抗なし || フェイルセーフバイアス抵抗を追加 |} </center> <br> ==== デバッグ方法 ==== MODBUS通信のデバッグには、以下に示す方法が有効である。<br> <br> <center> {| class="wikitable" |+ デバッグ方法 |- ! 方法 !! 説明 |- | LEDインジケータ || 送受信状態をLEDで表示して、動作を目視確認 |- | ロジックアナライザ || TXD、RXD、DE/RE信号をキャプチャして、タイミングを確認 |- | USB-RS485変換器 || PCとRS-485バス間でデータをモニタ |- | MODBUS ASCIIモード || デバッグ時はASCIIモードで通信内容を可視化 |- | CRCチェック無効化 || 開発初期はCRC検証を一時的にスキップして動作確認 |} </center> <br> ==== RS-485バスの配線ガイドライン ==== RS-485バスの配線では、以下に示す事柄に注意する必要がある。<br> <br> <center> {| class="wikitable" |+ RS-485配線ガイドライン |- ! 項目 !! 推奨値 !! 説明 |- | 最大通信距離 || 1200[m] || ボーレートとケーブルの品質に依存 |- | 最大ノード数 || 32台 || 標準ドライバの場合<br>(最大247台まで拡張可能) |- | ケーブル特性インピーダンス || 120[Ω] || ツイストペアケーブルを使用 |- | 終端抵抗 || 120[Ω] || バスの両端に配置 |- | バイアス抵抗 (プルアップ) || 470~680[Ω] || A端子をVCCに接続 |- | バイアス抵抗 (プルダウン) || 470~680[Ω] || B端子をGNDに接続 |- | 推奨ケーブル || ツイストペアシールド || 外部ノイズ対策 |} </center> <br> ==== 省電力モードの注意点 ==== MSP430G2553の省電力モードを使用する場合、UART受信時にマイコンを確実に起動させる必要がある。<br> そのため、受信割り込みで低消費電力モードを解除するようにしたほうがよい。<br> <br> <syntaxhighlight lang="c"> // 受信割り込み内で低消費電力モード解除 __bic_SR_register_on_exit(LPM0_bits); </syntaxhighlight> <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:MSP430]]
MSP430G2553 - MODBUS
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse