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の使い方面倒そうだけど…

2019年12月29日日曜日

Nextion HMIディスプレイを買ってみた

ArduinoとかでLCDディスプレイのUIを作ろうと思うとけっこう大変。Nextion HMIはLCDにマイコンが搭載されていてシリアル通信で制御できるっぽいので試しにポチってみた。LCDのUIは専用ツールから作成できるらしい。一応タッチディスプレイ。


今回はAliexpressでBASICモデルの2.8インチを購入。送料込みで17.38ドルだった。
NextionはTJCとそっくりなんだけど、TJCはNextionのツールが使えないので注意かも。TJCは中国語のツールらしい。



付属品はMicroUSBから電源を取るやつとケーブル。


裏面。MicroSDカードを差し込めるところがある。

Nextion HMIはNextion Editorという専用ツールで画面を作成できるんだけど、作成後にNextion HMIに転送する手法がシリアル接続とMicroSDから読み込みの2種類似対応している。


とりあえず手持ちのUSB TTL変換アダプタに接続。
電源を入れるとデモが流れる。

Nextion EditorではToolboxに用意されている部品を画面上に並べて行く感じでGUIが作成できるみたい。

Basicモデルで使用できるのはこのぐらい。

これらを並べて適当にシリアル接続で転送してみた。
Baudrateはデフォルトだと転送が遅かったので最大の921600pbsで転送してみたけど特に不具合はなくて結構早い。
Arduinoとの通信部分がまだ理解できていないのでちょっと調べてみようかな。

2019年12月28日土曜日

ArduinoでMTSプロトコルでデータを送信してみる

InnovateのMTSプロトコルをArduinoで出力してみた。といっても本家MTSのようにデイジーチェーンまでは実装していないけど…
とりあえずLogWorks3で認識してくれるようなレベルなSSI-4互換機的なものを作ってみた。
応用すればPLX SM-AFRのiMFDプロトコルをMTSプロトコルに変換したりとかできるかも?
一応プロトコルの仕様書とかSDKは公開されているのでそいつらを参考にした。
実機はRS-232CでコネクタはMicro-Fit 3.0もしくは2.5mmジャック。
白がRX、赤がTX、黒はGNDで青をGNDに落とすと接続検知になってる。
Baudrate 19200bpsで81.92ms間隔でのデータストリームになっているっぽい。
とりあえずSSI-4として振る舞い、Aux1~4にADCのデータを突っ込むサンプル。
MAX31855を使って熱電対の情報を入れたり、BMP388を使って圧力ログったりできるかも。

volatile unsigned long ofc = 0;
volatile boolean ovf = false;
volatile int res = 0;
volatile unsigned int aux[4]={0,0,0,0};


//Timer2 overflow interrupt vector handler
ISR(TIMER2_OVF_vect) {
  ofc++;
  //256(8bit)x1024(Prescaler)/16000000(Clock)*5 = 81.92ms
  if(!ovf && ofc >= 5){
    ofc = 0;
    ovf = true;
    mts_send();
    ovf = false;
  }
};

