Skip to content

Instantly share code, notes, and snippets.

@pyroesp
Last active September 22, 2019 11:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyroesp/b088bb76131b981d15bf9718ccc29edc to your computer and use it in GitHub Desktop.
Save pyroesp/b088bb76131b981d15bf9718ccc29edc to your computer and use it in GitHub Desktop.
Playstation 1 reset mod for PIC16F18325
/*
* File: main.c
* Author: pyroesp
*
* Playstation 1 Reset Mod for PIC16F18325
*
* Created on September 21, 2019, 5:33 PM
*/
// CONFIG1
#pragma config FEXTOSC = OFF // FEXTOSC External Oscillator mode Selection bits (HS (crystal oscillator) above 4 MHz)
#pragma config RSTOSC = HFINT32 // Power-up default value for COSC bits (HFINTOSC with 2x PLL (32MHz))
#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 (Writing to NOSC and NDIV is allowed)
#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 MCLR; Weak pull-up enabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#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 = OFF // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will not 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 = OFF // 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.
#define _XTAL_FREQ 32000000
#include <xc.h>
#include <stdint.h>
#define _BV(b) (1<<(b))
#define REBOOT_DELAY 2 // 30 seconds
#define PS1_LAT_IO LATC // IO port reg used for RESET
#define PS1_TRIS_IO TRISC // IO dir reg used for RESET
#define PS1_RESET 5 // Only IO that outputs a logic 0
/* Controller ID */
#define ID_DIG_CTRL 0x5A41 // digital
#define ID_ANP_CTRL 0x5A73 // analog/pad
#define ID_ANS_CTRL 0x5A53 // analog/stick
#define ID_GUNCON_CTRL 0x5A63 // light gun
/* PlayStation Commands */
#define CMD_SEL_CTRL_1 0x01 // select controller 1
#define CMD_SEL_MEMC_1 0x81 // select memory card 1
#define CMD_READ_SW 0x42 // read switch status from controller
/* PlayStation Buff Size */
#define PS1_CTRL_BUFF_SIZE 9 // max size of buffer needed for a controller
/* Key Combo */
#define KEY_COMBO_CTRL 0xFCF6 // select-start-L2-R2 1111 1100 1111 0110
#define KEY_COMBO_GUNCON 0x9FF7 // A-trigger-B 1001 1111 1111 0111
/* PlayStation Controller Command Union */
union PS1_Cmd{
uint8_t buff[PS1_CTRL_BUFF_SIZE];
struct{
uint8_t device_select; // 0x01 or 0x81
uint8_t command; // 0x42 for read switch
uint8_t unused[PS1_CTRL_BUFF_SIZE-2]; // always 0 for controller
};
};
/* PlayStation Controller Data Union */
union PS1_Ctrl_Data{
uint8_t buff[PS1_CTRL_BUFF_SIZE]; // buffer to read data
struct{
uint8_t unused; // always 0xFF
uint16_t id; // 0x5Ayz - y = type; z = # of half word
uint16_t switches; // controller switches
union{
// light gun only (8MHz clock counter since hsync)
uint16_t x_pos; // if 0x0001 then error, check y_pos
struct{
uint8_t adc0; // right joy X
uint8_t adc1; // right joy Y
};
};
union{
//light gun only (scanlines since vsync)
uint16_t y_pos; // if x_pos = 0x0001 && y_pos = 0x000A => not aimed at screen
struct{
uint8_t adc2; // left joy X
uint8_t adc3; // left joy Y
};
};
};
};
void clear_buff(uint8_t *p, uint8_t s);
void __delay_s(uint8_t s);
volatile union PS1_Cmd cmd;
volatile union PS1_Ctrl_Data data;
volatile uint8_t cmd_cnt, data_cnt;
// SPI interrupt cmd
void __interrupt() _spi_int(void) {
LATAbits.LATA5 ^= 1; // toggle PIN A5
if (PIR0bits.TMR0IF){
TMR0L = 0xF0;
TMR0H = 0xFF;
PIR0bits.TMR0IF = 0;
}
if (PIR1bits.SSP1IF){
cmd.buff[cmd_cnt] = SSP1BUF;
cmd_cnt++;
PIR1bits.SSP1IF = 0; // clear SPI1 flag
}
if (PIR2bits.SSP2IF){
data.buff[data_cnt] = SSP2BUF;
data_cnt++;
PIR2bits.SSP2IF = 0; // clear SPI2 flag
}
return;
}
/*
* SS2 : RA0
* SCK2 : RA1
* SDI2 : RA2
*
* SS1 : RC0
* SCK1 : RC1
* SDI1 : RC2
*
* SDO2 : RC4
* SDO1 : RC3
*/
void main(void){
// SETUP I/O
LATC = 0;
LATA = 0;
PS1_LAT_IO &= ~_BV(PS1_RESET); // clear reset output
PS1_TRIS_IO |= _BV(PS1_RESET); // set reset pin to input
TRISAbits.TRISA0 = 1; // SS2 set to input
TRISAbits.TRISA1 = 1; // SCK2 set to input
TRISAbits.TRISA2 = 1; // SDI2 set to input
TRISCbits.TRISC0 = 1; // SS1 set to input
TRISCbits.TRISC1 = 1; // SCK1 set to input
TRISCbits.TRISC2 = 1; // SDI1 set to input
TRISCbits.TRISC3 = 0; // SDO2 set to output
TRISCbits.TRISC4 = 0; // SDO1 set to output
// SETUP SPI1 & SPI2 I/O
SSP1SSPPSbits.SSP1SSPPS = 0x10; // SS1 = RC0
SSP1CLKPPSbits.SSP1CLKPPS = 0x11; // SCK1 = RC1
SSP1DATPPSbits.SSP1DATPPS = 0x12; // SDI1 = RC2
SSP2SSPPSbits.SSP2SSPPS = 0x00; // SS2 = RA0
SSP2CLKPPSbits.SSP2CLKPPS = 0x01; // SCK2 = RA1
SSP2DATPPSbits.SSP2DATPPS = 0x02; // SDI2 = RA2
RC4PPSbits.RC4PPS = 0x1B; // SDO2 = RC4
RC3PPSbits.RC3PPS = 0x19; // SDO1 = RC3
// SETUP SPI1 & SPI2
SSP1STATbits.SMP = 0; // slave mode SPI1
SSP1STATbits.CKE = 1; // transmit from active to idle
SSP1CON1bits.CKP = 1; // clock idle high
SSP1CON1bits.SSPM = 0x04; // slave mode: clk = sck pin; SS enabled
SSP1CON3bits.BOEN = 1; // ignore BF flag
SSP1CON1bits.SSPEN = 1; // enable SPI1
SSP2STATbits.SMP = 0; // slave mode SPI2
SSP2STATbits.CKE = 1; // transmit from active to idle
SSP2CON1bits.CKP = 1; // clock idle = high
SSP2CON1bits.SSPM = 0x04; // slave mode: clk = sck pin; SS enabled
SSP2CON3bits.BOEN = 1; // ignore BF flag
SSP2CON1bits.SSPEN = 1; // enable SPI2
SSP1BUF = 0xFF;
SSP2BUF = 0xFF;
// DEBUG setup here
ANSELAbits.ANSA5 = 0; // set pin to digital I/O
TRISAbits.TRISA5 = 0; // set pin to output
LATAbits.LATA5 = 1; // set output high
ANSELAbits.ANSA4 = 0; // set pin to digital I/O
TRISAbits.TRISA4 = 0; // set pin to output
LATAbits.LATA4 = 1; // set output high
RA4PPSbits.RA4PPS = 0x00;
RA5PPSbits.RA5PPS = 0x00;
// SETUP variables and arrays
data_cnt = 0;
cmd_cnt = 0;
clear_buff((uint8_t*)cmd.buff, PS1_CTRL_BUFF_SIZE);
clear_buff((uint8_t*)data.buff, PS1_CTRL_BUFF_SIZE);
// delay before enabling interrupts
__delay_s(REBOOT_DELAY); // wait 30 sec before next reset, this is to prevent multiple reset one after the other
// SETUP INTERRUPTS
PIR1bits.SSP1IF = 0; // clear SPI1 flag
PIR2bits.SSP2IF = 0; // clear SPI2 flag
PIE1bits.SSP1IE = 1; // enable MSSP interrupt (SPI1)
PIE2bits.SSP2IE = 1; // enable MSSP interrupt (SPI2)
T0CON0bits.T0EN = 1; // enable timer0 module
T0CON0bits.T016BIT = 1; // 16 bit mode timer0
T0CON1bits.T0ASYNC = 1; // not synched to Fosc/4
T0CON1bits.T0CS = 0x03; // clock source HFINTOSC
TMR0L = 0xF0;
TMR0H = 0xFF;
PIR0bits.TMR0IF = 0; // clear timer0 flag
PIE0bits.TMR0IE = 1; // enable timer0 interrupt
INTCONbits.PEIE = 1; // peripheral interrupt enable
INTCONbits.GIE = 1; // global interrupt enable
// MAIN LOOP
for(;;){
LATAbits.LATA4 ^= 1; // toggle output
if (data_cnt >= PS1_CTRL_BUFF_SIZE || cmd_cnt >= PS1_CTRL_BUFF_SIZE){
INTCONbits.PEIE = 0; // global interrupt disable
uint16_t key_combo = 0;
// Check first command for device selected
if (cmd.device_select == CMD_SEL_CTRL_1 && cmd.command == CMD_READ_SW){
// Check ID
switch(data.id){
case ID_GUNCON_CTRL:
key_combo = KEY_COMBO_GUNCON;
break;
case ID_DIG_CTRL:
case ID_ANP_CTRL:
case ID_ANS_CTRL:
key_combo = KEY_COMBO_CTRL;
break;
default:
key_combo = 0;
break;
}
// Check switch combo
if (key_combo != 0 && 0 == (data.switches ^ key_combo)){
// change RESET from input to output, logic low (PORT is already 0)
PS1_TRIS_IO &= ~_BV(PS1_RESET);
__delay_ms(100); // hold reset for 100ms
// change RESET back to input
PS1_TRIS_IO = _BV(PS1_RESET);
__delay_s(REBOOT_DELAY); // wait 30 sec before next reset, this is to prevent multiple reset one after the other
}
}
clear_buff((uint8_t*)data.buff, PS1_CTRL_BUFF_SIZE); // clear controller stuff
clear_buff((uint8_t*)cmd.buff, PS1_CTRL_BUFF_SIZE); // clear controller stuff
data_cnt = 0;
cmd_cnt = 0;
__delay_ms(50);
PIR1bits.SSP1IF = 0; // clear SPI1 flag
PIR2bits.SSP2IF = 0; // clear SPI2 flag
INTCONbits.PEIE = 1; // global interrupt enable
}
}
for(;;);
return;
}
void clear_buff(uint8_t *p, uint8_t s){
uint8_t i;
for (i = 0; i < s; i++)
p[i] = 0;
}
void __delay_s(uint8_t s){
for (; s > 0; s--)
__delay_ms(1000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment