WioTerminalでCAN通信~通信設定編~
本記事は、WioTerminal(ATSAMD51)でCAN通信をするために、MCP2515に送るSPIコマンドについて記します。
前回の記事で、WioTerminalとCANコントローラ"MCP2515"を接続する回路について記しました。
準備
実験に必要な機材、ソフトウェアを示します。
準備
実験に必要な機材、ソフトウェアを示します。
Hardware
- WioTerminal / Seeed
- CANBus Unit for M5Stack / M5Stack
- MCP2515 / Microchip
- ブレッドボードやユニバーサル基板
- 水晶発振子(16MHz or 20MHz)
- 15pF コンデンサ (積層セラミックコンデンサ15pF)2個
- 10kΩカーボン抵抗
- ジャンパワイヤ 11本以上
- USB CAN Module / InnoMaker (検証用)
- Windows PC
Library
- 学習のために自作したため、無し
MCP2515の紹介
- MCP2515は、一般的なマイコンに搭載さているシリアル通信である「SPI(Serial Peripheral Interface)」によってデータを制御し、CAN通信を実現(フレーム変換)するためのCANコントローラです。
Step 3 MCP2515用ライブラリ作成
なによりも先にやることは、データシート(ww1.microchip.com/downloads/jp/DeviceDoc)を眺めることです。
MCP2515との通信方法
データシートの冒頭に帯で書かれている通り、MCP2515はSPI通信で命令を発行し制御します。
MCP2515のSPI通信のフォーマットは、12章に書かれています。
1命令を発行する基本動作は、
- CSピンをLowにする
- CSピンは、通信相手を選択するための信号線です(対象 or 対象外の2択)
- 命令発行時以外はHIGHにしておきます
- クロックを発行しながら、命令データを送りつける
- 命令バイトは必ず1つ発行します
- 命令に続いて付随情報を流す場合は、続けて送ります
- クロックを発行しながら、応答データを返させる
- 応答データが無いときは、本手順をスキップします
- 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
データシートの話をまとめると、
- PS2 > SJW
- PropSeg + PS1 >= Tdelay
- Tdelayは1TQとすればよい
- 0.6 <= (t_SyncSeg +t_PropSeg + t_PS1) / t_bit <= 0.7
上記を満たすように、各パラメタを設定すればよいです。
よって、
- 水晶発振子を選択する
- CAN通信速度を選択する
- TQを次の条件を満たすように決定する
- BRPの最小は1
- TQ = 2×BRP×T_OSC
- TQ × n = 1/NBR
- 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以外のフレームを読み捨てる機能があります。
この設定を活用することによって、バッファ溢れの可能性を最小限にすることができます。
次回は、動作確認に使えるライブラリを記したいと思います。