WioTerminalでCAN通信~通信設定編~

本記事は、WioTerminal(ATSAMD51)でCAN通信をするために、MCP2515に送るSPIコマンドについて記します。

前回の記事で、WioTerminalとCANコントローラ"MCP2515"を接続する回路について記しました。

embaud-ot.hateblo.jp

準備

実験に必要な機材、ソフトウェアを示します。

準備

実験に必要な機材、ソフトウェアを示します。

Hardware

Software

Library

  • 学習のために自作したため、無し

References

shop.cqpub.co.jp

  • CAN通信の仕組み、MPU2515のレジスタ操作について解説されています。

MCP2515の紹介

  • MCP2515は、一般的なマイコンに搭載さているシリアル通信である「SPI(Serial Peripheral Interface)」によってデータを制御し、CAN通信を実現(フレーム変換)するためのCANコントローラです。

Step 3 MCP2515用ライブラリ作成

なによりも先にやることは、データシート(ww1.microchip.com/downloads/jp/DeviceDoc)を眺めることです。

MCP2515との通信方法

データシートの冒頭に帯で書かれている通り、MCP2515はSPI通信で命令を発行し制御します。

MCP2515のSPI通信のフォーマットは、12章に書かれています。
1命令を発行する基本動作は、

  1. CSピンをLowにする
    • CSピンは、通信相手を選択するための信号線です(対象 or 対象外の2択)
    • 命令発行時以外はHIGHにしておきます
  2. クロックを発行しながら、命令データを送りつける
    • 命令バイトは必ず1つ発行します
    • 命令に続いて付随情報を流す場合は、続けて送ります
  3. クロックを発行しながら、応答データを返させる
    • 応答データが無いときは、本手順をスキップします
  4. CSピンをHIGHにします

起動ステップ1 ~リセット~

10章で「動作モード」、9章「リセット」について述べられています。
いずれの記述からも分かるように、MCP2515を使い始める場合には、「リセット」を行います。


リセットの手法は、9章に書かれている通り、ハードかソフトかいずれかでリセット命令を発行します。

MCP2515は2つのリセットを識別します
1. ハードウェアリセット ―RESETピンをLow
2. SPIリセット ―SPIコマンドでリセット

ソフトウェアでリセットをかけるのが簡単だと思います。
命令フォーマットは、表12-1に示されています。
リセット命令は「1100 0000」です。

起動ステップ2 ~CAN通信速度の設定~

5章に「タイムセグメントのプログラミング」ということで、CAN通信速度の設定の仕方が書いてあります。
色々と用語が出てきます。
TQ、BRP、SyncSeg、PropSeg、PhaseSegment1 (PS1)、PhaseSegment2 (PS2)、SJW

データシートの話をまとめると、

  • TQはOSCの周波数に依存して決まる、CAN通信速度設定をするための最小時間単位
  • PropSeg + PS1 >= PS2
  • SJWはOSCに水晶発振子を繋げたのであれば「1」と決め打ってよい
  • PS2 > SJW
  • PropSeg + PS1 >= Tdelay
    • Tdelayは1TQとすればよい
  • 0.6 <= (t_SyncSeg +t_PropSeg + t_PS1) / t_bit <= 0.7

上記を満たすように、各パラメタを設定すればよいです。

よって、

  1. 水晶発振子を選択する
  2. CAN通信速度を選択する
  3. TQを次の条件を満たすように決定する
    • BRPの最小は1
    • TQ = 2×BRP×T_OSC
    • TQ × n = 1/NBR
  4. SyncSegを1 TQ、t_delayを1 TQ、SJWを1として、PropSeg,PS1、PS2を先述の条件を満たす組み合わせを選択する

おおよそ、下のような計算プログラムで求めることができます。
完璧な実装ではないので、参考程度にしてください。

#include <stdio.h>

#define uint8_t unsigned char

#define DEFAULT_CALCLOOPMAX 30

#define MCP2515_OSC4MHz     4000000
#define MCP2515_OSC8MHz     8000000
#define MCP2515_OSC16MHz   16000000
#define MCP2515_OSC20MHz   20000000

#define MCP2515_PARAM_OK 0
#define MCP2515_PARAM_NG 1

