前回の投稿と打って変わって組込系の話題を。
最近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;
}
一度でアクセスするようになってます。でも返値変わってて、最新版に置き換えるのは大変そう…