STM32のCANはボーレートの指定がちょっと面倒。USARTのように直接指定することができない。そしてCAN独特のパラメータにより、ボーレートの調整が面倒。
ペリフェラルの初期設定はあまりいじるところはないが、今回はループバックモードにしている。これにより、CANドライバ(シングルエンドと平衡接続の変換を行う、典型的に8pinのIC)が無くても動作できる。またCANのポートをフロートにした場合、RXがドミナントに固定されて問題が発生する(ISRが呼ばれ続ける?)場合があるので、CAN_RXをプルアップに変更している。
プリスケーラは8、TQ1が15、TQ2が5で250kbaudに設定している。
まずフィルタの初期設定を行う。
今回はフィルタの動作確認はしないので、とりあえず受信したデータをすべて受け取るというモード。
CAN_FilterConfTypeDef filter = { 0 };
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
filter.FilterNumber = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &filter);
フィルタモードがマスクの場合、フィルタが1のビットが一致したIdだけを受信する。今回の設定ではすべてのビットが0なので、すべてのビットに対してチェックを行わず、結果的にすべてのIdを受信することになる。
STM32F1でCANを試した時に、フィルタの挙動がよくわからなくて、結局すべてを受信するようにしてソフトウェアでフィルタリングしたような気がする。。。
CANの送信と受信の処理。
とりあえずUSARTからchar str[]バッファに1行分読み込まれ、それを解析して処理する、という流れになっている。
"can (arg)"コマンドでメッセージを送信し、"canrx[0-1]"でFIFO0かFIFO1からメッセージを受信する。
// 追記(2018/03/02)以下のコードにはバグが有るので最後の追記を読むこと
if (!strncmp(str, "can ", 4))
{
int flag = 1;
uint32_t id = 0;
char id_type = 'S';
char MSG_type = 'd';
uint32_t data_length = 0;
uint8_t data[8];
int parse_count = 0;
if (flag)
{
parse_count = sscanf(str + 4, "%lu %c %c %lu %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx", &id, &id_type, &MSG_type,
&data_length, &data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7]);
}
uint32_t IDE = CAN_ID_STD;
uint32_t RTR = CAN_RTR_DATA;
if (flag)
{
// parse argument
switch (id_type)
{
case 's':
case 'S':
IDE = CAN_ID_STD;
break;
case 'e':
case 'E':
IDE = CAN_ID_EXT;
break;
default:
flag = 0;
break;
}
switch (MSG_type)
{
case 'd':
case 'D':
RTR = CAN_RTR_DATA;
break;
case 'r':
case 'R':
RTR = CAN_RTR_REMOTE;
break;
default:
flag = 0;
break;
}
flag = flag && (parse_count == 4 + (RTR ? CAN_RTR_DATA == (int)data_length : 0));
flag = flag && IS_CAN_DLC(data_length);
flag = flag && IDE == CAN_ID_STD ? IS_CAN_STDID(id) : IS_CAN_EXTID(id);
}
CanTxMsgTypeDef TX_MSG = { 0 };
if (flag)
{
// set TX_MSG
if (IDE == CAN_ID_STD)
{
TX_MSG.StdId = id;
}
else
{
TX_MSG.ExtId = id;
}
TX_MSG.IDE = IDE;
TX_MSG.RTR = RTR;
TX_MSG.DLC = data_length;
for (uint32_t i = 0; i < data_length; i++)
{
TX_MSG.Data[i] = data[i];
}
}
if (flag)
{
// HAL_CAN_Transmit
hcan1.pTxMsg = &TX_MSG;
HAL_StatusTypeDef status = HAL_CAN_Transmit(&hcan1, 100);
hcan1.pTxMsg = 0;
flag = status == HAL_OK;
}
printf("%s\n", flag ? "ok" : "error");
}
if (!strncmp(str, "canrx", 5))
{
int flag = 1;
int FIFO_index = 0;
if (flag)
{
flag = 1 == sscanf(str + 5, "%d", &FIFO_index);
}
uint8_t FIFO_number = CAN_FIFO0;
CanRxMsgTypeDef RX_MSG = { 0 };
CAN_HandleTypeDef *CAN_handle = &hcan1;
if (flag)
{
switch (FIFO_index)
{
case 0:
FIFO_number = CAN_FIFO0;
CAN_handle->pRxMsg = &RX_MSG;
break;
case 1:
FIFO_number = CAN_FIFO1;
CAN_handle->pRx1Msg = &RX_MSG;
break;
default:
flag = 0;
break;
}
}
if (flag)
{
HAL_StatusTypeDef status = HAL_CAN_Receive(CAN_handle, FIFO_number, 10);
flag = status == HAL_OK;
}
switch (FIFO_index)
{
case 0:
CAN_handle->pRxMsg = 0;
break;
case 1:
CAN_handle->pRx1Msg = 0;
break;
}
printf("%s\n", flag ? "ok" : "error");
if (flag)
{
printf(" ID: %0*lX (%s)\n", RX_MSG.IDE == CAN_ID_STD ? 3 : 8, RX_MSG.IDE == CAN_ID_STD ? RX_MSG.StdId : RX_MSG.ExtId,
RX_MSG.IDE == CAN_ID_STD ? "STD" : "EXT");
printf(" RTR: %s\n", RX_MSG.RTR == CAN_RTR_DATA ? "DATA" : "REMOTE");
printf(" DLC: %lu\n", RX_MSG.DLC);
printf(" DATA: ");
if (RX_MSG.RTR == CAN_RTR_REMOTE)
{
printf("(null)");
}
else
{
for (uint32_t i = 0; i < RX_MSG.DLC; i++)
{
printf("%02hhX ", RX_MSG.Data[i]);
}
}
printf("\n");
printf(" FMI: %lu\n", RX_MSG.FMI);
printf(" FIFO: %lu\n", RX_MSG.FIFONumber);
}
}
/* コードフォーマットにAstyleを使ってるけど、これってブロック開始行にコメントを配置できないのね。改行されてしまうとブロックを畳んだ時にコメントが表示されない。。。 */
試しに
canrx0 can 1 s d 8 12 23 34 45 56 67 78 89 can 1 e r 4 canrx0 canrx0 canrx0というコマンドを送ると、
"canrx0"
error
"can 1 s d 8 12 23 34 45 56 67 78 89"
ok
"can 1 e r 4"
ok
"canrx0"
ok
ID: 001 (STD)
RTR: DATA
DLC: 8
DATA: 12 23 34 45 56 67 78 89
FMI: 0
FIFO: 0
"canrx0"
ok
ID: 00000001 (EXT)
RTR: REMOTE
DLC: 4
DATA: (null)
FMI: 0
FIFO: 0
"canrx0"
error
という結果が得られる。コマンドの内容は、最初に1回受信を行い、次にStd DATAを送信、続いてExt REMOTEを送信し、最後に3回受信を行う、という感じ。
最初に受信を行った最はFIFOにデータが無いので、エラーとなる。
データを送る際はokかerrorが表示される。
最後に受信を行った際は、Std DATAとExt REMOTEが表示され、3回目の受信はデータが無いのでerrorとなる。
HAL_CAN_Receiveは内部でFIFOの高さを確認しているので、いきなり呼んでも無意味なデータが取り出されてしまう、という事はない。
この時、CAN TXは以下のような感じになっている。
今回はマイコン1個で試しているので、誰もACKを返していない。
ZEROPLUSのパケット表示は、なんとなく見づらい気がする。CANのプロトコルに忠実に実装しているんだろうが、IDが29bitひとつづきじゃなかったりとか、その辺が、あくまでロジックアナライザなんだな、という感じ。
本来、だれもACKを返さない場合はずーっとデータを送信し続けるはずなんだが、そうはなっていない。ループバックモードではACKビットをサンプリングしないそうなので、その関係かも。
// せっかくCANが簡単に扱えそうだし、何か良い使い道ないかなー。3バイトのアドレッシング+8バイトのデータで極めて信頼性が要求される用途かぁ。何か有るかなぁ。。
追記:2018/03/02
上記のコード、CANの送信コマンドにバグが有る。一部を以下のように変更すること。
flag = flag && (parse_count == 4 + (RTR == CAN_RTR_DATA ? (int)data_length : 0)); flag = flag && (IS_CAN_DLC(data_length)); flag = flag && (IDE == CAN_ID_STD ? IS_CAN_STDID(id) : IS_CAN_EXTID(id));
バグの問題としては、&&の優先度を見誤っていた点、三項演算子を間違っていた点、の2つ。
// どーしてCには論理演算代入演算子が無いのか





















