PSNee further development

General information to do with the PlayStation 1 Hardware. Including modchips, pinouts, rare or obscure development equipment, etc.
battle100
Interested PSXDEV User
Interested PSXDEV User
Posts: 7
Joined: Jun 11, 2017

Post by battle100 » July 9th, 2017, 3:16 am

Sorry for the lack of updates. Reasons follow:

Firstly, lack of time. As always!

Secondly, I foolishly traded most of my PlayStation games many years ago, and it has taken several weeks of scouting around retro stores and eBay to get some titles for proper testing. I *did* create a wiring diagram for the PU-23 board, but it seems that culexus beat me to it (and franky, culexus's diagram/wiring job looks much better than mine anyhow).

I used alternate VCC and GND points matching an original wiring guide, and a 'top' install for SQCK & SUBQ.

Thirdly, the USB port on the 'Pro Micro' I used appears to have died. I honestly don't know if it was something I did, or the fact I was just using a cheap clone. Could be applying too much voltage on the USB (5V rather than 3.3V when plugged in). Should be noted that the chip itself still functioned, just the USB connection that died. Looks like its a common problem with the clones from my limited research.

Anyway, long story short, I junked the 'Pro Micro' and grabbed a few 'Pro Mini' boards instead - much better :)
rama3 wrote:New version:
- final modchip function I / Os: SQCK, SCLK, data, gate_wfck
- hysteresis for injections, fixes anti-mod occasionally triggering when using worn drives
- optimized injection timing for multi-region, multi BIOS versions (Sony added more protection checks over time)
- first attempt to make it more portable to other Arduino variants
- auto console detection works reliably, with and without Arduino bootloader present
- pin assignments changed for practical / installation reasons (ICSP capability, wire routing)
- so many changes, it surely contains all new bugs ;)
Everything appears to work with the new 'Pro Mini' hooked up to the updated pin assignments on the PU-23. I have noted a very slightly longer boot time delay when transitioning from the white 'Sony' logo to the black 'PlayStation' logo, but nothing significant. May have something to do with the new timings (e.g. longer injection delay) ?

Finally acquired a 'Libcrypt' protected title (Resident Evil: Nemesis) and it wouldn’t launch the 1:1 duplicate. Tested with last software release on the “Pro Micro” and the new software release with the “Pro Mini” installed - same result.
culexus wrote:Got a funny error when loading Wipeout 3, the intro plays nice but when it starts to load the game menu the controller starts to shake and the loading stops followed by graphich error. Like gpu memmory glitch. Any idea? :)
I purchased this game "day one" and it had a similar issue booting on my now defunct console. I remember the problem vividly as it was the first (and last) title I ever purchased for the PlayStation that failed to launch properly.

I vaguely remember reading a passing reference in a monthly gaming magazine at the time claiming it was due to a mastering problem with the first pressing of the discs - could be mistaken though, as I can't find ANYTHING online that supports this! Maybe I'm just losing my mind in my old age :D

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 9th, 2017, 10:17 am

Nice that you got it working :)

About the longer boot time:
Is it repeatable? Does it depend on the game (number of TOC entries)?

Libcrypt:
Those games require a libcrypt removal patch. No modchip exists that can fix this.
It might be possible to do, if the right people put in some good effort ;p

ATtiny:
Just booted the first game here. These chips are hell to work with!
Without Serial, the essential chip code just barely fits on an ATtiny45. The problem is far too little RAM..

User avatar
TriMesh
Verified
PSX Aptitude
PSX Aptitude
Posts: 225
Joined: Dec 20, 2013
PlayStation Model: DTL-H1202
Location: Hong Kong

Post by TriMesh » July 9th, 2017, 2:26 pm

rama3 wrote: Libcrypt:
Those games require a libcrypt removal patch. No modchip exists that can fix this.
It might be possible to do, if the right people put in some good effort ;p
The way libcrypt works is to intentionally corrupt the subcode data. The practical result of this is that the HC05 ignores that subcode block and doesn't update the current position. So if you are polling the read position using command 0x11 on the MIPS side and read past one of the errors then the indicated current position will appear to freeze for one CD block.

The exact pattern of which sectors have corrupt subcode CRC and which don't determines the key used to unscramble the data. For example, bit 0 of the key is stored at MSF 03:35:42 (and :47) - if these have correct CRC then bit 0 is a 0, if they have incorrect CRC, then bit 0 is a 1

On the CD DSP, the result of the subcode CRC calculation is communicated to the sub CPU using the same line that carries the Q subcode data - the basic process is that SCOR goes high (S0 or S1 sync detected) and SUBQ goes low then after the subcode CRC is validated SUBQ is driven high to indicate a valid subcode frame. If you override the signal and hold it low, then the sub CPU will not read the subcode bits and the position will not update.

The biggest problem I can see is that which blocks have corrupted subcode changes on a per-title basis, since the libcrypt key is different for each title. Hence although the actual error injection is quite simple, knowing when you want to do it is rather less so.

damopinn
Curious PSXDEV User
Curious PSXDEV User
Posts: 16
Joined: Jul 03, 2017

Post by damopinn » July 9th, 2017, 7:24 pm

rama3 wrote:Nice that you got it working :)

ATtiny:
Just booted the first game here. These chips are hell to work with!
Without Serial, the essential chip code just barely fits on an ATtiny45. The problem is far too little RAM..
Yep It's not fun but it will be nice to have a tiny chip in the psx when it's ready. Can you share your code? I'm very interested in seeing what you changed.

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 10th, 2017, 1:04 am

Sure. Consider it work in progress quality ;)
Main changes:
- figured out ATtiny pin assignments :p
- RAM use reduced by only storing the "SCE" part of the license string once. Using a somewhat farfetched method. Maybe someone can make it nicer, without the recursion and over engineering? :p
- tried getting debug prints but it doesn't look like it'll happen (on ATtiny45), too little RAM > the chip crashes
- SUBQ sampling timing reverted to sample while clock is low, instead of right after it goes high

Tested on: PU-22, PU-18

Code: Select all

//#include <SoftwareSerial.h>
//SoftwareSerial mySerial(-1, 3); // RX, TX
// This PsNee version is meant for Arduino boards.
// 16Mhz and 8Mhz variants are supported. "Pro Micro" etc supported and recommended
// "Arduino Pro Micro" has a different pin assignment and needs porting. (ToDo)

// PAL PM-41 support isn't implemented yet. (ToDo)

// This code is multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.

// Use PU22_MODE for PU-22, PU-23, PM-41 mainboards.
boolean pu22mode;

//#define ARDUINO_UNO_BOARD
#define ATTINY_CHIP

#ifdef ARDUINO_UNO_BOARD
  // board pins
  #define sqck 6          // connect to PSX HC-05 SQCK pin
  #define subq 7          // connect to PSX HC-05 SUBQ pin
  #define data 8          // connect to point 6 in old modchip diagrams
  #define gate_wfck 9     // connect to point 5 in old modchip diagrams
  // MCU input / output
  #define SUBQPORT PIND       // Atmel MCU port for the 2 SUBQ sampling inputs
  #define SQCKBIT 6           // ATmega PD6 "SQCK" Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
  #define SUBQBIT 7           // ATmega PD7 "SUBQ" Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
  #define GATEWFCKPORT PINB   // Atmel MCU port for the gate input (used for WFCK)
  #define DATAPORT PORTB      // Atmel MCU port for the gate input (used for WFCK)
  #define GATEWFCKBIT 1       // ATmega PB1
  #define DATABIT 0           // ATmega PB0
#endif
#ifdef ATTINY_CHIP
  // board pins
  #define sqck 0
  #define subq 1
  #define data 2
  #define gate_wfck 4
  // MCU input / output
  #define SUBQPORT PINB
  #define SQCKBIT 0
  #define SUBQBIT 1
  #define GATEWFCKPORT PINB
  #define DATAPORT PORTB
  #define GATEWFCKBIT 4
  #define DATABIT 2
#endif

