2019年12月30日月曜日

SSI-4の簡易的なエミュレータを作ってみた

ちょっと部屋の掃除をしていたらSTM32Duinoで遊ぼうと買っていたBluepillが出てきた。
そういやSTM32F103Cにはシリアルポート複数ついていたなということで、この前作ったInnovateのSSI-4エミュレータを移植してみた。一応シリアルポートが複数ついているのでデイジーチェーンもサポートしたつもり。

今回はArduino Core STM32を使用した。Arduino用の開発環境が複数あるようでタイマーでハマったけどなんとか動く用になった。
更に今回は一台のArduinoでSSI-4を2台分エミュレートしてみた。
まぁ今の所色々決め打ちだけど、きれいに整えたい。

//SSI-4 x2 emulator on Bluepill Arduino Core STM32
#define LED_BUILTIN PC13
//HardwareSerial Serial2(PA3, PA2);
HardwareSerial Serial3(PB11, PB10);
HardwareTimer *Timer1 = new HardwareTimer(TIM2);

volatile boolean mts_sending = false;
volatile int res = 0;
volatile unsigned int aux[8];

const char DEVICE_NAME0[8] = {'A', 'U', 'X', '1', 0x00, 0x00, 0x00, 0x00};
const char DEVICE_NAME1[8] = {'A', 'U', 'X', '2', 0x00, 0x00, 0x00, 0x00};
const char ID_SSI4[8] = {'S', 'S', 'I', '4'};
char INPUT_DEVICE_NAME[240];
char INPUT_DEVICE_TYPE[240];

int DEVICE_CNT = 2;
int STREAM_LEN = 0;
volatile boolean thru = false;
volatile int INPUT_STAT = 0;

void mts_send(HardwareTimer*){
  mts_sending = true;
  //Serial1.println(micros());
  byte datalenByte;
  switch (res){
    case 0:
      datalenByte = 8 + STREAM_LEN;
      //Header Word
      //1011 0010 1000 0100
      //0xB2 0x84
      Serial1.write(0xB2 | ((datalenByte & 0x80) >> 1));
      Serial1.write(0x80 | (datalenByte & 0x7F));
      
      //Aux Channels
      //0000 D10D9D8D7 0D6D5D4 D3D2D1D0
      for (int i = 0; i <= 7; ++i){
        byte auxword[2];
        auxword[1] = lowByte(aux[i]) & 0x7F;
        auxword[0] = ((highByte(aux[i]) & 0x07) << 1) | ((lowByte(aux[i]) & 0x80) >> 7);
        Serial1.write(auxword,2);
      }
      if(thru){
        for (int i = 0; i <= (STREAM_LEN * 2) - 1; ++i){
          Serial1.write(Serial3.read());
        }
        thru = false;
        digitalWrite(LED_BUILTIN, LOW);//debug
      }
      break;
    case 1://0xCE
      datalenByte = 9 + (DEVICE_CNT * 8);
      //Header Word
      //1010 0010 1000 0101
      //0xA2 0x85
      Serial1.write(0xA2 | ((datalenByte & 0x80) >> 1));
      Serial1.write(0x80 | (datalenByte & 0x7F));

      //0xCE
      //0000 0001 0100 1110
      //0x01 0x4E
      Serial1.write(0x01);
      Serial1.write(0x4E);

      //Device Name
      Serial1.write(DEVICE_NAME0,8);
      Serial1.write(DEVICE_NAME1,8);
      res = 0;
      
      //Input device
      Serial1.write(INPUT_DEVICE_NAME, DEVICE_CNT * 8);
      
      digitalWrite(LED_BUILTIN, LOW);//debug
      break;
     case 2://0xF3
      datalenByte = 9 + (DEVICE_CNT * 8);
      //Header Word
      //1010 0010 1000 0101
      //0xA2 0x85
      Serial1.write(0xA2 | ((datalenByte & 0x80) >> 1));
      Serial1.write(0x80 | (datalenByte & 0x7F));

      //0xF3
      //0000 0001 0111 0011
      //0x01 0x73
      Serial1.write(0x01);
      Serial1.write(0x73);
      //Firmware Version
      Serial1.write(0x10);
      Serial1.write(0x0F);
      //Identifier
      Serial1.write(ID_SSI4,4);
      //CPU
      Serial1.write(0x01);
      //Channnel/Flags
      Serial1.write(0x04);
      
      //Firmware Version
      Serial1.write(0x12);
      Serial1.write(0x3A);
      //Identifier
      Serial1.write(ID_SSI4,4);
      //CPU
      Serial1.write(0x01);
      //Channnel/Flags
      Serial1.write(0x04);
      //Input device
      Serial1.write(INPUT_DEVICE_TYPE, DEVICE_CNT * 8);

      res = 0;
      digitalWrite(LED_BUILTIN, LOW);//debug
      break;
  }
  mts_sending = false;
}


void setup() {
  Serial1.begin(19200);//out
  Serial3.setTimeout(82);
  Serial3.begin(19200);//in
  Timer1->setMode(2, TIMER_OUTPUT_COMPARE);
  Timer1->setPrescaleFactor(90);//72000000/90 = 800000
  Timer1->setOverflow(65536, TICK_FORMAT);//65536/800000 = 0.08192s(81.92ms)
  Timer1->attachInterrupt(mts_send);
  Timer1->resume();

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);//debug
  Serial3.print("H");
  Serial.begin(19200);//debug
}

