PICマイコンで、PS1コントローラを制御する

# PlayStationのコントローラが、SPI通信でボタンの押下状況を取り出せるという、ネットの情報があったので、PICマイコンで制御してみました。

![IMG_4649.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/dc9eaf45-80b0-523e-eac0-ae35cf196c2a.jpeg)

# １．PSコントローラの配線
![IMG_4625.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/f00612a0-a191-a9f6-f4cd-d7a5175fb9e9.jpeg)

| ピン番号　|　色　|　ピン名　|　方向　| PIC側SPI名 |
|:--------:|:-------:|:--------:|:-------:|:-------:|
|1|茶|data|controller->playStation|SDI|
|2|オレンジ|command|playStation->controller|SDO|
|3|-|NC|-||
|4|黒|Gnd|-||
|5|赤|VDD|-||
|6|黄|attention|playStation->controller|CS|
|7|青|clock|playStation->controller|SCK|
|8|-|NC|-||
|9|緑|acknoledge|controller->playStation||

:::note
**1.茶-Data** Controller=>PS
オープンコレクト出力。プルアップ抵抗が必要。SPIのMISO線 **PICだとSDIライン。**
**2.オレンジ-Command** PS->Controller
SPIのMOSI **PICのSDOライン**
**3.NC**
**4.黒-Gnd**
**5.赤-VDD**
3.3v～5v
**6.黄-Attention**
SPIで言うところの**CSチップセレクト**
**7.青-Clock**
SPIのクロック線、～500kHz。
**8.NC**
**9.緑-Acknowledge**
バイト送信ごとに12us間だけ出力。アクティブロー。オープンコレクタ出力。pullupが必要。
:::

:::note
**※配線は、ほぼ、SPIと同じです。SCK、SDI、SDO、CSの4本。９番ピンのAcknowledgeは、使用しませんでした。**

**※電源は、５Vで駆動しましたが、3.3vが標準らしいです。**

**※プロトコルが、SPIとは違います。LSBから出力、入力します。**
:::
# ２．コマンドの送信と、ボタンの押下状況の受信。
コントローラのデフォルトモードは、ボタンの押下状態を出力できる状態です。
以下の５バイトのcommand送信で、コントローラのボタン１４個の押下状態がわかります。
最初の３バイトがヘッダー、残りの２バイトが、ボタンの押下状態のデータです。
|byte番号|1|2|3|4|5|
|:----|:----|:----|:----|:----|:----|
|Command|0x01|0x42|0x00|0x00|0x00|
|Data|0xFF|0x41|0x5A|0xFF|0xFF|

**SPIモドキの通信なので、PIC側のMSSP SPIモジュールをすこし改良する必要があります。**

:::note warn
 **MSSPモジュールのSPIと、プレステの通信仕様の違い　その１**
PICマイコンのMSSPモジュールのSPIは、**MSB(最上位ビット)から出力**します。プレステコントローラは、**LSB（最下位ビット）として、ビットデータを取り込んでいきます。** **ビットが反転しています。**![SPI Connection.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/4baa8db6-c915-9ecb-8b03-95d6986e9a0b.jpeg)


例えば、Command(0x01)をPICから送信する場合、0x01をLSBから出力することになりますので、0x80を送信しないと、プレステコントローラーは、0x01を認識しません。

混乱するので、コマンドバイトをビットリバースする関数を用意しました。
:::
:::note warn 
**MSSPモジュールのSPIと、プレステの通信仕様の違い　その２**
SCKラインの極性：**Idle時はHigh。** 通信開始でLowになるように設定します。`SSP1CON1.CKP=1`![waveForm1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/735b551b-dc60-12c1-bf0a-96337e55350e.jpeg)


:::

# ３．0x42:ボタンポーリングコマンド
パワーオンリセットのデフォルトモードで、ボタンの押下状態をポーリングするコマンドです。
1バイト目から3バイト目までは、ヘッダーです。


|byte番号|SPI|値|機能|
|:----|:----|:----|:----|
|1|Command|0x01|スタートバイト|
| |Data|0xFF|スタートバイト返信|
|2|Command|**0x42**|ボタンポーリングコマンド|
| |Data|0x41|上位4ビット:mode｜
|3|Command|Always 0x00|ヘッダー|
| |Data|0x5A|Always 0x5A|

4バイト、5バイト目は、ボタン押下データ
|byte番号|SPI|値|機能|
|:----|:----|:----|:----|
|4|Command|0x42|0x00|空データ|
||Data|0xFF|ボタンデータ(Active Low)|
|5|Command|0x00|空データ|
||Data|0xFF|ボタンデータ(Active Low)|

