STM32 + レジスタをたたいてCAN通信を実装する その4 送受信処理の実装
こんにちは。LLにCANが存在しない→レジスタ打てばいいじゃないの4回目です。今回は送受信の関数の実装をしていきます。終わりが見えてきました。
↑前回の記事
事前準備編
データシートの説明を読む
送信処理関連
送信処理に書いてある通りに設定をしていくことで送信処理ができることがわかりました。CANには識別子によるバス内での送信順位の設定などがあるようです。詳しくはCANのプロトコルの通信規格(ISO 11898)を確認していただければと思います。図391の通信の流れを見ながら実装をしていきます。
受信処理関連
ありがたいことに、FIFOの管理は全てハードウェアでやってくれるそうです。今回の実装では受信割り込みを使用するので、受信割り込みについての記載を確認すると、メッセージがFIFOに格納されると割り込みリクエストが生成されるようです。
今回のCAN通信の実験ではバスに接続されたマイコンが2つであり、一対一の通信形式かつ一定周期でデータのやり取りを実装したためFIFOが満杯になることがないと検討したため、CAN_IERレジスタのFMPIEビットをセットして割り込み処理を行うこととしました。
バス上に複数のマイコンなどが接続される場合は、オーバーランやFIFOが満杯になったタイミングでの処理を入れた方がいいと思います。
CANプロトコルでは、I2Cのようにメッセージの識別子はノードのアドレスに関連付けられていないようです。代わりに受信ノード側でメッセージのヘッダ情報をもとにフィルタリングを行うそうです。フィルタリングもハードウェアでやってくれるそうです。前回設定したフィルタの初期化は受信に必要なものだということがわかりました。
詳しくは、データシートやプロトコルの通信規格(ISO 11898)を確認していただければと思います。
実装編
送受信用の構造体の用意
HALと同じように使えるように、HALと同じデータの構造体を作ります。(can_driver.hに追記)
typedef struct {
uint32_t StdId;
uint32_t ExtId;
uint32_t IDE;
uint32_t RTR;
uint32_t DLC;
FunctionalState TransmitGlobalTime;
} CAN_TxHeaderTypeDef;
typedef struct {
uint32_t StdId;
uint32_t ExtId;
uint32_t IDE;
uint32_t RTR;
uint32_t DLC;
uint32_t Timestamp;
uint32_t FilterMatchIndex;
} CAN_RxHeaderTypeDef;
#define CAN_RTR_DATA 0x00U // Data frame
#define CAN_RTR_REMOTE 0x02U // Remote frame
#define CAN_ID_STD 0x00U // Standard Id
#define CAN_ID_EXT 0x04U // Extended Id
#define CAN_RXFIFO0 0x00 // specification RXFIFO0
#define CAN_RXFIFO1 0x01 // specification RXFIFO1
uint32_t CAN_GetTxMailboxesFreeLevel(CAN_TypeDef* CANx);
int8_t CAN_AddTxMessage(CAN_TypeDef* CANx, CAN_TxHeaderTypeDef* txHeader,
uint8_t* tx_data, uint32_t* txMailbox);
int8_t CAN_GetRxMessage(CAN_TypeDef* CANx, uint32_t RXFIFO,
CAN_RxHeaderTypeDef* rxHeader, uint8_t* rx_data);
送信(割り込みなし)
uint32_t CAN_GetTxMailboxesFreeLevel(CAN_TypeDef* CANx) { uint32_t freelevel = 0; if ((CANx->TSR & CAN_TSR_TME0) != 0U) freelevel++; if ((CANx->TSR & CAN_TSR_TME1) != 0U) freelevel++; if ((CANx->TSR & CAN_TSR_TME2) != 0U) freelevel++;} return freelevel; } int8_t CAN_AddTxMessage(CAN_TypeDef* CANx, CAN_TxHeaderTypeDef* txHeader, uint8_t* tx_data, uint32_t* txMailbox) { uint32_t transmitmailbox; uint32_t tsr; tsr = READ_REG(CANx->TSR); if (((tsr & CAN_TSR_TME0) != 0U) || ((tsr & CAN_TSR_TME1) != 0U) ||((tsr & CAN_TSR_TME2) != 0U)) { transmitmailbox = (tsr & CAN_TSR_CODE) >> CAN_TSR_CODE_Pos; // データボックスに2つ以上データが入っていた場合はエラーを返す if (transmitmailbox > 2U) return -1; // メールボックスのデータを保存 *txMailbox = (uint32_t)1 << transmitmailbox; if (txHeader->IDE == CAN_ID_STD) { CANx->sTxMailBox[transmitmailbox].TIR =((txHeader->StdId << CAN_TI0R_STID_Pos) | txHeader->RTR); } else { CANx->sTxMailBox[transmitmailbox].TIR =((txHeader->ExtId << CAN_TI0R_EXID_Pos) | txHeader->IDE |txHeader->RTR); }
// データの長さを設定 CANx->sTxMailBox[transmitmailbox].TDTR = (txHeader->DLC); // TransmitGlobalTimeが有効の場合は設定をする if (txHeader->TransmitGlobalTime == ENABLE) SET_BIT(CANx->sTxMailBox[transmitmailbox].TDTR, CAN_TDT0R_TGT); // データをメールボックスに投げ込む WRITE_REG(CANx->sTxMailBox[transmitmailbox].TDHR, ((uint32_t)tx_data[7] << CAN_TDH0R_DATA7_Pos) | ((uint32_t)tx_data[6] << CAN_TDH0R_DATA6_Pos) | ((uint32_t)tx_data[5] << CAN_TDH0R_DATA5_Pos) | ((uint32_t)tx_data[4] << CAN_TDH0R_DATA4_Pos)); WRITE_REG(CANx->sTxMailBox[transmitmailbox].TDLR, ((uint32_t)tx_data[3] << CAN_TDL0R_DATA3_Pos) | ((uint32_t)tx_data[2] << CAN_TDL0R_DATA2_Pos) | ((uint32_t)tx_data[1] << CAN_TDL0R_DATA1_Pos) | ((uint32_t)tx_data[0] << CAN_TDL0R_DATA0_Pos)); // 送信を要求する SET_BIT(CANx->sTxMailBox[transmitmailbox].TIR, CAN_TI0R_TXRQ); } return 0; }
送信関連処理でリファレンスマニュアルで確認をした通りに実装をしました。
受信処理
int8_t CAN_GetRxMessage(CAN_TypeDef* CANx, uint32_t RXFIFO,
CAN_RxHeaderTypeDef* rxHeader, uint8_t* rx_data) {
// RX FIFOにデータが存在しない場合はエラー
if (RXFIFO == CAN_RXFIFO0) {
if ((CANx->RF0R & CAN_RF0R_FMP0) == 0U) return -1;
} else {
if ((CANx->RF1R & CAN_RF1R_FMP1) == 0U) return -1;
}
// IDEの取得
rxHeader->IDE = CAN_RI0R_IDE & CANx->sFIFOMailBox[RXFIFO].RIR;
// IDEによって、StdID or ExtIdを判断
if (rxHeader->IDE == CAN_ID_STD) {
rxHeader->StdId = (CAN_RI0R_STID & CANx->sFIFOMailBox[RXFIFO].RIR) >> CAN_TI0R_STID_Pos;
} else {
rxHeader->ExtId = ((CAN_RI0R_EXID | CAN_RI0R_STID) & CANx->sFIFOMailBox[RXFIFO].RIR) >>CAN_RI0R_EXID_Pos;
}
// RTRの取得
rxHeader->RTR = (CAN_RI0R_RTR & CANx->sFIFOMailBox[RXFIFO].RIR);
// データの長さの取得
rxHeader->DLC =(CAN_RDT0R_DLC & CANx->sFIFOMailBox[RXFIFO].RDTR) >> CAN_RDT0R_DLC_Pos;
// フィルターマッチインデックスの取得
rxHeader->FilterMatchIndex =(CAN_RDT0R_FMI & CANx->sFIFOMailBox[RXFIFO].RDTR) >> CAN_RDT0R_FMI_Pos;
// タイムスタンプの取得
rxHeader->Timestamp = (CAN_RDT0R_TIME & CANx->sFIFOMailBox[RXFIFO].RDTR) >> CAN_RDT0R_TIME_Pos;
// データを読み込む
rx_data[0] = (uint8_t)((CAN_RDL0R_DATA0 & CANx->sFIFOMailBox[RXFIFO].RDLR) >> CAN_RDL0R_DATA0_Pos);
rx_data[1] = (uint8_t)((CAN_RDL0R_DATA1 & CANx->sFIFOMailBox[RXFIFO].RDLR) >> CAN_RDL0R_DATA1_Pos);
rx_data[2] = (uint8_t)((CAN_RDL0R_DATA2 & CANx->sFIFOMailBox[RXFIFO].RDLR) >> CAN_RDL0R_DATA2_Pos);
rx_data[3] = (uint8_t)((CAN_RDL0R_DATA3 & CANx->sFIFOMailBox[RXFIFO].RDLR) >> CAN_RDL0R_DATA3_Pos);
rx_data[4] = (uint8_t)((CAN_RDH0R_DATA4 & CANx->sFIFOMailBox[RXFIFO].RDHR) >> CAN_RDH0R_DATA4_Pos);
rx_data[5] = (uint8_t)((CAN_RDH0R_DATA5 & CANx->sFIFOMailBox[RXFIFO].RDHR) >> CAN_RDH0R_DATA5_Pos);
rx_data[6] = (uint8_t)((CAN_RDH0R_DATA6 & CANx->sFIFOMailBox[RXFIFO].RDHR) >> CAN_RDH0R_DATA6_Pos);
rx_data[7] = (uint8_t)((CAN_RDH0R_DATA7 & CANx->sFIFOMailBox[RXFIFO].RDHR) >> CAN_RDH0R_DATA7_Pos);
// 出力メールボックスの解放
if (RXFIFO == CAN_RXFIFO0) SET_BIT(CANx->RF0R, CAN_RF0R_RFOM0);
else SET_BIT(CANx->RF1R, CAN_RF1R_RFOM1);
return 0;
}
リファレンスマニュアルで確認した通りに実装をしました。CANの受信用FIFOは2つあるようなのでそれぞれに対応するようにしました。
おわりに
LLのTIM InputCaptureと比べるとCANはたくさん説明が書いてあるので実装がしやすいかなと思いました。InputCaptureはLLがあるのにも関わらずはまりまくりました。
STM32のHALやArudinoなどの便利ツールではなく、レジスタをたたくと動作の順序がわかって楽しいのでおすすめです。
あ、アセンブリ言語や機械語はしんどいのであんまりやりたくないです。Z80で機械語やアセンブリ言語をいじりましたが大変でした。C言語が高級言語と呼ばれる理由を身をもって体験しました。