Tag Archives: CAN

stm32f10x_can.c のバグについて

前回の投稿と打って変わって組込系の話題を。

最近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;
  }

一度でアクセスするようになってます。でも返値変わってて、最新版に置き換えるのは大変そう…