RX マイコン内蔵 CAN ドライバーの実装
RXマイコンには、CAN を内蔵しているデバイスがある。
RX マイコンの CAN ペリフェラルは機能が豊富で、CAN 全体の構成も複雑なので、柔軟な「管理」を考えるとインターフェースが難しい・・
「送信」も、「受信」も、色々なバリエーションが考えられるので、どのような構成が「正解」なのか色々考える必要がある。
※機能が豊富過ぎると、どのような構成にすると、メリットが大きいのか考える必要があり、実験や調査に時間がかかりそうだ・・
とりあえず、どのような構成にするのがベストなのかイマイチ判らないのと、CAN についての知識が不足しているので、ハードウェアー機能を網羅するような対話形式のインターフェースを作り、実験しながら考えていく事にする。
※もう十年以上前に、仕事で、Autosar 準拠の機器で Linux ドライバーを実装した事があるが、その頃とは状況が異なっている・・
Autosar は上位の層で、自分が担当したのは、下位の層、ハードウェアーを直接操作して、CAN デバイスを操作するドライバーだった、Linux ドライバーは初めてで、それなりに苦労した事を覚えている・・・
- CAN の通信 IC は Philips 製で、それが、PCI を介して接続されていたと思う。
- この時は、単純な機能しか無かったので、ソフトで色々な機能をサポートしていた、ドライバーはシンプルなものだった。
RX24T は、初期のバージョンでは、CAN が無かったが、新しいバージョンでは、CAN が追加された、ただ、今までの CAN とは異なる構成になっている。
その為、同じドライバーでは、面倒を見れないと思える、これもどうするのが良いのか悩む要因の一つだ・・・
※RSCAN と呼ばれるペリフェラルに置き換わっている。
※RX66Tは、従来通りの CAN となっている。
別の問題として、CAN 関連レジスタにアクセスするテンプレートが一筋縄ではいかない構成なので、それもスマートに解決する必要がある。
※メールボックスレジスタ(32個ある)などにアクセスするテンプレートが思うように作れなかったので、中途な状態になっていた、最初は、それの改善方法を考える事から始める。
レジスタクラスを改造する
CAN には、「メールボックス」が32個あり、同等なアクセスを提供している。
しかし、レジスタ定義テンプレートクラスは、単独、1個のレジスタを想定して設計されている為、どのようにアクセスするか、悩んでいた。
テンプレートクラスの場合、アクセスするアドレスは固定となっている為、さらにインデックスを加えてアクセスするようには出来ていない。
そこで、「インデックス」付きアクセスを実現する為、最終的にレジスタメモリにアクセスするクラス「rw8_t」とは別クラスを追加した。
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief Read/Write 8 bits アクセス・テンプレート、インデックス付
@param[in] adr アドレス
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <address_type adr>
struct rw8_index_t {
typedef uint8_t value_type;
static uint32_t index;
//-----------------------------------------------------------------//
/*!
@brief アドレスの取得
@return アドレス
*/
//-----------------------------------------------------------------//
static address_type address() noexcept { return adr + index; }
//-----------------------------------------------------------------//
/*!
@brief 書き込み
@param[in] data 書き込み値
*/
//-----------------------------------------------------------------//
static void write(value_type data) noexcept {
wr8_(adr + index, data);
}
//-----------------------------------------------------------------//
/*!
@brief 読み出し
@return 読み出し値
*/
//-----------------------------------------------------------------//
static value_type read() noexcept {
return rd8_(adr + index);
}
...
};
改造は簡単な物で、変数として「index」を追加して、事前に「index」を設定してからアクセスする事で、基底アドレス+index にアクセス出来る。
注意しなければならないのは、「adr」が同じテンプレートの場合、static で宣言されている「index」は共有されてしまうが、都合の良い事に、そのような場合が起こるのは「稀」だと思える。
メールボックス制御レジスタクラスは、元々、MCTL0 ~ MCTL31 が定義されている。
しかし、そのインスタンスでアクセスするのでは、冗長になる。
そこで、上記の「rw8_index_t」クラスを使い、index 分のアクセスを行えるように改修した。
さらに、「operator []」でアクセスできるように、「[]」をオーバーロードした。
operator の「戻り」は参照を使って、自分を戻しているので、インデックス付きアクセスが出来る。
//-----------------------------------------------------------------//
/*!
@brief メッセージ制御レジスタ j( MCTLj )( j = 0 ~ 31 )
@param[in] ofs オフセット
*/
//-----------------------------------------------------------------//
template <uint32_t ofs>
struct mctl_t : public rw8_index_t<ofs> {
typedef rw8_index_t<ofs> io_;
using io_::operator =;
using io_::operator ();
using io_::operator |=;
using io_::operator &=;
void set_index(uint32_t j) { if(j < 32) { io_::index = j; } }
bit_rw_t <io_, bitpos::B0> SENTDATA; // Send mode
bit_rw_t <io_, bitpos::B0> NEWDATA; // Recv mode
bit_rw_t <io_, bitpos::B1> TRMACTIVE; // Send mode
bit_rw_t <io_, bitpos::B1> INVALDATA; // Recv mode
bit_rw_t <io_, bitpos::B2> TRMABT; // Send mode
bit_rw_t <io_, bitpos::B2> TMSGLOST; // Recv mode
bit_rw_t <io_, bitpos::B4> ONESHOT;
bit_rw_t <io_, bitpos::B6> RECREQ;
bit_rw_t <io_, bitpos::B7> TRMREQ;
mctl_t& operator [] (uint32_t idx)
{
set_index(idx);
return *this;
}
};
typedef mctl_t<base + 0x0620> MCTL_;
static MCTL_ MCTL;
この改修で、以下のように自然にアクセスする事が出来るようになった。
CAN::MCTL[idx] = CAN::MCTL.TRMREQ.b(1);
CAN::MCTL[idx].TRMREQ = 1;
RX64M で送信、受信実験
以前に CAN バス・トランシーバー VP230 が乗った基板を購入した(中華製)このトランシーバーは 3.3V で動作するので、RX64M で動かすのには都合が良い、ターミネーター抵抗(120 オーム)も付いている。
ドライバーを作りこむのに、実験環境を作らなければならないが、送信側、受信側を別々にすると、二台のボードで実験する事になり、デバッグの手順が複雑になる。
RX64M は、CAN を3チャネル持っているので、CAN0、CAN1 を使って、相互に接続し、1台で、送信と受信が出来るような構成にした。
ボーレートの設定
RX マイコンの CAN はボーレートの設定が多少複雑だ、ドライバークラスは、複雑な設定を隠蔽して、簡単な設定を自動で行えるようにしなければメリットが薄い。
また、間違った設定を構造的に出来なくする事も重要だ。
CAN は、1ビットを送受信する過程で、色々な処理をするので、このような複雑なものになっているようだ。
RX マイコンの CAN では、1 ビットを「TQ」と呼ばれる値(ビットタイム)で分割し、これが、8~25になるようにボーレートを設定する。
TQ は、SS、TSEG1、TSEG2、を合計した物で、以下のような関係になっている。
SS = 1 Tq
TSEG1 = 4 Tq ~ 16 Tq
TSEG2 = 2 Tq ~ 8 Tq
SJW = 1 Tq ~ 4 Tq
TSEG1 > TSEG2 ≧ SJW
まず、「余り」が0になるような TQ 値を求める。
※そうしないと、割り切れない場合、微妙な通信速度になり、標準的な機器と通信出来ない。
// 通信速度に対する、TQ 値 8 to 25 で適切な値を選ぶ
// より大きい値で適合した値を選択
uint32_t tq = 25;
while(1) {
if((get_can_clock_() % (static_cast<uint32_t>(speed) * tq)) == 0) {
break;
}
tq--;
if(tq < 8) { // 割り切れない場合エラーとする
format("(1)BRP divider indivisible...\n");
return false;
}
}
-「get_canclock()」は、PCLKB(60MHz)となる。
- 「speed」は、enum class で定義してあり、キャストして整数としている。
enum class SPEED {
_50K = 50'000, ///< 50 Kbps(標準ではない)
_100K = 100'000, ///< 100 Kbps(標準ではない)
_125K = 125'000, ///< 125 Kbps
_250K = 250'000, ///< 250 Kbps
_500K = 500'000, ///< 500 Kbps
_750K = 750'000, ///< 750 Kbps(標準ではない)
_1M = 1'000'000 ///< 1 Mbps
};
次に、TSEG1、TSEG2 の配分を決める。
uint32_t tseg1 = 16;
uint32_t tseg2 = 8;
uint32_t sjw = 1; // とりあえず固定
while(1) {
if(tq == (1 + tseg1 + tseg2)) break;
tseg1--;
if(tq == (1 + tseg1 + tseg2)) break;
else if(tseg1 < 4) {
format("(3)TSEG1 value indivisible...\n");
return false;
}
tseg1--;
if(tq == (1 + tseg1 + tseg2)) break;
else if(tseg1 < 4) {
format("(4)TSEG1 value indivisible...\n");
return false;
}
tseg2--;
if(tq == (1 + tseg1 + tseg2)) break;
else if(tseg2 < 2) {
format("(5)TSEG2 value indivisible...\n");
return false;
}
}
RX64M PCLKB=60MHz の場合、1Mbps に設定すると、以下のような値となる。
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7
※「SJW」はこの変数に関する説明が無かったので、とりあえず「1」にしているが、出来るだけ大きい方が良いのかもしれない・・
メールボックスをどのように割り振るか?
RX マイコンの CAN は、メールボックスを中心とした構成になっている。
メールボックスは32個あり、どのように割り振るのか、ケースバイケースとなると思うが、「受信」動作はかなり厄介だ・・
- 特定の ID だけ受信する(オペレーションモードにする前に決めなければならない)
- 全ての ID を受信して、ソフトで振り分ける。
- 上記二つの混合
「特定 ID」の受信では、初期化時に ID が決まっていなければならないが、ハードウェアーが自動で行うので、ソフトの負荷が小さくなる。
※その ID の数分、メールボックスを消費する。
「ソフト処理」では、どんな構成でも可能だが、その分、CPU 負荷が大きくなる。
CAN の場合、1つのパケットは 100 マイクロ秒くらいなので、120MHz の RX64M でも、流れるパケットによっては、処理がオーバーフローするかもしれない。
対話式インターフェース
CAN レジスタの説明を読んだ理解と、実際の動作が異なる場合があると思うので(CAN の理解不足などから)対話式インターフェースを実装した。
最初のステップとして、メールボックス「ありき」で対話式インターフェースを用意した。
※最終的には、メールボックスをアプリケーションユーザーに意識させるのは良くないと思うので実験用だけ。
Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7
# help
ch CH-no set current CAN channel (CH-no: 0, 1)
id number set current ID number
ext set ext-id mode
std set std-id mode
data MB-no [byte ...] set frame data (MB-no: 0 to 31)
list MB-no list frame (MB-no: 0 to 31)
reset MB-no reset MB (MB-no: 0 to 31)
send MB-no send frame (MB-no: 0 to 31)
recv MB-no recv frame (MB-no: 0 to 31)
stat MB-no stat frame (MB-no: 0 to 31)
state report CAN state
help command list (this)
簡単な通信を行い、送信、受信動作を確認。
# ch 1
# id 0x754
# data 0 0
# recv 0
# stat 0
MCTL: 0x40 (0b01000000)
# ch 0
# data 1 0x75 0x46 0xac 0xf8 0x40
# send 1
# ch 1
# stat 0
MCTL: 0x41 (0b01000001)
# list 0
ID: std 0x754 (1876)
DATA(5): 75 46 AC F8 40
TS: 31995
#
メールボックスの扱い方がある程度分かったと思う・・・
今回はここまで、次は、マネージメントを内部で自動で行い、扱いやすくする方法や構造を考える。
また、割り込みも使う必要があるので、その辺りを中心に実装する。