//Timing
const int delay_between_bits = 4000;      // 250 bits/s (microseconds)
const int delay_between_injections = 90;  // 72 in oldcrow. PU-22+ work best with 80 to 100 (milliseconds)

void inject_SCEX(char region, boolean firstPart)
{
  //SCEE: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01011101 00
  //SCEA: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01111101 00
  //SCEI: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01101101 00
  const boolean SCE[36] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1};
  const boolean EData[8] = {0,1,1,1,0,1,0,0}; //SCEE
  const boolean AData[8] = {1,1,1,1,0,1,0,0}; //SCEA
  const boolean IData[8] = {1,0,1,1,0,1,0,0}; //SCEI
  
  const boolean *SCEXData;
  byte limit;
  if (firstPart) {
    SCEXData = SCE;
    limit = 36;
  }
  else {
    switch (region){
      case 'e': SCEXData = EData; break;
      case 'a': SCEXData = AData; break;
      case 'i': SCEXData = IData; break;
    }
    limit = 8;
  }
  
  // pinMode(data, OUTPUT) is used more than it has to be but that's fine.
  for (byte bit_counter = 0; bit_counter < limit; bit_counter++)
  {
    if (*(SCEXData+bit_counter) == 0)
    {
      pinMode(data, OUTPUT);
      bitClear(GATEWFCKPORT,DATABIT); // data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      if (pu22mode) {
        pinMode(data, OUTPUT);
        unsigned long now = micros();
        do {
          bool wfck_sample = bitRead(GATEWFCKPORT, GATEWFCKBIT);
          bitWrite(DATAPORT,DATABIT,wfck_sample); // output wfck signal on data pin
        }
        while ((micros() - now) < delay_between_bits);
      }
      else { // not PU 22 mode
        pinMode(data, INPUT);
        delayMicroseconds(delay_between_bits);
      }
    }
  }

  if (firstPart){
    inject_SCEX(region, false);
  }

  pinMode(data, OUTPUT);
  bitClear(GATEWFCKPORT,DATABIT); // pull data low
  delay(delay_between_injections);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------
void setup()
{
  pinMode(data, INPUT); 
  pinMode(gate_wfck, INPUT); 
  pinMode(subq, INPUT); // PSX spi data in
  pinMode(sqck, INPUT); // PSX spi clock in
  
  //mySerial.begin (19200);
  //mySerial.print("f "); mySerial.print(F_CPU);
  
  // Board detection
  while (!digitalRead(sqck));   // wait for console power on (in case Arduino is powered externally)
  while (!digitalRead(gate_wfck));   // wait for gate / WFCK signal to appear

  // GATE: __-----------------------  // this is a PU-7 .. PU-20 board!
  //
  // WFCK: __-_-_-_-_-_-_-_-_-_-_-_-  // this is a PU-22 or newer board!

  unsigned int highs, lows = 0;
  unsigned long now = millis();
  do {
    if (digitalRead(gate_wfck) == 1) highs++;
    if (digitalRead(gate_wfck) == 0) lows++;
    delayMicroseconds(200);   // good for ~5000 reads in 1s
  }
  while ((millis() - now) < 1000); // sample 1s

  //Serial.print("highs: "); Serial.print(highs); Serial.print(" lows: "); Serial.println(lows);
  // typical readouts
  // PU-22: highs: 2449 lows: 2377
  if (lows > 100) {
    pu22mode = 1;
  }
  else {
    pu22mode = 0;
  } 
}

void loop()
{
  static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read.
  static unsigned int timeout_clock_counter = 0;
  static byte bitbuf = 0;   // SUBQ bit storage
  static bool sample = 0;
  
  byte scpos = 0;           // scbuf position
  
  noInterrupts(); // start critical section
// yes, a goto jump label. This is to avoid a return out of critical code with interrupts disabled.
// It prevents bad behaviour, for example running the Arduino Serial Event routine without interrupts.
// Using a function makes shared variables messier.
// SUBQ sampling is essential for the rest of the functionality. It is okay for this to take as long as it does.
start:
  for (byte bitpos = 0; bitpos<8; bitpos++) { // Capture 8 bits for 12 runs > complete SUBQ transmission
    do {
      // nothing, reset on timeout
      timeout_clock_counter++;
      if (timeout_clock_counter > 1000){
        scpos = 0;  // reset SUBQ packet stream
        timeout_clock_counter = 0;
        bitpos = 0;
        goto start;
      }
    }
    while (bitRead(SUBQPORT, SQCKBIT) == 1); // wait for clock to go low..

    __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t");
    // sample the bit now!
    sample = bitRead(SUBQPORT, SUBQBIT);
    bitbuf |= sample << bitpos;
    
    do {
      // nothing
    } while ((bitRead(SUBQPORT, SQCKBIT)) == 0); // and high again..
    
    timeout_clock_counter = 0; // no problem with this bit
  }
 
  scbuf[scpos] = bitbuf;
  scpos++;
  bitbuf = 0;

  // repeat for all 12 bytes
  if (scpos < 12){
    goto start;
  }

  interrupts(); // end critical section

  // log SUBQ packets
//  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){ // a bad sector read is all 0 except for the CRC fields. Don't log it.
//    for (int i = 0; i<12;i++) {
//      if (scbuf[i] < 0x10) mySerial.print("0"); // padding
//        mySerial.print(scbuf[i], HEX);
//        mySerial.print(" ");
//      }
//      mySerial.println("");
//  }
  
  // check if read head is in wobble area
  // We only want to unlock game discs (0x41) and only if the read head is in the outer TOC area.
  // We want to see a TOC sector repeatedly before injecting (helps with timing and marginal lasers).
  static byte hysteresis  = 0;

  // All this logic is because we don't know if the HC-05 is actually processing a getSCEX() command.
  // Hysteresis is used because older drives exhibit more wiggle room. They might see a few TOC sectors when they shouldn't.
  if ( 
    (scbuf[0] == 0x41 &&  scbuf[1] == 0x00 &&  scbuf[6] == 0x00) &&   // [0] = 41 means psx game disk. the other 2 checks are garbage protection
    (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 ||      // if [2] = A0, A1, A2 ..
    (scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02) ) )   // .. or = 01 but then [3] is either > 98 or < 02
  ) {
    hysteresis++;
  }
  else if ( hysteresis > 0 && 
          ((scbuf[0] == 0x01 || scbuf[0] == 0x41) && (scbuf[1] == 0x00 /*|| scbuf[1] == 0x01*/) &&  scbuf[6] == 0x00)
  ) {  // This CD has the wobble into CD-DA space. (started at 0x41, then went into 0x01)
    hysteresis++;
  }
  else if (hysteresis > 0) {
    hysteresis--; // None of the above. Initial detection was noise. Decrease the counter.
  }

  // Some anti mod routines position the laser very close to the TOC area. Only inject if we're pretty certain it is required.
  // hysteresis below 10 occasionally triggers injections in Silent Hill (NTSC-J) when using a worn drive
  if (hysteresis >= 14){ 
    hysteresis = 0;
    
    //mySerial.println("!");
    
    pinMode(data, OUTPUT);
    digitalWrite(data, 0); // pull data low
    if (!pu22mode){
      pinMode(gate_wfck, OUTPUT);
      digitalWrite(gate_wfck, 0);
    }
    
    // HC-05 is waiting for a bit of silence (pin low) before it begins decoding.
    delay(delay_between_injections);
    for (byte loop_counter = 0; loop_counter < 2; loop_counter++)
    {
      inject_SCEX('e', true); // e = SCEE, a = SCEA, i = SCEI
      inject_SCEX('a', true); // injects all 3 regions by default
      inject_SCEX('i', true); // makes it easier for people to get working
    }

    if (!pu22mode){
      pinMode(gate_wfck, INPUT); // high-z the line, we're done
    }
    pinMode(data, INPUT); // high-z the line, we're done
  }
// keep catching SUBQ packets forever
}

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 10th, 2017, 10:37 am

Update:
- finished porting to ATtinyX5 (25,45,85 although the 25 has too little resources. for now.)
- store the licensing symbols in flash again, frees a lot of RAM
- bit retrieval code lifted from AttyNee (Nice work guys!)
- extra RAM allows SoftwareSerial debugging prints on an ATtiny45!
- nicer intro readme ;p

