2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

最強マイコンPIC32MK MCMでCAN FD通信をする

Posted at

挨拶

マイコン使いの皆様、12月もマイコンで通信してますか?
今年(去年)もMDを燃やしてるロボット大好き限界博士学生woodロボです。
この記事はPICマイコン Advent Calendar 2025に投稿するために書いてます。PIC32MK MCMによるCAN FD通信がメイントピックですが、マイコンに関わらない話も出るので、ぜひCAN FD通信をする人は読んでね!

PIC32MK MCMが最強な理由

世の中には様々なマイコンがありますが、私はPIC信者としてPICマイコン(やその他のマイコン)を日々使用しています。マイコンの使用用途は多くありますが、特にロボット界隈だと基板やセンサと通信する用途は多いと思います。ここで、PIC32MK MCMのスペックを見てみましょう。PIC32MK公式サイトよりスペック表を持ってきました。

いくつかピックアップすると、こんな感じになります。(今回のメイントピックの)CAN FDが4個で、USBが2個はかなり多い構成ではないでしょうか。(欲言えば、High Speed USBがいい)
また、PIC32MK MCMはモータ制御向けのマイコンであり、ADCモジュールが7個も搭載されています。チャンネル数ではなくモジュール数なので、7個同時にAD変換ができます。例えばデュアルBLDCモータドライバ+汎用ADC(3*2+1)みたいな構成もできちゃうわけです。また、インクリメンタルエンコーダのABZ相をサポートするQEIモジュールも6個搭載されています。

  • CAN FDモジュール4個
  • Full Speed USBモジュール2個
  • 12bitADCモジュール7個
  • QEI(エンコーダ)モジュール6個

というわけで、君もPIC32MKを使おう!!!

pic32mk_spec_general.png

pic32mk_spec_motor.png

CAN FD通信って何?

では、今回のメイントピックとなるCAN FD通信について解説していきたいと思います。といっても、サニー技研さんの記事が最強なので、概要だけ解説して詳細は各自この記事を読んでください。CAN FDを解説するにはCANから解説しないといけません。これもサニー技研さんの記事が最強なので、・・・(以下略)。

というわけで、すーぱー雑解説をはじめます。CAN通信は差動の通信規格で、通信線はCANHとCANLの2本です。差動通信といえばRS485も有名ですが、RS485は信号の物理的な波形くらいしか定義していないのに対し、CANでは通信プロトコルも詳しく定義されています。そのため、CANに準拠したモジュールを使えば、通信時の面倒くさいあれこれ(アドレス、衝突、バッファ)を全部やってくれて超楽です。このCAN通信は車で多く使用されており、実際車をばらして解析してみると楽しいのでオススメです。

ロボット系限界博士学生なので、ロボット界隈にもふれてみましょう。近年では、BLDCモータを利用したサーボモータが特に中国メーカーから多く販売されており、CubeMars社のAKシリーズやRobStride Dynamics社のRobStrideが挙げられます。これらは、CAN通信で使用可能なサーボモータになります。これらのサーボモータはMITと呼ばれる、位置・速度・Pゲイン・Dゲイン・トルクを送るプロトコルに対応しており、位置速度を使用する強化学習、トルクを使用するモデルベース制御どちらとも相性が良いです。

では、CAN FDの話に入りましょう。CANは最大1Mbpsで最大8バイトまでの通信規格です。それに対してCAN FDではヘッダが最大1Mbps、データが最大8Mbpsで最大64バイトまで通信できます。通信速度も速くなるのですが、特に重要なのは通信容量が大きくなっているという事です。例えば、モータドライバで追加のセンサを搭載したり、Q軸とD軸の電圧電流情報を追加するとCANでは対応できませんが、CAN FDでは対応できます。

PIC32MK MCMでCAN FD通信をしよう

PIC32MK MCMでのCAN FD通信について解説する日本語ネット記事は全然ないです。公式ドキュメントと海外フォーラムを探す必要があり、マイコン初心者の方にはちょっと大変だと思うので、この記事を書いてます。(ドヤッ)

この記事では、MPLAB X IDE v6.25とMCC(の中のHarmony)を使って開発していきます。
PIC32マイコンの開発については、n年前に書いた同人誌(200ページ超え)で解説しているので、興味があればぜひ購入してみてください。

