PICマイコンで、スーファミコントローラを入力デバイスにする。

# **ネット上にあったスーパーファミコンのコントローラの動作仕様が、おもしろそうだったので、PICマイコンで動かしてみました。**
<iframe width="560" height="315" src="https://www.youtube.com/embed/tyLkbvRdIhY?si=GFGufNuhNWaa-zE9" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![IMG_4541.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/891e9d22-ae40-90d4-9fbd-1f58eb71bc8a.jpeg)


:::note info
 **マイコンのスイッチ入力に、スーパーファミコンのコントローラが使えれば、操作性がよくなり、アプリケーションの幅が広がりそうだったので、トライしてみました。**

**使用したマイコン：PIC16F18326**
**USBシリアル変換器：MCP2221A** 
:::

# 1.スーパーファミコンコントローラの基板

![IMG_4538.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/4c22bf1e-d949-1973-1ff2-0dfd4c25c4a4.jpeg)

基板には、V520Bと印字された20ピンのICが乗っています。

![IMG_4540.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/0cbc1bc3-4789-9c7b-7df2-a908d6fd8016.jpeg)

このV520Bの仕様が良くわからないのですが、ネット情報を集めて、端子機能をまとめてみると以下のようになりました。
![１.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/a73410f2-0927-a56e-6a4c-7df6e1fd3582.gif)
![IMG_4550.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/0d1e6ce0-0643-c055-2a73-0c90264d1559.jpeg)

スーファミオリジナルのコネクタは、使用せず、ケーブルを切断。個別の信号線をXHコネクタ端子に変更しました。

基板のコネクタに合わせて、４ピンコネクタと、３ピンコネクタに分けて、PICマイコンと接続しています。

![コネクタ写真.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/8e46f926-2a12-02cc-2529-efc61d09a1a4.jpeg)

:::note info

**電源ピンは5v(白)、GNDピン(茶)です。**
データ線は、全部で3本。CLOCK, P/S, DATA1

配線の色
| GND | NC | NC | DATA1 | P/S | CLOCK | VCC(5V) | 
|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|
|茶|-|-|赤|オレンジ|黄|白|


**・CLOCK端子(黄)は、V520Bの入力端子。
・P/S端子(オレンジ)は、パラレルインプットをラッチするトリガー入力端子。
・DATA1端子(赤)は、ボタン押下の16ビットデータのv520B出力端子。**

**＜PIC16F18326側の処理＞**

**１．　1ビット目は、P/S端子とClock端子を同期させて、1パルスを出力し、1ビットデータを取得。**

・P/S端子で、データ先頭の同期をとります。
・P/S端子は、webによっては、ラッチLatch端子と呼んでいることから、パラレルインプットをここで取り込んでいると思います。
・P/S端子をLowに落とすことで、データが確定しているはずです。**

**２．　2ビット目以降は、clock端子にパルスを出力、残り15ビットをシリアルデータでビットごとに、取得。**
:::

# 1.スーファミコントローラのタイミングチャート
:::note info
![SFCコントローラタイミングチャート.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/d34c41a7-ab36-3d6c-c4f9-da2c4a00378a.jpeg)


**DATA1(赤)は、アクティブロー出力です。Lowで出力されたビットに対応したボタンが押下(おうか)されたことになります。**
:::


:::note
# 3.PIC16F18326 ソースコード
押したボタンがシリアル出力されます。同時に押したボタンも、表示されます。
![シリアル出力.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/41008623-2841-404b-fdd9-814db4d3822b.png)

P/S:RC3(Output)
CLOCK:RC4(Outpu)
DATA1:RA4(Input)

```C:sFamiCon.h
//*******************************************//
//        スーファミコントローラ制御
//  PS：データ取得トリガー
//  CLK:クロック
//  DAT:データ(active low)
//*******************************************//    

#ifndef SFAMICON_H
#define	SFAMICON_H

#ifdef	__cplusplus
extern "C" {
#endif

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
        
//define pins---------------------------
#define sFami_PS      PORTCbits.RC3
#define sFami_CLK     PORTCbits.RC4
#define sFami_DAT     PORTAbits.RA4

//スーファミ構造体
typedef struct{
    uint16_t data;      //16bitsData
}_sFamiCon;

extern _sFamiCon SFC;
    
//functions------------------------------
extern void initSFamiCon(void);
extern uint16_t getSFamiConButtons(void);
extern void getSFamiConButtonName(uint16_t vButtonData);


#ifdef	__cplusplus
}
#endif

#endif	/* SFAMICONCONTROLLER_H */
```