Code: Select all

// PsNee / psxdev.net version
// For Arduino and ATtiny
//
// Quick start: Select your hardware via the #defines, compile + upload the code, install in PSX.
// There are some pictures in the development thread ( http://www.psxdev.net/forum/viewtopic.php?f=47&t=1262&start=120 )
//
// Arduinos:
//  - Arduino Pro Mini @8Mhz and @16Mhz (supported, tested)
//  - Arduino Uno @8Mhz and @16Mhz (supported, tested)
//  - Arduino Pro Micro has a different pin assignment and needs some easy porting. (ToDo)
//  - Use #define ARDUINO_BOARD
// ATtiny:
//  - ATtiny45: LFUSE 0xE2  HFUSE 0xDF > internal oscillator, full 8Mhz speed (supported, tested)
//  - ATtiny85: Should work the same as ATtiny45 (supported, untested)
//  - ATtiny25: Not yet supported. 2kB flash, 128 Bytes RAM. Tricky.
//  - Use #define ATTINY_X5
//
// Some extra libraries might be required, depending on the board / chip used.
// PAL PM-41 support isn't implemented yet. (ToDo)
// This code defaults to multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.
// You can optimize boot times for your console further. See "// inject symbols now" in the main loop.

// Choose your hardware! 
// 2 main branches available: 
//  - ATmega based > easy to use, fast and nice features for development
//  - ATtiny based > less features, internal clock has 10% variation

//#define ARDUINO_BOARD
#define ATTINY_X5

#ifdef ARDUINO_BOARD
  // board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
  #define sqck 6          // connect to PSX HC-05 SQCK pin
  #define subq 7          // connect to PSX HC-05 SUBQ pin
  #define data 8          // connect to point 6 in old modchip diagrams
  #define gate_wfck 9     // connect to point 5 in old modchip diagrams
  // MCU I/O definitions
  #define SUBQPORT PIND       // MCU port for the 2 SUBQ sampling inputs
  #define SQCKBIT 6           // PD6 "SQCK" < Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
  #define SUBQBIT 7           // PD7 "SUBQ" < Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
  #define GATEWFCKPORT PINB   // MCU port for the gate input (used for WFCK)
  #define DATAPORT PORTB      // MCU port for the gate input (used for WFCK)
  #define GATEWFCKBIT 1       // PB1
  #define DATABIT 0           // PB0
#endif
#ifdef ATTINY_X5 // ATtiny 25/45/85
  // extras
  #include <SoftwareSerial.h>
  #include <avr/pgmspace.h>
  SoftwareSerial mySerial(-1, 3); // RX, TX. (RX -1 = off)
  // board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
  #define sqck 0
  #define subq 1
  #define data 2
  #define gate_wfck 4
  #define debugtx 3
  // MCU I/O definitions
  #define SUBQPORT PINB
  #define SQCKBIT 0
  #define SUBQBIT 1
  #define GATEWFCKPORT PINB
  #define DATAPORT PORTB
  #define GATEWFCKBIT 4
  #define DATABIT 2
#endif

#define NOP __asm__ __volatile__ ("nop\n\t")

// Setup() detects which (of 2) injection methods this PSX board requires, then stores it in pu22mode.
boolean pu22mode;

//Timing
const int delay_between_bits = 4000;      // 250 bits/s (microseconds) (ATtiny 8Mhz works from 3950 to 4100)
const int delay_between_injections = 90;  // 72 in oldcrow. PU-22+ work best with 80 to 100 (milliseconds)

// borrowed from AttyNee. Bitmagic to get to the SCEX strings stored in flash (because Harvard architecture)
bool readBit(int index, const unsigned char *ByteSet)
{
  int byte_index = index >> 3;
  byte bits = pgm_read_byte(&(ByteSet[byte_index]));
  int bit_index = index & 0x7; // same as (index - byte_index<<3) or (index%8)
  byte mask = 1 << bit_index;
  return (0 != (bits & mask));
}

void inject_SCEX(char region)
{
  //SCEE: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01011101 00
  //SCEA: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01111101 00
  //SCEI: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01101101 00
  //const boolean SCEEData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  //const boolean SCEAData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  //const boolean SCEIData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  static const PROGMEM unsigned char SCEEData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11101010, 0b00000010};
  static const PROGMEM unsigned char SCEAData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11111010, 0b00000010};
  static const PROGMEM unsigned char SCEIData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11011010, 0b00000010};
  
  // pinMode(data, OUTPUT) is used more than it has to be but that's fine.
  for (byte bit_counter = 0; bit_counter < 44; bit_counter++)
  {
    if (readBit(bit_counter, region == 'e' ? SCEEData : region == 'a' ? SCEAData : SCEIData) == 0)
    {
      pinMode(data, OUTPUT);
      bitClear(GATEWFCKPORT,DATABIT); // data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      if (pu22mode) {
        pinMode(data, OUTPUT);
        unsigned long now = micros();
        do {
          bool wfck_sample = bitRead(GATEWFCKPORT, GATEWFCKBIT);
          bitWrite(DATAPORT,DATABIT,wfck_sample); // output wfck signal on data pin
        }
        while ((micros() - now) < delay_between_bits);
      }
      else { // PU-18 or lower mode
        pinMode(data, INPUT);
        delayMicroseconds(delay_between_bits);
      }
    }
  }

  pinMode(data, OUTPUT);
  bitClear(GATEWFCKPORT,DATABIT); // pull data low
  delay(delay_between_injections);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------

void setup()
{
  pinMode(data, INPUT); 
  pinMode(gate_wfck, INPUT); 
  pinMode(subq, INPUT); // PSX subchannel bits
  pinMode(sqck, INPUT); // PSX subchannel clock
#ifdef ATTINY_X5
  pinMode(debugtx, OUTPUT); // software serial tx pin
  mySerial.begin(57600);
  mySerial.print("f "); mySerial.println(F_CPU);
#else
  pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug.
  digitalWrite(LED_BUILTIN, HIGH); // mark begin of setup
  Serial.begin(115200); // there is a relationship between symbol rate here and getting odd readings in the logs.
  Serial.print("MCU frequency: "); Serial.print(F_CPU); Serial.println(" Hz");
  Serial.println("Waiting for SQCK..");
#endif
  // Board detection
  while (!digitalRead(sqck));   // wait for console power on (in case Arduino is powered externally)
  while (!digitalRead(gate_wfck));   // wait for gate / WFCK signal to appear

  // GATE: __-----------------------  // this is a PU-7 .. PU-20 board!
  //
  // WFCK: __-_-_-_-_-_-_-_-_-_-_-_-  // this is a PU-22 or newer board!

  unsigned int highs, lows = 0;
  unsigned long now = millis();
  do {
    if (digitalRead(gate_wfck) == 1) highs++;
    if (digitalRead(gate_wfck) == 0) lows++;
    delayMicroseconds(200);   // good for ~5000 reads in 1s
  }
  while ((millis() - now) < 1000); // sample 1s

  //Serial.print("highs: "); Serial.print(highs); Serial.print(" lows: "); Serial.println(lows);
  // typical readouts
  // PU-22: highs: 2449 lows: 2377
  if (lows > 100) {
    pu22mode = 1;
  }
  else {
    pu22mode = 0;
  }
  
#ifdef ATTINY_X5
  mySerial.print("m "); mySerial.println(pu22mode);
  mySerial.flush();
#else
  Serial.print("pu22mode: "); Serial.println(pu22mode);
  Serial.flush();
  // Power saving
  // Disable the ADC by setting the ADEN bit (bit 7)  of the ADCSRA register to zero.
  ADCSRA = ADCSRA & B01111111;
  // Disable the analog comparator by setting the ACD bit (bit 7) of the ACSR register to one.
  ACSR = B10000000;
  // Disable digital input buffers on all analog input pins by setting bits 0-5 of the DIDR0 register to one.
  DIDR0 = DIDR0 | B00111111;
  digitalWrite(LED_BUILTIN, LOW); // setup complete
#endif
}