回路編

では、CAN FDの開発していこう!!!
まずは、必要な回路の準備です。今回はこんな構成で組んでみました。USB-CAN FD変換とCAN FDのデバイスみたいなよくある構成ですね。USB-CAN FD変換は公式のマイコンボードを用いて、ソフトウェアは†気合い†で実装しました。パソコンとの通信プロトコルはCOBSを用いています。

PC <- USBシリアル -> 公式マイコンボード <- CAN FD -> 自作ボード

自作ボードにはCAN FDのトランシーバとしてMCP2562FDを使用しました。マイコンから出る信号(CANTX, CANRX)は0V or 3.3Vの信号なので、それを差動信号に変換するために、CAN FDトランシーバを使用する必要があります。

original_canfd_schematic.png

通信相手には、公式マイコンボードのPIC32MK MCM CURIOSITY PRO DEVELOPMENT BOARDを使用しました。高級マイコンボードゆえに、マイコン以外の回路がたくさんのってます。USBコネクタが6個搭載されており、はじめに見た時はビックリしました。またCAN FDトランシーバが4個搭載されており、簡単にCAN FDの開発をはじめることができます。回路図は以下に示すものです。ESD Protectionが入ってて流石ですね。

officical_canfd_schematic.png

ソフトウェア編(GUI編)

MPLAB X IDE v6系では、Harmony 3という関数生成システムが、MCCに吸収されました。ですが、実際中身はほぼ同じです。MPLAB X IDEを開いてNew Projectを選び、PIC32MK MCMのプロジェクトを作ります。上記の同人誌を書いた頃は関数生成を使用するには、IDEをごにょごにょする必要があったのですが、最近は関数生成を使う事がわりとデフォルトになってきてるので、気が付いたら関数生成ツールが起動する感じになってます。

Harmonyを起動して、Clock Diagramを開きます。ここでは以下のように設定しました。

  • 今回製作した自作ボードには16MHzの外部振動子を搭載しているので、POSCをHSにして16MHzと入力
  • System PLLモジュールを使用してPOSCの16MHzからシステムクロックとして120MHzを生成
  • CAN FDモジュールに入力する周波数は決まっているので、Reference Clock4を用いて120MHzから40MHzを生成
  • USB PLLやSOSCは使用しない

harmony_clock.png

基本的には、よくある設定ですが、CAN FD用にReference Clockを用意するのが大きな違いとなります。というのも、PIC32MK MCMのデータシートのCAN FDモジュールの1ページ目には、モジュールのクロックに関するNoteが書かれていて、20,40,80MHzが推奨されています。このマイコンの最大周波数は120MHzなので、120MHzの状態でCAN FDモジュールを動作させるにはシステムクロックを分周して、推奨周波数を生成する必要があります。その例として細かく調整が可能なReference Clockが紹介されています。もちろん、システムの周波数が推奨周波数の場合は、Reference Clockを使用する必要はありません。

pic32mkmcm_datasheet_canfd.png

次にピンの設定を行います。PICマイコンにはPPSモジュールが搭載されており、ピン配置を柔軟に変更することができます。今回設計した基板にあわせて、1番ピンをC1RX、3番ピンをC1TXに設定しました。また、MCP2562FDには低消費電力モードに入るかを決定するSTBYピンがあるので、適当なピンをGPIO(OUT)として設定しました。なお、灰色になっている箇所はすでに使用されているピンを示しています。この記事は、CAN FDを対象としていますが、実際には他の処理もするためです。

harmony_pin.png

(メイントピックではないですが)delay関数を使うために、Device Resources→Libaries→Harmony→Peripherals→CORE TIMER→CORE TIMERを追加します。また、UARTでのprintをするために、Device Resources→Libaries→Harmony→Peripherals→UART→UART2を追加します。私の環境では以下のように設定しました。こちらもピン設定する必要があるので、環境にあわせてTXとRXを設定してください。

harmony_uart.png

では、CAN FDモジュールの設定を行います。IDEの左にあるDevice Resources→Libaries→Harmony→Peripherals→CAN→CAN1を追加します。そして以下のように設定しました。

