「MSP430F149 - CANBUS」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

 
(同じ利用者による、間の1版が非表示)
3行目: 3行目:


== システムの構成 ==
== システムの構成 ==
MSP430F149はマイクロコントローラとして全体を制御し、SPI通信を使用してMCP2515 CANコントローラーチップに命令を送信する。<br>
MSP430F149はマイクロコントローラとして全体を制御し、SPI通信を使用してMCP2515 CANコントローラチップに命令を送信する。<br>
MCP2515がCANプロトコルの処理を行い、その先にCANトランシーバ (TJA1050等) が物理層の信号変換を担当する。<br>
MCP2515がCANプロトコルの処理を行い、その先にCANトランシーバIC (TJA1050等) が物理層の信号変換を担当する。<br>
<br>
MSP430F149 -- SPI <--> MCP2515 <--> MCP2551 / TJA1050等 <--> CANBUS
<br>
<br>
この3層構造により、MSP430F149のような汎用マイコンでもCAN通信 (CAN 2.0B通信) が可能になる。<br>
この3層構造により、MSP430F149のような汎用マイコンでもCAN通信 (CAN 2.0B通信) が可能になる。<br>
<br>
<center>
{| class="wikitable"
|+ 役割
|-
! チップ !! 役割 !! 規格
|-
| MCP2515 || CANコントローラ (プロトコル処理) || ISO 11898-1
|-
| MCP2551 / TJA1050 / SN65HVD230等 || CANトランシーバ (物理層・差動信号) || ISO 11898-2
|}
</center>
<br>
よく使用されるICの組み合わせを以下に示す。<br>
* MCP2515 + MCP2551 (Microchip)
* MCP2515 + TJA1050 (NXP製トランシーバ)
<br>
<br>
また、次のような拡張も可能である。<br>
また、次のような拡張も可能である。<br>

2026年2月21日 (土) 10:11時点における最新版

概要



システムの構成

MSP430F149はマイクロコントローラとして全体を制御し、SPI通信を使用してMCP2515 CANコントローラチップに命令を送信する。
MCP2515がCANプロトコルの処理を行い、その先にCANトランシーバIC (TJA1050等) が物理層の信号変換を担当する。

MSP430F149 -- SPI <--> MCP2515 <--> MCP2551 / TJA1050等 <--> CANBUS


この3層構造により、MSP430F149のような汎用マイコンでもCAN通信 (CAN 2.0B通信) が可能になる。

役割
チップ 役割 規格
MCP2515 CANコントローラ (プロトコル処理) ISO 11898-1
MCP2551 / TJA1050 / SN65HVD230等 CANトランシーバ (物理層・差動信号) ISO 11898-2


よく使用されるICの組み合わせを以下に示す。

  • MCP2515 + MCP2551 (Microchip)
  • MCP2515 + TJA1050 (NXP製トランシーバ)


また、次のような拡張も可能である。

  • フィルタとマスクを適切に設定することにより、特定のメッセージIDのみを受信する。
  • 複数の送信バッファを使用して送信の優先度を制御する。
  • エラーハンドリングを実装して、バスオフ状態からの回復を行う。



ハードウェア接続

MSP430F149とMCP2515の接続は、MSP430F149のUSARTモジュールをSPIモードで使用する。

  • MCP2515 VCC
    3.3[V] または 5[V] (MCP2515の仕様に応じる)
  • MCP2515 GND
    GND
  • MCP2515 CS
    P3.0 (チップセレクト、任意のGPIOピン)
  • MCP2515 SO (MISO)
    P3.2 (SOMI0)
  • MCP2515 SI (MOSI)
    P3.1 (SIMO0)
  • MCP2515 SCK
    P3.3 (UCLK0)
  • MCP2515 INT
    P2.0 (割り込み用、任意のGPIOピン)
  • MCP2515 RESET
    P3.4 (リセット用、任意のGPIOピン)