:::note warn
Commandデータは、PICマイコンから送信するので、ビットリバースして、LSBから出力します。
Dataは、プレステコントローラから送られてくるデータですが、これも、LSBから送信されてきます。
:::
# ４．0x42コマンド ボタン押下状況ビットマップ
４バイト目、５バイト目にボタンの押下データが格納されています。

アクティブロー出力です。

PICは、LSBから受信しますので、最初に飛び込んでくるビットデータは、LSBビットである、”SELECT”ボタンになります。

4バイト目
|ボタン名|Select|-|-|Start|Up|Right|Down|Left|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|byte.bit|4.0|4.1|4.2|4.3|4.4|4.5|4.6|4.7|

5バイト目
|ボタン名|L2|R2|L1|R1|△|〇|☓|□|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|byte.bit|5.0|5.1|5.2|5.3|5.4|5.5|5.6|5.7|

:::note 
すべてのボタンは、アクティブロー出力です。（黄色：SCK　ピンク：SDI）
![ボタンオシロ画面.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/26b06f3b-1d68-2e87-d750-20ee6e3cf5e0.jpeg)

:::

# プレステコントローラ制御コード
```C:SPI.h
#ifndef SPI_H
#define	SPI_H

#ifdef	__cplusplus
extern "C" {
#endif
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "Peripheral18326.h"


#define SPI_SDO_TRIC TRISC2
#define SPI_SS LATCbits.LATC3
#define Low 0
#define High 1

#define MCP23Sadd 0x40
#define MCP23Sadd1 0x42
#define MCP23S_R 1
#define MCP23S_W 0

    
typedef struct{
    uint8_t ret;
    bool error;
}_SPI_RD;    

_SPI_RD SPI_RD;
extern void SPI_Init(void); 
extern void SPI_EN(bool _en);
extern bool SPI_Write(uint8_t _data);
extern _SPI_RD SPI_Read(void);
extern void SPI_Stop(void);
/*-----------------------------------*/
//MCP23S08 I/O expander
/*-----------------------------------*/ 

typedef enum 
{
        IODIR=0x00,
        IPOL,
        GPINTEN,
        DEFVAL,
        INTCN,
        IOCON,
        GPPU,
        mINTF,
        INTCAP,
        GPIO,
        OLAT=0x0A
}_reg;

extern void MCP23S08_Init(uint8_t _deviceAdd);
extern bool MCP23S08_SendControl(uint8_t _device_add, uint8_t _reg_add, uint8_t RW);
extern bool MCP23S08_WriteReg(uint8_t _device_add,uint8_t _reg_add, uint8_t _data);
extern uint8_t MCP23S08_b1ReadReg(uint8_t _device_add, uint8_t _reg_add);

/*----------------------------------------------*/
//PlayStation
/*----------------------------------------------*/
typedef union
{
    uint8_t byte[2];
    uint16_t word;
}psData;

typedef struct
{
    psData data;
}PSC;
extern PSC psc;//プレステデータ構造体
//PlayStationCotroller　functions
extern uint16_t getPSCbuttonData(void);
extern uint8_t bitReverse(uint8_t _val);
extern void getPSCButtonName(uint16_t _btnValue);
#ifdef	__cplusplus
}
#endif

#endif	/* SPI_H */
```

