ESP32 が不明なビットレートの CAN バスに接続されている場合、TWAI ドライバは決して同期しない——バスエラーが見えるだけ。すべての標準ボーレートを試す代わりに、測定できる:CAN RX ピンに GPIO エッジ割り込みを付け、エッジをタイムスタンプし、最も短いパルスからビット時間を推定する。これは TWAI ドライバ実行中でも機能する。GPIO マトリックス経由の GPIO 入力パスがペリフェラルと並行して利用可能なため。
エッジキャプチャ ISR
すべてのエッジをリングバッファにタイムスタンプ(esp_timer_get_time による µs 精度):
BaudrateDetection.cpp
static constexpr size_t MAX_EDGES = 4096;
static volatile int64_t edge_timestamps[MAX_EDGES];
static volatile size_t edge_write_idx = 0;
static void IRAM_ATTR gpio_isr_handler(void* /*arg*/) {
size_t idx = edge_write_idx;
if (idx < MAX_EDGES) {
edge_timestamps[idx] = esp_timer_get_time(); // µs 精度
edge_write_idx = idx + 1;
}
}TWAI を邪魔せずにサンプリング
重要なトリック:gpio_config() を呼ばない——これはピンの機能セレクタをリセットし TWAI から切り離してしまう。ISR ハンドラを追加してエッジ割り込みを有効にするだけ:
BaudrateDetection.cpp
edge_write_idx = 0;
gpio_install_isr_service(ESP_INTR_FLAG_IRAM); // 既にインストール済みでも OK
gpio_isr_handler_add(Pin_CAN_RXD, gpio_isr_handler, nullptr);
gpio_set_intr_type(Pin_CAN_RXD, GPIO_INTR_ANYEDGE);
gpio_intr_enable(Pin_CAN_RXD);
vTaskDelay(pdMS_TO_TICKS(2000)); // 2 秒間サンプリング
gpio_intr_disable(Pin_CAN_RXD);
gpio_isr_handler_remove(Pin_CAN_RXD);
gpio_set_intr_type(Pin_CAN_RXD, GPIO_INTR_DISABLE);ビット時間の推定
すべてのパルスの幅(連続エッジ間の時間)を計算し、外れ値を捨て、ソートし、下位約 20% の平均を取る。最も短いパルスはシングルビットのドミナント/レセッシブ遷移に対応する——ISR ジッタで容易に歪む絶対最小値を使うよりはるかに堅牢:
BaudrateDetection.cpp
std::vector<float> pulse_widths;
for (size_t i = 1; i < num_edges; i++) {
float dt = (float)(edge_timestamps[i] - edge_timestamps[i - 1]);
if (dt > 0.5f && dt < 500.0f) // 0.5 µs–500 µs ≈ 2 Mbps–2 kbps
pulse_widths.push_back(dt);
}
std::sort(pulse_widths.begin(), pulse_widths.end());
size_t cluster_size = std::max<size_t>(pulse_widths.size() / 5, 3);
float sum = 0;
for (size_t i = 0; i < cluster_size; i++) sum += pulse_widths[i];
float bit_time_us = sum / cluster_size;
float baudrate = 1000000.0f / bit_time_us;既知のボーレートとの照合
測定されたビット時間を標準レートのテーブルと約 20% の許容誤差で比較:
BaudrateDetection.cpp
struct KnownBaudrate { int32_t baudrate; float bit_time_us; const char* name; };
static const KnownBaudrate known[] = {
{ 1000000, 1.0f, "1 Mbps" },
{ 500000, 2.0f, "500 kbps" },
{ 250000, 4.0f, "250 kbps" },
{ 125000, 8.0f, "125 kbps" },
{ 100000, 10.0f, "100 kbps" },
{ 50000, 20.0f, "50 kbps" },
{ 47600, 21.01f,"47.6 kbps"},
{ 25000, 40.0f, "25 kbps" },
};
const KnownBaudrate* matchBitTime(float bit_time_us) {
const KnownBaudrate* best = nullptr;
float best_error = 1e9f;
for (auto& k : known) {
float error = std::fabs(bit_time_us - k.bit_time_us);
if (error < k.bit_time_us * 0.20f && error < best_error) {
best_error = error; best = &k;
}
}
return best;
}注意点
- トラフィックが必要。 サンプリングウィンドウ内のエッジが 10 未満なら測定するものがない——先にノードに何か送信させる(例:プロトコルにあればブロードキャストリクエスト)。
- スタッフビットとアイドルギャップ はより長いパルスを生む;平均ではなく最小クラスタを使うのはまさにそのため。
- ISR レイテンシ次第で約 1 Mbps を超えると検出は信頼できなくなる——
ESP_INTR_FLAG_IRAMは必須。
関連記事
この記事が役に立った場合は、TechOverflow での新しい記事の調査と公開を支援するため、「Buy Me a Coffee」でのご支援、または PayPal 経由でのご寄付をご検討ください