void loop()
{
  static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read.
  static unsigned int timeout_clock_counter = 0;
  static byte bitbuf = 0;   // SUBQ bit storage
  static bool sample = 0;
  
  byte scpos = 0;           // scbuf position
  
  noInterrupts(); // start critical section
// yes, a goto jump label. This is to avoid a return out of critical code with interrupts disabled.
// It prevents bad behaviour, for example running the Arduino Serial Event routine without interrupts.
// Using a function makes shared variables messier.
// SUBQ sampling is essential for the rest of the functionality. It is okay for this to take as long as it does.
start:
  for (byte bitpos = 0; bitpos<8; bitpos++) { // Capture 8 bits for 12 runs > complete SUBQ transmission
    do {
      // nothing, reset on timeout
      timeout_clock_counter++;
      if (timeout_clock_counter > 1400){
        scpos = 0;  // reset SUBQ packet stream
        timeout_clock_counter = 0;
        bitpos = 0;
        goto start;
      }
    }
    while (bitRead(SUBQPORT, SQCKBIT) == 1); // wait for clock to go low..

    // sample the bit 3 no-ops after the clock went low. Tested on ATtiny45 @8Mhz 
    NOP;NOP;NOP;
    sample = bitRead(SUBQPORT, SUBQBIT);
    bitbuf |= sample << bitpos;
    do {
      // nothing
    } while ((bitRead(SUBQPORT, SQCKBIT)) == 0); // and high again..

    timeout_clock_counter = 0; // no problem with this bit
  }
 
  scbuf[scpos] = bitbuf;
  scpos++;
  bitbuf = 0;

  // repeat for all 12 bytes
  if (scpos < 12){
    goto start;
  }

  interrupts(); // end critical section

  // log SUBQ packets
#ifdef ATTINY_X5
  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){ // a bad sector read is all 0 except for the CRC fields. Don't log it.
    for (int i = 0; i<12;i++) {
      if (scbuf[i] < 0x10) {mySerial.print("0");} // padding
      mySerial.print(scbuf[i], HEX);
      mySerial.print(" ");
    }
    mySerial.println("");
  }
#else
  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){
    for (int i = 0; i<12;i++) {
      if (scbuf[i] < 0x10) {Serial.print("0");} // padding
      Serial.print(scbuf[i], HEX);
      Serial.print(" ");
    }
    Serial.println("");
//    Serial.flush();
//    if (scbuf[0] != 0x41)
//      digitalWrite(LED_BUILTIN, HIGH); 
//    else 
//      digitalWrite(LED_BUILTIN, LOW); 
  }
#endif

  // check if read head is in wobble area
  // We only want to unlock game discs (0x41) and only if the read head is in the outer TOC area.
  // We want to see a TOC sector repeatedly before injecting (helps with timing and marginal lasers).
  // All this logic is because we don't know if the HC-05 is actually processing a getSCEX() command.
  // Hysteresis is used because older drives exhibit more variation in read head positioning. 
  // While the laser lens moves to correct for the error, they can pick up a few TOC sectors.  
  static byte hysteresis  = 0;
  
  if ( 
    (scbuf[0] == 0x41 &&  scbuf[1] == 0x00 &&  scbuf[6] == 0x00) &&   // [0] = 41 means psx game disk. the other 2 checks are garbage protection
    (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 ||      // if [2] = A0, A1, A2 ..
    (scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02) ) )   // .. or = 01 but then [3] is either > 98 or < 02
  ) {
    hysteresis++;
  }
  else if ( hysteresis > 0 && 
          ((scbuf[0] == 0x01 || scbuf[0] == 0x41) && (scbuf[1] == 0x00 /*|| scbuf[1] == 0x01*/) &&  scbuf[6] == 0x00)
  ) {  // This CD has the wobble into CD-DA space. (started at 0x41, then went into 0x01)
    hysteresis++;
  }
  else if (hysteresis > 0) {
    hysteresis--; // None of the above. Initial detection was noise. Decrease the counter.
  }

  // hysteresis value "optimized" using very worn but working drive on ATmega328 @ 16Mhz
  // should be fine on other MCUs and speeds, as the PSX dictates SUBQ rate
  if (hysteresis >= 14){ 
    hysteresis = 0;

#ifdef ATTINY_X5
    mySerial.println("!");
#else
    Serial.println("INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!");
#endif

    pinMode(data, OUTPUT);
    digitalWrite(data, 0); // pull data low
    if (!pu22mode){
      pinMode(gate_wfck, OUTPUT);
      digitalWrite(gate_wfck, 0);
    }
    
    // HC-05 waits for a bit of silence (pin low) before it begins decoding.
    delay(delay_between_injections);
    // inject symbols now. 2 x 3 runs seems optimal to cover all boards
    for (byte loop_counter = 0; loop_counter < 2; loop_counter++)
    {
      inject_SCEX('e'); // e = SCEE, a = SCEA, i = SCEI
      inject_SCEX('a'); // injects all 3 regions by default
      inject_SCEX('i'); // optimize boot time by sending only your console region letter (all 3 times per loop)
    }

    if (!pu22mode){
      pinMode(gate_wfck, INPUT); // high-z the line, we're done
    }
    pinMode(data, INPUT); // high-z the line, we're done
  }
// keep catching SUBQ packets forever
}


culexus
Curious PSXDEV User
Curious PSXDEV User
Posts: 14
Joined: Jun 28, 2017

Post by culexus » July 10th, 2017, 7:29 pm

I see that PAL PM-41 support isn't implemented yet. hm what is missing exactly ?

This is the PSone PAL SCPH-102 right? I did install the first arduino into this console and it is working and loading backups as it should. Did I miss something :)?

xsp
What is PSXDEV?
What is PSXDEV?
Posts: 1
Joined: Jul 10, 2017

Post by xsp » July 10th, 2017, 9:01 pm

Just got this running with my Nano on my SCPH-1001.

Code: Select all

#define ARDUINO_NANO        //Make that "#define ARDUINO_UNO" if you want to compile for Arduino Uno instead of ATTiny25/45/85
 
#ifdef ARDUINO_NANO
//Pins
int data = 11;         //The pin that outputs the SCEE SCEA SCEI string
int gate = 12;         //The pin that outputs the SCEE SCEA SCEI string
int lid = 10;
int delay_ntsc = ????;
int delay_between_bits = 4;
int delay_between_injections = 74;
#endif
And make sure you set it to use SCEA in inject_playstation().


There seems to be an issue with the delay. If I external power the Nano, wait 5 seconds and then turn on the system, it loads the backup just fine.

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 10th, 2017, 9:46 pm

xsp:
You are reporting on the original PsNee code. This thread isn't meant for that.
Anyway, you can fix your issue by commenting out "ntsc_fix()".

culexus:
I'm missing the ntsc_fix() that patches a PAL PM-41 BIOS, so that all region games get accepted.
If you install the current code, it will still work, but only PAL license disks will boot.

kalymnos77
Curious PSXDEV User
Curious PSXDEV User
Posts: 23
Joined: Jun 06, 2017

Post by kalymnos77 » July 10th, 2017, 10:40 pm

good Work.

Seen the work done by rama3, I thought soon to pass the branch psxdev in master on github. I know that support not yet ntsc_fix(), and the Attiny 25, and A13, it just seems like it's not vital.

What do you think :?:

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 11th, 2017, 11:36 pm

Would be fine by me, kalymnos77. I have added lots of code yesterday though, so maybe you want to wait for the update.
ATtiny25 will be supported then as well.

Also, let me just fix up the PsNee v6. It should then be named PsNee v7.
Update: Actually, v6 is pretty broken. It would be better to archive the branch (on Github) and recommend using AttyNee instead.
There is a newer update for it that has an interresting NTSC_fix() that uses a pin change interrupt.
http://forum.amperka.ru/threads/%D0%A0% ... %B8.10688/
AttyNee fixes the issues with PsNee v6 and adds more supported chips.
Otherwise it's still "simple" code with no real stealth.
For stealth, I recommend you recommend my code ;p