まずBit Timingに関する設定です。ここではReference Clock4をクロックとして選択します。また、ヘッダを1Mbps、データを4Mbpsに設定します。パラメータが多くて難しいと思うのですが、1つ解説するとCANやCAN FDではTime Quantaという概念が重要となります。Time Quantaは通信のサンプリングで使用する1マスの事を意味しています。簡易的なシリアル通信のUARTでは通信で使用する周波数の4倍の周波数を使う事が多いですが、CANやCAN FDではそれよりも大きな周波数でサンプリングを行います。というのも、CANやCAN FDは1度の通信に使用するビット数が多く、リアルタイムでクロックのずれを修正する処理が必要です。そのため、細かくサンプリングして後で調整しているのです。

harmony_canfd1.png

次に、バッファやフィルタの設定です。バッファまわりの設定はそのままで、フィルタだけ変更を行いました。Filter IDは、どのIDのデータを受け取るか設定する箇所で、今回はID0のものを取得するように設定しました。Mask IDは上記のFilter IDのうちどのビットを有効とするかを決める箇所です。0x7F8は下位3ビットは何でも良いということを意味します。沼りポイントとしては、標準フォーマットで通信する場合、HarmonyのMask IDには、11ビット以下の値を入れるほうが良いです。適当にFFFFみたいな値を入れると想像と違う解釈をされるので気を付けましょう。(ソースコードを読んで気づいた)

harmony_canfd2.png

ここまで来たらGenerateボタンを押して、GUI設定編終了です。
C++が良い人は、System→Device & Project Configuration→Project Configuration→Generate C++ Projectをチェックすると、生成されるmainファイルがC++になります。

ソフトウェア編(コード編)

コードはMicrochipが出しているサンプルを参考にしています。ただ、このサンプルはけっこう難しいので、以下に示すコードが簡単で分かりやすいと思います。

このコードは、1000ms間隔で64バイトのCAN FDメッセージを送信、受信したCAN FDメッセージをUART2に出力するものです。CAN1_MessageReceive関数はぱっと見、この関数を読んだ瞬間にバッファから取り出しそうな感じがしますが、実際は「この変数に入れといて」的な指示を出す関数です。

main.c
#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include "definitions.h"                // SYS function prototypes
#include <stdio.h>

const uint8_t CAN_FIFO_TX_NUM = 1;
const uint8_t CAN_FIFO_RX_NUM = 2;
const uint8_t CAN_RX_RECEIVE_NOT_COMPLETE_LENGTH = 0xff;
uint32_t can_rx_id = 0;
uint8_t can_rx_length = CAN_RX_RECEIVE_NOT_COMPLETE_LENGTH;
uint8_t can_rx_buf[64];
uint32_t can_rx_timestamp = 0;
CANFD_MSG_RX_ATTRIBUTE can_rx_attr = CANFD_MSG_RX_DATA_FRAME;

void canTransmitCallback(uintptr_t context){
    char str[128];
    uint32_t status = CAN1_ErrorGet();

    if ((status & (CANFD_ERROR_TX_RX_WARNING_STATE | CANFD_ERROR_RX_WARNING_STATE |
                   CANFD_ERROR_TX_WARNING_STATE | CANFD_ERROR_RX_BUS_PASSIVE_STATE |
                   CANFD_ERROR_TX_BUS_PASSIVE_STATE | CANFD_ERROR_TX_BUS_OFF_STATE)) == CANFD_ERROR_NONE){
    }else{
        sprintf(str, "[CAN TX]error\r\n");
        UART2_Write((uint8_t*)str, strlen(str));
    }
}