typedef struct
{
    double tq;                   /* Time Quantum        */
    uint8_t brp;                  /* Bitrate Prescaler   */
    uint8_t syncSeg;              /* Sync Segment        */
    uint8_t propSeg;              /* Propagation Segment */
    uint8_t ps1;                  /* PhaseSegment 1      */
    uint8_t ps2;                  /* PhaseSegment 2      */
    uint8_t sjw;
    uint8_t tdelay;
    uint8_t tbit;
}canctlprm;


void printDouble(double vald);
uint8_t autocalcParams(int oscfreq, int canbps, canctlprm *params);

int main()
{
    printf("Hello, World!\n");
    canctlprm myParam = {1,1,1,1,1,2,1,1,1};  /* Default minumum Value*/
    
    /* ユーザ環境によって予め変更すべきパラメタ*/
    myParam.sjw     = 1;     /* 1 to 4 */
    myParam.propSeg = 2; /* 1 to 8 */
    myParam.tdelay  = 1;  /*  1 to 2 */
  
   
    autocalcParams(MCP2515_OSC16MHz, 500000, &myParam);
    return 0;
}

/*
 * @func autocalcParams
 * @param[input]         oscfreq      MCP2515の外部クロック周波数
 * @param[input]         canbps    CAN-Busビットレート
 * @param[input,output]  params    MCP2515に設定する値の初期値/算出値
 * @return    パラメタ作成完=MCP2515_PARAM_OK, パラメタ作成失敗=MCP2515_PARAM_NG 
 *
*/
uint8_t autocalcParams(int oscfreq, int canbps, canctlprm *params){
    printf("Start Caluculation\n");
    const double oscts = (double)1 / oscfreq; 
    const double cants = (double)1 / canbps;
    uint8_t ret =  MCP2515_PARAM_NG;                        /* デフォルトの戻り値は作成失敗  */
    int lp0 = 0;           /* 繰り返し処理用捨て変数 */
    int lp1 = 0;           /* 繰り返し処理用捨て変数 */
    int lp2 = 0;           /* 繰り返し処理用捨て変数 */

    uint8_t cond1 = 1, cond2 = 1, cond3 = 1, total = 1;   /* パラメタ条件確認用変数 */

   params->syncSeg = 1;           /* 1 ~ 1 */

    printf(" OSC %d,CAN %d\n\n",oscfreq, canbps);       /* クロック周波数とCANBUSビットレートの設定値*/

    for(lp0 = 1; lp0 < 64; lp0++){
        //printf("%d\n",lp0);
        params->tq = (double)(2 * lp0) / (double)oscfreq;
       
        double tmpd = cants / params->tq;
 
        if((tmpd - (double)((int)tmpd)) == 0){
            params->brp = lp0;
            printf(" tq = ");
            printDouble(params->tq);
            printf(", brp = %d",params->brp);
            
            params->tbit = (uint8_t)tmpd;
            
            printf(", Tbit = %d\n",params->tbit);
            lp0 = 999;                                                               /*   最初に見つかったBRP候補条件1つでパラメタ算出開始  */
        }
    }
        
    while(params->ps2 <= params->sjw){                          /*   パラメタ条件(1)  */
        params->ps2 += 1;
    }
      
    int limit = DEFAULT_CALCLOOPMAX;
    while(limit > 0 && ret == MCP2515_PARAM_NG){
        cond1 = params->propSeg + params->ps1;
        cond2 = params->syncSeg + cond1;
        cond3 = params->ps2;
        total = cond2 + cond3;
        
        while(limit == DEFAULT_CALCLOOPMAX && params->tbit < total){
            if(params->tdelay > 1 || params->propSeg > 1 || params->sjw > 1){
                if(params->tdelay > 1)
                    params->tdelay -= 1;
                else if(params->propSeg > 1){
                    params->propSeg -= 1;
                }else if(params->sjw > 1){
                    params->sjw -= 1;
                    if(params->ps2 > params->sjw + 1){
                        params->ps2 -= 1;
                    }else;
                }else;
                
                cond1 = params->propSeg + params->ps1;
                cond2 = params->syncSeg + cond1;
                cond3 = params->ps2;
                total = cond2 + cond3;
            }else{
                break;
            }
        }
        
        printf("  %d  %d  %d  %d\n",params->syncSeg, params->propSeg, 
                            params->ps1, params->ps2);
        
        
        if(cond1 >= params->ps2 &&                                     /* パラメタ条件(2)   */
          ((float)cond2/(float)total > 0.59) && 
          ((float)cond2/(float)total < 0.71))
        {
            printf("   [pos] %f\n", (float)cond2/(float)total);
            if(cond1 >= params->tdelay)                                    /* パラメタ条件(3)   */
            {
                if(total == params->tbit){                                      /* パラメタ条件(4)  */
                    printf("end\n");
                    ret = MCP2515_PARAM_OK;                              /* パラメタ作成完了 */
                }else;
                printf("   total  %d < %d tq\n", total, params->tbit);
                
            }else;
            
        }else;
        
        if(ret == MCP2515_PARAM_NG){                            /* パラメタ未決定時に加算調整 */
            //printf("%f\n",(float)(cond2 + 1)/(float)(total + 1) - (float)0.65);
            //printf("%f\n",(float)(cond2    )/(float)(total + 1) - (float)0.65);
            float v1 = ((float)(cond2 + 1)/(float)(total + 1) - (float)0.65);
            float v2 = ((float)(cond2    )/(float)(total + 1) - (float)0.65);
            if(v1*v1 < v2*v2){
                params->ps1 += 1;     /* 1 ~ 8 */
            }else{
                params->ps2 += 1;     /* 2 ~ 8 */
            }
        }
        
        limit -= 1;
        if(total > params->tbit){
            limit = 0;
        }
        
    }

    
    if(ret == MCP2515_PARAM_OK){
        printf(" - SJW     %d tq\n",params->sjw);
        printf(" * SyncSeg %d tq\n",params->syncSeg);
        printf(" * PropSeg %d tq\n",params->propSeg);
        printf(" * PhSeg1  %d tq\n",params->ps1);
        printf(" * PhSeg2  %d tq\n",params->ps2);
        printf("   [pos] %f\n", (float)cond2/(float)total);
        printf("   total   %d tq\n", params->tbit);
    }else{
        printf("# avort!\n");
    }

    return ret;
}