```C:SPI.c
#include "SPI.h"

//*******************************
//MSSP SPI初期化
//CKE:0 CKP:1
//*******************************
void SPI_Init(void)
{
    SSP1CON=0x3A;//SPI masterMode:Fosc/(4*(SSPADD+1))
    SSP1STAT=0x00;
    SSP1ADD=0x3B;//12Mhz 50kh
    //SSP1ADD=0x1D;//12Mhz 100kh
    //SSP1ADD=0x05;//12Mhz 500kh
    SPI_SS=High;
}

void SPI_EN(bool _en)
{
    SSP1CONbits.SSPEN=_en;
}


//*******************************
//MSSP SPI スタート(CS=>Low)
//*******************************
void SPI_Start(void)
{
    SPI_SS=Low;
}
//*******************************
//MSSP SPI バイト送信
//_data:送信バイト
//return:送信成功
//*******************************
bool SPI_Write(uint8_t _data)
{
    uint8_t buf;
    SSP1BUF=_data;//書込みとほぼ同時に出力
    while(!SSP1STATbits.BF);//8ビット出力完了？
    return true;
}
//*******************************
//MSSP SPIバイト読込
//return:_SPI_RD構造体
//*******************************
_SPI_RD SPI_Read(void)
{
    SPI_RD.error=false;
    SSP1BUF=0x00;//8bits空クロック出力
    while(!SSP1STATbits.BF);//8ビット入力完了？
    SPI_RD.ret=SSP1BUF;//受信データ取出し
    return SPI_RD;
}
//*******************************
//MSSP SPI　ストップ(CS=>High)
//*******************************   
void SPI_Stop(void)
{
    SPI_SS=High;
}

/*-----------------------------------*/
//MCP23S08 I/O expander
/*-----------------------------------*/
//*******************************************
//MCP23S08 レジスタ初期化
//*******************************************
void MCP23S08_Init(uint8_t _deviceAdd)
{
    //IODIR:IOピンディレクション：出力
    MCP23S08_WriteReg(_deviceAdd,IODIR,0x00);
    
    //IOCONレジスタ
    //シーケンシャルリード：禁止
    //ハードウェアｱﾄﾞﾚｽ：許可
    MCP23S08_WriteReg(_deviceAdd,IOCON,0x28);
}

//*******************************
//MCP23S08 Controlbyte送信
//_device_add:ｽﾚｰﾌﾞｱﾄﾞﾚｽ
//_reg_add:レジスタアドレス
//RW:0:write 1:read
//*******************************
bool MCP23S08_SendControl(uint8_t _device_add, uint8_t _reg_add, uint8_t RW)
{
    bool ret=false;
   
    ret=SPI_Write(_device_add|RW);
    ret=SPI_Write(_reg_add);
    return ret;
}

//*******************************
//MCP23S08 レジスタ書込み
//_device_add:ｽﾚｰﾌﾞｱﾄﾞﾚｽ
//_reg_add:レジスタアドレス
//_data:書込みデータ
//*******************************
bool MCP23S08_WriteReg(uint8_t _device_add,uint8_t _reg_add, uint8_t _data)
{
    bool ret=false;
    SPI_Start();
    MCP23S08_SendControl(_device_add, _reg_add, MCP23S_W);
    ret=SPI_Write(_data);
    SPI_Stop();
    return ret;
}        


//*******************************
//MCP23S08 レジスタ読込み
//_device_add:ｽﾚｰﾌﾞｱﾄﾞﾚｽ
//_reg_add:レジスタアドレス
//return:_SPI_RD構造体(エラー番号、受信バイト)
//*******************************
uint8_t MCP23S08_b1ReadReg(uint8_t _device_add, uint8_t _reg_add)
{
    _SPI_RD RD;
    SPI_Start();
    MCP23S08_SendControl(_device_add,_reg_add,MCP23S_R);
    RD=SPI_Read();  
    SPI_Stop();
    return RD.ret;
}

//PlayStationContorller functions ans variables
PSC psc;
/*************************************/
//PlayStationController
//button state get
//return:button data
/*************************************/
#if 0
//ビットリバース関数を使う
uint16_t getPSCbuttonData(void)
{
    uint8_t sendData;
    _SPI_RD RD;
    SPI_Start();
    sendData = bitReverse(0x01);
    SPI_Write(sendData);//ヘッダー：スタート
    sendData = bitReverse(0x42);
    SPI_Write(sendData);//ヘッダー：Command
    sendData = bitReverse(0x00);
    SPI_Write(sendData);//ヘッダー：always 0x00
    RD = SPI_Read();
    psc.data.byte[1]=RD.ret;
    RD = SPI_Read();
    psc.data.byte[0]=RD.ret;
    SPI_Stop();
    return psc.data.word;
}
#else
//ビットリバース関数を使用しない
uint16_t getPSCbuttonData(void)
{
    uint8_t sendData;
    _SPI_RD RD;
    SPI_Start();
    SPI_Write(0x80);//ヘッダー：スタート
    SPI_Write(0x42);//ヘッダー：Command
    SPI_Write(0x00);//ヘッダー：always 0x00
    RD = SPI_Read();
    psc.data.byte[1]=RD.ret;
    RD = SPI_Read();
    psc.data.byte[0]=RD.ret;
    SPI_Stop();
    return psc.data.word;
}
 
#endif
 
 /************************************/
 //PlayStationController
 //bit Reverse
 //PSC sends LSB first. MSSP sends MSB first.
 //_val:8bitsコマンドデータ
 //***********************************/
 uint8_t bitReverse(uint8_t _val)
 {
     uint8_t reverseVal=0x00;
     uint8_t i,buf,mask;
     buf=_val;
     mask=0x00;
     for(i=0; i<8; i++)
     {
        mask = buf&0x01;
        mask<<=7-i;
        reverseVal |= mask;
        buf>>=1;
     }
     //printf("befor:%02X after:%02X\n",_val, reverseVal);
     return reverseVal;
 }
 
/****************************************/
//PlayStationController
//function:getPSCButtonName
//_btnValue:ボタン押下データ(16bits)
//***************************************/

void getPSCButtonName(uint16_t _btnValue)
{
    printf("0x%02X ",_btnValue);
    if(!(_btnValue&0x8000))
        printf("SELECT ");
    if(!(_btnValue&0x1000))
        printf("START ");
    if(!(_btnValue&0x0800))
        printf("Up ");
    if(!(_btnValue&0x0400))
        printf("Right ");
    if(!(_btnValue&0x0200))
        printf("Down ");
    if(!(_btnValue&0x0100))
        printf("Left ");
    if(!(_btnValue&0x0080))
        printf("L2 ");
    if(!(_btnValue&0x0040))
        printf("R2 ");
    if(!(_btnValue&0x0020))
        printf("L1 ");
    if(!(_btnValue&0x0010))
        printf("R1 ");
    if(!(_btnValue&0x0008))
        printf("Triangle ");
    if(!(_btnValue&0x0004))
        printf("Circle ");
    if(!(_btnValue&0x0002))
        printf("X ");
    if(!(_btnValue&0x0001))
        printf("Square ");
    putch('\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 = OFF       // 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 <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "Peripheral18326.h"
#include "SPI.h"

#define LED PORTAbits.RA5
void portInit(void);

void __interrupt()isr()
{
   
    unsigned char _ch;
    INTCONbits.GIE=0;
    //USART1 受信割込み処理--------------------------------
    if(PIR1bits.RCIF)
    { 
        if(RC1STAbits.OERR==1)//overRun Error?
        {
            RC1STAbits.CREN=0;  
            RC1STAbits.CREN=1;            
        }
        _ch=getch();      //USART受信バッファから読み出し
        usart.rxBuf[usart.length]=_ch;//User受信バッファに格納
        usart.length++;     //受信長をｲﾝｸﾘﾒﾝﾄ
        if(usart.length>15) usart.length=0;
        if(_ch==0x0A)//\nを受信したら処理開始。
        {
            PIE1bits.RCIE=false;
            usart.rxBuf[usart.length-2]=0x00;
            usart.rxCompleted=true;//main内で処理。    
        }
    }
    
    //Timer0割込み処理100ms
    if(PIR0bits.TMR0IF)
    {     
        PIR0bits.TMR0IF=0;
        tm0.cnt++;
        if(tm0.cnt==5)
        {
            tm0.up=true;
            PIE0bits.TMR0IE=false;
            tm0.cnt=0;
        }else
        {
            TMR0L=0x5E;//100ms
        } 
    }
   
    INTCONbits.GIE=1;
}


int main(int argc, char** argv) {
  
    uint8_t cVal,i,retVal;
    uint8_t spiVal;
    uint16_t pscData;
    //Oscillator Setting
    OSCCON1bits.NOSC=0b110;
    OSCCON1bits.NDIV=0b000;
    OSCFRQbits.HFFRQ=0b101; //100:8MHz 101:12Mhz
    //Peripheral initializing
    portInit();
    USART_INIT();
    timer0Init();
    SPI_Init();
    //SPI device initializing
   
    interruptStart();
    
    printf("Start18325\n\r");
 
    cVal=0;
    spiVal=0x55;
    while(1)
    {
        //USART受信割込み処理--------------------
       if(usart.rxCompleted)
       {
            usart.rxCompleted=false;;
            printf("len=%d\n",usart.length);
            printf("str:%s\n",usart.rxBuf);
            usart.length = 0;
            PIE1bits.RCIE=1;
            INTCONbits.GIE=1;
       }
       //Timer0割込み処理-----------------------
       if(tm0.up)
       {
            tm0.up=false;
            LED=~LED;
            //printf("%d\n",cVal++);
            PIR0bits.TMR0IF=0;
            TMR0L=0x5E;
            PIE0bits.TMR0IE=1;
        }
       pscData = getPSCbuttonData();
       if(pscData!=0xFFFF)
       {
           printf("%04X\r\n",pscData);//Unity用
           //getPSCButtonName(pscData);
       }
       __delay_ms(30);
    }
    return (EXIT_SUCCESS);
}

void portInit(void)
{
    //PORTA initialize
    TRISA = 0x08;   //RA3 is  input-only.
    ODCONA = 0x00;  
    ANSELA = 0x00;
    WPUA =0x00;
    PORTA=0x00;

    //PORTC initialize
    TRISC = 0x22;//RC5:RX RC1:SDI
    ODCONC =0x00;
    ANSELC=0x00;
    WPUC=0x08;
    PORTC=0xFF;
   

    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RXPPS =0x15;    //RC5:RX
    RC4PPS=0x14;    //output TX
    TXPPS =0x14;    //RC4:TX
    
    //MSSP1 SPI
    WPUCbits.WPUC3=1;
    WPUCbits.WPUC2=1;
    WPUCbits.WPUC1=1;
    WPUCbits.WPUC0=1;
    SSP1DATPPS = 0x11;//MSSP1module DAT  RC1
    RC0PPS=0x18; //output SCK1
    RC2PPS=0x19; //output SDO1
    //PPS
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
}
```