2024年5月4日土曜日

ESP32でSLCANプロトコルを使ってみた。

 PythonでCANアダプタを使っていてPCANの受信のレスポンスがちょっと遅い気がしたのでESP32でSLCANデバイスを作って試してみた。

前にESP-WROOM-32でCANを使ってみたときと同じハードで、最新のArduino CoreでCANライブラリを使わずに使えるようにしてみた。

#include "driver/twai.h"

#define SERIAL_BAUD 921600
const gpio_num_t CAN_TX_PIN = GPIO_NUM_32;
const gpio_num_t CAN_RX_PIN = GPIO_NUM_35;

boolean enableSLCAN = false;
boolean timestamp = false;
boolean cr = false;
int can_speed = 250;
static const char hexval[17] = "0123456789ABCDEF";

void slcan_ack() { Serial.write('\r'); }
void slcan_nack() { Serial.write('\a'); }

twai_timing_config_t get_timing(int speed_kbps) {
  switch(speed_kbps) {
    case 100:  return TWAI_TIMING_CONFIG_100KBITS();
    case 125:  return TWAI_TIMING_CONFIG_125KBITS();
    case 250:  return TWAI_TIMING_CONFIG_250KBITS();
    case 500:  return TWAI_TIMING_CONFIG_500KBITS();
    case 800:  return TWAI_TIMING_CONFIG_800KBITS();
    case 1000: return TWAI_TIMING_CONFIG_1MBITS();
    default:   return TWAI_TIMING_CONFIG_250KBITS();
  }
}

void send_canmsg(char *buf, bool rtr, bool ext) {
  if (!enableSLCAN) return;

  twai_message_t tx_frame = {};
  int msg_id = 0, msg_ide = 0, msg_len = 0;

  if (rtr) {
    if (ext) sscanf(&buf[1], "%04x%04x", &msg_ide, &msg_id), tx_frame.flags = TWAI_MSG_FLAG_EXTD | TWAI_MSG_FLAG_RTR;
    else sscanf(&buf[1], "%03x", &msg_id), tx_frame.flags = TWAI_MSG_FLAG_RTR;
  } else {
    if (ext) sscanf(&buf[1], "%04x%04x", &msg_ide, &msg_id), tx_frame.flags = TWAI_MSG_FLAG_EXTD;
    else sscanf(&buf[1], "%03x", &msg_id), tx_frame.flags = 0;
  }

  tx_frame.identifier = ext ? (msg_ide * 65536 + msg_id) : msg_id;

  if (ext) sscanf(&buf[9], "%01x", &msg_len);
  else sscanf(&buf[4], "%01x", &msg_len);

  tx_frame.data_length_code = msg_len;

  for (int i = 0; i < msg_len; i++) {
    int candata;
    if (ext) sscanf(&buf[10 + i*2], "%02x", &candata);
    else sscanf(&buf[5 + i*2], "%02x", &candata);
    tx_frame.data[i] = candata;
  }

  twai_transmit(&tx_frame, pdMS_TO_TICKS(10));
}

