PIC18F26K83とTeensy3.5でCAN通信をする

はじめに

この記事はNITKC ProLab Advent Calendar 2020の15日目の記事です。

概要

Teensy3.5からPIC18F26K83にCAN通信を使ってデータを送信して、PICからPWMでLEDを点灯させました。

未来の自分のために、備忘録的になってます。

使用したソースコードは、Teensy3.5とPIC18F26K83共にGoogle Driveに置いています。

注意

今回使用した素子については、このページだけではなくデータシート等も絶対に参照してください。

やりたかったこと

Teensy3.5からPIC18F26K83にCAN通信(速度1Mbps)でデータを送信して、PIC18F26K83から2個のLEDを個別に光らせたい。

ソフトウェア環境

(ここから先、以上のソフトウェア環境を満足している前提で解説します。)

ハードウェア環境*1

  • Teensy3.5
  • PIC18F26K83-I/SP
  • PICkit4(リンクはPICkit4)
  • CANトランシーバー:MCP2562FD-E/P×2
  • CANトランシーバーの電源作成用:TA4805S
  • CANバスの終端抵抗用120Ω抵抗×2
  • 任意の回路作成用の道具(ブレッドボード、基板、ジャンパ線など)

PIC側の操作

プロジェクトの作成は省略

MCC側での初期設定

[System Module]->[Easy Setup]タブを選択して、クロック周波数とウォッチドッグタイマ、低電圧プログラミングの有効/無効について設定します。

ここで設定したクロック周波数が一定以下だと、1MbpsでのCAN通信ができない場合があります。

例として、自分の場合の設定を以下に書いておきます。

  • INTERNAL OSCILLATOR
    • Oscillator Select: HFINTOSC
    • HF Internal Clock: 32_MHz
    • Clock Divider: 1
  • WWDT
    • Watchdog Timer Enable: WDT Disabled; SWDTEN is ignored
  • Programming
    • Low-Voltage Programming Enable: Disable

また、MCLRを無効化したいならLow-Voltage Programingを無効化した上で[Registers]タブから以下のように設定します。

  • Register:CONFIG2L
    • MCLRE: If LVP=0, MCLR pin function is port defined function; If LVP=1, RE3 pin function is MCLR

ECANモジュールの有効化

左にある[Device Resources]から、[Peripherals]->[CAN]->[ECAN]の隣にある"+"アイコンをクリックして、ECANモジュールを追加します。

f:id:Wisteliabook:20201216001058p:plain

その後、[System Module]タブと同じところに[ECAN]タブが開くので、そこからCAN通信の設定を行います。

今回は - 1Mbps - 受信のみ - 個別に光らせることができればいいので2種類のIDのメッセージのみを受信したい

の3つの要件が存在するので、以下の画像のように設定しました。

f:id:Wisteliabook:20201216001314p:plain f:id:Wisteliabook:20201216001416p:plain

Vector割り込みの設定

[Interrupt Module]タブから、Vectored Interrupt EnableをTrueにして割り込みベクタを有効化します。

PWMの有効化

ECANモジュールと同様に[Device Resources]から、[Peripherals]->[PWM]->[PWM5],[PWM6]と[Peripherals]->[Timer]->[TMR2]を選択します。

[TMR2]タブを開いて、以下の設定を必ず行います。 - Hardware Settings - Enable Timer: True - Timer Clock - Clock Source: FOSC/4

その後、Prescaler,PostScalerなどの設定を適当に行います。以下は設定の一例です。

f:id:Wisteliabook:20201216001609p:plain

[PWM5],[PWM6]は共通で、Enable PWMをTrueにしてSelect a TimerをTimer2にします。

それ以外の設定は自分で実装するプログラム側から簡単に変えられるので特に変える必要はないですが、こだわりがある人は変えておきます。

ピンの割り当て

画面下部にある[Pin Manager: Grid View]から、それぞれのピンの割り当て(CANRX,CANTX0,PWM5,PWM6)を他のピン設定に被らないように設定します。

f:id:Wisteliabook:20201216001729p:plain

コードの自動生成

左の[Project Resources]から[Generate]を選択して、コードを自動生成します。

生成されたコードはプロジェクトの

Header Files/MCC Generated Files

Source Files/MCC Generated Files

に保存されています。

余談:コードの修正

Header Files/MCC Generated Files/ecan.hには、以下のような共用体が定義されています。

typedef union {

    struct {
        uint8_t idType;
        uint32_t id;
        uint8_t dlc;
        uint8_t data0;
        uint8_t data1;
        uint8_t data2;
        uint8_t data3;
        uint8_t data4;
        uint8_t data5;
        uint8_t data6;
        uint8_t data7;
    } frame;
    uint8_t array[14];
} uCAN_MSG;

このままでもちろん使えますが、個人的に死ぬほど気に入らないので以下のように修正します。

typedef union {

    struct {
        uint8_t idType;
        uint32_t id;
        uint8_t dlc;
        uint8_t data[8];
    } frame;
    uint8_t array[14];
} uCAN_MSG;

これに伴って、Source Files/MCC Generated Files/ecan.c内のdatanを参照している箇所をdata[n]に書き換えます。

以下の解説は、この書き換えた状態で行うので書き換えなかった方は適宜読み替えてください。

main.c

ECAN割り込み関数の作成

Header Files/MCC Generated Files/ecan.hに宣言されている