void mts_send(){
  switch (res){
    case 0:
      //Header Word
      //1011 0010 1000 0100
      //0xB2 0x84
      Serial.write(0xB2);
      Serial.write(0x84);
      
      //Aux Channels
      //0000 D10D9D8D7 0D6D5D4 D3D2D1D0
      for (int i = 0; i <= 3; ++i){
        byte auxword[2];
        auxword[1] = lowByte(aux[i]) & 0x7F;
        auxword[0] = ((highByte(aux[i]) & 0x07) << 1) | ((lowByte(aux[i]) & 0x80) >> 7);
        Serial.write(auxword,2);
      }

      break;
    case 1://0xCE
      //Header Word
      //1010 0010 1000 0101
      //0xA2 0x85
      Serial.write(0xA2);
      Serial.write(0x85);

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

      //Device Name
      Serial.write(0x53);//S
      Serial.write(0x53);//S
      Serial.write(0x49);//I
      Serial.write(0x34);//4
      Serial.write(0x00);
      Serial.write(0x00);
      Serial.write(0x00);
      Serial.write(0x00);
      res = 0;
      digitalWrite(LED_BUILTIN, LOW);//debug
      break;
     case 2://0xF3
      //Header Word
      //1010 0010 1000 0101
      //0xA2 0x85
      Serial.write(0xA2);
      Serial.write(0x85);

      //0xF3
      //0000 0001 0111 0011
      //0x01 0x73
      Serial.write(0x01);
      Serial.write(0x73);
      //Firmware Version
      Serial.write(0x12);
      Serial.write(0x3A);
      //Identifier
      Serial.write(0x53);
      Serial.write(0x53);
      Serial.write(0x49);
      Serial.write(0x34);
      //CPU
      Serial.write(0x01);
      //Channnel/Flags
      Serial.write(0x04);

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


void setup() {
  //Timer2 Settings: Timer Prescaler /1024, WGM mode 0
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;

  //Timer2 Overflow Interrupt Enable  
  TIMSK2 = 1<<TOIE2;

  //reset timer
  TCNT2 = 0;

  Serial.begin(19200);
  pinMode(LED_BUILTIN, OUTPUT);//debug

}

void loop() {
  aux[0] = analogRead(A0);
  aux[1] = analogRead(A1);
  aux[2] = analogRead(A2);
  aux[3] = analogRead(A3);
}

void serialEvent() {
  while (Serial.available()) {
    switch (Serial.read()) {
      digitalWrite(LED_BUILTIN, HIGH);//debug
      case 0xCE:
        res = 1;//obtain device names
        break;
      case 0xF3:
        res = 2;//obtain device types
        break;
      case 'H':
        TCNT2 = 0;
        break;
    }
  }
}

こんなもんでLogWorks3で認識されてる。

今回は81.92ms間隔でデータを送信するのに8bitタイマーを5回オーバーフローさせて使ってるけど(本家は16bitタイマーっぽい表記があるけど)そこらへんの精度の問題とかもよくわからん…
あとADのデータを割り込み内で取り込むべきかどうかとかとかも悩む。

こいつを応用すればArduino 1台で複数のMTS対応デバイスをエミュレートできるかも。
Arduino一つでSSI-4を2台分とか。

2019年12月21日土曜日

D02HWのSIMロック解除してDocomo系SIMを使ってみた

ラズパイでモバイル通信するのに安いモデムを探していたら昔友人からもらったD02HWがあったのを思い出し、どうやらSIMロック解除できるっぽいので解除してみた。
対応バンドも少ないし、LTEにも対応していないけどラズパイ用途なら問題なさそうだし。
まずは、SIMフリー化によりDocomoのSIMカードが使えるか試してみた。


しばらくぶりに出してきたのでDC UnlockerでSIMロックの状態を確認してみた。
実はこれATコマンドでも見れるようなので、DC Unlockerは使わなくてもいいかも。
EMOBILE HW Utilityで認識できる状態で虫眼鏡マークを押すだけで確認できる。
D02HWの自動インストール結構微妙かも…差し込むたびにautorun動かさないとCOMポートが認識されない…

それはさておきLockedなのでUnlockコードを入手する必要があるんだけど、フリーで生成してくれるサイトがあったのでそちらを利用させてもらった。
必要なのはIMEIなので、本体の裏側をみるか、EMOBILEのユーティリティーからコピペでもOK。

何個か解除コードが生成されるんだけど、今回はよくわからなかったので"Old code V1"を利用した。

コードを入力したら適当なシリアル接続ができるツールでモデムに接続する。ボーレートは9600だった。COMポートはデバイスマネージャーでHUAWEI Mobile Connect -3G PC UI Interfaceってなってるやつの番号。

今回はCooltermのLine Modeを使用した。
実はSIMロックの状態はATコマンドでも確認できる。
AT^CARDLOCK?
と送ると帰ってくる値が1から始まっていたらロックされている
2だったらロック解除済みらしい。次の数字は残りの試行回数。
ロック解除パスワードを10回ミスるとロック解除できなくなるらしい。

次にロック解除コマンド
AT^CARDLOCK="88888888"
8がいっぱい入ってるところは自分のIMEIから生成したロック解除コードに置き換えて送信。
もう一回AT^CARDLOCK?コマンドで2から始まる番号が帰ってきたらSIMロック解除成功。


一応DC Unlockerでも確認してみたらロック解除されてる。


SIM変換アダプタを使用してnano SIMを入れてみた
しかしずっと赤ランプ点滅で電波を掴まないし、EMOBILE HW Utilityからも接続ボタンがグレーアウトして押せない…

調べてみるとMobile Partnerというツールを使うとEMOBILE HW Utilityよりも詳細設定ができて、検索Bandを変更できる?らしい。
早速ダウンロードしてインストールし、ToolのOptionからNetworkを開いて

Network TypeをWCDMA preferredに、BandをGSM900/GSM1800/WCDMA2100に設定する。
実はここのBand設定で結構つまずいて、最初All Bandsにしてたんだけどなかなかつながらなくて、最終的にGSM900/GSM1800/WCDMA2100にすることによりつながった。一旦GSM900/GSM1800/WCDMA2100を選択してApplyを押すとAll Bandsを選択してからApplyを押してもつながるという不思議な感じ…


Registration ModeをManualにしてRefreshすると検索が始まる。
うまくいくとDocomoが表示されるので選択してRegisterを押せばおk。
すると本体が赤ランプ点滅から青ランプ点滅に変わる。

Mobile Partnerからでも設定すればつながるみたいだけど、EMOBILE HW UtilityのほうにAPN設定してしまったのでEMOBILE HW Utilityから接続してみた。

接続ボタンが押せるようになってる!


おつながった!
もともとWDCMA1700しか使えないようにBand無効化されてたのかな
まぁ確かにWeb上の仕様だと1.7GHz帯しか対応していないことになっているし…

とりあえずこの状態でラズパイにつないでもちゃんと青ランプが点滅しているので一旦Mobile Partnerで設定すればDocomoの電波を掴むようになるっぽい。
ラズパイの設定しないと。


2019年12月15日日曜日

イヤホンをMMCX化してみた

友人からイヤホンの断線修理を頼まれたのでMMCX化してみた。
ウォークマンのノイキャン用のマイク付きのイヤホンらしいけど、どうせ壊れているのでノイキャン機能はなくてもいいということだったので…


MMCX端子はAliexpressで5個入りで1.4ドルだった。
本来はPCBに水平に取り付けるタイプかな。


イヤホン側、どうやって分解しようかなと思って引っ張ってみたら簡単に割れた…
根本からケーブルハンダできるタイプだったので、半田でケーブルを外してMMCX端子を付けただけ。
MMCX端子は足を少しカットする必要があった。

あとは接着剤で張り合わせて固定。


最後にエポキシパテで端子の周りを固めてみた。
初めてMMCX化したけどMMCX端子は丸いタイプのほうが合わせやすいかも?
ちなみにケーブルの方はeBayで3ドルぐらいのMMCXケーブルをとりあえず付けておいた。
これで次回断線しても本人にケーブルだけ購入してもらえば良いし。

2019年12月7日土曜日

Xbox Oneコントローラをアップデートしてみた。

HaloがSteamで発売ということで久しぶりにパッドでFPSしてみようということでXbox Oneコントローラを出してきたんだけども、Xboxアクセサリーアプリで確認したらファームウェアアップデートがあったのでアップデートしてみた。

前もBluetooth対応版(オーディオジャック付き)に買い替えたときにもアップデートをしたらBluetoothでの再接続が安定したりしていたのでたまに確認するといいかも。

Xbox One本体を持っていないのでWindowsでアップデートする場合はXboxアクセサリーと言うアプリをインストールする必要がある。
Windows 10のMicrosoft Storeからダウンロードできる。

Eliteコントローラとかの設定とかには必要みたいだけどノーマルコントローラだとあんまりいらないアプリだと思っていたらいつの間にかボタンを入れ替えたりみたいなカスタマイズできるようになっていたりしていた…


Bluetoothで接続されているとUSBでつなぐように促される。


USBで接続したら"…"をクリック。

するとアップデートがある場合は更新するかどうか確認されるので更新ボタンを押す。

更新中に抜き差しとかケーブルの接触悪いと失敗してしまうので静かに放置。
ちなみに失敗した場合でUSBでつなぐと"Xbox One Controller DFU"と認識されている場合はもう一度Xboxアクセサリーアプリを立ち上げるとコントローラーの更新が必要ですというボタンが現れるのでそのボタンを押せば再試行できるらしい。

今回のアップデートでv4.8.1923.0にアップデートされた。
2019年10月23日に更新されていたようで、更新内容は
・コンソールとのワイヤレス接続信頼性の向上
・Bluetoothペアリングの改善
・Android端末にBluetooth接続した際のしいたけボタンの挙動変更
・Bluetooth接続時の信頼性の修正
らしい。

ちなみにアップデート後はBluetooth再ペアリングしないとつながらなかった。

さてHALO Reachプレイするか