void pars_slcancmd(char *buf) {
  switch(buf[0]) {
    case 'O': { // OPEN CAN
      if (!enableSLCAN) {
        twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(CAN_TX_PIN, CAN_RX_PIN, TWAI_MODE_NORMAL);
        twai_timing_config_t t_config = get_timing(can_speed);
        twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
        if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK &&
            twai_start() == ESP_OK) enableSLCAN = true;
      }
      slcan_ack();
    } break;

    case 'C': { // CLOSE CAN
      if (enableSLCAN) {
        twai_stop();
        twai_driver_uninstall();
        enableSLCAN = false;
      }
      slcan_ack();
    } break;

    case 't': send_canmsg(buf, false, false); slcan_ack(); break;
    case 'T': send_canmsg(buf, false, true);  slcan_ack(); break;
    case 'r': send_canmsg(buf, true, false);  slcan_ack(); break;
    case 'R': send_canmsg(buf, true, true);   slcan_ack(); break;

    case 'Z': timestamp = (buf[1] == '1'); slcan_ack(); break;

    case 'S': {
      int new_speed = 0;
      switch(buf[1]) {
        case '3': new_speed = 100; break;
        case '4': new_speed = 125; break;
        case '5': new_speed = 250; break;
        case '6': new_speed = 500; break;
        case '7': new_speed = 800; break;
        case '8': new_speed = 1000; break;
        default: slcan_nack(); return;
      }

      if (enableSLCAN) {
        twai_stop();
        twai_driver_uninstall();
        enableSLCAN = false;
      }

      can_speed = new_speed;

      twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(CAN_TX_PIN, CAN_RX_PIN, TWAI_MODE_NORMAL);
      twai_timing_config_t t_config = get_timing(can_speed);
      twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

      if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK &&
          twai_start() == ESP_OK) {
        enableSLCAN = true;
        slcan_ack();
      } else slcan_nack();
    } break;

    case 'V': Serial.print("V1234"); slcan_ack(); break;
    case 'N': Serial.print("N2208"); slcan_ack(); break;
    case 'F': Serial.print("F00");   slcan_ack(); break;
    default: slcan_nack(); break;
  }
}

void transfer_tty2can() {
  static char cmdbuf[32];
  static int cmdidx = 0;

  while(Serial.available()) {
    char val = Serial.read();
    cmdbuf[cmdidx++] = val;
    if (cmdidx == 32) { slcan_nack(); cmdidx=0; }
    else if (val == '\r') {
      cmdbuf[cmdidx]='\0';
      pars_slcancmd(cmdbuf);
      cmdidx = 0;
    }
  }
}

void transfer_can2tty() {
  if (!enableSLCAN) return;
  twai_message_t rx_frame;
  if (twai_receive(&rx_frame, pdMS_TO_TICKS(3)) == ESP_OK) {
    String command = "";
    if (rx_frame.flags & TWAI_MSG_FLAG_EXTD) {
      command += (rx_frame.flags & TWAI_MSG_FLAG_RTR) ? "R" : "T";
      command += hexval[(rx_frame.identifier >> 28) & 0xF];
      command += hexval[(rx_frame.identifier >> 24) & 0xF];
      command += hexval[(rx_frame.identifier >> 20) & 0xF];
      command += hexval[(rx_frame.identifier >> 16) & 0xF];
      command += hexval[(rx_frame.identifier >> 12) & 0xF];
      command += hexval[(rx_frame.identifier >> 8) & 0xF];
      command += hexval[(rx_frame.identifier >> 4) & 0xF];
      command += hexval[rx_frame.identifier & 0xF];
    } else {
      command += (rx_frame.flags & TWAI_MSG_FLAG_RTR) ? "r" : "t";
      command += hexval[(rx_frame.identifier >> 8) & 0xF];
      command += hexval[(rx_frame.identifier >> 4) & 0xF];
      command += hexval[rx_frame.identifier & 0xF];
    }
    command += hexval[rx_frame.data_length_code & 0xF];
    for (int i = 0; i < rx_frame.data_length_code; i++) {
      command += hexval[(rx_frame.data[i] >> 4) & 0xF];
      command += hexval[rx_frame.data[i] & 0xF];
    }
    if (timestamp) {
      uint16_t t = millis() % 60000;
      command += hexval[(t >> 12) & 0xF];
      command += hexval[(t >> 8) & 0xF];
      command += hexval[(t >> 4) & 0xF];
      command += hexval[t & 0xF];
    }
    command += '\r';
    Serial.print(command);
    if (cr) Serial.println();
  }
}

void setup() {
  Serial.begin(SERIAL_BAUD);
}

void loop() {
  transfer_can2tty();
  transfer_tty2can();
}

ESP32でSLCANを実装していた人がいたのでそのCAN部分をTWAIドライバに変更してみただけ。たしかにシリアル通信なのでBluetooth経由でもCANが使えるようにできるわけか…
Python-Canはソケット通信にも対応してるのでそのままWiFi化とかもできそう。




0 件のコメント:

コメントを投稿