/* 小数点以下 10億分の1まで表現する簡易デバッグ出力 */
void printDouble(double vald){
    int lp0 = 0;
    int lp1 = 0;
    int lp2 = 0;
    
    if(vald >= 1){
        printf("%d.",(int)vald);
    }else{
        printf("0.");
    }
    
    vald -= (double)((int)vald);
    
    if(vald < 1 && vald > 0){
        for(int lp1 = 1000000000; lp1 > 1; lp1 /= 1000){
            int tmpi = (int)(vald * lp1);
            if((int)(vald * lp1) < 1000 && (int)(vald * lp1) > 0){
                for(lp2 = lp1; lp2 > 1000; lp2 /= 10){
                    printf("0");
                }
                for(lp2 = 100; lp2 > 1; lp2 /= 10){
                    if(tmpi < lp2){
                        printf("0");
                    }
                }
                printf("%d", tmpi);
            }
        }
    }else{
        //printf("%f",vald);
        printf("0");
    }
}

起動ステップ3 ~動作開始~

MCP2515でCAN通信を行います。
動作変更は、CANCTRLレジスタ内の4bitを書き換えることでできます。
送受信をするのであれば、10.5節にあるように、MCP2515唯一のメッセージをCANバスに送り出せる、「通常モード」を選びます。
受信だけでよければ、「リスンオンリーモード」を選びます。

100 コンフィギュレーションモード
000 通常モード
011 リスンオンリーモード

他にもモードはありますが、本記事では説明を省略します。


CAN通信の応用設定

CANバスには大量のフレームが流れてきます。
SPI通信によるデータ取り出しには、通信時間という制約があるため、MCP2515の受信バッファ溢れを起こしてしまいます。
MCP2515の受信バッファは2フレーム分しかありません。

そこで、MCP2515には必要なID以外のフレームを読み捨てる機能があります。
この設定を活用することによって、バッファ溢れの可能性を最小限にすることができます。


次回は、動作確認に使えるライブラリを記したいと思います。