※注意
MSP430F149は3.3[V]で駆動するが、MCP2515は5[V]駆動するモデルもある。
そのため、電圧レベルが異なる場合はレベルシフタの使用を検討すること。


サンプルコード

レジスタの定義

MCP2515は多くのレジスタを持っており、それぞれが特定の機能を制御する。

 #include <msp430f149.h>
 #include <stdint.h>
 
 // MCP2515のレジスタアドレス定義
 #define MCP_RXF0SIDH    0x00
 #define MCP_RXF0SIDL    0x01
 #define MCP_RXM0SIDH    0x20
 #define MCP_RXM0SIDL    0x21
 #define MCP_CNF3        0x28
 #define MCP_CNF2        0x29
 #define MCP_CNF1        0x2A
 #define MCP_CANINTE     0x2B
 #define MCP_CANINTF     0x2C
 #define MCP_EFLG        0x2D
 #define MCP_CANSTAT     0x0E
 #define MCP_CANCTRL     0x0F
 #define MCP_TXB0CTRL    0x30
 #define MCP_TXB0SIDH    0x31
 #define MCP_TXB0SIDL    0x32
 #define MCP_TXB0DLC     0x35
 #define MCP_TXB0DATA    0x36
 #define MCP_RXB0CTRL    0x60
 #define MCP_RXB0SIDH    0x61
 #define MCP_RXB0SIDL    0x62
 #define MCP_RXB0DLC     0x65
 #define MCP_RXB0DATA    0x66
 
 // MCP2515の命令セット
 // これらはSPI経由でMCP2515に送る命令コード
 #define MCP_WRITE       0x02
 #define MCP_READ        0x03
 #define MCP_BITMOD      0x05
 #define MCP_LOAD_TX0    0x40
 #define MCP_RTS_TX0     0x81
 #define MCP_READ_RX0    0x90
 #define MCP_READ_STATUS 0xA0
 #define MCP_RESET       0xC0
 
 // CANコントロールレジスタのモード設定
 #define MODE_NORMAL     0x00
 #define MODE_SLEEP      0x20
 #define MODE_LOOPBACK   0x40
 #define MODE_LISTENONLY 0x60
 #define MODE_CONFIG     0x80
 
 // ピン定義
 #define CS_PIN          BIT0  // P3.0
 #define RESET_PIN       BIT4  // P3.4
 #define INT_PIN         BIT0  // P2.0
 
 // チップセレクトのマクロ
 #define CS_LOW()        (P3OUT &= ~CS_PIN)
 #define CS_HIGH()       (P3OUT |= CS_PIN)
 #define RESET_LOW()     (P3OUT &= ~RESET_PIN)
 #define RESET_HIGH()    (P3OUT |= RESET_PIN)


SPI通信

SPI通信は、MSP430F149とMCP2515の間のデータ転送手段である。
MSP430F149のUSART0モジュールをSPIモードで設定する必要がある。

