LoginSignup
0
0

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

Last updated at Posted at 2024-05-16

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

IMG_4649.JPG

1.PSコントローラの配線

IMG_4625.JPG

ピン番号   色   ピン名   方向  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

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が必要。

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

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

※プロトコルが、SPIとは違います。LSBから出力、入力します。

2.コマンドの送信と、ボタンの押下状況の受信。

コントローラのデフォルトモードは、ボタンの押下状態を出力できる状態です。
以下の5バイトのcommand送信で、コントローラのボタン14個の押下状態がわかります。
最初の3バイトがヘッダー、残りの2バイトが、ボタンの押下状態のデータです。

byte番号 1 2 3 4 5
Command 0x01 0x42 0x00 0x00 0x00
Data 0xFF 0x41 0x5A 0xFF 0xFF

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

MSSPモジュールのSPIと、プレステの通信仕様の違い その1
PICマイコンのMSSPモジュールのSPIは、MSB(最上位ビット)から出力します。プレステコントローラは、LSB(最下位ビット)として、ビットデータを取り込んでいきます。 ビットが反転しています。SPI Connection.jpg

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

混乱するので、コマンドバイトをビットリバースする関数を用意しました。

MSSPモジュールのSPIと、プレステの通信仕様の違い その2
SCKラインの極性:Idle時はHigh。 通信開始でLowになるように設定します。SSP1CON1.CKP=1waveForm1.jpg

3.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)

Commandデータは、PICマイコンから送信するので、ビットリバースして、LSBから出力します。
Dataは、プレステコントローラから送られてくるデータですが、これも、LSBから送信されてきます。

4.0x42コマンド ボタン押下状況ビットマップ

4バイト目、5バイト目にボタンの押下データが格納されています。

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

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

すべてのボタンは、アクティブロー出力です。(黄色:SCK ピンク:SDI)
ボタンオシロ画面.jpg

プレステコントローラ制御コード

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 */
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');
}
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;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0