LiGhTMaGiCk
Curious PSXDEV User
Curious PSXDEV User
Posts: 11
Joined: Jul 12, 2017

Post by LiGhTMaGiCk » July 12th, 2017, 7:24 am

rama3 wrote:Pro mini 16Mhz is perfect and use it with 3.5V.
I've tested these boards installed in a PSX with variable 3.5V supply and they work fine down to 3.0V, which is the minimum before the PSX power supply controller shuts off the rail ;)

Element18592 uses an Arduino with a different layout. I will have to adapt the code to those models.
It'll be a while before my batch arrives from China.

By the way, I did this install yesterday. It's an altered code and the mini is installed in a LATE PU-8. Just shows a nice location for the board and where to get good power and ground.
I plan to do more pics of working installs later.
Image
Ok so I've got a SCPH-1001 model with PU-8 board and I installed this with the most current code you posted and physically identical to your picture of a similar model but my system won't play the games and the drive spins whether the lid is open or closed. Any ideas where I screwed up?
Last edited by LiGhTMaGiCk on July 12th, 2017, 8:51 am, edited 1 time in total.

damopinn
Curious PSXDEV User
Curious PSXDEV User
Posts: 16
Joined: Jul 03, 2017

Post by damopinn » July 12th, 2017, 7:34 am

Hi, I just tested on attiny85 it works at 16mhz but not at 8mhz for me.

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 12th, 2017, 8:59 am

LiGhTMaGiCk: Post a picture please.

damopinn: I found / fixed a problem that could be causing this. I'll update soon.

LiGhTMaGiCk
Curious PSXDEV User
Curious PSXDEV User
Posts: 11
Joined: Jul 12, 2017

Post by LiGhTMaGiCk » July 12th, 2017, 12:09 pm

rama3 wrote:LiGhTMaGiCk: Post a picture please.

damopinn: I found / fixed a problem that could be causing this. I'll update soon.
I knew you would say that, I'm the doofus who decided to put it back together screws and all before verifying that it worked. :P https://www.dropbox.com/s/ifep4yzeo74fj ... 5.jpg?dl=0

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 12th, 2017, 5:25 pm

Okay, that looks to be correct :)
How did you program it? I don't see remains of an ISP flasher.

LiGhTMaGiCk
Curious PSXDEV User
Curious PSXDEV User
Posts: 11
Joined: Jul 12, 2017

Post by LiGhTMaGiCk » July 12th, 2017, 8:32 pm

rama3 wrote:Okay, that looks to be correct :)
How did you program it? I don't see remains of an ISP flasher.
I used an FT232RL FTDI USB to TTL serial converter adapter, like this one,
I'll try re flashing it but it showed that it completed successfully.

damopinn
Curious PSXDEV User
Curious PSXDEV User
Posts: 16
Joined: Jul 03, 2017

Post by damopinn » July 12th, 2017, 9:12 pm

Hi could part of the problem be that we need to calibrate the oscillator on the attiny? +-10% means there could be 20% difference between yours and mine. I read somewhere that it can be important to use serial. i found this page about it
http://www.instructables.com/id/ATtiny- ... libration/

LiGhTMaGiCk
Curious PSXDEV User
Curious PSXDEV User
Posts: 11
Joined: Jul 12, 2017

Post by LiGhTMaGiCk » July 12th, 2017, 9:16 pm

LiGhTMaGiCk wrote:
rama3 wrote:Okay, that looks to be correct :)
How did you program it? I don't see remains of an ISP flasher.
I used an FT232RL FTDI USB to TTL serial converter adapter, like this one,
I'll try re flashing it but it showed that it completed successfully.
Ok reflashing produced the same result using this code. The only thing I changed was to define it as an arduino board by uncommenting that and commenting out attiny.

Code: Select all

// PsNee / psxdev.net version
// For Arduino and ATtiny
//
// Quick start: Select your hardware via the #defines, compile + upload the code, install in PSX.
// There are some pictures in the development thread ( http://www.psxdev.net/forum/viewtopic.php?f=47&t=1262&start=120 )
//
// Arduinos:
//  - Arduino Pro Mini @8Mhz and @16Mhz (supported, tested)
//  - Arduino Uno @8Mhz and @16Mhz (supported, tested)
//  - Arduino Pro Micro has a different pin assignment and needs some easy porting. (ToDo)
//  - Use #define ARDUINO_BOARD
// ATtiny:
//  - ATtiny45: LFUSE 0xE2  HFUSE 0xDF > internal oscillator, full 8Mhz speed (supported, tested)
//  - ATtiny85: Should work the same as ATtiny45 (supported, untested)
//  - ATtiny25: Not yet supported. 2kB flash, 128 Bytes RAM. Tricky.
//  - Use #define ATTINY_X5
//
// Some extra libraries might be required, depending on the board / chip used.
// PAL PM-41 support isn't implemented yet. (ToDo)
// This code defaults to multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.
// You can optimize boot times for your console further. See "// inject symbols now" in the main loop.

// Choose your hardware! 
// 2 main branches available: 
//  - ATmega based > easy to use, fast and nice features for development
//  - ATtiny based > less features, internal clock has 10% variation

//#define ATTINY_X5
#define ARDUINO_BOARD

#ifdef ARDUINO_BOARD
  // board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
  #define sqck 6          // connect to PSX HC-05 SQCK pin
  #define subq 7          // connect to PSX HC-05 SUBQ pin
  #define data 8          // connect to point 6 in old modchip diagrams
  #define gate_wfck 9     // connect to point 5 in old modchip diagrams
  // MCU I/O definitions
  #define SUBQPORT PIND       // MCU port for the 2 SUBQ sampling inputs
  #define SQCKBIT 6           // PD6 "SQCK" < Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
  #define SUBQBIT 7           // PD7 "SUBQ" < Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
  #define GATEWFCKPORT PINB   // MCU port for the gate input (used for WFCK)
  #define DATAPORT PORTB      // MCU port for the gate input (used for WFCK)
  #define GATEWFCKBIT 1       // PB1
  #define DATABIT 0           // PB0
#endif
#ifdef ATTINY_X5 // ATtiny 25/45/85
  // extras
  #include <SoftwareSerial.h>
  #include <avr/pgmspace.h>
  SoftwareSerial mySerial(-1, 3); // RX, TX. (RX -1 = off)
  // board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
  #define sqck 0
  #define subq 1
  #define data 2
  #define gate_wfck 4
  #define debugtx 3
  // MCU I/O definitions
  #define SUBQPORT PINB
  #define SQCKBIT 0
  #define SUBQBIT 1
  #define GATEWFCKPORT PINB
  #define DATAPORT PORTB
  #define GATEWFCKBIT 4
  #define DATABIT 2
#endif

#define NOP __asm__ __volatile__ ("nop\n\t")

// Setup() detects which (of 2) injection methods this PSX board requires, then stores it in pu22mode.
boolean pu22mode;

//Timing
const int delay_between_bits = 4000;      // 250 bits/s (microseconds) (ATtiny 8Mhz works from 3950 to 4100)
const int delay_between_injections = 90;  // 72 in oldcrow. PU-22+ work best with 80 to 100 (milliseconds)

// borrowed from AttyNee. Bitmagic to get to the SCEX strings stored in flash (because Harvard architecture)
bool readBit(int index, const unsigned char *ByteSet)
{
  int byte_index = index >> 3;
  byte bits = pgm_read_byte(&(ByteSet[byte_index]));
  int bit_index = index & 0x7; // same as (index - byte_index<<3) or (index%8)
  byte mask = 1 << bit_index;
  return (0 != (bits & mask));
}