SPIは同期式のシリアル通信であり、クロック信号に同期してデータを送受信する。
マスター (MSP430F149) がクロックを生成して、スレーブ (MCP2515) がそれに従う。
全2重通信のため、1バイト送信すると同時に1バイト受信することになる。(SPI_Transfer関数で送受信を同時に行っている理由)

 // SPI通信の初期化関数
 // MSP430F149のUSART0をSPIマスターモードで設定する
 void SPI_Init(void)
 {
    // USART0をSPIモードで初期化
    // まず、USARTを無効化してから設定を行う
    U0CTL = SWRST;                    // USARTをリセット状態に
 
    // SPI設定:マスターモード、8ビットデータ、MSBファースト
    // 3ピンモード、同期モード (SPI)
    U0CTL |= CHAR + SYNC + MM;        
 
    // クロック極性とフェーズの設定
    // CKPL=0, CKPH=1はMCP2515の要求に合わせた設定である
    U0TCTL = CKPL + SSEL1 + SSEL0 + STC;  // SMCLK使用、3ピンモード
 
    // ボーレート設定
    // SMCLK = 8[MHz] の場合、分周比を2に設定して4[MHz]動作
    // MCP2515は最大10[MHz]まで対応しているが、安全マージンを取る
    U0BR0 = 0x02;                     
    U0BR1 = 0x00;                     
    U0MCTL = 0x00;                    // モジュレーション無効
 
    // ピンの機能設定
    // P3.1, P3.2, P3.3をSPI機能として使用
    P3SEL |= BIT1 + BIT2 + BIT3;      
 
    // USARTを有効化
    ME1 |= USPIE0;                    
    U0CTL &= ~SWRST;                  // リセット解除
 }
 
 // SPI経由で1バイト送受信する関数
 uint8_t SPI_Transfer(uint8_t data)
 {
    // 送信バッファが空になるまで待機
    // これにより前回の送信が完了していることを確認
    while (!(IFG1 & UTXIFG0));
 
    // 1バイトのデータを送信
    U0TXBUF = data;
 
    // 受信完了まで待機
    // SPIは全2重通信のため、1バイト送信すると同時に1バイト受信する
    while (!(IFG1 & URXIFG0));
 
    // 受信したデータを返す
    return U0RXBUF;
 }


MCP2515の初期化とCAN設定

CAN通信の核心部分である。
MCP2515を初期化して、CANバスの通信速度やフィルタ等を設定する。

※ビットレート設定について
CANバスの通信速度は重要であり、ネットワーク上の全てのノードが同じ速度に設定されている必要がある。
以下の例では、500[kbps]に設定しているが、これは産業用途で用いられる一般的な速度となる。
また、125[kbps]、250[kbps]、1[Mbps]等も広く使用されている。

 // MCP2515を初期化してCAN通信を準備する
 // この関数はプログラム起動時に1回だけ呼び出す
 uint8_t MCP2515_Init(void)
 {
    // ハードウェアリセット (オプション)
    RESET_LOW();
    __delay_cycles(10000);
    RESET_HIGH();
    __delay_cycles(10000);
 
    // ソフトウェアリセット
    MCP2515_Reset();
 
    // コンフィグレーションモードに入る
    // このモードでのみ、ビットレートなどの重要な設定が変更できる
    MCP2515_SetMode(MODE_CONFIG);
 
    // CAN通信速度の設定 (500[kbps] @ 8[MHz]水晶振動子)
    // この設定は使用する水晶振動子の周波数に依存する
    // ビットタイミングの計算は複雑であるが、以下のものは実績のある設定値である
 
    // CNF1: BRP=0 (分周比1)、SJW=00 (1TQ)
    MCP2515_Write(MCP_CNF1, 0x00);    
 
    // CNF2: BTLMODE=1、サンプルポイント設定
    // PHSEG1=110 (7TQ)、PRSEG=001 (2TQ)
    MCP2515_Write(MCP_CNF2, 0xD1);    
 
    // CNF3: PHSEG2=010 (3TQ)、ウェイクアップフィルタ無効
    MCP2515_Write(MCP_CNF3, 0x02);    
 
    // これらの設定により、次のビットタイミングが実現される:
    // 1ビット = 1TQ + PRSEG + PHSEG1 + PHSEG2 = 1 + 2 + 7 + 3 = 16TQ
    // ビットレート = 8MHz / (2 * (BRP+1) * 16) = 500[kbps]
 
    // 受信フィルタとマスクの設定
    // 全てのメッセージを受信するように設定 (フィルタ無効)
    MCP2515_Write(MCP_RXB0CTRL, 0x60); // 受信バッファ0 : 全て受信、ロールオーバー有効
 
    // マスクを0に設定すると、全てのIDを受け入れる
    MCP2515_Write(MCP_RXM0SIDH, 0x00);
    MCP2515_Write(MCP_RXM0SIDL, 0x00);
 
    // 割り込み設定
    // 受信割り込みを有効化
    MCP2515_Write(MCP_CANINTE, 0x01);  // RX0IE: 受信バッファ0割り込み有効
 
    // ノーマルモードに移行してCAN通信を開始
    MCP2515_SetMode(MODE_NORMAL);
 
    // 初期化が成功したか確認
    if ((MCP2515_Read(MCP_CANSTAT) & 0xE0) == MODE_NORMAL) {
       return 1;  // 成功
    }
 
    return 0;      // 失敗
 }


