aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2022-02-09 22:59:16 +0100
committerspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2022-02-09 22:59:16 +0100
commitaca79b2a75c9a6106bc0047f767a475a2c3aaf8e (patch)
treea9536efe30ce2d3a40414948494a0eda3e45dbbd /examples
parentc083d3f18ecf80297b45eeda2abdf2fd6719cd7b (diff)
downloadpsn00bsdk-aca79b2a75c9a6106bc0047f767a475a2c3aaf8e.tar.gz
Rename hwregs_a definitions, add hwregs_c, fix io/pads
Diffstat (limited to 'examples')
-rw-r--r--examples/io/pads/main.c57
-rw-r--r--examples/io/pads/spi.c103
-rw-r--r--examples/io/pads/spi.h15
-rw-r--r--examples/sound/spustream/main.c46
4 files changed, 102 insertions, 119 deletions
diff --git a/examples/io/pads/main.c b/examples/io/pads/main.c
index d100482..17bf331 100644
--- a/examples/io/pads/main.c
+++ b/examples/io/pads/main.c
@@ -15,12 +15,12 @@
* but the code in spi.c can be used to read/write sectors on a memory card and
* combined with a higher-level filesystem driver for full support.
*
- * IMPORTANT: this example hasn't yet been tested on real hardware and/or with
- * unofficial controllers, which might behave differently at higher poll rates.
- * Also keep in mind that many emulators emulate controllers and memory cards
- * inaccurately. It is thus recommended to test controller I/O code extensively
- * and handle as many edge cases as possible (e.g. partial but valid responses,
- * zerofilled responses, slow replies) for maximum compatibility.
+ * IMPORTANT: some controller models seem to be unable to respond to config
+ * mode commands reliably, even though simple high-speed polling usually works
+ * without issues. Also keep in mind that many emulators emulate controllers
+ * and memory cards inaccurately. It is thus recommended to test controller I/O
+ * code extensively and handle as many edge cases as possible (e.g. partial but
+ * valid responses, zerofilled responses, slow replies) for best compatibility.
*/
#include <stdint.h>
@@ -116,7 +116,7 @@ void display(CONTEXT *ctx) {
static volatile uint8_t pad_buff[2][34];
static volatile size_t pad_buff_len[2];
-static volatile uint32_t pad_digital_only[2] = { 0, 0 };
+static volatile uint32_t pad_config_attempt[2] = { 0, 0 };
// Just a wrapper around SPI_CreateRequest(). This does not send the command
// immediately but adds it to the driver's request queue.
@@ -148,7 +148,8 @@ void send_pad_cmd(
// This callback determines whether a pad that identified as digital is
// actually a DualShock in digital mode by checking if it started identifying
-// as CONFIG_MODE after receiving a configuration command.
+// as CONFIG_MODE after receiving a configuration command. Calls to printf()
+// had to be commented out due to them being too slow.
void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) {
PadResponse *pad = (PadResponse *) buff;
@@ -157,13 +158,13 @@ void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_le
(pad->prefix != 0x5a) ||
(pad->type != PAD_ID_CONFIG_MODE)
) {
- printf("no, pad is digital-only (len = %d)\n", rx_len);
+ //printf("no, pad is digital-only (len = %d)\n", rx_len);
- pad_digital_only[port] = 1;
+ pad_config_attempt[port]++;
return;
}
- printf("yes, forcing analog mode (len = %d)\n", rx_len);
+ //printf("yes, forcing analog mode (len = %d)\n", rx_len);
// Issue further commands to force analog mode on, unlock rumble (not used
// in this example) and enable longer responses containing button pressure
@@ -171,6 +172,7 @@ void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_le
// TODO: find out if passing 0x03 instead of 0x02 in PAD_CMD_SET_ANALOG
// locks the analog button, as emulated by DuckStation...
// https://gist.github.com/scanlime/5042071
+ send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x01, 0x00, 0);
send_pad_cmd(port, PAD_CMD_SET_ANALOG, 0x01, 0x02, 0);
send_pad_cmd(port, PAD_CMD_INIT_PRESSURE, 0x00, 0x00, 0); // Ignored by DualShock 1
send_pad_cmd(port, PAD_CMD_REQUEST_CONFIG, 0x00, 0x01, 0);
@@ -189,29 +191,37 @@ void poll_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) {
PadResponse *pad = (PadResponse *) buff;
- // If this pad identifies as a digital pad and hasn't been flagged as a
- // digital-only pad already, attempt to put it into analog mode by entering
- // configuration mode. It this fails, it will be flagged as digital-only.
- // The digital-only flag is reset when the controller is unplugged or stops
+ // If this pad identifies as a digital pad, attempt to put it into analog
+ // mode up to 3 times by entering configuration mode. Once the attempt
+ // counter exceeds the threshold, it will be treated as digital-only. The
+ // attempt counter is reset when the controller is unplugged or stops
// returning digital pad responses.
+ // NOTE: according to nocash docs, there is a hardware bug in DualShock
+ // controllers that causes the prefix byte (normally 0x5a) to turn into
+ // 0x00 if the analog button is pressed after config commands have been
+ // used.
if (
rx_len &&
- (pad->prefix == 0x5a) &&
+ ((pad->prefix == 0x5a) || !(pad->prefix)) &&
(pad->type == PAD_ID_DIGITAL)
) {
- if (!pad_digital_only[port]) {
- printf("Detecting if pad %d supports config mode... ", port + 1);
+ if (pad_config_attempt[port] < 3) {
+ /*printf(
+ "Detecting if pad %d supports config mode: attempt %d... ",
+ port + 1,
+ pad_config_attempt[port] + 1
+ );*/
// The pad only identifies as CONFIG_MODE after at least another
// command is sent.
send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x01, 0x00, 0);
- send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x01, 0x00, &dualshock_init_cb);
+ send_pad_cmd(port, PAD_CMD_READ, 0x00, 0x00, &dualshock_init_cb);
}
} else {
- //printf("Clearing digital-only flag for pad %d\n", port + 1);
+ //printf("Clearing attempt counter for pad %d\n", port + 1);
- pad_digital_only[port] = 0;
+ pad_config_attempt[port] = 0;
}
}
@@ -240,11 +250,6 @@ int main(int argc, const char* argv[]) {
PadResponse *pad = (PadResponse *) pad_buff[port];
- // According to nocash docs, there is a hardware bug in DualShock
- // controllers that causes the prefix byte (normally 0x5a) to turn
- // into 0x00 if the analog button is pressed after configuration
- // commands have been used. Thus making sure the prefix is 0x5a
- // isn't enough to reliably detect pads.
/*if ((pad->prefix != 0x5a) && (pad->type != PAD_ID_ANALOG)) {
FntPrint(-1, "\n\nPORT %d: INVALID RESPONSE\n", port + 1);
if ((counter % 64) < 32)
diff --git a/examples/io/pads/spi.c b/examples/io/pads/spi.c
index ef75ffc..05a0e59 100644
--- a/examples/io/pads/spi.c
+++ b/examples/io/pads/spi.c
@@ -32,43 +32,27 @@
#include <psxetc.h>
#include <psxapi.h>
#include <psxpad.h>
+#include <hwregs_c.h>
#include "spi.h"
-/* Register definitions */
-
-#define F_CPU 33868800UL
-
-#define TIM_VALUE(N) *((volatile uint32_t *) 0x1f801100 + 4 * (N))
-#define TIM_CTRL(N) *((volatile uint32_t *) 0x1f801104 + 4 * (N))
-#define TIM_RELOAD(N) *((volatile uint32_t *) 0x1f801108 + 4 * (N))
-
-// IMPORTANT: even though JOY_TXRX is a 32-bit register, it should only be
-// accessed as 8-bit. Reading it as 16 or 32-bit works fine on real hardware,
-// but leads to problems in some emulators.
-#define JOY_TXRX *((volatile uint8_t *) 0x1f801040)
-#define JOY_STAT *((volatile uint16_t *) 0x1f801044)
-#define JOY_MODE *((volatile uint16_t *) 0x1f801048)
-#define JOY_CTRL *((volatile uint16_t *) 0x1f80104a)
-#define JOY_BAUD *((volatile uint16_t *) 0x1f80104e)
-
/* Internal structures and globals */
-typedef struct _SPI_CONTEXT {
+typedef struct _SPI_Context {
uint8_t tx_buff[SPI_BUFF_LEN];
uint8_t rx_buff[SPI_BUFF_LEN];
uint32_t tx_len, rx_len, port;
SPI_Callback callback;
} SPI_Context;
-static volatile SPI_Context ctx;
-static volatile SPI_Request volatile *current_req;
-static SPI_Callback default_cb;
+static volatile SPI_Context _context;
+static volatile SPI_Request volatile *_current_req;
+static volatile SPI_Callback _default_cb;
/* Request queue management */
static void _spi_create_poll_req(void) {
- PadRequest *req = (PadRequest *) ctx.tx_buff;
+ PadRequest *req = (PadRequest *) _context.tx_buff;
req->addr = 0x01;
req->cmd = PAD_CMD_READ;
@@ -76,27 +60,31 @@ static void _spi_create_poll_req(void) {
req->motor_l = 0x00;
req->motor_r = 0x00;
- ctx.tx_len = 4;
- ctx.rx_len = 0;
- ctx.port ^= 1;
- ctx.callback = default_cb;
+ _context.tx_len = 4;
+ _context.rx_len = 0;
+ _context.port ^= 1;
+ _context.callback = _default_cb;
}
static void _spi_next_req(void) {
// Copy the contents of the first request in the queue into the TX buffer.
- memcpy((void *) ctx.tx_buff, (void *) current_req->data, current_req->len);
+ memcpy(
+ (void *) _context.tx_buff,
+ (void *) _current_req->data,
+ _current_req->len
+ );
- ctx.tx_len = current_req->len;
- ctx.rx_len = 0;
- ctx.port = current_req->port;
- ctx.callback = current_req->callback;
+ _context.tx_len = _current_req->len;
+ _context.rx_len = 0;
+ _context.port = _current_req->port;
+ _context.callback = _current_req->callback;
// Pop the first request from the queue by deallocating it and adjusting
// the pointer to the first queue item.
- SPI_Request *next = current_req->next;
+ SPI_Request *next = _current_req->next;
- free((void *) current_req);
- current_req = next;
+ free((void *) _current_req);
+ _current_req = next;
}
/* Interrupt handlers */
@@ -105,13 +93,13 @@ static void _spi_poll_irq_handler(void) {
// Fetch the last response byte, which wasn't followed by a pulse on /ACK,
// from the RX FIFO.
if (JOY_STAT & 0x0002)
- ctx.rx_buff[ctx.rx_len - 1] = (uint8_t) JOY_TXRX;
+ _context.rx_buff[_context.rx_len - 1] = (uint8_t) JOY_TXRX;
- if (ctx.callback)
- ctx.callback(ctx.port, ctx.rx_buff, ctx.rx_len);
+ if (_context.callback)
+ _context.callback(_context.port, _context.rx_buff, _context.rx_len);
// If the request queue is empty, create a pad polling request.
- if (current_req)
+ if (_current_req)
_spi_next_req();
else
_spi_create_poll_req();
@@ -119,17 +107,18 @@ static void _spi_poll_irq_handler(void) {
// Prepare the SPI port by clearing any pending IRQ, pulling /CS high and
// enabling the /ACK IRQ. In order to communicate with controllers, /CS has
// to be driven low again for about 20 us before sending the first byte.
+ // TODO: these delays can be probably tweaked for better performance
JOY_CTRL = 0x0010;
- for (uint32_t i = 0; i < 50; i++)
- __asm__("nop");
+ for (uint32_t i = 0; i < 1000; i++)
+ __asm__ volatile("");
- JOY_CTRL = 0x1003 | (ctx.port << 13);
- for (uint32_t i = 0; i < 500; i++)
- __asm__("nop");
+ JOY_CTRL = 0x1003 | (_context.port << 13);
+ for (uint32_t i = 0; i < 2000; i++)
+ __asm__ volatile("");
// Send the first byte indicating which device to address. If the matching
// device is connected, it will reply by triggering the /ACK IRQ.
- JOY_TXRX = ctx.tx_buff[0];
+ JOY_TXRX = _context.tx_buff[0];
}
static void _spi_ack_irq_handler(void) {
@@ -137,29 +126,29 @@ static void _spi_ack_irq_handler(void) {
// byte. According to nocash docs, this has to be done before resetting the
// IRQ.
while (JOY_STAT & 0x0080)
- __asm__("nop");
+ __asm__ volatile("");
// Keep /CS pulled low and acknowledge the IRQ (bit 4) to ensure it can be
// triggered again.
- JOY_CTRL = 0x1013 | (ctx.port << 13);
+ JOY_CTRL = 0x1013 | (_context.port << 13);
- if (!ctx.rx_len) {
+ if (!_context.rx_len) {
// We just sent the first address byte. Obviously the response we
// received was read from an open bus, so the SPI port's internal FIFO
// must be flushed (by performing dummy reads) to ensure we are only
// going to read valid data from now on.
JOY_TXRX;
- } else if (ctx.rx_len <= SPI_BUFF_LEN) {
+ } else if (_context.rx_len <= SPI_BUFF_LEN) {
// If this is not the first byte, put it in the RX buffer.
- ctx.rx_buff[ctx.rx_len - 1] = (uint8_t) JOY_TXRX;
+ _context.rx_buff[_context.rx_len - 1] = (uint8_t) JOY_TXRX;
}
// Send the next byte, or a null byte if there is no more data to send and
// we're just reading a response.
- ctx.rx_len++;
- if (ctx.rx_len < ctx.tx_len)
- JOY_TXRX = (uint32_t) ctx.tx_buff[ctx.rx_len];
+ _context.rx_len++;
+ if (_context.rx_len < _context.tx_len)
+ JOY_TXRX = (uint32_t) _context.tx_buff[_context.rx_len];
else
JOY_TXRX = 0x00;
}
@@ -176,10 +165,10 @@ SPI_Request *SPI_CreateRequest(void) {
// Find the last queued request by traversing the linked list and append a
// pointer to the new request.
- if (!current_req) {
- current_req = req;
+ if (!_current_req) {
+ _current_req = req;
} else {
- volatile SPI_Request *volatile last = current_req;
+ volatile SPI_Request *volatile last = _current_req;
while (last->next)
last = last->next;
@@ -213,6 +202,6 @@ void SPI_Init(SPI_Callback callback) {
JOY_BAUD = 0x0088; // 250000 bps
SPI_SetPollRate(250);
- current_req = 0;
- default_cb = callback;
+ _current_req = 0;
+ _default_cb = callback;
}
diff --git a/examples/io/pads/spi.h b/examples/io/pads/spi.h
index c50e065..7d4d75b 100644
--- a/examples/io/pads/spi.h
+++ b/examples/io/pads/spi.h
@@ -9,8 +9,9 @@
#include <stdint.h>
#include <psxpad.h>
-// Maximum request/response length (34 bytes for pads, 140 for memory cards)
-//#define SPI_BUFF_LEN 34
+// Maximum request/response length (34 bytes for pads, 140 for memory cards).
+// Must be a multiple of 4 to avoid memory alignment issues.
+//#define SPI_BUFF_LEN 36
#define SPI_BUFF_LEN 140
/* Request structures */
@@ -30,6 +31,10 @@ typedef struct _SPI_Request {
/* Public API */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/**
* @brief Allocates a new request object and adds it to the request queue. The
* object must be populated afterwards by setting the length, callback and
@@ -49,7 +54,7 @@ void SPI_SetPollRate(uint32_t value);
/**
* @brief Installs the SPI and timer 2 interrupt handlers and starts the poll
* timer. By default the polling rate is set to 250 Hz (125 Hz per port),
- * however it can be changed at any time by calling spi_set_poll_rate().
+ * however it can be changed at any time by calling SPI_SetPollRate().
*
* The provided callback (if any) is called to report the result of poll
* requests, which are issued automatically when no other request is in the
@@ -59,4 +64,8 @@ void SPI_SetPollRate(uint32_t value);
*/
void SPI_Init(SPI_Callback callback);
+#ifdef __cplusplus
+}
+#endif
+
#endif
diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c
index be095cb..6284c6d 100644
--- a/examples/sound/spustream/main.c
+++ b/examples/sound/spustream/main.c
@@ -11,8 +11,8 @@
* SPU ADPCM data (one for each channel, so a stereo stream would have 2
* buffers per chunk). All buffers in a chunk are played simultaneously using
* multiple SPU channels; each buffer has the loop flag set at the end, so each
- * channel will jump to its loop address (SPU_CHANNELS[n].loop_addr) once the
- * chunk is played.
+ * channel will jump to its loop address (SPU_CH_LOOP_ADDR(n)) once the chunk
+ * is played.
*
* Since the loop point doesn't necessarily have to be within the chunk itself,
* we can abuse it to "queue" another set of buffers to be played immediately
@@ -94,6 +94,7 @@
#include <psxpad.h>
#include <psxspu.h>
#include <psxcd.h>
+#include <hwregs_c.h>
// To maximize STREAM.BIN packing efficiency and get rid of padding between
// chunks, buffer size should be a multiple of sector size (2048 bytes). Buffer
@@ -105,27 +106,6 @@
#define NUM_CHANNELS 2
#define CHANNEL_MASK 0x03
-/* Register definitions */
-
-// For some reason SpuVoiceRaw doesn't actually match the layout of SPU
-// registers, so here we go.
-typedef struct {
- uint16_t vol_left;
- uint16_t vol_right;
- uint16_t freq;
- uint16_t addr;
- uint32_t adsr_param;
- uint16_t _reserved;
- uint16_t loop_addr;
-} SPUChannel;
-
-#define SPU_CTRL *((volatile uint16_t *) 0x1f801daa)
-#define SPU_IRQ_ADDR *((volatile uint16_t *) 0x1f801da4)
-#define SPU_KEY_ON *((volatile uint32_t *) 0x1f801d88)
-#define SPU_KEY_OFF *((volatile uint32_t *) 0x1f801d8c)
-
-// SPU RAM is addressed in 8-byte units, using 16-bit pointers.
-#define SPU_CHANNELS ((volatile SPUChannel *) 0x1f801c00)
#define SPU_RAM_ADDR(x) ((uint16_t) (((uint32_t) (x)) >> 3))
/* Display/GPU context utilities */
@@ -252,7 +232,7 @@ void spu_irq_handler(void) {
SPU_IRQ_ADDR = SPU_RAM_ADDR(str_ctx.spu_addr);
for (uint32_t i = 0; i < NUM_CHANNELS; i++)
- SPU_CHANNELS[i].loop_addr = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i);
+ SPU_CH_LOOP_ADDR(i) = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i);
// Start loading the next chunk. cd_event_handler() will be called
// repeatedly for each sector until the entire chunk is read.
@@ -317,7 +297,7 @@ void init_spu_channels(void) {
SPU_KEY_OFF = 0x00ffffff;
for (uint32_t i = 0; i < 24; i++)
- SPU_CHANNELS[i].addr = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR);
+ SPU_CH_ADDR(i) = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR);
SPU_KEY_ON = 0x00ffffff;
}
@@ -347,18 +327,18 @@ void start_stream(void) {
SPU_KEY_OFF = CHANNEL_MASK;
for (uint32_t i = 0; i < NUM_CHANNELS; i++) {
- SPU_CHANNELS[i].addr = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i);
- SPU_CHANNELS[i].freq = SAMPLE_RATE;
- SPU_CHANNELS[i].adsr_param = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087
+ SPU_CH_ADDR(i) = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i);
+ SPU_CH_FREQ(i) = SAMPLE_RATE;
+ SPU_CH_ADSR(i) = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087
}
// Unmute the channels and route them for stereo output. You'll want to
// edit this if you are using more than 2 channels, and/or if you want to
// provide an option to output mono audio instead of stereo.
- SPU_CHANNELS[0].vol_left = 0x3fff;
- SPU_CHANNELS[0].vol_right = 0x0000;
- SPU_CHANNELS[1].vol_left = 0x0000;
- SPU_CHANNELS[1].vol_right = 0x3fff;
+ SPU_CH_VOL_L(0) = 0x3fff;
+ SPU_CH_VOL_R(0) = 0x0000;
+ SPU_CH_VOL_L(1) = 0x0000;
+ SPU_CH_VOL_R(1) = 0x3fff;
SPU_KEY_ON = CHANNEL_MASK;
spu_irq_handler();
@@ -446,7 +426,7 @@ int main(int argc, const char* argv[]) {
// Only set the sample rate registers if necessary.
if (pad->btn != 0xffff) {
for (uint32_t i = 0; i < NUM_CHANNELS; i++)
- SPU_CHANNELS[i].freq = sample_rate;
+ SPU_CH_FREQ(i) = sample_rate;
}
last_buttons = pad->btn;