スーパーファミコン コントローラーの無線化プロジェクト:2ch化(SFC側)動作します!

前回のコントローラーの無線化プロジェクト:2ch化の続き。

前回SFCのコントローラー内のシリアル/パラレル変換のICが3.3Vでも動くという事がわかったので、ひょうとして、SFC本体側も3.3VでHIGH、LOW(ボタンが押されているか?おされていないか?)を判定できるのでは?そもそも5Vの半分は2.5Vで、3.3Vはそれを超えるのでアイパターンは目を開いてくれていて判定できるはず・・・


2CH側へ、ジャンバーケーブルを突っ込んで、コネクタ中の端子に接触させる。黄色いテープで無理やり固定。


赤いケーブルがDATの出力。レベルコンバーターの5V側から出してやって動く事を確認。

その後、赤いケールを写真のようにLOW側、つまり3.3Vロジックの方に接続。それでもちゃんと動いてくれる事を確認!

つまりDATはコントローラー側からSFC本体にどのボタンが押されているかの信号を送り込むラインだが3.3VでOK! 

次に、P/SのトリガーがSFC本体から送られてきて、これをマイコンCH32V003でトリガーを検出する。この検出には11ピンをアサインしている。この11ピンはPC1で、このピンは5Vトレラント!つまり5Vを入力しても壊れないピン。つまり、3.3Vロジック系と5Vロジック系の信号のやり取りのために、レベルコンバーターを用意していたが、結論は不要!!

非常に回路が簡素化される。

最終的には、上のようになった。2つのマイコンの間のデータのやり取りのためのSPIの接続4つだけというシンプル化。

【SFC側のESPのスケッチ】
前回https://funasover.blogspot.com/2025/05/blog-post.htmlは1ch対応で動作確認していたのを2ch化する。

#include <ESP8266WiFi.h>  // WiFi
#include <WiFiUDP.h>      // UDP
#include "SPISlave.h"

#define DAT_OUTPIN_BIT (1 << 4)
uint32_t button_status;
uint16_t button_status1;
uint16_t button_status2;
// SSIDとパスワード
const char *ssid = "esp8266";
const char *password = "12345678";
//create UDP instance
static WiFiUDP udp;

#define LOCAL_PORT 5000   // port number for local
#define REMOTE_PORT 5000  // port number for remote
// IPアドレス
IPAddress localIP;   //local IP address
IPAddress remoteIP;  // remote address

void onRising() {
  noInterrupts();
  //B Y SELECT  START 上 下 左 右 A X L R HHH
  //button_status1 = 0b1010101010101011; // for test

  delayMicroseconds(5);
  for (int j = 0; j < 16; j++) {
    WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (8 - 4 * (bitRead(button_status1, 15 - j))), DAT_OUTPIN_BIT);

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

  interrupts();
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);

  pinMode(LED_BUILTIN, OUTPUT);


  button_status1 = 0b1010101010101111;
  button_status2 = 0b1010101010101111;
  // AP setting
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);
  delay(100);
  localIP = WiFi.softAPIP();
  Serial.println();
  Serial.print("AP IP address: ");
  Serial.println(localIP);

  // start udp
  udp.begin(LOCAL_PORT);
  delay(100);


  SPISlave.onDataSent([]() {
    uint8_t data_to_send[4] ;//= { Counter, 10 };
    data_to_send[0]=button_status1>>8;
    data_to_send[1]=button_status1 & 0x00FF;
    data_to_send[2]=button_status2>>8;
    data_to_send[3]=button_status2 & 0x00FF;
    SPISlave.setData(data_to_send, 4);
  });

  SPISlave.begin();
}
void loop() {
  static char packetBuffer[10];
  static char packetBuffer1[5];
  static char packetBuffer2[5];
  int packetSize = udp.parsePacket(); //read new packet size.
  if (packetSize) { //when new packet is not exit(packeSize is zero), values does not change. keep privious values.
    int len = udp.read(packetBuffer, packetSize);
    if (len > 0) packetBuffer[len] = '\0';

      for (int i=0;i<5;i++)
  {
    packetBuffer1[i]=packetBuffer[i];
    packetBuffer2[i]=packetBuffer[i+5];
  }
      button_status1 = atoi(packetBuffer1);
      button_status2 = atoi(packetBuffer2);
  }

  Serial.print(button_status1,BIN);
  Serial.print(" ");
  Serial.println(button_status2,BIN);
}
動作の説明は以下。
・パケットを受信するが、16ビットの整数は最大で65535、5桁の数字の文字で受け取る。
・ch1とch2が連続で送られてくるので10文字分パケットを読み取る。
・最初の5桁をpaketBuffer1,次の5桁をpacketBuffer2に格納。
・button_status1とbutton_status2と2つ用意してあげ、atoiで数値に戻してそれぞれに格納。
・SPIのホストから要求があったら特に送信するサブルーチン内data_to_sendの並列に順に格納して4バイト分を送信する
という動作。