```C:sFamiCon.c
#include "sFamiCon.h"
#include "peripheral.h"

//スーファミ構造体
_sFamiCon SFC;

//スーファミ初期化
void initSFamiCon(void)
{
    sFami_PS=0;
    sFami_CLK=0;
    SFC.data=0;
}

//***********************************************
//スーファミコントローラ　押下ボタン取得関数
//return 16bitボタンデータ
//***********************************************
uint16_t getSFamiConButtons(void)
{
    uint16_t buttonData;    //押下ボタンデータ
    uint8_t clockCount;     //クロックカウンタ
    
    //0.変数初期化-----------------------------------------------
    buttonData=0x0000;
    clockCount=0;

    //1.通信開始PS出力&クロック-----------------------------
    sFami_PS=1;
    sFami_CLK=1;
    __delay_us(10);
    sFami_PS=0;
    sFami_CLK=0;
    __delay_us(10);

    //ビットデータ取込み（1ビット目）----------------------------
    buttonData|=sFami_DAT;
    buttonData<<=1;
    clockCount++;
    
    //2.ビットデータ取込み（２ビット目以降１６ビットまで)----------
    do{
        sFami_CLK=1;
        __delay_us(5);
        sFami_CLK=0;
       // __delay_us(5);
        buttonData|=sFami_DAT;
        if(clockCount!=15)
        {
            buttonData<<=1;
        }
        clockCount++;
    }while(clockCount!=16);
    //１６ビットデータ取込み終了------------------------------------
    
    //3.押下ボタンデータを返信
    return (buttonData&0xFFF0);//MSB16bits返信
}

//***********************************************
//スーファミコントローラ　押下ボタン名　取得関数
//vButtonData:押下ボタンデータ
//***********************************************
void getSFamiConButtonName(uint16_t vButtonData)
{
    if((vButtonData&0x8000)==0x0)
        printf("B ");
    if((vButtonData&0x4000)==0x0)
        printf("Y ");
    if((vButtonData&0x2000)==0x0)
        printf("SELECT ");
    if((vButtonData&0x1000)==0x0)
        printf("START ");
    if((vButtonData&0x0800)==0x0)
        printf("UP ");
    if((vButtonData&0x0400)==0x0)
        printf("DOWN ");
    if((vButtonData&0x0200)==0x0)
        printf("LEFT ");
    if((vButtonData&0x0100)==0x0)
        printf("RIGHT ");
    if((vButtonData&0x0080)==0x0)
        printf("A ");
    if((vButtonData&0x0040)==0x0)
        printf("X ");
    if((vButtonData&0x0020)==0x0)
        printf("L ");
    if((vButtonData&0x0010)==0x0)
        printf("R ");
    printf("\n");
}
```

