まずCubeでCanRxMsgTypeDefのキューを作る。今回はCAN1_FIFO0_RX_queueという名前で、osMessageQId CAN1_FIFO0_RX_queueHandleという変数経由でアクセスする。これはQueueHandle_tのtypedefで、QueueHandle_tはvoid*のtypedefになっている。ポインタのtypedefなので、ポインタ渡しの関数は直接渡す。
最初にhcan1.pRxMsgに適当なポインタを設定しておく。
static CanRxMsgTypeDef RX_MSG = { 0 }; hcan1.pRxMsg = &RX_MSG;
割り込みハンドラ(CAN1_RX0_IRQHandler)でHAL_CAN_IRQHandlerが呼ばれる前に、以下の処理を行う。
if (hcan1.pRxMsg) { hcan1.pRxMsg->StdId = 0x80000000; hcan1.pRxMsg->ExtId = 0x80000000; }
コールバック(HAL_CAN_RxCpltCallback)で以下の処理を行う。
if (hcan->Instance == CAN1) { if (hcan->pRxMsg && (!(hcan->pRxMsg->StdId & 0x80000000) || !(hcan->pRxMsg->ExtId & 0x80000000))) { BaseType_t higher_priority_task_woken = pdFALSE; xQueueSendFromISR(CAN1_FIFO0_RX_queueHandle, hcan->pRxMsg, &higher_priority_task_woken); __HAL_CAN_ENABLE_IT(hcan, CAN_IT_FOV0 | CAN_IT_FMP0); portEND_SWITCHING_ISR(higher_priority_task_woken); } }
コールバック関数ではどのFIFOから読み出したかを判断できないので、FIFO0とFIFO1を併用する場合はこのような手段で判断する必要がある(もっとスマートな方法がありそうな気がするけど)。
Idは32bit符号なしの変数で、実際に使われるのは11bitか29bitなので、最上位ビットが1になることはない。ということから、あらかじめ最上位ビットをセットしておき、これがクリアされたら新しい値が設定された、と判断している。Stdを受信した場合はExtが書き換わらず、Extで受信した場合はStdが書き換わらないので、両方をチェックする必要がある。!(Std & Ext & 0x8000000)でも良いかもしれないが、未確認。
コールバックの中でキューに送信し、キューに入れたあとで再び割り込みを有効化し、最後にタスクスイッチを呼んで終了する。
HALの割り込み処理は、一旦割り込みがかかると以降の割り込みを停止してしまう。そのため自分で再び割り込みを有効化する必要がある。
これは、メッセージを保管する場所が1つしか無いため、メインループ側でメッセージを取り出す前に新しい割り込みがかかると、古いメッセージが破壊されてしまうため、このような実装になっていると思われる。
実際に使う時は、HAL_CAN_Receive_IT(&hcan1, CAN_FIFO0);で割り込みを開始する。
あとはメッセージを受信すればキューに転送されるので、適当なタスクでキューをチェックするなりすれば良い。
HALの処理はコード数が長く、またQueueの転送もかなり大きな範囲を転送する。ということで、CANx_RXy_IRQHandlerが呼ばれると、かなりの期間のリソースを専有してしまう。CANは3段FIFOがあるので、CAN RXの割り込み優先度は若干低めに設定し、タイミングクリティカルな割り込みを妨害しないように注意する必要がある。
IRQHandlerの最初と最後でGPIOをトグルした所、8usec程度のパルスが出力された。168MHzなので、1.2kクロックくらいか。意外と短い。けどまあ、割り込みハンドラの中でやる処理としては長い方だと思うので、その点は覚えておくこと。
***
ということで、やっとCANの送信、フィルタ、受信割り込みが最低限使えるようになってきた。月曜から初めた気がするから、1週間丸々使ったのか。何か良い応用先探しておかないとなぁ。
0 件のコメント:
コメントを投稿