void canReceiveCallback(uintptr_t context){
    char str[128];
    uint32_t status = CAN1_ErrorGet();

    if ((status & (CANFD_ERROR_TX_RX_WARNING_STATE | CANFD_ERROR_RX_WARNING_STATE |
                   CANFD_ERROR_TX_WARNING_STATE | CANFD_ERROR_RX_BUS_PASSIVE_STATE |
                   CANFD_ERROR_TX_BUS_PASSIVE_STATE | CANFD_ERROR_TX_BUS_OFF_STATE)) == CANFD_ERROR_NONE){
        sprintf(str, "[CAN RX]id:%d len:%d data:", can_rx_id, can_rx_length);
        UART2_Write((uint8_t*)str, strlen(str));
        for(int i=0;i<can_rx_length;i++){
            sprintf(str, "%02x ", can_rx_buf[i]);
            UART2_Write((uint8_t*)str, strlen(str));
        }
        sprintf(str, "\r\n");
        UART2_Write((uint8_t*)str, strlen(str));
    }else{
        sprintf(str, "[CAN RX]error\r\n");
        UART2_Write((uint8_t*)str, strlen(str));
    }
    can_rx_length = CAN_RX_RECEIVE_NOT_COMPLETE_LENGTH;
    CAN1_MessageReceive(&can_rx_id, &can_rx_length, can_rx_buf, &can_rx_timestamp, CAN_FIFO_RX_NUM, &can_rx_attr);
}

int main ( void )
{
    /* Initialize all modules */
    CFD1TDCbits.TDCMOD = 0;
    CFD1TDCbits.TDCO = 0;
    CFD1TDCbits.TDCV = 0;
    SYS_Initialize ( NULL );
    
    CAN1_CallbackRegister(canTransmitCallback, NULL, CAN_FIFO_TX_NUM);
    CAN1_CallbackRegister(canReceiveCallback, NULL, CAN_FIFO_RX_NUM);
    CAN1_MessageReceive(&can_rx_id, &can_rx_length, can_rx_buf, &can_rx_timestamp, CAN_FIFO_RX_NUM, &can_rx_attr);
    GPIO_PinWrite(CAN_STBY_PIN, false);

    while ( true )
    {
        /* Maintain state machines of all polled MPLAB Harmony modules. */
        SYS_Tasks ( );
        uint8_t data[64];
        uint8_t length = 64;
        for(int i=0;i<length;i++){
            data[i] = i;
        }
        CAN1_MessageTransmit(0x08, length, data, CAN_FIFO_TX_NUM, CANFD_MODE_FD_WITH_BRS, CANFD_MSG_TX_DATA_FRAME);
        CORETIMER_DelayMs(1000);
        
        char str[128];
        sprintf(str, "[loop]hello\r\n");
        UART2_Write((uint8_t*)str, strlen(str));
    }

    /* Execution should not come here during normal operation */

    return ( EXIT_FAILURE );
}

注意ポイントとしては、SYS_Initialize関数を呼ぶ前に、CFD1TDCレジスタに書き込んでいます。これは、海外フォーラムでも議論されているのですが、CANトランシーバの遅延特性に関連する設定です。CANコントローラはCAN RXを常に監視しており、自身が送信するCAN TXと受信するCAN RXを比較し、値が違うとエラー判定をおこしてしまいます。
MCP2562FDのデータシートによると、送信してから受信するまでの遅延(tTXD-RXD)は120nsほどかかるようです。この値は1Mbpsであればなんとかなりますが、高速域ではエラーになる可能性があります。これを補正するためにマイコン側でオフセットをかけてあげる機能があり、それをCFD1TDCレジスタで制御できます。デフォルトでは少し補正するようになっているのですが、それがMCP2562FDの特性とあっておらず逆に邪魔になるので、ここでは補正を切りました。
ご参考までに、私の環境で実験したところ2Mbpsではデフォルト設定で成功しました。4Mbpsでは、デフォルト設定では失敗、機能オフで成功するようになりました。8Mbpsでは補正を調整しないと通信に失敗しました。

mcp2562fd_datasheet.png

終わりに

PIC32MKマイコンの良さと、CANと比較したCAN FDの良さについて解説しました。また、PIC32MK MCMによるCAN FDについて解説し、多くの沼ポイントがあったのでまとめておきます。

  • CAN FDモジュールに供給するクロックは20,40,80MHzである必要がある
  • HarmomyのMask ID欄は標準フォーマットでは11ビット以下の値をいれる
  • CAN1_MessageReceive関数は、受け取った値を入れる変数を指示する関数
  • 高速域ではCFD1TDCレジスタの設定が必要

これを見た方が良きマイコン通信ライフを送れることを祈っています!
また次の記事でお会いしましょう、アディオス!!!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?