「MSP430F149 - CANBUS」の版間の差分
| (同じ利用者による、間の1版が非表示) | |||
| 3行目: | 3行目: | ||
== システムの構成 == | == システムの構成 == | ||
MSP430F149はマイクロコントローラとして全体を制御し、SPI通信を使用してMCP2515 | MSP430F149はマイクロコントローラとして全体を制御し、SPI通信を使用してMCP2515 CANコントローラチップに命令を送信する。<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メッセージを送信する。
送信プロセスを以下に示す。
- まず、メッセージIDを設定する。
- 次に、データ長を指定する。
- 実際のデータを書き込む。
- 最後に、送信要求を出す。
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]になっていればよい。