前回の投稿と打って変わって組込系の話題を。
最近STM32でCANとか使っているのですが、時々送信に失敗してしまうという問題がありました。
たまになので問題ないのですが、気になったので調べてみるとSTMicroが用意しているSTM32F10x standard peripheral libraryのバグでした。バージョンは04/16/2010 のV3.3.0と古いやつですが。
具体的に言うと、現在送信しようとしているメッセージの状態を確認する関数
CAN_TransmitStatus にあります。
ここでは CANxのTSRレジスタのTSS_RQCP0、TSR_TXOK0、およびTSR_TME0ビットの値を見て、
CANTXPENDING(送信中)、CANTXFAILED(失敗)、CANTXOK(成功)を返しています。
が、こんな風にレジスタにアクセスするようなコードになってます。
switch (TransmitMailbox) { case (0): state |= (uint8_t)((CANx->TSR & TSR_RQCP0) << 2); state |= (uint8_t)((CANx->TSR & TSR_TXOK0) >> 0); state |= (uint8_t)((CANx->TSR & TSR_TME0) >> 26); break; case (1): state |= (uint8_t)((CANx->TSR & TSR_RQCP1) >> 6); state |= (uint8_t)((CANx->TSR & TSR_TXOK1) >> 8); state |= (uint8_t)((CANx->TSR & TSR_TME1) >> 27); break; case (2): state |= (uint8_t)((CANx->TSR & TSR_RQCP2) >> 14); state |= (uint8_t)((CANx->TSR & TSR_TXOK2) >> 16); state |= (uint8_t)((CANx->TSR & TSR_TME2) >> 28); break; default: state = CANTXFAILED; break; }
これでは、レジスタに3回アクセスしてしまっており、アクセスとアクセスの間でレジスタの値が変わってしまうと変なstatusになってしまいます。その結果 default で CANTXFAILED がセットされてしまうというのが送信失敗の原因です(実際にはこの瞬間に成功しているが、失敗したと判断してしまう)。
最も修正量の少ない解決策は簡単で、CANx->TSRの値を一旦変数に入れてやればいいのです。
uint32_t _tsr = CANx->TSR; switch (TransmitMailbox) { case (0): state |= (uint8_t)((_tsr & TSR_RQCP0) << 2); state |= (uint8_t)((_tsr & TSR_TXOK0) >> 0); state |= (uint8_t)((_tsr & TSR_TME0) >> 26); break; case (1): state |= (uint8_t)((_tsr & TSR_RQCP1) >> 6); state |= (uint8_t)((_tsr & TSR_TXOK1) >> 8); state |= (uint8_t)((_tsr & TSR_TME1) >> 27); break; case (2): state |= (uint8_t)((_tsr & TSR_RQCP2) >> 14); state |= (uint8_t)((_tsr & TSR_TXOK2) >> 16); state |= (uint8_t)((_tsr & TSR_TME2) >> 28); break; default: state = CANTXFAILED; break; }
こんな風に。
で、最新のV3.5.0ではちゃんと修正されてました。
switch (TransmitMailbox) { case (CAN_TXMAILBOX_0): state = CANx->TSR & (CAN_TSR_RQCP0 | CAN_TSR_TXOK0 | CAN_TSR_TME0); break; case (CAN_TXMAILBOX_1): state = CANx->TSR & (CAN_TSR_RQCP1 | CAN_TSR_TXOK1 | CAN_TSR_TME1); break; case (CAN_TXMAILBOX_2): state = CANx->TSR & (CAN_TSR_RQCP2 | CAN_TSR_TXOK2 | CAN_TSR_TME2); break; default: state = CAN_TxStatus_Failed; break; }
一度でアクセスするようになってます。でも返値変わってて、最新版に置き換えるのは大変そう…