use USBemani codebase #1

Closed
CrazyRedMachine wants to merge 2 commits from usbemani into main
4 changed files with 384 additions and 240 deletions
+55 -43
View File
@@ -24,9 +24,9 @@ Let's get acquainted with the Playstation cable and color codes. It is always a
* **CIPO:** (Controller-In / Peripheral-Out) This is data from the controller to the console. It is held HIGH via a pull-up resistor inside the console. We will use one of the transistors to pull this line to GND. Also known as MISO.
* **COPI:** (Controller-Out / Peripheral-In) This is the data from the console to the controller. Also known as MOSI.
* **7.6V:** This typically powers the rumble motors, but we can use it to power the arduino.
* **7.6V:** This typically powers the rumble motors, but we can use it to power the arduino (see `Powering the arduino` in additional notes section).
* **GND:** The common ground for the console, also known as 0V.
* **3.3V:** This is power coming from the console. We will not be using this.
* **3.3V:** This is power coming from the console. We can use it to power the arduino (see `Powering the arduino` in additional notes section).
* **CS:** (Chip Select) This line goes LOW when the console is requesting data from a controller. This is how the console selects which player's controller to read from. Also known as SS.
* **N/C:** This wire is not used (not connected).
* **ACK:** This is how the controller tells the console it has finished sending data. It is held HIGH via a pull-up resistor inside the console. The second transistor will pull this line to GND.
@@ -47,56 +47,56 @@ The example program follows the wiring below. Please refer to your specific tran
### Pins that can be changed
* ACK
* ACK (see `ps2.c`)
## Basic Use
The example includes D-Pad controls. Connect `A0`, `A1`, `A2`, or `A3`.
### setup()
- Prepare your input pins by setting them as INPUT_PULLUP
- Call the `PS2_MapInput(uint16_t *input, uint16_t mask, PS2_INPUT buttons)` function multiple times to setup your button mapping. This will create the link between one bit of your input bitfield (via the mask) and one or several PS2_INPUT buttons
- (optional) You may also call `PS2_AlwaysInput(PS2_INPUT buttons)` to add constantly pressed inputs to the mapping if needed (some dedicated game controllers work this way)
- Call `PS2_Init()`
Valid values for PS2_INPUT buttons are listed in ps2.h :
```
button_state = 0;
if (digitalRead(A0) == LOW) {
button_state |= PS_LEFT;
}
if (digitalRead(A1) == LOW) {
button_state |= PS_DOWN;
}
if (digitalRead(A2) == LOW) {
button_state |= PS_UP;
}
if (digitalRead(A3) == LOW) {
button_state |= PS_RIGHT;
}
PS2_SELECT
PS2_L3
PS2_R3
PS2_START
PS2_UP
PS2_RIGHT
PS2_DOWN
PS2_LEFT
PS2_L2
PS2_R2
PS2_L1
PS2_R1
PS2_TRIANGLE
PS2_CIRCLE
PS2_CROSS
PS2_SQUARE
```
To set a button, `BITWISE OR` the correct define with `button_state`. A button is released if it is not pressed during the loop.
### loop()
Valid defines are:
- To set a button, `BITWISE OR` the correct define with `button_state`. A button is released if it is not pressed during the loop
```
PS_SELECT
PS_L3
PS_R3
PS_START
PS_UP
PS_RIGHT
PS_DOWN
PS_LEFT
PS_L2
PS_R2
PS_L1
PS_R1
PS_TRIANGLE
PS_CIRCLE
PS_CROSS
PS_SQUARE
```
- Call `PS2_Task()` to poll the console and update inputs
### Example
The sketch example includes D-Pad controls and start button, connected to `A0`, `A1`, `A2`, `A3` and `A4`.
### Additional Notes
#### Changing the ACK Pin
At the top of the `*.ino` file will be a section to change the ACK pin location.
At the top of the `ps2.c` file will be a section to change the ACK pin location.
```
/* USER CUSTOMIZABLE SETTINGS */
@@ -107,28 +107,40 @@ At the top of the `*.ino` file will be a section to change the ACK pin location.
The pin number refers to the `PORT` number, not the standard Arduino number. Refer to the purple tags in the image above.
#### Powering the Arduino
There are several **mutually exclusive** ways to power the arduino :
- Use an USB cable with a wall charger (this unfortunately won't work via the PS2 usb port)
- Wire the 7.6 rumble motor line to the arduino `Vin` pin
- Wire the 3.3V line to the arduino `5v` pin (yes, 5v pin, not a typo)
All of these ways are mutually exclusive. **DO NOT** wire multiple power sources at the same time, this can fry your console.
#### Bypassing the Transistors
For safety reasons, I suggest using the transistors as shown above to prevent backfeeding voltage into your console. This has the potential to cause harm.
If you do not use the transistors and directly wire your Arduino to the console, the following line must be changed
If you do not use the transistors and directly wire your Arduino to the console, the following line must be changed at the top of `ps2.c`
```
#define INVERT_OUTPUT 1 // Set to 1 if CIPO and ACK are open-drain via transistors
#define INVERT_CIPO 1 // Set to 1 if CIPO is open-drain via transistor (recommended)
#define INVERT_ACK 1 // Set to 1 if ACK is open-drain via transistor (recommended)
```
to
```
#define INVERT_OUTPUT 0 // Set to 1 if CIPO and ACK are open-drain via transistors
#define INVERT_CIPO 0 // Set to 1 if CIPO is open-drain via transistor (recommended)
#define INVERT_ACK 0 // Set to 1 if ACK is open-drain via transistor (recommended)
```
***THIS IS NOT TESTED AND I DO NOT RECOMMEND IT, I AM NOT LIABLE FOR ANY DAMAGE CAUSED TO YOUR PLAYSTATION.***
***WHILE THIS HAS BEEN TESTED USING THE 3.3V LINE TO 5V PIN, I DO NOT RECOMMEND IT, I AM NOT LIABLE FOR ANY DAMAGE CAUSED TO YOUR PLAYSTATION.***
## Credits
Wiring and protocol information from [Curious Inventor's page on the Playstation Controller](https://store.curiousinventor.com/guides/PS2)
Code base adapted from [CrazyRedMachine's Ultiamte Pop'n Controller](https://github.com/CrazyRedMachine/UltimatePopnController/tree/PSX), which itself is based on [busslave's PSX_RECEIVER.cpp](https://nfggames.com/forum2/index.php?topic=5001.0).
Code base adapted from [progmem's USBemani v3](https://github.com/progmem/re-usbemani/).
Discord User GoroKaneda for getting me to work on and document this, as well as additional testing.
+99 -197
View File
@@ -1,197 +1,99 @@
/* USER CUSTOMIZABLE SETTINGS */
#define ACK_PORT PORTB
#define ACK_DDR DDRB
#define ACK_PIN 4 // PB4 (Pin 8 on Micro)
/* RECOMMENDED DO NOT CHANGE */
#define INVERT_OUTPUT 1 // Set to 1 if CIPO and ACK are open-drain via transistors
/* PSX DEFINE */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define SPI_PORT PORTB
#define SPI_PINS PINB
#define SPI_DDR DDRB
#define SPI_PINS PINB
#define ATT_PIN 0 // ~CS
#define CMD_PIN 2 // COPI
#define DATA_PIN 3 // CIPO
#define CLK_PIN 1 // SCK
#define DATA_LEN 5
#define PS_SELECT (1 << 0)
#define PS_L3 (1 << 1)
#define PS_R3 (1 << 2)
#define PS_START (1 << 3)
#define PS_UP (1 << 4)
#define PS_RIGHT (1 << 5)
#define PS_DOWN (1 << 6)
#define PS_LEFT (1 << 7)
#define PS_L2 (1 << 8)
#define PS_R2 (1 << 9)
#define PS_L1 (1 << 10)
#define PS_R1 (1 << 11)
#define PS_TRIANGLE (1 << 12)
#define PS_CIRCLE (1 << 13)
#define PS_CROSS (1 << 14)
#define PS_SQUARE (1 << 15)
/* /PSX DEFINE */
/* PSX globals */
#if INVERT_OUTPUT
volatile uint8_t data_buff[DATA_LEN] = {0xBE, 0xA5, 0x00, 0x00, 0x00}; //Reply.
#else
volatile uint8_t data_buff[DATA_LEN] = {0x41, 0x5A, 0xFF, 0xFF, 0xFF}; //Reply.
#endif
volatile uint8_t command_buff[DATA_LEN] = {0x01, 0x42, 0x00, 0x00, 0x00};
volatile uint8_t curr_byte = 0;
volatile uint8_t next_byte = 0;
byte byte4;
byte byte5;
/* Read bytes in byte4 and byte5 as follows:
+----------+--------+----+----+----+----+----+----+----+----+
| byte |command | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
+==========+========+====+====+====+====+====+====+====+====+
| 1st byte | 0x01 | ----- |
+----------+--------+---------------------------------------+
| 2nd byte | 0x42 | 0x41 | 'A'
+----------+--------+---------------------------------------+
| 3rd byte | 0x00 | 0x5a | 'Z'
+----------+--------+----+----+----+----+----+----+----+----+
| 4th byte | 0x00 | L | DW | R | UP | ST | 1 | 1 |SEL |
+----------+--------+----+----+----+----+----+----+----+----+
| 5th byte | 0x00 | [] | X | O | <| | R1 | L1 | R2 | L2 |
+----------+--------+----+----+----+----+----+----+----+----+
*/
/* NOTE: a bit set to 1 is unpressed, a bit set to 0 is pressed */
void convertInputToPSX(uint16_t state) {
byte4 = ~(state & 0xFF);
byte5 = ~((state >> 8) & 0xFF);
#if INVERT_OUTPUT
data_buff[2] = ~byte4;
data_buff[3] = ~byte5;
#else
data_buff[2] = byte4;
data_buff[3] = byte5;
#endif
}
ISR(SPI_STC_vect) {
uint8_t inbyte = SPDR;
if (inbyte == command_buff[curr_byte]) {
#if INVERT_OUTPUT == 0
SPI_DDR |= (1 << DATA_PIN); // output
#endif
SPDR = data_buff[curr_byte];
curr_byte++;
if (curr_byte < DATA_LEN) {
_delay_us(8); // Necessary delay for PS1 (not needed for PS2)
// Set ACK low
#if INVERT_OUTPUT
ACK_PORT |= (1 << ACK_PIN);
#else
ACK_DDR |= (1 << ACK_PIN); // output
ACK_PORT &= ~(1 << ACK_PIN);
#endif
_delay_us(1);
// Set ACK high
#if INVERT_OUTPUT
ACK_PORT &= ~(1 << ACK_PIN); // Release ACK
#else
ACK_DDR &= ~(1 << ACK_PIN); // input
ACK_PORT &= ~(1 << ACK_PIN); // ensure pullup is off
#endif
} else {
SPDR = 0xFF;
curr_byte = 0;
}
} else {
SPDR = 0xFF;
curr_byte = 0;
}
}
/* /PSX globals */
uint16_t button_state = 0;
void setup() {
/* PSX setup */
#if INVERT_OUTPUT
ACK_DDR |= (1 << ACK_PIN); // output
ACK_PORT &= ~(1 << ACK_PIN); // set LOW (open drain -- pull-up on console)
SPI_DDR |= (1 << DATA_PIN); // output
SPI_PORT &= ~(1 << DATA_PIN); //set LOW (open drain -- pull-up on console)
#else
ACK_DDR &= ~(1 << ACK_PIN); // input
ACK_PORT &= ~(1 << ACK_PIN); // ensure pullup is off
SPI_DDR &= ~(1 << DATA_PIN); // input
SPI_PORT &= ~(1 << DATA_PIN); // ensure pullup is off
#endif
//SPI setup
SPCR |= (1 << SPR1); // Fosc/64. @16MHz==250KHz.
SPCR |= (1 << CPHA); // Setup @ leading edge, sample @ falling edge.
SPCR |= (1 << CPOL); // Leading edge is falling edge, trailing edge is rising edge.
SPCR &= ~(1 << MSTR); // MSTR bit is zero, SPI is slave.
SPCR |= (1 << DORD); // Byte is transmitted LSB first, MSB last.
SPCR |= (1 << SPE); // Enable SPI.
SPCR |= (1 << SPIE); // Enable Serial Transfer Complete (STC) interrupt.
SPDR = 0xFF;
sei(); // Enable global interrupts
/* /PSX setup */
/* --- --- --- --- --- --- */
/* USER CODE STARTS HERE */
/* --- --- --- --- --- --- */
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
}
void loop() {
button_state = 0;
if (digitalRead(A0) == LOW) {
button_state |= PS_LEFT;
}
if (digitalRead(A1) == LOW) {
button_state |= PS_DOWN;
}
if (digitalRead(A2) == LOW) {
button_state |= PS_UP;
}
if (digitalRead(A3) == LOW) {
button_state |= PS_RIGHT;
}
/* --- --- --- --- --- --- */
/* USER CODE ENDS HERE */
/* --- --- --- --- --- --- */
convertInputToPSX(button_state);
#if INVERT_OUTPUT == 0
if ((SPI_PORT & (1 << ATT_PIN) > 0)) {
SPI_DDR &= ~(1 << DATA_PIN); // input
SPI_PORT &= ~(1 << DATA_PIN); // ensure pullup is off
}
#endif
}
#include "ps2.h"
/* internal button state define */
#define BUTTON_1 (1<<0)
#define BUTTON_2 (1<<1)
#define BUTTON_3 (1<<2)
#define BUTTON_4 (1<<3)
#define BUTTON_5 (1<<4)
#define BUTTON_6 (1<<5)
#define BUTTON_7 (1<<6)
#define BUTTON_8 (1<<7)
#define BUTTON_9 (1<<8)
#define BUTTON_10 (1<<9)
uint16_t button_state = 0;
void setup() {
/* Pins setup */
pinMode(0, INPUT_PULLUP);
pinMode(1, INPUT_PULLUP);
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, INPUT_PULLUP);
pinMode(7, INPUT_PULLUP);
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
/* PS2 Input Mapping */
// map bits from internal button state bitfield to the corresponding PS buttons
PS2_MapInput(&button_state, BUTTON_1, PS2_UP);
PS2_MapInput(&button_state, BUTTON_2, PS2_DOWN);
PS2_MapInput(&button_state, BUTTON_3, PS2_LEFT);
PS2_MapInput(&button_state, BUTTON_4, PS2_RIGHT);
PS2_MapInput(&button_state, BUTTON_5, PS2_TRIANGLE);
PS2_MapInput(&button_state, BUTTON_6, PS2_CROSS);
PS2_MapInput(&button_state, BUTTON_7, PS2_SQUARE);
PS2_MapInput(&button_state, BUTTON_8, PS2_CIRCLE);
PS2_MapInput(&button_state, BUTTON_9, PS2_START);
PS2_MapInput(&button_state, BUTTON_10,PS2_SELECT);
// if you need your controller to keep some buttons always pressed (e.g. pop'n controller)
//PS2_AlwaysInput(PS2_LEFT|PS2_DOWN|PS2_RIGHT);
/* PS2 Init */
// Indicate how MISO will be used for the PS2.
// * PS2_TRANSISTOR is the most reliable method, requiring an N-Channel MOSFET or transistor.
// * Connect the AVR MISO pin to the base/gate.
// * Connect the PS2 MISO pin to the collector/drain.
// * Connect the emitter/source to ground.
// * The BS170 N-Channel MOSFET works well with no gate resistor.
// * PS2_DIRECT can be used if running at 3.3V.
// * This will provide varying levels of success based on a number of factors.
// * Cable quality and the presence of a ferrite core are factors that come to play.
// * A generic PS1 extension with no ferrites can work better than Konami's cable!
// * If PS2_DIRECT doesn't work for you, you must use PS2_TRANSISTOR.
//
// Additionally, indicate which pin you want to use for PS2 "Acknowledge" line in the header of ps2.c
PS2_Init(PS2_DIRECT);
}
void loop() {
button_state = 0;
if (digitalRead(0) == LOW) {
button_state |= BUTTON_1;
}
if (digitalRead(1) == LOW) {
button_state |= BUTTON_2;
}
if (digitalRead(2) == LOW) {
button_state |= BUTTON_3;
}
if (digitalRead(3) == LOW) {
button_state |= BUTTON_4;
}
if (digitalRead(4) == LOW) {
button_state |= BUTTON_5;
}
if (digitalRead(5) == LOW) {
button_state |= BUTTON_6;
}
if (digitalRead(6) == LOW) {
button_state |= BUTTON_7;
}
if (digitalRead(7) == LOW) {
button_state |= BUTTON_8;
}
if (digitalRead(8) == LOW) {
button_state |= BUTTON_9;
}
if (digitalRead(9) == LOW) {
button_state |= BUTTON_10;
}
PS2_Task();
}
+178
View File
@@ -0,0 +1,178 @@
#include "ps2.h"
/* USER CUSTOMIZABLE SETTINGS */
#define ACK_PORT PORTB
#define ACK_DDR DDRB
#define ACK_PIN 4 // PB4 (Pin 8 on Micro)
/* END OF USER CUSTOMIZABLE SETTINGS */
// Port and Pin mask to setup
uint8_t PinMask = (1<<ACK_PIN);
// Stores a constructed packet for the PS2.
uint16_t Data = 0;
// When set, ignore all data until chip select goes high again
// Shoutouts to @nicolasnoble for the insight on how the PS1 handles chip select
uint8_t QuietTime = 0;
// List of available PS2 inputs.
PS2_InputList_t *PS2Input = NULL;
// Current PS2 state.
void (*PS2Handler)(uint8_t) = NULL;
// Invert mask for PS2 data.
uint8_t InvertMask = 0x00;
inline uint8_t ps2_byte(uint8_t data) {
return data ^ InvertMask;
}
void PS2_Acknowledge(void) {
// Burn a few cycles before acknowledging
asm volatile(
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
);
ACK_DDR |= PinMask;
// 40 cycles of delay should give us the same delay as a real PS1 controller
asm volatile(
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
"nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
);
ACK_DDR &= ~(PinMask);
}
// Default implementation methods
void PS2_Listen(uint8_t in);
void PS2_Addressed(uint8_t in);
void PS2_HeaderFinished(uint8_t in);
void PS2_LowerSent(uint8_t in);
// Idle state.
void PS2_Listen(uint8_t in) {
// Report as a digital controller when addressed
if (in == 0x01) {
SPDR = ps2_byte(0x41);
PS2Handler = PS2_Addressed;
PS2_Acknowledge();
return;
}
// Otherwise, ignore all incoming traffic until our task performs a reset
DDRB &= ~0x08;
QuietTime = 255;
}
// When polling is requested, begin responding
void PS2_Addressed(uint8_t in) {
if (in == 0x42) {
SPDR = ps2_byte(0x5A);
PS2Handler = PS2_HeaderFinished;
PS2_Acknowledge();
}
}
// After end-of-header sent, send the first byte
void PS2_HeaderFinished(uint8_t in) {
uint8_t *data = (uint8_t *)&Data;
SPDR = ps2_byte(*data);
PS2Handler = PS2_LowerSent;
PS2_Acknowledge();
}
// After first byte sent, send the second and go back to listening.
void PS2_LowerSent(uint8_t in) {
uint8_t *data = (uint8_t *)&Data + 1;
SPDR = ps2_byte(*data);
PS2Handler = PS2_Listen;
PS2_Acknowledge();
}
void PS2_Init(PS2_INVERT invert) {
cli();
InvertMask = invert;
ACK_PORT &= ~(PinMask);
PS2_Acknowledge();
// Set MISO as an output pin
DDRB |= 0x08;
// Setup data on falling edge, sample on rising edge (SPI mode 3)
SPCR = (1 << CPOL) | (1 << CPHA)
// Transmit LSB first
| (1 << DORD)
// Enable interrupts for SPI
| (1 << SPIE)
// Enable SPI
| (1 << SPE);
// Set the first byte up
SPDR = ps2_byte(0xFF);
// Setup our default and current handlers
PS2Handler = PS2_Listen;
// Re-enable interrupts
sei();
}
// Update the stored data packet
void PS2_Task(void) {
// If chip select is high (not selected), quiet time is over. Reset state.
if (PINB & 0x01) {
QuietTime = 0;
DDRB |= 0x08;
SPDR = ps2_byte(0xFF);
}
PS2_InputList_t *map = PS2Input;
uint16_t new_data = 0;
while(map) {
if (!map->input || (*map->input & map->mask))
new_data |= map->buttons;
map = map->parent;
}
Data = ~new_data;
}
void PS2_MapInput(uint16_t *input, uint16_t mask, PS2_INPUT buttons) {
PS2_InputList_t *child = calloc(1, sizeof(PS2_InputList_t));
child->input = input,
child->mask = mask,
child->buttons = buttons,
child->parent = PS2Input;
PS2Input = child;
}
void PS2_AlwaysInput(PS2_INPUT buttons) {
PS2_InputList_t *child = calloc(1, sizeof(PS2_InputList_t));
child->input = NULL,
child->mask = 0,
child->buttons = buttons,
child->parent = PS2Input;
PS2Input = child;
}
// When a transfer is complete, determine what to do next
ISR(SPI_STC_vect) {
// If chip select is still enabled, stay in quiet mode if set.
if (QuietTime) return;
uint8_t input = SPDR;
// If our current input packet is polling the controller, re-enable writes and listen
if (input == 0x01)
PS2Handler = PS2_Listen;
PS2Handler(input);
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <avr/interrupt.h>
typedef enum {
PS2_DIRECT = 0x00,
PS2_TRANSISTOR = 0xFF,
} PS2_INVERT;
typedef enum {
PS2_NC = 0,
PS2_SELECT = (1 << 0),
PS2_L3 = (1 << 1),
PS2_R3 = (1 << 2),
PS2_START = (1 << 3),
PS2_UP = (1 << 4),
PS2_RIGHT = (1 << 5),
PS2_DOWN = (1 << 6),
PS2_LEFT = (1 << 7),
PS2_L2 = (1 << 8),
PS2_R2 = (1 << 9),
PS2_L1 = (1 << 10),
PS2_R1 = (1 << 11),
PS2_TRIANGLE = (1 << 12),
PS2_CIRCLE = (1 << 13),
PS2_CROSS = (1 << 14),
PS2_SQUARE = (1 << 15),
} PS2_INPUT;
typedef struct PS2_InputList_t PS2_InputList_t;
struct PS2_InputList_t {
uint16_t *input; // Input source; NULL is always true
uint16_t mask; // Mask to check; (*input & mask)
PS2_INPUT buttons; // OR the following if true
PS2_InputList_t *parent;
};
#ifdef __cplusplus
extern "C"{
#endif
void PS2_Init(PS2_INVERT invert);
void PS2_Task(void);
void PS2_MapInput(uint16_t *input, uint16_t mask, PS2_INPUT buttons);
void PS2_AlwaysInput(PS2_INPUT buttons);
#ifdef __cplusplus
}
#endif