void ECAN_SetWatermarkInterruptHandler(void (*handler)(void))

を利用して、割り込み関数を登録します。

割り込み関数内で受信データを読み取ることもできますが、今回は割り込みフラグを立てるだけとします。コードは以下の通りです。

volatile unsigned char CANInterrupt = 0;

void ISRHandler(void){
    PIE5bits.FIFOWMIE = 0;
    CANInterrupt = 1;
    PIE5bits.FIFOWMIE = 1;
}

セットアップ部

セットアップ部ではシステムの初期化と割り込みの有効化を行います。

void main(void){
    // システムの初期化、Header Files/MCC Generated Files/mcc.hで宣言
    SYSTEM_Initialize();
    
    // ECAN割り込み関数の登録
    ECAN_SetWatermarkInterruptHandler(ISRHandler);

    // 全体の割り込みの有効化
    INTCON0bits.GIEH = 1;
    INTCON0bits.GIEL = 1;

    while (1){
       // ルーチン部
    }
}

ルーチン部

ルーチン部では、ECANの割り込みがあったときのみCANバスからデータを取得してそのIDとデータを見てPWM5と6の値を変えるようにします。

while (1){
        if(CANInterrupt == 1){
            uCAN_MSG msg;
            CANInterrupt = 0;
            // CANバスからデータを取得、Header Files/MCC Generated Files/ecan.hで宣言
            CAN_receive(&msg);
            uint16_t dutyValue = ((uint16_t)msg.frame.data[0]) << 8 | msg.frame.data[1];
            if((msg.frame.id) == 0x10){
                // PWM5のduty比の変更、Header Files/MCC Generated Files/pwm5.hで宣言
                PWM5_LoadDutyValue(dutyValue);
            }
            else if((msg.frame.id) == 0x11){
                // PWM5_LoadDutyValueと同様
                PWM6_LoadDutyValue(dutyValue);
            }
        }
}

これでPIC側のプログラムは終了です。

Teensy側の操作

ファイル作成は省略。

Teensyduinoに組み込まれているFlexCANというライブラリを使います。 こちらはライブラリにあるサンプルをコピペして改変しただけのものなのでかなり雑に書いてあります。

グローバル部

FlexCANのinclude、CANメッセージ構造体とduty比格納用の変数の定義を行っています。

#include <FlexCAN.h>


CAN_message_t Msg[2];
uint16_t PWMA = 0;
uint16_t PWMB = 0;

セットアップ部

CAN通信と送信データ値操作用のシリアル通信の有効化、2つのメッセージの共通部分の固定を行っています。

void setup(void){
    delay(1000);
    Serial.begin(115200);

    Can0.begin(1000000);
    Msg[0].ext = 0;
    Msg[0].id = 0x10;
    Msg[0].len = 2;
    Msg[1].ext = 0;
    Msg[1].id = 0x11;
    Msg[1].len = 2;
}

ルーチン部

PCとのシリアル通信で受信した値によって送信データの値を操作して、CAN通信で送信しています。

void loop(void)
{
    while(Serial.available() > 0){
        byte data = Serial.read();
        switch(data){
            case 'A':
                PWMA+=50;
                break;
            case 'a':
                PWMA-=50;
                break;
            case 'B':
                PWMB+=50;
                break;
            case 'b':
                PWMB-=50;
                break;
            default:
                break;
        }
    }
    PWMA%=1024;
    PWMB%=1024;
    Msg[0].buf[0] = (PWMA >> 8);
    Msg[0].buf[1] = (PWMA);
    Msg[1].buf[0] = (PWMB >> 8);
    Msg[1].buf[1] = (PWMB);
    Serial.print(((uint16_t)Msg[0].buf[0])<<8 | Msg[0].buf[1]);Serial.print(',');
    Serial.print(((uint16_t)Msg[1].buf[0])<<8 | Msg[1].buf[1]);Serial.println(',');
    Can0.write(Msg[0]);
    Can0.write(Msg[1]);
}

これでTeensy側のプログラムは終了です。

回路の接続

MCP2562とTeensy3.5、PIC18F26K83のデータシートを参照しながら回路をつなぎます。

このとき、VIOピンにはそれぞれのVss(3.3V)を、STBYピンはGNDに繋ぐことに注意してください。

また、CANバスには終端抵抗として120Ω抵抗をバスの両端に 繋いでください。

f:id:Wisteliabook:20201216002456j:plain

ここまで出来たら、実際にプログラムを動かしてみます。

恐らくここまでで筆者か読者のどちらかに致命的なミスが無ければ動くはずです。

ここからの発展

  • PICからTeensyへの送信
  • 自動生成された関数のリファクタ
  • 関数のオーバーヘッドをなくすために1から自分で書く

参考文献

*1:PC本体、USBケーブルは除く

いろんなものを短めに布教をするだけの記事

はじめに

この記事はNITKC (Pro|Fav)lab GOTTANI Advent Calendar 201921日目の記事です。

続きを読む

人生で初めてJavascriptをさわった話

はじめに

 この記事には、Javascriptの環境構築とか基本文法とかは一切書いていません。ついでにこの構文が何なのか分らないようなものもあります。
 ただただ筆者が遊ぶために見様見真似でコードを書き、それをふとTwitterMastodonのあるインスタンスに投下した結果コードゴルフのような何かをされた結果を書いたものです。ご了承ください。

続きを読む