MCP2515との通信

MCP2515の特定のレジスタを読み書きする。

 // MCP2515のレジスタから1バイト読み取る
 uint8_t MCP2515_Read(uint8_t address)
 {
    uint8_t data;
 
    CS_LOW();                         // チップセレクトをアクティブに
    SPI_Transfer(MCP_READ);           // 読み取り命令を送信
    SPI_Transfer(address);            // レジスタアドレスを送信
    data = SPI_Transfer(0x00);        // ダミーデータを送信してレジスタ値を受信
    CS_HIGH();                        // チップセレクトを非アクティブに
 
    return data;
 }
 
 // MCP2515のレジスタに1バイト書き込む
 void MCP2515_Write(uint8_t address, uint8_t data)
 {
    CS_LOW();                         // チップセレクトをアクティブに
    SPI_Transfer(MCP_WRITE);          // 書き込み命令を送信
    SPI_Transfer(address);            // レジスタアドレスを送信
    SPI_Transfer(data);               // データを送信
    CS_HIGH();                        // チップセレクトを非アクティブに
 }
 
 // MCP2515のレジスタの特定ビットを変更する関数
 // マスクで指定したビットのみを変更できるため、他のビットに影響を与えずに設定を変更できる
 void MCP2515_BitModify(uint8_t address, uint8_t mask, uint8_t data)
 {
    CS_LOW();                         
    SPI_Transfer(MCP_BITMOD);         // ビット変更命令
    SPI_Transfer(address);            // レジスタアドレス
    SPI_Transfer(mask);               // 変更するビットのマスク
    SPI_Transfer(data);               // 新しい値
    CS_HIGH();                        
 }
 
 // MCP2515のモード設定
 // コンフィグモード、ノーマルモードなどを切り替える
 void MCP2515_SetMode(uint8_t mode)
 {
    // CANCTRLレジスタの上位3ビットでモードを設定
    MCP2515_BitModify(MCP_CANCTRL, 0xE0, mode);
 
    // モード変更が完了するまで待機
    // CANSTATレジスタを読み取って確認します
    uint8_t timeout = 255;
    while ((MCP2515_Read(MCP_CANSTAT) & 0xE0) != mode && timeout > 0) {
       __delay_cycles(1000);         // 短い遅延
       timeout--;
    }
 }
 
 // MCP2515をリセットする
 void MCP2515_Reset(void)
 {
    CS_LOW();
    SPI_Transfer(MCP_RESET);          // リセット命令を送信
    CS_HIGH();
    __delay_cycles(10000);            // リセット後の安定待ち(約1ms)
 }


CANメッセージの送信

CANメッセージを送信する。

送信プロセスを以下に示す。

  1. まず、メッセージIDを設定する。
  2. 次に、データ長を指定する。
  3. 実際のデータを書き込む。
  4. 最後に、送信要求を出す。


