前記事では 、北斗電子さんHSBRH850F1KH176 をターゲットにRH850自動コード生成を行いました。しかしCANドライバは自動コード生成の対象になっていません。そこで自動コード生成とHSBRH850F1KH176 に合わせたCANドライバプログラムを用意することにしました。その覚え書きです。
サンプルコードの入手
CANドライバ部は、RH850は設定項目も多く、RH850のCAN機能のアプリケーションノートも無いようです。一からは大変そうなのでサンプルコードを探します。現在、ルネサスから提供されているのは、RH850/U2A 用だけでした。同サンプルコードは、製品コンパイラやマイコン購入者でなくても、ルネサスアカウントを持っていればダウンロードできました。
取り急ぎ RH850F1KH の設定でbuildしてみると、ほぼほぼ通ってしまいます。それはRH850/U2A 専用にSFRを定義が作成しれていて、RH850F1KH のiodefine.h は参照していないからです。SFRアドレスもCAN構成も大分違います。RH850F1KH 用に移植が必要です。
RS-CANFDの理解 1
RH850 CAN機能はこりまでと異なる用語等があります。実用ベースでの見たところ以下のように理解しました。
用語・機能 解釈 グローバルCAN 「グローバル=世界」 ではないです。RH850のみでのCAN2.0、CAN-FD、複数のCAN UNITを管理するための機構です。 クラシカルCAN CAN2.0B のこと。 CAN UNIT 所謂CAN コントローラ、この配下に複数のCANチャンネルが連なります。 SFR添え字 UINT番号、CH番号等の略記号。マニュアルの表 24.9 添字 に説明あり。 受信ルール 従来のCAN Message Box のCAN IDマスクと同意。 送信Buffer 従来のCAN Message Box の送信専用版。 受信Buffer 従来のCAN Message Box の受信専用版。 データBit Rate 少なくとも CAN2.0B では通常Bit Rateと同じ。 FIFO Buffer 従来のCAN Message Boxとは別の先入れ先出し方式のMessage Boxの模様。未だ動作未確認です。 割り込みベクタ方式 一般的な「テーブル参照方式」と、「優先度に基づいた直接分岐方式」があります。
U2A→F1KH 移植: 定義編
— SFR # d e f i n e 名 を 変 更 – —
U2A 用サンプルコードのSFR定義: r_rh850_can_sfr.h を変更していきます。ルネサスのマニュアルには、UNIT番号、Channel番号、Buffer番号などぱ可変要素としてSFR名が記載されています。例えば、RCFDCn CFDCm DCFG のn はUNIT、m はChannelを示します。実名は RCFDC0CFDC0~7DCFG、iodefine.h はでは RCFDC0.CFDC0~7DCFG となります。よってマニュアルに合わせたSFR名でコードを書いた方が検索性に優れます。 U2A 用サンプルコードはそのようになっていますがF1KH と命名が違うため合わせていきます。
RSCFD nXXX は、 RCFDC nXXX に一括置換。
これでマニュアルとSFRの対比がつかめるようになります。
–– – ア ド レ ス を 変 更 – —
U2A 用サンプルコードのSFR定義: r_rh850_can_sfr.h では、SFRアドレスは CAN UNIT 毎のベースアドレスからの計算式になっています。そこでまず r_rh850_can_cfg.h にて CAN UNIT のベースアドレスを変更します。U2A 用は CAN UNIT は一つと決め打ちです。F1KH の最多pin種では2つのため、ベースアドレスはグローバル変数とし逐次切替式としました。
uint32_t CAN_START_ADDR;
#define R_CAN_SelectUnit( ch ) ( CAN_START_ADDR = CAN_START_ADDR [ ch ] )
const uint32_t CAN_UINT_ADDR [ 2 ] = { 0xFFD00000UL /* UNIT 0 */ , 0xFFD20000UL /* UNIT 1 */ };
あとは SFR名 で マニュアルを検索し、アドレスを変更していきます。アドレスはGlobal系の数個以外は全部違います。50個近くあるので大変です。ついでに日本語名もコメント付与します。計算式のオフセットやCH毎増分など違う場合もあるので修正します。以下 r_rh850_can_cfg.h の一例です。
#define RCFDCnCFDTMCp( p ) CAN_REG8 ( 0x0250 + (p)) // 送信バッファ制御(p)
・・・中略・・・
#define RCFDCnCFDCFCCk( k ) CAN_REG32 ( 0x0118 + ( 0x04 * (k))) // 送受信FIFOバッファ構成/制御(k)
#define RCFDCnCFDCFSTSk( k ) CAN_REG32 ( 0x0178 + ( 0x04 * (k))) // 送受信FIFOバッファステース(x)
— 過 不 足 S F R の 追 加 / 削 除 —
U2A にしかないSFRの削除、F1KH しかないSFRの追加をします。こちらはマニュアルと照らし合わせていくしかありません。手間がかかります。以下 r_rh850_can_sfr.h の一例です。
#define RCFDCnCFDGTINTSTS0 CAN_REG32 ( 0x0610 ) // Global TX割り込みステータス0
#define RCFDCnCFDGTINTSTS1 CAN_REG32 ( 0x0614 ) // Global TX割り込みステータス1 ☆
#define RCFDCnCFDGFDCFG CAN_REG32 ( 0x0624 ) // Global FDコンフィグレーション ☆
— B I T 構 成 の 変 更 —
U2A とF1KH で、同じSFRでBit構成が異なるのものがあります。こちらはマニュアルと照らしあわせていくしかありません。弊方ではコメントを付与ついでに調べていきます。幸い数はすくないです。決定的に変更しないとまずいのはBaudrate( RCFDCnCFDCmNCFG, RCFDCnCFDCmDCFG )です。 まさかと思い弊方では直ぐに気付けませんでした。トラップです。以下 r_rh850_can_sfr.h の一例です。
#define CAN_DTSEG2_BIT_POS 20U // Data BitRate Timeセグメント2 (3bit)
#define CAN_DTSEG1_BIT_POS 16U // Data BitRate Timeセグメント1 (4bit)
— CAN構成の変更加 —
U2A 用では、CAN構成は r_rh850_can_cfg.[ch] に記述されています。F1KH に構成が異なるため変更が必要です。弊方では CAN UNIT 毎に定義できるようにしています。
#define MAX_CH_NUM_U0 8
#define MAX_CH_NUM_U1 4 // 324pinの場合のみ
#define MAX_CH_NUM( unit ) ( unit == 0 ? MAX_CH_NUM_U0 : MAX_CH_NUM_U1 )
#define USED_UNIT_NUM 1 // 324pinは2
#define MAX_TX_BUF_OF_CH 32 // CH毎の送信Buffer数
送信バッファ数はF1KH が多いので追加します。こちらは r_rh850_can_drv.h。
#define CAN_TXBUF16 16U
#define CAN_TXBUF17 17U
・・・中略・・・
#define CAN_TXBUF31 31U
#define CAN_MAX_TXBUF_NUM 32U
— 構 造 体 の 変 更 —
ドライバプログラムで取り扱う構造体もF1KH では少し違います。CAN Frame と CAN Channel 定義 を修正します。以下CAN Frameの例です。
typedef struct {
・・・中略・・・
uint32_t TS : 16 ; /* 受信時刻 */
uint32_t RESERVE: 12 ; /* 空き */
uint32_t DLC : 4 ; /* DLC(データ部長) */
uint32_t TMESI: 1 ; /* CAN-FD 0:ERRアクティブNode 1:ERRパッシブNode */
uint32_t TMBRS: 1 ; /* CAN-FD DATA領域Bitrate 0:変わらない 1:変わる */
uint32_t TMFDF: 1 ; /* CAN-FD 0:CAN2.0 1:CAN-FD */
uint32_t RESERVE2: 13 ; /* FD Status(TMESI,TMESI */
uint32_t LBL: 16 ; /* 受信バッファラベル */
・・・中略・・・
} can_frame_t ;
U2A→F1KH 移植: ロジック編
— 不要.c の 除 外 —
自動コード生成部を使う部分、セルフテスト部は特に不要です。以下.c は、Build対象外にします。
r_rh850_can_int.c
r_rh850_can_io.c
r_rh850_can_drv2.c
— A P I 部 の 日 本 語 コ メ ン ト 付 与 と CAN UNIT番号の指定化—
r_rh850_can_drv.c が CANドライバAPI 部です。コメントは少な目で英語なので、内容を確認しながらコメントを追記していきます。全APIで行うとタイヘンなので取り急ぎ使いそうなもののみです。併せてCAN UNIT番号を指定可能にしておきます。以下一部例です。
/******************************************************************************
* Function Name: R_Can_Init
* Description : CAN UNIT毎に初期化を行う。
* - CAN用RAMクリア
* - Gobal STOPモード解除を待つ。
* - CAN2.0 と FD切替え
* - ch毎のBaudrate設定
* - 受信Ruleの設定
* - 受信Bufferの設定
* 各設定は、r_rh850_can_cfg.c で行う。
・・・中略・・・
* CAN_RTN_ARG_ERROR -
* パラメータエラー
******************************************************************************/
Can_RtnType R_CAN_Init (
const uint8_t unitNum , // i : CAN Unit
const uint8_t chNum , // i : 使用CAN ch数
const BOOL useCanFd // i : CAN FD使用
) {
— 仮 B u i l d —
修正内容の整合性があっているか、仮Buildします。自己あやまり以外で引っかかる部分が少しあります。RCFDCnCFDTMIECm のポインタ型はU2A 用では16bitでしたがF1KH では32bitにする必要があります。
・・・中略・・・
volatile uint32_t *p_TMIEC;
・・・中略・・・
— そ の 他 追 加 —
オリジナルのままでは使いにくそうなので多少機能追加しています。 Callback 機構とそのための割込みに仕込む EVEVT Dispacher です。
/******************************************************************************
* Function Name: R_CAN_DispatchEevent
* Description : CAN ENEVTを処理する
* 割込ハンドラから呼ばれます
* 割込ペリフェラルとのI/F部です。
* Remark:
* - 現在は、送信Bufferによる送信のみサポート。
* - RH850では、受信Bufferの割込みはない。
******************************************************************************/
void R_CAN_DispatchEevent (
const uint8_t unitNum, // i : CAN Unit
can_ch_t ch_idx, // i : CAN Ch番号
const uint8_t event // i : ENEVT No.
) {
・・・中略・・・
/******************************************************************************
* Function Name: R_CAN_SetRecvCallback
* Description : CAN受信Callbckを設定する。
******************************************************************************/
void R_CAN_SetRecvCallback (
const uint8_t unitNum, // i : CAN Unit
can_ch_t ch_idx, // i : CAN Ch番号
CAN_RECV_CALLBAK func // i : 受信Callbak
) {
・・・中略・・・
/******************************************************************************
* Function Name: R_CAN_SetSendCallback
* Description : CAN送信Callbckを設定する。
******************************************************************************/
void R_CAN_SetSendCallback (
const uint8_t unitNum, // i : CAN Unit
can_ch_t ch_idx, // i : CAN Ch番号
CAN_SEND_CALLBAK func // i : 受信Callbak
) {
・・・中略・・・
/******************************************************************************
* Function Name: R_CAN_SetErrCallback
* Description : CANエラーCallbckを設定する。
******************************************************************************/
void R_CAN_SetErrCallback (
const uint8_t unitNum, // i : CAN Unit
can_ch_t ch_idx, // i : CAN Ch番号
CAN_ERR_CALLBAK func // i : 受信Callbak
) {
U2A→F1KH 移植: main実装
— m a i n へ の 組 込 み —
自動コード生成でのmainモジュール: r_cg_main.c の実装は以下のような感じです。
・・・中略・・・
/* Start user code for include. Do not edit comment generated here */
#include "r_rh850_can_sfr.h"
#include "r_rh850_can_drv.h"
/* End user code. Do not edit comment generated here */
・・・中略・・・
void main ( void )
{
・・・中略・・・
r_main_userinit ();
r_main_startCan ();
while ( 1U ) {
・・・中略・・・
}
void r_main_startCan ( void )
{
// CAN Controller 初期化
if ( R_CAN_Init ( 0 /*UNIT*/ , 2 /*CH数*/ , FALSE /*CANFD*/ ) != CAN_RTN_OK ) {
return ;
}
// Ch0 Callbak設定
R_CAN_SetErrCallback ( 0 /*UNIT*/ , 0 /*ch.*/ , r_main_errCan0 );
R_CAN_SetRecvCallback ( 0 /*UNIT*/ , 0 /*ch.*/ , r_main_recvCan0 );
R_CAN_SetSendCallback ( 0 /*UNIT*/ , 0 /*ch.*/ , r_main_sendCan0 );
// Ch1 Callbak設定
R_CAN_SetErrCallback ( 0 /*UNIT*/ , 1 /*ch.*/ , r_main_errCan1 );
R_CAN_SetRecvCallback ( 0 /*UNIT*/ , 1 /*ch.*/ , r_main_recvCan1 );
R_CAN_SetSendCallback ( 0 /*UNIT*/ , 1 /*ch.*/ , r_main_sendCan1 );
// Global CAN開始
if ( R_CAN_GlobalStart ( 0 /*UNIT*/ ) != CAN_RTN_OK ) {
return ;
}
// CAN 0ch 開始
if ( R_CAN_ChStart ( 0 /*UNIT*/ , 0 /* Ch. */ ) != CAN_RTN_OK ) {
return ;
}
// CAN 1ch 開始
if ( R_CAN_ChStart ( 0 /*UNIT*/ , 1 /* Ch. */ ) != CAN_RTN_OK ) {
return ;
}
— C A N ペ リ フ ェ ラ ル と 割 込 み の 追 加 —
CAN機能と割込は場合、HSBRH850F1KH176 の回路実装から各種割当ては以下のようになります。なお受信Buffer による受信割込みはRH850は無いです。 送受信FIFOはFIFO Buffer専用の割込みのようです。
Chanel 機能 ポート名 ポート番号 送信完割込番号 送受信FIFO割込番号 エラー割込番号 0 受信 CAN0RX P10_0 — 25 24 0 送信 CAN0TX P10_1 26 25 24 1 受信 CAN1RX P0_2 — 114 113 1 送信 CAN1TX P0_3 115 114 113
CANペリフェラルの定義は、e2stdioの自動生成で以下のように行います。
上記の設定で、対応するポートの入出力の設定はされるので、以下2つの個別の設定はしないです。
CANペリフェラルの定義は、Pin.c の R_Pins_Create に生成されます。しかし生成されるだけで、 Config_PORT.c などからcallされていません。呼出しコードは自分で書く必要がありました 。弊方では直ぐ気付けずトラップでした。弊方では、Config_PORT_user.c から以下のように呼出追加しました。
void R_Config_PORT_Create_UserInit ( void )
{
/* Start user code for user init. Do not edit comment generated here */
R_Pins_Create ();
/* End user code. Do not edit comment generated here */
}
割込みの設定は一旦以下ようにします。これでハンドラのベクタテーブルへの定義とスケルトン、割込み有効無効のAPIが生成されます。
しかしCAN割込みの場合、割込み優先度 と 割り込みベクタ方式 の設定コードは自動生成されませんでした。タイマ等では自動生成されるので、ここはトラップです 。デフォルトは割込み優先度 です。そのまま動かすと優先度15割込み未定義で無限loopしました。 boot.asm を見ると自動生成デフォルト状態では _Dummy_EI で無限loopになってます。この方式は使わない方が無難なようです。
割込み設定に関するコード例は以下のとおりです。r_main_startCan() の各Ch.初期化後に追記しましたた。
/* CH0 割込み方式とLEVEL設定 */
INTC1.ICRCAN0ERR.BIT.TBRCAN0ERR = _INT_TABLE_VECTOR;
INTC1.ICRCAN0REC.BIT.TBRCAN0REC = _INT_TABLE_VECTOR;
INTC1.ICRCAN0TRX.BIT.TBRCAN0TRX = _INT_TABLE_VECTOR;
INTC1.ICRCAN0ERR.UINT16 &= _INT_PRIORITY_LEVEL4;
INTC1.ICRCAN0REC.UINT16 &= _INT_PRIORITY_LEVEL4;
INTC1.ICRCAN0TRX.UINT16 &= _INT_PRIORITY_LEVEL4;
/* CH1 割込み方式とLEVEL設定 */
INTC2.ICRCAN1ERR.BIT.TBRCAN1ERR = _INT_TABLE_VECTOR;
INTC2.ICRCAN1REC.BIT.TBRCAN1REC = _INT_TABLE_VECTOR;
INTC2.ICRCAN1TRX.BIT.TBRCAN1TRX = _INT_TABLE_VECTOR;
INTC2.ICRCAN1ERR.UINT16 &= _INT_PRIORITY_LEVEL4;
INTC2.ICRCAN1REC.UINT16 &= _INT_PRIORITY_LEVEL4;
INTC2.ICRCAN1TRX.UINT16 &= _INT_PRIORITY_LEVEL4;
// CH0 割込み有効化
IntCAN0Error_enable_interrupt ( );
IntCAN0TxRxFIFORxCmp_enable_interrupt ( );
IntCAN0Tx_enable_interrupt ( );
// CH1 割込み有効化
IntCAN1Error_enable_interrupt ( );
IntCAN1TxRxFIFORxCmp_enable_interrupt ( );
IntCAN1Tx_enable_interrupt ( );
以上おおざっぱなところ、初期化は通過し、送受信スタンパイまで行き着きました。しかし試行錯誤や細かい部分でハードルもあり、ここまで延べて2weekくらいは要したかと思います。
送信編につづきます。