【CH32V003のプログラム】
どうもESPのGPIOインターラプトがかかる時間がばらつきが大きい事から、CH32V003をわざわざ使っている。
のコードを2ch対応に変更する。

MountRiver Studioのプログラム


//CH32V003
//    -----------------------
//1 - PD4                PD3 - 20
//2 - PD5(UTX)           PD2 - 19
//3 - PD6(URX)     (SWIO)PD1 - 18
//4 - PD7          (MISO)PC7 - 17
//5 - PA1          (MOSI)PC6 - 16
//6 - PA2          (SCK) PC5 - 15
//7 - VSS (GNG)          PC4 - 14
//8 - PD0                PC3 - 13
//9 - VDD                PC2 - 12
//10- PC0                PC1 - 11
//    -----------------------


#include "debug.h"
#include "string.h"

/* SPI Mode Definition */
#define HOST_MODE   0
#define SLAVE_MODE   1

/* SPI Communication Mode Selection */
//#define SPI_MODE   HOST_MODE
#define SPI_MODE   HOST_MODE

/* Global define */
#define Size 32


/* Global Variable */

uint8_t rxBuf[34];
uint16_t rxvalue1;
uint16_t rxvalue2;


uint8_t readBits(uint16_t value, uint8_t bit_position) {
  return (value >> bit_position) & 1; // Returns 1 if the specified bit is 1, otherwise returns 0.
}

void Delay_Ns(uint32_t ns) {
    for(int i=0;i<ns;i++) {
        asm("NOP"); // Insert an empty command and wait
    }
}

void GPIO_INIT(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure={0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 |  GPIO_Pin_3 |GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void EXTI0_INT_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    /* GPIO PC1 setting */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    /*EXTernal Interrupt(EXTI)
    /* GPIOD ----> EXTI_Line1 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource1);
    EXTI_InitStructure.EXTI_Line = EXTI_Line1;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//EXTI_Trigger_Falling EXTI_Trigger_Rising
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    /* NVIC*/
    NVIC_InitStructure.NVIC_IRQChannel = EXTI7_0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
void EXTI7_0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI7_0_IRQHandler(void)
{

    EXTI_ClearFlag(EXTI_Line1); // rest exti
    for(int i=0; i<16; i++){
       // uint16_t test=0b0101010101010000;

       //GPIO_WriteBit(GPIOC, GPIO_Pin_2, readBits(rxvalue1, 15-i));
       //GPIO_WriteBit(GPIOC, GPIO_Pin_3, readBits(rxvalue2, 15-i));

        GPIOC->BSHR = (readBits(rxvalue1, 15-i)<<2|readBits(rxvalue2, 15-i)<<3|~readBits(rxvalue1, 15-i)<<2+16|~readBits(rxvalue2, 15-i)<<3+16);
        if (i==0){
            Delay_Us(18);
            Delay_Ns(3);
        }else{
            Delay_Us(8);
            Delay_Ns(1);
        }
    }

       // GPIO_WriteBit(GPIOC, GPIO_Pin_0, readBits(rxvalue1, 14));
    GPIOC->BSHR = (1<<2+16|1<<3+16);

       // GPIO_WriteBit(GPIOC, GPIO_Pin_2, 0);
      //  GPIO_WriteBit(GPIOC, GPIO_Pin_3, 0);

}
/*********************************************************************
 * @fn      SPI_FullDuplex_Init
 *
 * @brief   Configuring the SPI for full-duplex communication.
 *
 * @return  none
 */
void SPI_FullDuplex_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    SPI_InitTypeDef SPI_InitStructure={0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1, ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

#if (SPI_MODE == HOST_MODE)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

#elif (SPI_MODE == SLAVE_MODE)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

#endif

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//(SPI_DIRECTION_2LINES_RXONLY //SPI_Direction_2Lines_FullDuplex

#if (SPI_MODE == HOST_MODE)
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

#elif (SPI_MODE == SLAVE_MODE)
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;

#endif

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;// SPI_CPOL_Low SPI_CPOL_High
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//SPI_FirstBit_MSB
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init( SPI1, &SPI_InitStructure );

    SPI_Cmd( SPI1, ENABLE );
}

void SPISendReceiveBytes(uint8_t *sendData, uint8_t *getData, uint32_t length)
{
    for(int i = 0; i  < length; i ++)
    {
        //Send SPI Byte
        while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) == RESET ); // wait while flag is zero or TX buffer not empty
        SPI_I2S_SendData( SPI1, sendData[i] );

        //Receive SPI Byte
        while(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) == RESET ); // wait while flag is zero or RX buffer is empty
        getData[i] = SPI_I2S_ReceiveData( SPI1 );

    }
}
void printBinary(int num) {
    for (int i = 15; i >= 0; i--) {
        printf("%d", (num >> i) & 1);
    }
}