MCP2515は送信バッファを持っているため、複数のメッセージを効率的に処理することができる。

 // CANメッセージ送信関数
 // id : CANメッセージID (標準フォーマット11ビット)
 // dlc : データ長(0~8バイト)
 // data : 送信するデータの配列
 uint8_t CAN_SendMessage(uint16_t id, uint8_t dlc, uint8_t *data)
 {
    // 送信バッファが空いているか確認
    // TXB0CTRLレジスタのTXREQビットが0なら送信可能
    if (MCP2515_Read(MCP_TXB0CTRL) & 0x08) {
       return 0;  // 送信バッファが使用中、送信失敗
    }
 
    // メッセージIDの設定
    // 11ビットの標準IDを2つのレジスタに分割して格納
    // 上位8ビット (ID10-ID3) をSIDHレジスタに
    MCP2515_Write(MCP_TXB0SIDH, (uint8_t)(id >> 3));
 
    // 下位3ビット (ID2-ID0) をSIDLレジスタの上位3ビットに
    // ビット4はEXIDE (拡張ID有効ビット) で、標準フォーマットなので0
    MCP2515_Write(MCP_TXB0SIDL, (uint8_t)(id << 5));
 
    // データ長コード (DLC) の設定
    // 下位4ビットがデータ長を表します(0~8)
    if (dlc > 8) dlc = 8;  // 最大8バイトに制限
    MCP2515_Write(MCP_TXB0DLC, dlc);
 
    // データの書き込み
    // 送信するデータをデータレジスタに順次書き込む
    for (uint8_t i = 0; i < dlc; i++) {
       MCP2515_Write(MCP_TXB0DATA + i, data[i]);
    }
 
    // 送信要求
    // TXB0CTRLレジスタのTXREQビットを1にセットして送信開始
    MCP2515_BitModify(MCP_TXB0CTRL, 0x08, 0x08);
 
    // 送信完了待ち (オプション)
    // 実際のアプリケーションでは割り込みを使用することが多い
    uint16_t timeout = 1000;
    while ((MCP2515_Read(MCP_TXB0CTRL) & 0x08) && timeout > 0) {
       __delay_cycles(100);
       timeout--;
    }
 
    if (timeout == 0) {
       return 0;  // タイムアウト、送信失敗
    }
    
    return 1;  // 送信成功
 }


CANメッセージの受信

MCP2515は受信したメッセージを内部バッファに格納するため、それを読み出す必要がある。

受信では、まず受信バッファにメッセージが存在するかを確認して、存在する場合はメッセージID、データ長、データ本体を順次読み出す。
最後に受信フラグをクリアすることにより、次のメッセージの受信に備える。

 // CANメッセージ受信
 // 受信バッファにメッセージがある場合、それを読み出す
 // id : 受信したメッセージIDを格納するポインタ
 // dlc : 受信したデータ長を格納するポインタ
 // data : 受信データを格納する配列
 uint8_t CAN_ReceiveMessage(uint16_t *id, uint8_t *dlc, uint8_t *data)
 {
    // 受信バッファにメッセージがあるか確認
    // CANINTFレジスタのRX0IFビットが1なら受信あり
    if (!(MCP2515_Read(MCP_CANINTF) & 0x01)) {
       return 0;  // 受信メッセージなし
    }
 
    // メッセージIDの読み取り
    // SIDHレジスタから上位8ビットを取得
    uint8_t sidh = MCP2515_Read(MCP_RXB0SIDH);
 
    // SIDLレジスタから下位3ビットを取得
    uint8_t sidl = MCP2515_Read(MCP_RXB0SIDL);
 
    // 11ビットの標準IDを復元
    // SIDH (8ビット) を左に3ビットシフトし、SIDLの上位3ビットを加算
    *id = (uint16_t)(sidh << 3) | (uint16_t)(sidl >> 5);
 
    // データ長の読み取り
    *dlc = MCP2515_Read(MCP_RXB0DLC) & 0x0F;  // 下位4ビットがDLC
 
    // データの読み取り
    // 受信したデータをデータレジスタから順次読み出す
    for (uint8_t i = 0; i < *dlc; i++) {
       data[i] = MCP2515_Read(MCP_RXB0DATA + i);
    }
 
    // 受信割り込みフラグをクリア
    // 次のメッセージを受信できるようにする
    MCP2515_BitModify(MCP_CANINTF, 0x01, 0x00);
 
    return 1;  // 受信成功
 }


メイン処理

