スーパーファミコン コントローラーの無線化プロジェクト:ESPからの制御

 前回(https://funasover.blogspot.com/2025/03/blog-post.html)、SFCのコントローラーの仕様というのを確認したので、次のステップとしてEPSのマイコンを使って、信号をSFCに送り込む事を目指す。


P/SのシグナルをGPIO5に入力し、パルスの立ち上がえりを検出。割り込みを入れ、コントローターのボタンの押されている状態にあわせて、DATを出力する。

ESPを使っているので、3.3V系とSFCの5Vの電圧違いを吸収するために、レベルコンバーターを間に挟んでいる。



スケッチは、以下。

#define PS_READ_PIN 5
#define DAT_OUTPIN 4

unsigned int button_status;

void onRising() {
  //B Y SELECT  START 上 下 左 右 A X L R HHH
  button_status = 0b0101010101010000; //ボタンが押されていたら1

  for (int i = 0; i < 16; i++) {
    if (bitRead(button_status, 15 - i) == 0B00) {
      digitalWrite(DAT_OUTPIN, HIGH);
    } else {
      digitalWrite(DAT_OUTPIN, LOW);
    }
    if (i == 0) {
      delayMicroseconds(17);
      for (int i = 0; i < 2; i++) { asm("nop \n"); };  // i<10 0.65usec
    } else {
      delayMicroseconds(10);
      for (int i = 0; i < 3; i++) { asm("nop \n"); };  // i<10 0.65usec
    }
  }
  digitalWrite(DAT_OUTPIN, LOW);
}

void setup() {

  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(DAT_OUTPIN, OUTPUT);  
  pinMode(PS_READ_PIN, INPUT);
  digitalWrite(DAT_OUTPIN, LOW);
  attachInterrupt(digitalPinToInterrupt(PS_READ_PIN), onRising, RISING);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, LOW);  // Turn the LED on (Note that LOW is the voltage level
  // but actually the LED is on; this is because
  // it is active low on the ESP-01)
  delay(1000);                      // Wait for a second
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
  delay(2000);                      // Wait for two seconds (to demonstrate the active low LED)
}

button_statusとして16ビットのintを用意し目的ボタンが押されていたら1という事にする。デバッグしやすいように、B→Y→SELECT→START→上→下→左→A→X→L→Rで交互に押す、押さないとして、0b0101010101010000;とゼロと1を交互にテストのため並べた。

まず、PSが立ち上がりをattachInterruptで検出するが、これには5usec程度かかる。こればかりは仕方がない。
しかし、最初のBボタンを押されているか?の判定はP/Sパルス12usecに続いて6usec経過した所なので、十分に時間余裕はあり、次のクロックの立ち上がりまでで調整。

      delayMicroseconds(17);
      for (int i = 0; i < 2; i++) { asm("nop \n"); };  // i<10 0.65usec

ここがその調整の努力の跡で、17msec+ちょっとHIGHの状態を続けている。1msec以下の時間調整のためにfor (int i = 0; i < 2; i++) { asm("nop \n"); };を使っている。
asm("nop \n");一回で0.065usecの調整がおよそできていた。

      delayMicroseconds(10);
      for (int i = 0; i < 3; i++) { asm("nop \n"); };  // i<10 0.65usec

それ以外の所は、12usecの周期でクロックが変化が、12usecの待ち時間を入れるとif文の処理時間なのでそれより長くなるので、実際の所は、10usecの遅延+ちょっと調整でちょうど12usecに調整。

各クロックの立下りで状態を示す信号幅のほぼ中心に調整できている。

実際のクロックは12usecピッタリではないが、誤差の範囲であって、後半のボタン入力においてもクロック立下りの位置でボタン状態を読み取れることになる。

いざ、SFCと接続してみる。先ほど接続してなかったDATをSFCのDATとレベルコンバーターを介して接続。
ソースも、少し編集して、1秒おきに、STARTボタンと、Aボタンを交互に押すようにESPマイコンからコントローターの信号を出してやる。

#define PS_READ_PIN 5
#define DAT_OUTPIN 4

unsigned int button_status;

void onRising() {
  //B Y SELECT  START 上 下 左 右 A X L R HHH
 // button_status = 0b0101010101010000; //ボタンが押されていたら1

  for (int i = 0; i < 16; i++) {
    if (bitRead(button_status, 15 - i) == 0B00) {
      digitalWrite(DAT_OUTPIN, HIGH);
    } else {
      digitalWrite(DAT_OUTPIN, LOW);
    }
    if (i == 0) {
      delayMicroseconds(17);
      for (int i = 0; i < 2; i++) { asm("nop \n"); };  // i<10 0.65usec
    } else {
      delayMicroseconds(10);
      for (int i = 0; i < 3; i++) { asm("nop \n"); };  // i<10 0.65usec
    }
  }
  digitalWrite(DAT_OUTPIN, LOW);
}

void setup() {

  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);  // Initialize the LED_BUILTIN pin as an output
  pinMode(DAT_OUTPIN, OUTPUT);   // Initialize the LED_BUILTIN pin as an output
  pinMode(PS_READ_PIN, INPUT);
  digitalWrite(DAT_OUTPIN, LOW);
  attachInterrupt(digitalPinToInterrupt(PS_READ_PIN), onRising, RISING);
  button_status = 0b0000000000000000;
}

// the loop function runs over and over again forever
void loop() {
   //B  Y SELECT  START 上 下 左 右 A X L R HHH
   button_status = 0b0001000000000000;//start
  digitalWrite(LED_BUILTIN, LOW);  // Turn the LED on (Note that LOW is the voltage level
  // but actually the LED is on; this is because
  // it is active low on the ESP-01)
  delay(1000);                      // Wait for a second
  button_status = 0b0000000001000000;//A
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
  delay(1000);                      // Wait for two seconds (to demonstrate the active low LED)
}

実際、動かした様子。ドンキーコングだが電源を入れてから、STARTとAを交互に押しているのでゲームがスタートするところまで、自動で進んでいく!

これだけでも、ESPマイコンによるRPAツールができてRPGとかのゲームで自動レベルアップとかできぞう・・・。https://pokemonit.com/splatoon3-auto-nawabattler/


ロードマップ上、マイコンで自動制御できるところまではできた。button_statusにあたる2バイト(16ビット)の信号を無線で飛ばしてくれば、無線化ができぞう・・・もう少しかかるが・・・



コメント

このブログの人気の投稿

Attiny85とAQM0802A(LCD)のI2C接続

Attiny85 FuseRest