2025年10月4日土曜日

ESP32-C6でCANを2ポート使ってみた。

 モータドライバをCAN対応で作ったので、ESP32からコントロールしていたんだけども、ESP32-C6はCANが2つあるのでモータドライバのID変更が面倒なときは2ポートそれぞれに同じIDのモータドライバ接続できたら便利なんじゃ?という意味不明なことをやってみたくて買ってみた。せっかくデイジーチェーンできる規格なのにもったいない。(ただ新しいESP32シリーズを試したかっただけ

Seeed Studio XIAO ESP32C6

というわけでESP32-C6が使いやすい状態になっているSeeed Studio XIAO ESP32C6にしてみた。M5Stack NanoC6だとIOが少なすぎるしね…

今までのESP32シリーズのCANはSJA1000互換実装だったんだけど、ESP32-C6はCANの実装が変わっているようで今までのライブラリが使えなさそう。ESP32版SLCANを作ったときみたいにIDFのTWAIドライバを直接使えば良さそうなんだけど、こちらもTWAI driver v2になってるっぽい?ESP32-C6はTWAI driver v2じゃないと動かないかも?

今回はArduino ESP32 v3.3.1を使用してみた。これならIDF5.5.1ベースなのでESP32-C6のDual TWAIにも対応しているはず…

#include <Arduino.h>
#include "esp_twai.h"
#include "esp_twai_onchip.h"

#define CAN0_TX GPIO_NUM_2
#define CAN0_RX GPIO_NUM_21
#define CAN1_TX GPIO_NUM_22
#define CAN1_RX GPIO_NUM_23
#define LED_BUILTIN 15

static twai_node_handle_t can0 = NULL;
static twai_node_handle_t can1 = NULL;
//CAN受信コールバック
static bool twai_rx_cb(twai_node_handle_t handle,
                       const twai_rx_done_event_data_t *edata,
                       void *user_ctx) {
  static uint8_t rx_buf[8];
  twai_frame_t frame = {
    .buffer = rx_buf,
    .buffer_len = sizeof(rx_buf),
  };
  if (twai_node_receive_from_isr(handle, &frame) == ESP_OK) {
    if (handle == can0) {
      digitalWrite(LED_BUILTIN, HIGH);
      Serial.print("[CAN0 RX] ");
    } else if (handle == can1) {
      digitalWrite(LED_BUILTIN, LOW);
      Serial.print("[CAN1 RX] ");
    }
    Serial.printf("ID=0x%X DLC=%d DATA=",
                  frame.header.id,
                  frame.header.dlc);
    for (int i = 0; i < frame.header.dlc; i++) {
      Serial.printf("%02X ", rx_buf[i]);
    }
    Serial.println();
  }
  return false;
}
//CAN初期化関数
esp_err_t CANbegin(
  gpio_num_t tx_gpio,
  gpio_num_t rx_gpio,
  uint32_t bitrate,
  twai_node_handle_t *out_node) {
  twai_onchip_node_config_t cfg = {
    .io_cfg = {
      .tx = tx_gpio,
      .rx = rx_gpio,
      .quanta_clk_out = GPIO_NUM_NC,
      .bus_off_indicator = GPIO_NUM_NC,
    },
    .bit_timing = {
      .bitrate = bitrate,
    },
    .tx_queue_depth = 5,
  };
  esp_err_t ret = twai_new_node_onchip(&cfg, out_node);
  if (ret != ESP_OK) {
    return ret;
  }
  twai_event_callbacks_t cbs = {
    .on_rx_done = twai_rx_cb,
  };
  ret = twai_node_register_event_callbacks(*out_node, &cbs, NULL);//受信コールバック
  if (ret != ESP_OK) {
    return ret;
  }
  ret = twai_node_enable(*out_node);
  if (ret != ESP_OK) {
    return ret;
  }
  return ESP_OK;
}
//CAN送信関数
esp_err_t CANsend(
  twai_node_handle_t node,
  uint32_t id,
  bool ext,
  bool rtr,
  const uint8_t *data,
  size_t len) {
  twai_frame_t frame = {
    .header = {
      .id = id,
      .ide = ext,
      .rtr = rtr,
    },
    .buffer = (uint8_t *)data,
    .buffer_len = len,
  };
  return twai_node_transmit(node, &frame, 100);
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  delay(1000);
  esp_err_t ret = CANbegin(CAN0_TX, CAN0_RX, 250000, &can0);
  if (ret != ESP_OK) {
    Serial.printf("CAN begin failed: %s\n", esp_err_to_name(ret));
  }
  ret = CANbegin(CAN1_TX, CAN1_RX, 250000, &can1);
  if (ret != ESP_OK) {
    Serial.printf("CAN begin failed: %s\n", esp_err_to_name(ret));
  }
}

void loop() {
  static uint8_t buf0[8] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
  static uint8_t buf1[8] = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };

  static unsigned long last = 0;
  if (last + 1000 <= millis())  //ms周期
  {
    last = millis();  //次の実行のために時間更新
    if (can0) {
      if (CANsend(can0, 0x016, false, false, buf0, sizeof(buf0)) == ESP_OK) {
        Serial.println("CAN0 TX OK");
      }
    }
    if (can1) {
      if (CANsend(can1, 0x036, false, false, buf1, sizeof(buf1)) == ESP_OK) {
        Serial.println("CAN1 TX OK");
      }
    }
  }
}

SLCANを移植したときに使ったTWAI driver v1の感じでTWAI driver v2でtwai_driver_install_v2()を使ってみたんだけど、1ポートは初期化できるんだけど、もう一回別ポートを初期化するために呼び出すとエラーが出てしまった。なので2ポート使いたいときは"esp_twai_onchip.h"を使ってtwai_new_node_onchip()をしてやる必要がありそう。これだと2ポートともちゃんと初期化されて使用可能になった。

もはやArduinoでIDFのコード動かしている感じになってしまってArduino感がなくなってしまったので多少関数化をしておいた。GPIOの設定は"GPIO_NUM_2"みたいにgpio_num_t型で指定してやる必要があった。ライブラリにしてしまえばもうちょっとスッキリするかも。

ESP32-C6-CAN

CANトランシーバももちろん2セット必要なのでSN65HVD230を使用してみた。トランシーバーがちゃんと接続されていない場合はある程度送信したあとに"esp_twai: _node_queue_tx(580): node is bus off"が出た。CANトランシーバが正常に繋がっている場合はどっちも同時に送信できてそう。

受信の方もとりあえずIDFのリファレンスを参考にコールバックで受信できるように設定してある。受信するとCANポート番号とメッセージがシリアルコンソールで確認できる。とりあえずArduino環境で、CAN-BUSを2ポート同時に送信も受信も確認できた。

試しにM5Atom Liteでも試してみたけど(CANbeginを1回のみに変更)、こっちでもちゃんと使えたので他のESP32シリーズでもこの方法でTWAI driver v2でCAN通信が使えるっぽい。逆に言えばESP32-C6に対応させればTWAIが1ポートな今までのシリーズは大丈夫そうかな。ちなみにESP32-P4だとTWAIコントローラが3個入ってるのでCAN通信を同時に3ポート使えるっぽい。

0 件のコメント:

コメントを投稿