/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();

    USART_Printf_Init(115200);//460800
    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    GPIO_WriteBit(GPIOC, GPIO_Pin_4, Bit_RESET);
    GPIO_ResetBits(GPIOC, GPIO_Pin_4); // CS LOW
    GPIO_ResetBits(GPIOC, GPIO_Pin_2); //  LOW
    GPIO_ResetBits(GPIOC, GPIO_Pin_3); //  LOW
#if (SPI_MODE == SLAVE_MODE)
    printf("Slave Mode\r\n");
    Delay_Ms(1000);

#endif

    SPI_FullDuplex_Init();

#if (SPI_MODE == HOST_MODE)
    printf("Host Mode\r\n");
    Delay_Ms(2000);

#endif
    GPIO_INIT();
    EXTI0_INT_INIT();

    uint8_t txBuf[34];


    for(int i = 0; i < 34; i++){
        txBuf[i] = 0x0;
        rxBuf[i] = 0x0;
    }
    txBuf[0] = 0x03;

    while(1)
    {
        GPIO_SetBits(GPIOC, GPIO_Pin_4); // CS HIGH
        Delay_Us(5);
        GPIO_ResetBits(GPIOC, GPIO_Pin_4); // CS LOW
        SPISendReceiveBytes(txBuf,rxBuf,6);
        GPIO_SetBits(GPIOC, GPIO_Pin_4); // CS HIGH
        Delay_Us(5);
        GPIO_ResetBits(GPIOC, GPIO_Pin_4); // CS LOW

        rxvalue1=(rxBuf[2] << 8) | rxBuf[3];
        rxvalue2=(rxBuf[4] << 8) | rxBuf[5];
        printBinary(rxvalue1);
        printf(" ");
        printBinary(rxvalue2);
        printf("\r\n");
        //}
        Delay_Ms(10);

    }
}


以前1chからの変更点

一つ目は、以前はSPIホストとして、0x03、0x00を送り込むとスレーブ側が反応して送り返してきてくれる。読み取る際は、最初の0x03、0x00も受信して合わせて6バイト読み込む事になる

SPISendReceiveBytes(txBuf,rxBuf,6);

受信したら、rxBuf[2] とrxBuf[3]をつなげてあげて、CH1のボタン状態16ビットに、 rxBuf[4] とrxBuf[5]をつなげてCH2のボタン状態16ビットに。

        rxvalue1=(rxBuf[2] << 8) | rxBuf[3];

        rxvalue2=(rxBuf[4] << 8) | rxBuf[5];

これでCH1のDATの出力ピンGPIO_Pin_2、CH2のDAT出力ピン、GPIO_Pin_3に送り出してやる。

GPIO_WriteBit(GPIOC, GPIO_Pin_2, readBits(rxvalue1, 15-i));

GPIO_WriteBit(GPIOC, GPIO_Pin_3, readBits(rxvalue2, 15-i));

と記載してしまうと、GPIO_Pin_2の処理が終わってから、GPIO_Pin_3になるので、タイムラグが発生する。


赤がCH1のDAT、一番下がCH2のDATタイミングがずれる。

そこで、GPIOのレジスタに値をセットして、同じタイミングで出力されるようにする。 
GPIOC->BSHR = (readBits(rxvalue1, 15-i)<<2|readBits(rxvalue2, 15-i<<3|~readBits(rxvalue1, 15-i)<<2+16|~readBits(rxvalue2, 15-i)<<3+16);

赤を一番したの黄色が同じタイミングになっている事がわかる。(Yボタンを同時に押した際の出力のキャプチャー)。

GPIOCのGPIOピン2をHIGHにする時は、
GPIOC->BSHR = (1<<2);
で設定できる。同じくLOWにする時は16ビットずらしてあげる。
GPIOC->BSHR = (1<<2+16);

GPIOC->BSHR = (0<<2);としてもLOWにはならいので、16ビット先を変更するようにしないといけない。

GPIOC->BSHR = (readBits(rxvalue1, 15-i)<<2|readBits(rxvalue2, 15-i<<3|~readBits(rxvalue1, 15-i)<<2+16|~readBits(rxvalue2, 15-i)<<3+16);

こう記述するとrxvalue1が0の時は、~readBitsとしているので、つまり+16している側が1になりLOWに設定されて、rxvalue1が1の時は、HIGHの設定が有効になって~readBits側はゼロが入る。

これで実際に2プレイヤーでSFCのゲームをしてみたが、ちゃんと2コンとも動作!!
ながかったが、コントローラーの無線化プロジェクトに目途、ブレッドボード上では2ch分無線で飛ばして遊ぶ事ができる!!

次は、PCBの基板を起こす設計へ。





コメント

このブログの人気の投稿

Attiny85とAQM0802A(LCD)のI2C接続

CH9329で日本語キーボード109で正しく表示する方法