void inject_SCEX(char region)
{
  //SCEE: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01011101 00
  //SCEA: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01111101 00
  //SCEI: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01101101 00
  //const boolean SCEEData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  //const boolean SCEAData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  //const boolean SCEIData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  static const PROGMEM unsigned char SCEEData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11101010, 0b00000010};
  static const PROGMEM unsigned char SCEAData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11111010, 0b00000010};
  static const PROGMEM unsigned char SCEIData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11011010, 0b00000010};
  
  // pinMode(data, OUTPUT) is used more than it has to be but that's fine.
  for (byte bit_counter = 0; bit_counter < 44; bit_counter++)
  {
    if (readBit(bit_counter, region == 'e' ? SCEEData : region == 'a' ? SCEAData : SCEIData) == 0)
    {
      pinMode(data, OUTPUT);
      bitClear(GATEWFCKPORT,DATABIT); // data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      if (pu22mode) {
        pinMode(data, OUTPUT);
        unsigned long now = micros();
        do {
          bool wfck_sample = bitRead(GATEWFCKPORT, GATEWFCKBIT);
          bitWrite(DATAPORT,DATABIT,wfck_sample); // output wfck signal on data pin
        }
        while ((micros() - now) < delay_between_bits);
      }
      else { // PU-18 or lower mode
        pinMode(data, INPUT);
        delayMicroseconds(delay_between_bits);
      }
    }
  }

  pinMode(data, OUTPUT);
  bitClear(GATEWFCKPORT,DATABIT); // pull data low
  delay(delay_between_injections);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------

void setup()
{
  pinMode(data, INPUT); 
  pinMode(gate_wfck, INPUT); 
  pinMode(subq, INPUT); // PSX subchannel bits
  pinMode(sqck, INPUT); // PSX subchannel clock
#ifdef ATTINY_X5
  pinMode(debugtx, OUTPUT); // software serial tx pin
  mySerial.begin(57600);
  mySerial.print("f "); mySerial.println(F_CPU);
#else
  pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug.
  digitalWrite(LED_BUILTIN, HIGH); // mark begin of setup
  Serial.begin(115200); // there is a relationship between symbol rate here and getting odd readings in the logs.
  Serial.print("MCU frequency: "); Serial.print(F_CPU); Serial.println(" Hz");
  Serial.println("Waiting for SQCK..");
#endif
  // Board detection
  while (!digitalRead(sqck));   // wait for console power on (in case Arduino is powered externally)
  while (!digitalRead(gate_wfck));   // wait for gate / WFCK signal to appear

  // GATE: __-----------------------  // this is a PU-7 .. PU-20 board!
  //
  // WFCK: __-_-_-_-_-_-_-_-_-_-_-_-  // this is a PU-22 or newer board!

  unsigned int highs, lows = 0;
  unsigned long now = millis();
  do {
    if (digitalRead(gate_wfck) == 1) highs++;
    if (digitalRead(gate_wfck) == 0) lows++;
    delayMicroseconds(200);   // good for ~5000 reads in 1s
  }
  while ((millis() - now) < 1000); // sample 1s

  //Serial.print("highs: "); Serial.print(highs); Serial.print(" lows: "); Serial.println(lows);
  // typical readouts
  // PU-22: highs: 2449 lows: 2377
  if (lows > 100) {
    pu22mode = 1;
  }
  else {
    pu22mode = 0;
  }
  
#ifdef ATTINY_X5
  mySerial.print("m "); mySerial.println(pu22mode);
  mySerial.flush();
#else
  Serial.print("pu22mode: "); Serial.println(pu22mode);
  Serial.flush();
  // Power saving
  // Disable the ADC by setting the ADEN bit (bit 7)  of the ADCSRA register to zero.
  ADCSRA = ADCSRA & B01111111;
  // Disable the analog comparator by setting the ACD bit (bit 7) of the ACSR register to one.
  ACSR = B10000000;
  // Disable digital input buffers on all analog input pins by setting bits 0-5 of the DIDR0 register to one.
  DIDR0 = DIDR0 | B00111111;
  digitalWrite(LED_BUILTIN, LOW); // setup complete
#endif
}

void loop()
{
  static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read.
  static unsigned int timeout_clock_counter = 0;
  static byte bitbuf = 0;   // SUBQ bit storage
  static bool sample = 0;
  
  byte scpos = 0;           // scbuf position
  
  noInterrupts(); // start critical section
// yes, a goto jump label. This is to avoid a return out of critical code with interrupts disabled.
// It prevents bad behaviour, for example running the Arduino Serial Event routine without interrupts.
// Using a function makes shared variables messier.
// SUBQ sampling is essential for the rest of the functionality. It is okay for this to take as long as it does.
start:
  for (byte bitpos = 0; bitpos<8; bitpos++) { // Capture 8 bits for 12 runs > complete SUBQ transmission
    do {
      // nothing, reset on timeout
      timeout_clock_counter++;
      if (timeout_clock_counter > 1400){
        scpos = 0;  // reset SUBQ packet stream
        timeout_clock_counter = 0;
        bitpos = 0;
        goto start;
      }
    }
    while (bitRead(SUBQPORT, SQCKBIT) == 1); // wait for clock to go low..

    // sample the bit 3 no-ops after the clock went low. Tested on ATtiny45 @8Mhz 
    NOP;NOP;NOP;
    sample = bitRead(SUBQPORT, SUBQBIT);
    bitbuf |= sample << bitpos;
    do {
      // nothing
    } while ((bitRead(SUBQPORT, SQCKBIT)) == 0); // and high again..

    timeout_clock_counter = 0; // no problem with this bit
  }
 
  scbuf[scpos] = bitbuf;
  scpos++;
  bitbuf = 0;

  // repeat for all 12 bytes
  if (scpos < 12){
    goto start;
  }

  interrupts(); // end critical section

  // log SUBQ packets
#ifdef ATTINY_X5
  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){ // a bad sector read is all 0 except for the CRC fields. Don't log it.
    for (int i = 0; i<12;i++) {
      if (scbuf[i] < 0x10) {mySerial.print("0");} // padding
      mySerial.print(scbuf[i], HEX);
      mySerial.print(" ");
    }
    mySerial.println("");
  }
#else
  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){
    for (int i = 0; i<12;i++) {
      if (scbuf[i] < 0x10) {Serial.print("0");} // padding
      Serial.print(scbuf[i], HEX);
      Serial.print(" ");
    }
    Serial.println("");
//    Serial.flush();
//    if (scbuf[0] != 0x41)
//      digitalWrite(LED_BUILTIN, HIGH); 
//    else 
//      digitalWrite(LED_BUILTIN, LOW); 
  }
#endif

  // check if read head is in wobble area
  // We only want to unlock game discs (0x41) and only if the read head is in the outer TOC area.
  // We want to see a TOC sector repeatedly before injecting (helps with timing and marginal lasers).
  // All this logic is because we don't know if the HC-05 is actually processing a getSCEX() command.
  // Hysteresis is used because older drives exhibit more variation in read head positioning. 
  // While the laser lens moves to correct for the error, they can pick up a few TOC sectors.  
  static byte hysteresis  = 0;
  
  if ( 
    (scbuf[0] == 0x41 &&  scbuf[1] == 0x00 &&  scbuf[6] == 0x00) &&   // [0] = 41 means psx game disk. the other 2 checks are garbage protection
    (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 ||      // if [2] = A0, A1, A2 ..
    (scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02) ) )   // .. or = 01 but then [3] is either > 98 or < 02
  ) {
    hysteresis++;
  }
  else if ( hysteresis > 0 && 
          ((scbuf[0] == 0x01 || scbuf[0] == 0x41) && (scbuf[1] == 0x00 /*|| scbuf[1] == 0x01*/) &&  scbuf[6] == 0x00)
  ) {  // This CD has the wobble into CD-DA space. (started at 0x41, then went into 0x01)
    hysteresis++;
  }
  else if (hysteresis > 0) {
    hysteresis--; // None of the above. Initial detection was noise. Decrease the counter.
  }

  // hysteresis value "optimized" using very worn but working drive on ATmega328 @ 16Mhz
  // should be fine on other MCUs and speeds, as the PSX dictates SUBQ rate
  if (hysteresis >= 14){ 
    hysteresis = 0;

#ifdef ATTINY_X5
    mySerial.println("!");
#else
    Serial.println("INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!");
#endif

    pinMode(data, OUTPUT);
    digitalWrite(data, 0); // pull data low
    if (!pu22mode){
      pinMode(gate_wfck, OUTPUT);
      digitalWrite(gate_wfck, 0);
    }
    
    // HC-05 waits for a bit of silence (pin low) before it begins decoding.
    delay(delay_between_injections);
    // inject symbols now. 2 x 3 runs seems optimal to cover all boards
    for (byte loop_counter = 0; loop_counter < 2; loop_counter++)
    {
      inject_SCEX('e'); // e = SCEE, a = SCEA, i = SCEI
      inject_SCEX('a'); // injects all 3 regions by default
      inject_SCEX('i'); // optimize boot time by sending only your console region letter (all 3 times per loop)
    }

    if (!pu22mode){
      pinMode(gate_wfck, INPUT); // high-z the line, we're done
    }
    pinMode(data, INPUT); // high-z the line, we're done
  }