void loop() {
  switch(INPUT_STAT){
    case 1:
      Serial3.write(0xF3);
      break;
    case 2:
      Serial3.write(0xCE);
      break;
  }
  if(!mts_sending){
    aux[0] = analogRead(PA0);
    aux[1] = analogRead(PA1);
    aux[2] = analogRead(PA2);
    aux[3] = analogRead(PA3);
    aux[4] = analogRead(PA4);
    aux[5] = analogRead(PA5);
    aux[6] = analogRead(PA6);
    aux[7] = analogRead(PA7);
  }
  
  serialEvent1();
  serialEvent3();
}

void serialEvent1() {//out
  while (Serial1.available()) {
    digitalWrite(LED_BUILTIN, HIGH);//debug
    switch (Serial1.read()) {
      case 0xCE:
        res = 1;//obtain device names
        break;
      case 0xF3:
        res = 2;//obtain device types
        break;
      case 'c':
        Serial3.write('c');//pass to upstream
        break;
      case 'H':
        Timer1->refresh();//Timer1 reset
        break;
    }
  }
}

void serialEvent3() {//in
  while (Serial3.available() && !thru) {
    digitalWrite(LED_BUILTIN, HIGH);//debug
    byte mtshead[2];
    Serial3.readBytes(mtshead,1);
    if((mtshead[0] & 0xA2) == 0xA2){
      mtshead[1] = mtshead[0];
      Serial3.readBytes(mtshead,1);
      if((mtshead[1] & 0x80) == 0x80){
        if(INPUT_STAT <= 0){
          INPUT_STAT = 1;
        }
        STREAM_LEN = int(((mtshead[1] & 0x01) << 7) | (mtshead[0] & 0x7F));
        if((mtshead[1] & 0x10) == 0x00){
          byte response[2];
          Serial3.readBytes(response,2);
          byte responseByte = (((response[0] & 0x01) << 7) | (response[1] & 0x7F));
          switch(responseByte){
            case 0xF3:
              INPUT_STAT = 2;
              DEVICE_CNT = 2 + (STREAM_LEN - 1) / 4;
              Serial3.readBytes(INPUT_DEVICE_TYPE , ((STREAM_LEN - 1) * 2));
              break;
            case 0xCE:
              INPUT_STAT = 3;
              Serial3.readBytes(INPUT_DEVICE_NAME, ((STREAM_LEN - 1) * 2));
              break;
              }
        }
        //Serial.println(STREAM_LEN);
        thru = true;
        return;
      }else{
        return;
      }
    }else{
      return;
    }
  }
}

Input側のヘッダ判別するあたりでSerial.read()を使うと何故か2バイト目が0xFFになってしまうのにハマったのでSerial.readByteで1バイトずつ読み込むようにしてる。
条件分岐入れないとちゃんと1バイトずつ読めてる感じがするんだけどよくわからん…
多分ここらへんはもっと効率よく取り込める方法がありそうだけど、これで動いてるので良しとしよう。

STM32Duinoは3.3V駆動でADもMax3.3Vなんだけど、ADは12Bitあるので抵抗分圧で分圧してうまいこと10Bitにmapして使えば5V以上の入力とかにもソフト側の変更だけで柔軟に対応できるかも(MTSの仕様は今の所10Bitまでだし)
20Vまで入力できるように9.1kと1.8kで分圧してやって10bit分だけ使えば丁度0~5Vになるのでは?とか思ったり。10V入力したいときはその分だけmapやら2分の1してやればよいし。
他のMTS対応デバイスとつなぐときはRS-232トランシーバを介せばおk。フローコントロール使わないので1個でinputとoutput側使えそう。LC-1はMAX202を1個でやってるみたいだし。
ST232CDRは5V対応なんだけど試しに3.3Vで駆動してみたらちゃんと動いたからとりあえずこれで…
とりあえず2台作ってデイジーチェーンしてみた。

1台でSSI-4を2台分エミュレートしてるので16chのUSB接続ロガーとして使える!
まぁサンプリングレートが81.92msだけどね。
今回はInput側になにかつながっているかをパケットで判断しているんだけど、本家はハードで判断してそう。途中でデバイス数変わったときにも対応できるようにしておいたほうが良いのかなぁ…
MTSの仕様を全部実装したわけじゃないので他のデバイスとつないだときにどうなるかはわからないという。なんかinput側の受信処理あたりで条件分岐が怪しいような気がしてるんだけど動いてるから良しとしてる。
Bluepillはまだシリアルポートがもう1ポートあるので、ELM327とか使ってOBD2からデータとってそのままMTSに乗せたりできるかも。

一応Input側も実装したことによりこいつをMTSのチェーンの一番最後に入れてOutput側をそのままUSBデバイスとして認識させたりとか、TTLレベルのままRN-42とかESP32でBluetooth化とかもできて便利かもしれないな。Bluetooth化するならESP32にそのまま移植すればいいのか…ADの使い方面倒そうだけど…

0 件のコメント:

コメントを投稿