```C:main.c

// PIC16F18325 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FEXTOSC = OFF    // FEXTOSC External Oscillator mode Selection bits (Oscillator not enabled)
#pragma config RSTOSC = HFINT1  // Power-up default value for COSC bits (HFINTOSC (1MHz))
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2)
#pragma config CSWEN = ON      // Clock Switch Enable bit (The NOSC and NDIV bits cannot be changed by user software)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config MCLRE = ON     // Master Clear Enable bit (MCLR/VPP pin function is digital input; MCLR internally disabled; Weak pull-up under control of port pin's WPU control bit.)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config WDTE = OFF       // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored)
#pragma config LPBOREN = OFF    // Low-power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bits (Brown-out Reset disabled)
#pragma config BORV = LOW       // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V)
#pragma config PPS1WAY = ON     // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset)
#pragma config DEBUG = OFF      // Debugger enable bit (Background debugger disabled)

// CONFIG3
#pragma config WRT = OFF        // User NVM self-write protection bits (Write protection off)
#pragma config LVP = ON        // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.)

// CONFIG4
#pragma config CP = OFF         // User NVM Program Memory Code Protection bit (User NVM code protection disabled)
#pragma config CPD = OFF        // Data NVM Memory Code Protection bit (Data NVM code protection disabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <PIC16f18326.h>

#include "peripheral.h"
#include "sFamiCon.h"

//Port初期化PPS割り付け
void port_init(void);

void __interrupt()isr()
{
    if(PIR0bits.TMR0IF)
    {
        PIR0bits.TMR0IF=0;
        TMR0L=0x56; //10msec
        tm0.count++;
        if(tm0.count==50)
        {
            tm0.count=0;
            PORTAbits.RA5=~PORTAbits.RA5;
        }
    }
}


void main(void)
{    
    //Oscillator 初期化
    OSCCON1bits.NOSC=0b110; //HFINTOSC(1MHz)
    OSCCON1bits.NDIV=0b000; //divide 1
    OSCFRQbits.HFFRQ=0b101; //100:8MHz 101:12Mhz
    //ペリフェラル初期化
    port_init();
    USART_INIT();
    Timer0_init();
    //スーファミ初期化
    initSFamiCon();
    Interrupt_START();
    
    while(1)
    {
        
        SFC.data = getSFamiConButtons();//スーファミボタンデータ取得
        //どこが押されたか、照合---------------------------------
        if(SFC.data!=0xFFF0)
            getSFamiConButtonName(SFC.data);
        
        __delay_ms(10);
    }   
}

//****************************************//
//PIC16F18326ポート初期化
//****************************************//
void port_init(void)
{
    //PORTA initialize---------------
    TRISA = 0x00;
    TRISAbits.TRISA4=1;
    ODCONA = 0x00;  
    ANSELA = 0x00; 
    WPUA =0x00;
    PORTA=0x00;
    
    //PORTC initialize---------------
    TRISC=0x00;
    TRISCbits.TRISC5=1;//RX
    ODCONC =0x00;
    ANSELC=0x00;
    WPUC =0x00;
    PORTC=0x00;

    //PPS ペリフェラルピン割り付け-----
    //USART
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RXPPS =0x15;    //RC5:RX
    RC2PPS=0x14;    //RC2 output pin for TX
    TXPPS =0x12;    //RC2:TX
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
}
```
```C:peripheral.h

#ifndef PERIPHERAL_H_
#define	PERIPHERAL_H_

#include <xc.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

//oscillator speed 12MHz
#define _XTAL_FREQ 12000000
//USART
#define BAUDRATE 115200
//USART---------------------------------------------------------------
#define SET_SPBRG_DATA  (((unsigned int)((_XTAL_FREQ/16)/BAUDRATE))-1)
#define SET_SPBRG_DATA1 (((unsigned int)((_XTAL_FREQ/4)/BAUDRATE))-1)

#define USART_BUF_SIZE 30 

//USART data structure 
typedef struct {
    uint8_t length;
    uint8_t rxBuf[USART_BUF_SIZE];
    bool rxCompleted;
}_usart;
extern _usart usart;

//function prototype
extern void USART_INIT(void);
extern void putch(uint8_t byte);

typedef struct{
    uint8_t count;
}_tm;
extern _tm tm0;
extern void Timer0_init(void);
extern void Interrupt_START(void);

#endif	/* PERIPHERAL_H_ */

```
```C:peripheral.c

#include <xc.h>

#include <stdio.h>
#include <stdlib.h>
#include "peripheral.h"

_usart usart;
/*-----------------------------------------
 *USART
 *-----------------------------------------*/
void USART_INIT(void)
{
    unsigned char buf;
    unsigned int brg;
    //SPBRG value set
    brg = SET_SPBRG_DATA1;
    SP1BRGL =(unsigned char) brg;
    SP1BRGH =(unsigned char) (brg>>8); 
    //TX-------------------------------------
    TX1REG=0x00;
    //TXSTA SYNC:1 BRGH:1 TXEN:1 TX9:0
    buf =TX1STA & 0x83;
    TX1STA = buf | 0x24;
    //RX-------------------------------------
    //RCSTA  SPEN:1 RX9:0 CREN:1
    buf =RC1STA & 0x2F;
    RC1STA =buf | 0x90;
    //BAUR RATE CONTROL REGISTER
    BAUD1CON = 0x08;
    //if interrupt is desired, TXIE set(PIE1)
    PIE1bits.TXIE=0;//disable
    //if interrupts are desired, RCIE set(PIE1)
    PIE1bits.RCIE=1;//enable
}


void putch(unsigned char byte)
{
    while(!PIR1bits.TXIF)continue;
    TX1REG=byte;
}

/*---------------------------------------------------
 Timer0
 ---------------------------------------------------*/
_tm tm0;

void Timer0_init(void)
{
    TMR0L=0x56; //10msec
    TMR0H=0xFC; //10msec
    T0CON0=0x90;//16bits timer
    T0CON1=0x45;//prescaler 1/32
    PIE0bits.TMR0IE=1;
    tm0.count=0;
}

void Interrupt_START(void)
{
    INTCONbits.PEIE=1;
    INTCONbits.GIE=1;
}
```
:::