// keep catching SUBQ packets forever
}

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » July 12th, 2017, 11:48 pm

I haven't tested my latest changes on all configurations but I know it might fix some issues you guys have.
So here you go.

Code: Select all

// PsNee / psxdev.net version
// For Arduino and ATtiny
//
// Quick start: Select your hardware via the #defines, compile + upload the code, install in PSX.
// There are some pictures in the development thread ( http://www.psxdev.net/forum/viewtopic.php?f=47&t=1262&start=120 )
//
// Arduinos:
//  - Arduino Pro Mini @8Mhz and @16Mhz (supported, tested)
//  - Arduino Uno @8Mhz and @16Mhz (supported, tested)
//  - Arduino Pro Micro has a different pin assignment and needs some easy porting. (ToDo)
//  - Use #define ARDUINO_BOARD
// ATtiny:
//  - ATtiny85: Should work the same as ATtiny45 (supported, untested)
//  - ATtiny45: LFUSE 0xE2  HFUSE 0xDF > internal oscillator, full 8Mhz speed (supported, tested)
//  - ATtiny25: Should work the same as ATtiny45 but doesn't have enough Flash nor RAM for PSNEEDEBUG (supported, untested)
//  - Use #define ATTINY_X5
//
// Some extra libraries might be required, depending on the board / chip used.
// PAL PM-41 support isn't implemented yet. (ToDo)
// This code defaults to multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.
// You can optimize boot times for your console further. See "// inject symbols now" in the main loop.

// Choose your hardware! 
// 2 main branches available: 
//  - ATmega based > easy to use, fast and nice features for development
//  - ATtiny based > less features, internal clock has 10% variation

//#define ARDUINO_BOARD
//#define ATTINY_X5

//#define PSNEEDEBUG

#include <avr/pgmspace.h>

#if defined(ARDUINO_BOARD)
 // board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
 #define sqck 6          // connect to PSX HC-05 SQCK pin
 #define subq 7          // connect to PSX HC-05 SUBQ pin
 #define data 8          // connect to point 6 in old modchip diagrams
 #define gate_wfck 9     // connect to point 5 in old modchip diagrams
 // MCU I/O definitions
 #define SUBQPORT PIND       // MCU port for the 2 SUBQ sampling inputs
 #define SQCKBIT 6           // PD6 "SQCK" < Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
 #define SUBQBIT 7           // PD7 "SUBQ" < Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
 #define GATEWFCKPORT PINB   // MCU port for the gate input (used for WFCK)
 #define DATAPORT PORTB      // MCU port for the gate input (used for WFCK)
 #define GATEWFCKBIT 1       // PB1
 #define DATABIT 0           // PB0
#elif defined(ATTINY_X5) // ATtiny 25/45/85
 // extras
 #define USINGSOFTWARESERIAL
 // board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
 #define sqck 0
 #define subq 1
 #define data 2
 #define gate_wfck 4
 #define debugtx 3
 // MCU I/O definitions
 #define SUBQPORT PINB
 #define SQCKBIT 0
 #define SUBQBIT 1
 #define GATEWFCKPORT PINB
 #define DATAPORT PORTB
 #define GATEWFCKBIT 4
 #define DATABIT 2
#else
 #error "Select a board!"
#endif

#if defined(PSNEEDEBUG) && defined(USINGSOFTWARESERIAL)
 #include <SoftwareSerial.h>
 SoftwareSerial mySerial(-1, 3); // RX, TX. (RX -1 = off)
 #define DEBUG_PRINT(x)     mySerial.print(x)
 #define DEBUG_PRINTHEX(x)  mySerial.print(x, HEX)
 #define DEBUG_PRINTLN(x)   mySerial.println(x)
 #define DEBUG_FLUSH        mySerial.flush()
#elif defined(PSNEEDEBUG) && !defined(USINGSOFTWARESERIAL)
 #define DEBUG_PRINT(x)     Serial.print(x)
 #define DEBUG_PRINTHEX(x)  Serial.print(x, HEX)
 #define DEBUG_PRINTLN(x)   Serial.println(x)
 #define DEBUG_FLUSH        Serial.flush()
#else
 #define DEBUG_PRINT(x)     
 #define DEBUG_PRINTHEX(x)  
 #define DEBUG_PRINTLN(x)
 #define DEBUG_FLUSH        
#endif 

#define NOP __asm__ __volatile__ ("nop\n\t")

// Setup() detects which (of 2) injection methods this PSX board requires, then stores it in pu22mode.
boolean pu22mode;

//Timing
const int delay_between_bits = 4000;      // 250 bits/s (microseconds) (ATtiny 8Mhz works from 3950 to 4100)
const int delay_between_injections = 90;  // 72 in oldcrow. PU-22+ work best with 80 to 100 (milliseconds)

// borrowed from AttyNee. Bitmagic to get to the SCEX strings stored in flash (because Harvard architecture)
bool readBit(int index, const unsigned char *ByteSet)
{
  int byte_index = index >> 3;
  byte bits = pgm_read_byte(&(ByteSet[byte_index]));
  int bit_index = index & 0x7; // same as (index - byte_index<<3) or (index%8)
  byte mask = 1 << bit_index;
  return (0 != (bits & mask));
}

void inject_SCEX(char region)
{
  //SCEE: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01011101 00
  //SCEA: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01111101 00
  //SCEI: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01101101 00
  //const boolean SCEEData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  //const boolean SCEAData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  //const boolean SCEIData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0};
  static const PROGMEM unsigned char SCEEData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11101010, 0b00000010};
  static const PROGMEM unsigned char SCEAData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11111010, 0b00000010};
  static const PROGMEM unsigned char SCEIData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11011010, 0b00000010};
  
  // pinMode(data, OUTPUT) is used more than it has to be but that's fine.
  for (byte bit_counter = 0; bit_counter < 44; bit_counter++)
  {
    if (readBit(bit_counter, region == 'e' ? SCEEData : region == 'a' ? SCEAData : SCEIData) == 0)
    {
      pinMode(data, OUTPUT);
      bitClear(GATEWFCKPORT,DATABIT); // data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      if (pu22mode) {
        pinMode(data, OUTPUT);
        unsigned long now = micros();
        do {
          bool wfck_sample = bitRead(GATEWFCKPORT, GATEWFCKBIT);
          bitWrite(DATAPORT,DATABIT,wfck_sample); // output wfck signal on data pin
        }
        while ((micros() - now) < delay_between_bits);
      }
      else { // PU-18 or lower mode
        pinMode(data, INPUT);
        delayMicroseconds(delay_between_bits);
      }
    }
  }

  pinMode(data, OUTPUT);
  bitClear(GATEWFCKPORT,DATABIT); // pull data low
  delay(delay_between_injections);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------

