Conmanx360
Member
Hi all,
I'm currently working on creating an open-source ODE (optical drive emulator) for the Sega Saturn. I've been prototyping using a Teensy 4.1 and an FPGA, and just managed to create a basic 'modchip', which streams the correct security ring data from the Teensy->FPGA->Saturn using non-blocking SPI. Here it is in action: https://www.youtube.com/watch?v=gNPMG-Da28I
Also, the code in case anyone is interested:
So, now the next step is to begin transferring data from an SD card to the Saturn. I'd like to do this in a non-blocking way, just like I'm doing currently with the two SPI interfaces. I'm not having much luck finding example code that uses the SD card interface on the Teensy 4.1 in a non-blocking manner, and was hoping someone could point me to some if it exists.
I'm also curious if there will be a limit as to how many non-blocking transfers I can have going at once. I'm already using two SPI interfaces in this way, so I'm hoping I'll be able to have the SD card working too. I can likely cut that down to just one SPI interface without too much trouble if there's a limitation there.
Thanks for reading.
I'm currently working on creating an open-source ODE (optical drive emulator) for the Sega Saturn. I've been prototyping using a Teensy 4.1 and an FPGA, and just managed to create a basic 'modchip', which streams the correct security ring data from the Teensy->FPGA->Saturn using non-blocking SPI. Here it is in action: https://www.youtube.com/watch?v=gNPMG-Da28I
Also, the code in case anyone is interested:
Code:
#include <SPI.h>
#include <EventResponder.h>
#define SPI_SPEED 16000000
const int SPI0_CS_PIN = 10;
const int SPI1_CS_PIN = 0;
const int CD_COM_READY_INT = 31;
const int CD_DATA_READY_INT = 32;
volatile byte cd_data_ready;
volatile byte cd_com_ready;
EventResponder cd_data_event;
volatile bool cd_data_transfer_complete = false;
#define COM_TRANSFER_GET_CURRENT_VALS 0
#define COM_TRANSFER_SET_STATUS 1
EventResponder cd_com_event;
volatile bool cd_com_transfer_complete = false;
volatile bool cd_com_transfer_active = false;
uint8_t cd_com_transfer_type = 0;
bool cd_com_status_needs_update = false;
#define BUFFER_SIZE 0x12000l // More than 64K
uint8_t *cd_data_send_buf;
DMAMEM uint8_t cd_data_recv_buf[BUFFER_SIZE];
uint8_t *cd_com_send_buf;
DMAMEM uint8_t cd_com_recv_buf[BUFFER_SIZE];
void cd_data_event_responder(EventResponderRef event_responder)
{
digitalWriteFast(SPI0_CS_PIN, HIGH);
cd_data_transfer_complete = true;
}
void cd_com_event_responder(EventResponderRef event_responder)
{
digitalWriteFast(SPI1_CS_PIN, HIGH);
cd_com_transfer_complete = true;
cd_com_transfer_active = false;
}
void cd_data_interrupt_func() {
cd_data_ready = true;
}
void cd_com_interrupt_func() {
cd_com_ready = true;
}
void setup() {
uint32_t i;
Serial.begin(2000000);
// start the SPI library:
SPI.begin();
SPI1.begin();
SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0));
SPI1.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0));
pinMode(SPI0_CS_PIN, OUTPUT);
pinMode(SPI1_CS_PIN, OUTPUT);
digitalWriteFast(SPI0_CS_PIN, HIGH);
digitalWriteFast(SPI1_CS_PIN, HIGH);
pinMode(CD_DATA_READY_INT, INPUT_PULLDOWN);
pinMode(CD_COM_READY_INT, INPUT_PULLDOWN);
attachInterrupt(digitalPinToInterrupt(CD_DATA_READY_INT), cd_data_interrupt_func, RISING);
attachInterrupt(digitalPinToInterrupt(CD_COM_READY_INT), cd_com_interrupt_func, RISING);
cd_data_send_buf = (uint8_t *)malloc(BUFFER_SIZE);
memset(cd_data_send_buf, 0, BUFFER_SIZE);
cd_com_send_buf = (uint8_t *)malloc(BUFFER_SIZE);
memset(cd_com_send_buf, 0, BUFFER_SIZE);
cd_data_send_buf[0] = 0x03;
memset(&cd_data_send_buf[1], 0xff, 12);
cd_com_send_buf[0] = 0x02;
cd_data_event.attachImmediate(&cd_data_event_responder);
cd_com_event.attachImmediate(&cd_com_event_responder);
cd_data_ready = 0;
cd_com_ready = 0;
cd_data_transfer_complete = 0;
cd_com_transfer_complete = 0;
digitalWriteFast(SPI1_CS_PIN, LOW);
/* Tell FPGA to just transfer Saturn status data for now. */
SPI1.transfer(0x01);
digitalWriteFast(SPI1_CS_PIN, HIGH);
delay(100);
}
byte command_data[13];
byte status_data[13];
int mod_state = 0;
uint32_t cur_lba = 0;
uint32_t cur_channel = 0;
static uint32_t get_current_lba()
{
uint32_t lba;
lba = command_data[1] << 16;
lba |= command_data[2] << 8;
lba |= command_data[3];
return lba;
}
static void lba_to_bcd_msf(uint32_t lba, uint8_t *min, uint8_t *sec, uint8_t *frame)
{
/* 75 frames per second * 60 seconds per minute = 4500 frames in a minute. */
*min = lba / 4500;
*sec = (lba % 4500) / 75;
*frame = (lba % 4500) % 75;
/* Now convert to BCD? Or do before. Who cares I guess. */
*min = (((*min) / 10) << 4) + ((*min) % 10);
*sec = (((*sec) / 10) << 4) + ((*sec) % 10);
*frame = (((*frame) / 10) << 4) + ((*frame) % 10);
}
/*
* A frame is 24 bytes, 12 bytes left, 12 bytes right channel. 98 frames make up a sector,
* so 98x24 = 2352.
* We're addressing by channel here, so there's 1176 (2352/2) individual
* channels of data since each channel is 2 bytes.
*/
static void get_sec_ring_data_channel(uint32_t channel_cnt, uint8_t *buf)
{
uint8_t m, s, f;
/* Subcode is apparently irrelevant, or ignored by Saturn. */
buf[0] = 0x00;
switch (channel_cnt)
{
case 0:
buf[1] = 0xff;
buf[2] = 0x00;
break;
case 1 ... 4:
buf[1] = 0xff;
buf[2] = 0xff;
break;
case 5:
buf[1] = 0x00;
buf[2] = 0xff;
break;
case 6:
lba_to_bcd_msf(cur_lba, &m, &s, &f);
buf[1] = s ^ 0x80;
buf[2] = m ^ 0x01;
break;
case 7:
lba_to_bcd_msf(cur_lba, &m, &s, &f);
buf[1] = 0x02 ^ 0x60; /* CD mode 2, XOR against 0x60. */
buf[2] = f;
break;
case 8:
buf[1] = 0x28;
buf[2] = 0x00;
break;
case 9:
buf[1] = 0x1e;
buf[2] = 0x28;
break;
case 10:
buf[1] = 0x08;
buf[2] = 0x80;
break;
case 11:
buf[1] = 0x06;
buf[2] = 0x48;
break;
case 12 ... 1173:
buf[1] = 0x59;
buf[2] = 0xa8;
break;
case 1174:
buf[1] = 0xdd;
buf[2] = 0x72;
break;
case 1175:
buf[1] = 0x99;
buf[2] = 0xe5;
break;
/* Shouldn't hit this. */
default:
buf[1] = 0x00;
buf[2] = 0x00;
break;
}
}
/* Retrieves four frames from the given start frame. */
static void get_sec_ring_data_channels(uint32_t start_channel, uint8_t *buf)
{
uint32_t i;
for (i = 0; i < 4; i++)
get_sec_ring_data_channel(start_channel + i, &buf[i * 3]);
}
static bool check_seek_to_sec_ring()
{
if ((command_data[0] == 0x02) && (get_current_lba() >= 0x3ef00))
{
cur_lba = get_current_lba();
cd_com_status_needs_update = true;
Serial.println("mod_state = 1");
return true;
}
return false;
}
static bool check_read_at_sec_ring()
{
if ((command_data[0] == 0x06) && (get_current_lba() >= 0x3df00))
{
/* Need to start 4 blocks before requested LBA. */
cur_lba = get_current_lba() - 4;
cur_channel = 0;
cd_com_status_needs_update = true;
Serial.println("mod_state = 2");
return true;
}
return false;
}
static bool check_normal_command()
{
if ((command_data[0] != 0x00) && (get_current_lba() < 0x3d000))
{
cur_lba = 0;
return true;
}
return false;
}
static uint8_t get_status_parity(uint8_t *buf)
{
uint8_t parity, i;
for (i = parity = 0; i < 11; i++)
parity += buf[i];
return ~parity;
}
static void get_seek_to_sec_ring_status(uint8_t *buf)
{
buf[0] = 0xb6;
buf[1] = 0x44;
buf[2] = 0xf1;
buf[3] = (cur_lba >> 16) & 0xff;
buf[4] = (cur_lba >> 8) & 0xff;
buf[5] = cur_lba & 0xff;
buf[6] = 9;
buf[7] = 9;
buf[8] = 9;
buf[9] = 9;
buf[10] = 0;
buf[11] = get_status_parity(buf);
buf[12] = 0;
}
static void get_read_at_sec_ring_status(uint8_t *buf)
{
uint8_t m, s, f;
lba_to_bcd_msf(cur_lba, &m, &s, &f);
buf[0] = 0x36;
buf[1] = 0x01;
buf[2] = 0xaa;
buf[3] = 0x01;
buf[4] = m;
buf[5] = s;
buf[6] = f;
buf[7] = 4;
buf[8] = m;
buf[9] = s;
buf[10] = f;
buf[11] = get_status_parity(buf);
buf[12] = 0;
}
void loop() {
int i;
if (cd_data_ready)
{
cd_data_transfer_complete = false;
digitalWriteFast(SPI0_CS_PIN, LOW);
SPI.transfer(cd_data_send_buf, cd_data_recv_buf, 13, cd_data_event);
cd_data_ready = false;
}
if (cd_com_status_needs_update && !cd_com_transfer_active)
{
cd_com_transfer_complete = false;
cd_com_send_buf[0] = 0x03;
if (mod_state == 1)
get_seek_to_sec_ring_status(&cd_com_send_buf[1]);
else if (mod_state == 2)
get_read_at_sec_ring_status(&cd_com_send_buf[1]);
/* Need to set send buffer with correct status. */
cd_com_transfer_type = COM_TRANSFER_SET_STATUS;
cd_com_transfer_active = true;
digitalWriteFast(SPI1_CS_PIN, LOW);
SPI1.transfer(cd_com_send_buf, cd_com_recv_buf, 14, cd_com_event);
cd_com_status_needs_update = false;
}
if (cd_com_ready && !cd_com_transfer_active)
{
cd_com_transfer_complete = false;
digitalWriteFast(SPI1_CS_PIN, LOW);
memset(cd_com_recv_buf, 0, 26);
memset(&cd_com_send_buf[1], 0, 26);
cd_com_send_buf[0] = 0x02;
SPI1.transfer(cd_com_send_buf, cd_com_recv_buf, 27, cd_com_event);
cd_com_transfer_type = COM_TRANSFER_GET_CURRENT_VALS;
cd_com_transfer_active = true;
cd_com_ready = false;
}
if (cd_data_transfer_complete || cd_com_transfer_complete)
{
if (cd_data_transfer_complete)
{
if (status_data[0] == 0xb6)
{
Serial.println("data");
for (i = 0; i < 12; i++)
{
Serial.println(cd_data_recv_buf[i + 1], HEX);
}
Serial.println("");
}
/* if cur_channel == 1176, update lba/msf, update status, loop back around. */
if (mod_state == 2)
{
get_sec_ring_data_channels(cur_channel, &cd_data_send_buf[1]);
cur_channel += 4;
if (cur_channel == 1176)
{
/* Update LBA and status. */
cur_lba++;
cd_com_status_needs_update = true;
cur_channel = 0;
}
}
else
memcpy(&cd_data_send_buf[1], &cd_data_recv_buf[1], 12);
cd_data_transfer_complete = false;
}
else if (cd_com_transfer_complete && (cd_com_transfer_type == COM_TRANSFER_GET_CURRENT_VALS))
{
Serial.println("com");
for (i = 0; i < 26; i++)
{
Serial.print(cd_com_recv_buf[i + 1], HEX);
Serial.print(" ");
if (i == 12)
Serial.println("");
}
Serial.println("");
Serial.println("");
memcpy(status_data, &cd_com_recv_buf[1], 13);
memcpy(command_data, &cd_com_recv_buf[14], 13);
if (check_seek_to_sec_ring())
mod_state = 1;
else if (check_read_at_sec_ring())
mod_state = 2;
else if (check_normal_command())
{
if (mod_state != 0)
{
digitalWriteFast(SPI1_CS_PIN, LOW);
SPI1.transfer(0x01);
digitalWriteFast(SPI1_CS_PIN, HIGH);
}
mod_state = 0;
}
cd_com_transfer_complete = false;
}
else if (cd_com_transfer_complete && (cd_com_transfer_type == COM_TRANSFER_SET_STATUS))
{
Serial.println("com status updated");
for (i = 0; i < 13; i++)
{
Serial.print(cd_com_send_buf[i + 1], HEX);
Serial.print(" ");
}
Serial.println("");
Serial.println("");
cd_com_transfer_complete = false;
}
}
}
So, now the next step is to begin transferring data from an SD card to the Saturn. I'd like to do this in a non-blocking way, just like I'm doing currently with the two SPI interfaces. I'm not having much luck finding example code that uses the SD card interface on the Teensy 4.1 in a non-blocking manner, and was hoping someone could point me to some if it exists.
I'm also curious if there will be a limit as to how many non-blocking transfers I can have going at once. I'm already using two SPI interfaces in this way, so I'm hoping I'll be able to have the SD card working too. I can likely cut that down to just one SPI interface without too much trouble if there's a limitation there.
Thanks for reading.