PlayStationのコントローラが、SPI通信でボタンの押下状況を取り出せるという、ネットの情報があったので、PICマイコンで制御してみました。
1.PSコントローラの配線
ピン番号 | 色 | ピン名 | 方向 | 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モジュールをすこし改良する必要があります。
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 |
プレステコントローラ制御コード
#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 */
#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');
}
// 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;
}