use USBemani codebase #1
@@ -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
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user