まず、全てのハードウェアを初期化して、その後無限ループの中で定期的にメッセージを送信し、受信を確認する。

実務では、送信タイミングや受信メッセージの処理内容は用途に応じて変更する必要がある。

 volatile uint8_t message_received = 0;  // 受信フラグ
 
 int main(void)
 {
    // ウォッチドッグタイマの停止
    WDTCTL = WDTPW + WDTHOLD;
 
    // クロック設定 (DCO = 8MHz)
    DCOCTL = CALDCO_8MHZ;
    BCSCTL1 = CALBC1_8MHZ;
 
    // GPIO初期化
    P3DIR |= CS_PIN + RESET_PIN;      // CS、RESETピンを出力に設定
    P3OUT |= CS_PIN;                  // CS初期状態はHIGH(非選択)
    P3OUT |= RESET_PIN;               // RESET初期状態はHIGH (非リセット)
 
    P2DIR &= ~INT_PIN;                // INTピンを入力に設定
    P2IE |= INT_PIN;                  // INTピンの割り込みを有効化
    P2IES |= INT_PIN;                 // 立ち下がりエッジで割り込み
    P2IFG &= ~INT_PIN;                // 割り込みフラグをクリア
 
    // SPI初期化
    SPI_Init();
 
    // MCP2515初期化
    if (!MCP2515_Init()) {
       // 初期化失敗時の処理
       // 例 : LEDを点滅させる等
       while(1) {
          __delay_cycles(8000000);  // 無限ループ
       }
    }
 
    // グローバル割り込み有効化
    __enable_interrupt();
 
    // メッセージ送信の例
    uint16_t tx_id = 0x123;           // 送信メッセージID
    uint8_t tx_dlc = 8;               // データ長8バイト
    uint8_t tx_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
 
    // 受信用変数
    uint16_t rx_id;
    uint8_t rx_dlc;
    uint8_t rx_data[8];
 
    // メインループ
    while(1) {
       // 定期的にメッセージを送信(例:1秒ごと)
       if (CAN_SendMessage(tx_id, tx_dlc, tx_data)) {
          // 送信成功時の処理
          // ...略
       }
 
       // メッセージ受信のチェック
       if (message_received) {
          message_received = 0;  // フラグをクリア
 
          // メッセージを読み出し
          if (CAN_ReceiveMessage(&rx_id, &rx_dlc, rx_data)) {
             // 受信したメッセージの処理
             // 例 : 受信IDに応じて異なる処理を行う
             switch(rx_id) {
                case 0x100:
                   // ID 0x100のメッセージを処理
                   break;
                case 0x200:
                   // ID 0x200のメッセージを処理
                   break;
                default:
                   // その他のメッセージの処理
                   break;
             }
          }
       }
  
       // 次の送信まで待機 (約1秒)
       __delay_cycles(8000000);
    }
 
    return 0;
 }
 
 // MCP2515の割り込みハンドラ
 // INTピンが立ち下がるとこの関数が呼ばれる
 #pragma vector=PORT2_VECTOR
 __interrupt void Port2_ISR(void)
 {
    if (P2IFG & INT_PIN) {
       // 受信フラグをセット
       message_received = 1;
 
       // 割り込みフラグをクリア
       P2IFG &= ~INT_PIN;
    }
 }



デバッグ

CANバスは最低2つのノードが必要なため、もう1台のCAN対応デバイス (別のMSP430 + MCP2515、Arduino、CANアナライザ等) を準備する。

デバッグ時に確認すべき点は以下の通りである。

  • まず、SPI通信が正しく動作しているかどうかを確認する。
    オシロスコープやロジックアナライザがある場合、クロック信号とデータ信号を観察する。
  • 次に、MCP2515の初期化が成功しているかどうかを、CANSTATレジスタを読んで確認する。
  • また、CANバス上の電圧レベルを確認する。
    アイドル状態において、CAN_HighとCAN_Lowの差が約2[V]になっていればよい。