void setup()
{
  pinMode(data, INPUT); 
  pinMode(gate_wfck, INPUT); 
  pinMode(subq, INPUT); // PSX subchannel bits
  pinMode(sqck, INPUT); // PSX subchannel clock
  
  #if defined(PSNEEDEBUG) && defined(USINGSOFTWARESERIAL)
  pinMode(debugtx, OUTPUT); // software serial tx pin
  mySerial.begin(115200); // 13,82 bytes in 12ms, max for softwareserial. (expected data: ~13 bytes / 12ms)
  #elif defined(PSNEEDEBUG) && !defined(USINGSOFTWARESERIAL)
  Serial.begin(500000); // 60 bytes in 12ms (expected data: ~26 bytes / 12ms)
  DEBUG_PRINT("MCU frequency: "); DEBUG_PRINT(F_CPU); DEBUG_PRINTLN(" Hz");
  DEBUG_PRINTLN("Waiting for SQCK..");
  #endif

  #if defined(ARDUINO_BOARD)
  pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug.
  digitalWrite(LED_BUILTIN, HIGH); // mark begin of setup
  #endif
  
  // wait for console power on and stable signals
  while (!digitalRead(sqck));   
  while (!digitalRead(gate_wfck));

  // Board detection
  //
  // GATE: __-----------------------  // this is a PU-7 .. PU-20 board!
  //
  // WFCK: __-_-_-_-_-_-_-_-_-_-_-_-  // this is a PU-22 or newer board!

  unsigned int highs, lows = 0;
  unsigned long now = millis();
  do {
    if (digitalRead(gate_wfck) == 1) highs++;
    if (digitalRead(gate_wfck) == 0) lows++;
    delayMicroseconds(200);   // good for ~5000 reads in 1s
  }
  while ((millis() - now) < 1000); // sample 1s

  // typical readouts
  // PU-22: highs: 2449 lows: 2377
  if (lows > 100) {
    pu22mode = 1;
  }
  else {
    pu22mode = 0;
  }
  
  #ifdef ATTINY_X5
  DEBUG_PRINT("m "); DEBUG_PRINTLN(pu22mode);
  #else
  DEBUG_PRINT("highs: "); DEBUG_PRINT(highs); DEBUG_PRINT(" lows: "); DEBUG_PRINTLN(lows);
  DEBUG_PRINT("pu22mode: "); DEBUG_PRINTLN(pu22mode);
  // Power saving
  // Disable the ADC by setting the ADEN bit (bit 7)  of the ADCSRA register to zero.
  ADCSRA = ADCSRA & B01111111;
  // Disable the analog comparator by setting the ACD bit (bit 7) of the ACSR register to one.
  ACSR = B10000000;
  // Disable digital input buffers on all analog input pins by setting bits 0-5 of the DIDR0 register to one.
  DIDR0 = DIDR0 | B00111111;
  #endif
  
  #if defined(ARDUINO_BOARD)
  digitalWrite(LED_BUILTIN, LOW); // setup complete
  #endif
  
  DEBUG_FLUSH; // empty serial transmit buffer
}

void loop()
{
  static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read.
  static unsigned int timeout_clock_counter = 0;
  static byte bitbuf = 0;   // SUBQ bit storage
  static bool sample = 0;
  static byte bitpos = 0; 
  byte scpos = 0;           // scbuf position
  
  noInterrupts(); // start critical section
start:
  // Capture 8 bits for 12 runs > complete SUBQ transmission
  bitpos = 0;
  for (; bitpos<8; bitpos++) {
    while (bitRead(SUBQPORT, SQCKBIT) == 1){
      // wait for clock to go low..
      // a timeout resets the 12 byte stream in case the PSX sends malformatted clock pulses, as happens on bootup
      timeout_clock_counter++;
      if (timeout_clock_counter > 1000){
        scpos = 0;  // reset SUBQ packet stream
        timeout_clock_counter = 0;
        bitbuf = 0;
        goto start;
      }
    }
    
    // wait for clock to go high..
    while ((bitRead(SUBQPORT, SQCKBIT)) == 0); 
    
    sample = bitRead(SUBQPORT, SUBQBIT);
    bitbuf |= sample << bitpos;

    timeout_clock_counter = 0; // no problem with this bit
  }

  // one byte done
  scbuf[scpos] = bitbuf;
  scpos++;
  bitbuf = 0;

  // repeat for all 12 bytes
  if (scpos < 12){
    goto start;
  }
  interrupts(); // end critical section

  // log SUBQ packets. We only have 12ms to get the logs written out. Slower MCUs get less formatting.
  #ifdef ATTINY_X5
  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){ // a bad sector read is all 0 except for the CRC fields. Don't log it.
    for (int i = 0; i<12;i++) {
      if (scbuf[i] < 0x10) {DEBUG_PRINT("0");} // padding
      DEBUG_PRINTHEX(scbuf[i]);
    }
    DEBUG_PRINTLN("");
  }
  #else
  if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){
    for (int i = 0; i<12;i++) {
      if (scbuf[i] < 0x10) {DEBUG_PRINT("0");} // padding
      DEBUG_PRINTHEX(scbuf[i]);
      DEBUG_PRINT(" ");
    }
    DEBUG_PRINTLN("");
  }
  #endif

  // check if read head is in wobble area
  // We only want to unlock game discs (0x41) and only if the read head is in the outer TOC area.
  // We want to see a TOC sector repeatedly before injecting (helps with timing and marginal lasers).
  // All this logic is because we don't know if the HC-05 is actually processing a getSCEX() command.
  // Hysteresis is used because older drives exhibit more variation in read head positioning. 
  // While the laser lens moves to correct for the error, they can pick up a few TOC sectors.  
  static byte hysteresis  = 0;
  
  if ( 
    (scbuf[0] == 0x41 &&  scbuf[1] == 0x00 &&  scbuf[6] == 0x00) &&   // [0] = 41 means psx game disk. the other 2 checks are garbage protection
    (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 ||      // if [2] = A0, A1, A2 ..
    (scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02) ) )   // .. or = 01 but then [3] is either > 98 or < 02
  ) {
    hysteresis++;
  }
  else if ( hysteresis > 0 && 
          ((scbuf[0] == 0x01 || scbuf[0] == 0x41) && (scbuf[1] == 0x00 /*|| scbuf[1] == 0x01*/) &&  scbuf[6] == 0x00)
  ) {  // This CD has the wobble into CD-DA space. (started at 0x41, then went into 0x01)
    hysteresis++;
  }
  else if (hysteresis > 0) {
    hysteresis--; // None of the above. Initial detection was noise. Decrease the counter.
  }

  // hysteresis value "optimized" using very worn but working drive on ATmega328 @ 16Mhz
  // should be fine on other MCUs and speeds, as the PSX dictates SUBQ rate
  if (hysteresis >= 14){ 
    // If the read head is still here after injection, resending should be quick.
    // Hysteresis naturally goes to 0 otherwise (the read head moved).
    hysteresis = 11;

    #ifdef ATTINY_X5
    DEBUG_PRINTLN("!");
    #else
    DEBUG_PRINTLN("INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!");
    #endif
    #if defined(ARDUINO_BOARD)
    digitalWrite(LED_BUILTIN, HIGH);
    #endif
    
    pinMode(data, OUTPUT);
    digitalWrite(data, 0); // pull data low
    if (!pu22mode){
      pinMode(gate_wfck, OUTPUT);
      digitalWrite(gate_wfck, 0);
    }
    
    // HC-05 waits for a bit of silence (pin low) before it begins decoding.
    delay(delay_between_injections);
    // inject symbols now. 2 x 3 runs seems optimal to cover all boards
    for (byte loop_counter = 0; loop_counter < 2; loop_counter++)
    {
      inject_SCEX('e'); // e = SCEE, a = SCEA, i = SCEI
      inject_SCEX('a'); // injects all 3 regions by default
      inject_SCEX('i'); // optimize boot time by sending only your console region letter (all 3 times per loop)
    }

    if (!pu22mode){
      pinMode(gate_wfck, INPUT); // high-z the line, we're done
    }
    pinMode(data, INPUT); // high-z the line, we're done
    #if defined(ARDUINO_BOARD)
    digitalWrite(LED_BUILTIN, LOW);
    #endif
  }
// keep catching SUBQ packets forever
}

Last edited by rama3 on July 13th, 2017, 4:23 am, edited 1 time in total.

Post Reply

Who is online

Users browsing this forum: No registered users and 4 guests