Sega Saturn: digital controller protocol

Started by micro, September 08, 2012, 07:29:10 PM

Previous topic - Next topic

micro

(picking up from: http://nfggames.com/forum2/index.php?topic=4393.msg32536#msg32536)

Quote from: busslave on September 07, 2012, 09:11:58 PM
Thank you for your fast answer.

Last night I changed the address variable to local, and I got the following results: 0,1,2,3,2,3,2,3,... So, the TR pin does change, after all. I's certainly not in the order I expected (I was expecting 3,2,1,0,3,...), but hey, at least I didn't blow up the console port.

I'm only guessing here, but it seems the console keeps repeating 2,3,... because the Saturn encodes the Peripheral ID in these nibbles. In my case, they are:

Nibble3 (12): 11 00
Nibble2 (07): 01 11

The Saturn peripheral ID is:

bit3: (1|1)=1
bit2: (0|0)=0
bit1: (0|1)=1
bit0: (1|1)=1

which gives 1011=11. According to the SMPC User's Manual, this is OK.

Actually, Sega seems to have followed some sort of MegaDrive/Genesis peripheral ID convention. Anyway, I already expected this result, that's why I chose 12 for the third nibble value. That's because:

bit3: (L TRG, don't care | always 1, tied to VCC inside the pad) = 1
bit2: (always 0, tied to GND | always 0, tied to GND) = 0
bit1: (RIGHT | LEFT) = 1, since they can't both be pressed.
bit0: (UP | DOWN) = 1, same here.

So I guess I'm dealing with a timing issue, after all.

Yes, I do have an optimization option, which is optimizing for size (I'm not using the Arduino IDE, I'm using AtmelStudi6 with the Arduino libraries). I'm a total noob (this is my second project) and I'm learning as I go. I've never dealt with ASM before, but I'll try to understand your code and implement your suggestions.

Many thanks, micro.

Are you sure that 2,3,2,3 means the TR is changing? You mean 3 = TH TR = 1 1 and 2 = TH TR = 1 0?
I'd rather believe the Saturn is not recognizing a controller. In this case TR stays high and the Saturn is giving a negative pulse on the TH line... Something seems to be wrong. Say, did you also tie TL to high? (on the standard digital controller TL is connected to +5 V all the time)

But as I said I discovered a much, much easier method: I was trying to figure out the protocol and timing of the Saturn 3D controller. I was amazed that the protocol of the 3D controller, when in digital mode, is way different from the standard digital controller protocol which you are struggling to implement.

Instead it's just like the analog protocol but without the data for the analog stick and the L+R triggers. It's very easy to implement as there are no speed issues at all. I was even able to do it with the micrcontroller running at 1 MHz.

Unfortunately I'm not at home right now but I've uploaded a .pdf that I made: https://docs.google.com/open?id=0B-8yIdDqBCSaME5JaGlEeFk5OUE

TH & TR should be inputs on the microcontroller, TL, R,L, D, U outputs.
So when TH goes low your interrupt should be triggered. Then you wait for the Saturn to toggle TR. This means you should output the next data nibble. After you've written the nibble to your port pins YOU tell the saturn you're ready by toggling the TL pin.
Everything is shown in the PDF. There shouldn't be any speed problems at all. You should give it a try, it's so comfortable...  ;D

The gamesx Saturn controller page need a big overhaul. I'd love to do it but my english is too crappy... :-[

busslave

You are right, 2,3,2,3 means the TR line is not changing at all (always high). And yes, TL is tied to +5V all the time.
For clarification, in the picture you posted with the Saturn select lines polling:

CH1 is the Select0 line (aka TH, which is pin 4 on the Saturn).
CH2 is the Select1 line (aka TR, which is pin 5 on the Saturn).

Is this correct?

I find the SMPC user's manual somewhat confusing.
From what I understood, there are two control modes:

1) SMPC control mode.
This mode is 8-bit wide and the Saturn peripheral ID is 2 (bit1 set).
I don't know how the console gets this ID from a standard digital pad, but that's what Table 3.18 (SATURN Standard PAD Data Format in SMPC Control Mode) seems to suggest.

2) SH-2 direct mode.
In SH-2 direct mode, there are three protocols:

- TH control method (Mega Drive pads).
- TH and TR control method (Saturn digital pad, TL tied to +5V).
- 3-Line handshake method (which is the one that uses the TL line as acknowledgment).

I suppose this last method is the one you're using now?

I was trying to use the TH and TR control method:
TH is connected to INT0.
My INT0 ISR is executed whenever TH changes level (either from 0 to 1, or from 1 to 0).
Inside the ISR, the values of both TH and TR pins are read.
This 2-bit value is used as the address of the nibble to output, which is stored in an array of size 4 (0 to 3).

Unless I'm making some assumptions wrong (which I might), this can only be a timing issue.
I guess your protocol implementation is the only way to go. And it should be more reliable too.

micro

Quote from: busslave on September 11, 2012, 04:09:06 AM
You are right, 2,3,2,3 means the TR line is not changing at all (always high). And yes, TL is tied to +5V all the time.
For clarification, in the picture you posted with the Saturn select lines polling:

CH1 is the Select0 line (aka TH, which is pin 4 on the Saturn).
CH2 is the Select1 line (aka TR, which is pin 5 on the Saturn).

Is this correct?
Yes, that's right.
And here's a screenshot showing the Saturn looking for a controller (controller unplugged):


QuoteIn SH-2 direct mode, there are three protocols:

- TH control method (Mega Drive pads).
- TH and TR control method (Saturn digital pad, TL tied to +5V).
- 3-Line handshake method (which is the one that uses the TL line as acknowledgment).

I suppose this last method is the one you're using now?
Yes :)

QuoteI was trying to use the TH and TR control method:
TH is connected to INT0.
My INT0 ISR is executed whenever TH changes level (either from 0 to 1, or from 1 to 0).
Inside the ISR, the values of both TH and TR pins are read.
This 2-bit value is used as the address of the nibble to output, which is stored in an array of size 4 (0 to 3).

Unless I'm making some assumptions wrong (which I might), this can only be a timing issue.
I guess your protocol implementation is the only way to go. And it should be more reliable too.
Well, it's possible to do that on a AVR running at 16 MHz, but most likely it's not possible without using asm.
That idea with the array is nice and clean but I'm afraid it just takes too much time.
If you're using AVR Studio you can use the built-in simulator to trigger an interrupt and measure the time it takes for the program to output the data nibble =) If it's not faster than 1.8 µs it won't work...

But if I were you I'd forget the TH/TR control method because the 3-line handshake method is just much much more easier to use...

busslave

I've been trying the 3-line handshake method, without success.
Here is my bare-bones code (without infrared receiver):

volatile uint8_t data_num;
volatile uint8_t command_buff[8]={0,2,7,15,15,15,0,1};

void setup() {
   DDRD=0x10; //PD4 as output (TL).
   DDRB=0x0F; //PB3:0 as outputs.
   PORTD|=_BV(PORTD4); //Set TL high.
   PORTB=1; //Set U bit.
   EICRA=6; //Interrupt on falling edge of INT0 and on INT1 toggle.
   EIMSK=1; //Enable INT0.
   sei();
}

ISR (INT0_vect) {//TH has gone low (TH connected to INT0).
   data_num=0;
   EIMSK=2; //Disable INT0, enable INT1.
}

ISR (INT1_vect) {//TR toggled (TR connected to INT1).
   PORTB=command_buff[data_num];
   data_num++;
   if (data_num>7) {EIMSK=1;} //Disable INT1, enable INT0.
   PORTD^=_BV(PORTD4); //Toggle TL.
}
//End code.

While this seems correct to me, I'm not shure if enabling external interrupts in this fashion doesn't trigger the interrupt itself. Should I clear the interrupt flag after (or before) changing EIMSK?

I'm sorry to be posting software questions in a controller related forum, but I'm completely stuck right now.

Also, I've looked at the lss file output and noticed that the compiler pushes a lot of registers into the stack, and pops them out afterward. This consumes quite a few MCU cycles.

Regarding the 3-line handshake method, I'm not so shure right now if it's a better approach. I mean, after an handshake has been established, does TR wait forever for TL to toggle? Wouldn't the Saturn loose sync with the controller after 2us or so, and default to an unknown device?

Anyway, here are my thoughts regarding this method:

1) The default state should be 1. This would generate a Mega Drive peripheral ID of 0x5 after having been read twice (while TR is still high).

2) A Mega Drive peripheral ID of 0x5 would tell the console that the next two nibbles would be the Saturn peripheral ID.

3) According to table 3.10 (Saturn Digital Device Standard Format), the Saturn Peripheral ID has two nibbles: the first is zero, and the second is 2 (the data size - 2 bytes).

However, Sega's SMPC User's Manual states that "the peripherals that support this protocol are": the tap, mouse, analog joystick, keyboard and 6P multitap. These have different Saturn Peripheral IDs from the one in 3).

I guess my question is: will the console accept this Saturn ID? Will it accept a digital controller in a 0x5 mode?

busslave

Update:
I just noticed the Saturn Peripheral ID is also referenced in table 3.18 (Saturn Standard Pad Data Format in SMPC Control Mode). But we're using SH-2 Direct Mode. Is this ID common to both modes?
What is SMPC control mode, anyway? :o
I'm pretty confused right now!

micro

#5
Don't think too much about the ID's and the SH2 direct / SMPC control mode  :P

Your code should work BUT there's one thing: After enabling the interrupts, the INTF1 flag gets set and as soon as you enable INT1 in the EIMSK register and return from your ISR0 routine then you'll enter the ISR1 routine, even though TR aka PIND3 hasn't changed at all!

So insert "EIFR |= (1<<INTF1);" after "sei();" to clear that INTF1 and it should work. (At least it does when it's running in the AVR Simulator).

Another (potential) problem in your code is that once TH goes low and triggers INT0 you never check TH again. I don't know for sure but I can imagine that under special circumstances (changing from Saturn menu to the game; pushing the reset button) TH might rise BEFORE all those data nibbles are read by the Saturn...

EDIT: Also it may be a good idea to wait for TH and TR both to be high before enabling interrupts with "sei();"


busslave

Thank you micro, for your support.

I think I was over complicating things: having both INT0 and INT1 is no good, since it will delay the response even further. Also, I have another interrupt routine that could get served in between the two (I've not enabled it yet).

Meanwhile, I've learned how to use the debbuger tool and came up with a more compact code:

ISR (INT0_vect) { //TH has gone low.
   uint8_t data_num=0;
   
   do {
      uint8_t RL_val=PIND&24;
      
      if ((RL_val==8)|(RL_val==16)){ //TR and TL are different.
         PORTB=command_buff[data_num]; //Write data.
         data_num++;
         PORTD^=_BV(PORTD4); //Toggle TL.
      }

   } while (data_num< 8 ) ;
      
}

However, if TH goes low when TR is low too, the ISR will be triggered. As you mentioned, I might have to wait for both TH and TR to be high, before enabling the INT0 interrupt. So I added

   EIMSK=0; //Disable INT0.

as the last line of my ISR, and in the main loop:

   do {TR_read=PIND&12;} //Wait for TH and TR to be high.
   while (TR_read!=12);
   EIMSK=1;

but it doesn't work. Maybe TR is going low before the ISR executes?
I've tried to enclose the whole ISR routine in an if clause, so that it only runs when TR is high. No success either.

There is a weird behaviour I must mention: if I disconnect the TL wire (leave it floating) and then reconnect it, the cursor moves in the defined direction once. If I repeat it, the cursor moves once again.

Also, if I remove the 0 (zero) value after the four data nibbles, I can no longer obtain this behaviour. This is something I really need to investigate further.

micro

Quote from: busslave on September 27, 2012, 04:28:51 AM
if ((RL_val==8)|(RL_val==16)){ //TR and TL are different.
How about "||" instead of "|" ? ;D

Assuming you got the right data in your "command_buff" array your code should work, I think.
You could also add a little TH check to your while-loop condition:

do {
...
} while ( (datanum<8) && !(PIND&(1<<PD2)) )

So in case TH should rise in the middle of the transmission the MCU will exit the ISR.

Waiting for TH and TR to be high is only neccessary at the beginning of your program when you're enabling the interrupts. Of course you already need to have the right values in your command_buff array at that time.

Here's another oscilloscope screenshot showing TH (top) and TR (bottom) while the 3D controller in digital mode is read by the saturn. You can see the whole transmission takes about 380 us.

busslave

Stupid me! Of course it's "||" (and "&&"). Java is imprinted in my brain. ;D

I think I understand now what you mean by "waiting for TH and TR to be high only at the beginning". It allows the Saturn and controller to synchronize communication. After the Saturn recognises the controller, the INT0 interrupt should remain enabled.

However, in case of a synch loss, shouldn't this condition be regularly checked?

I imagine the Saturn allows mode change between communications, that's why the controller must keep sending the [IDs]+[data size] nibbles (1,1,0,2). Or maybe it was just more convenient that way. Either way, I think my problem is related to how I'm leaving the protocol. I'm only guessing here, but since the cursor only moves once, this must mean that synch is lost after the first successfull communication.

Anyway, this is good news, since doing it in the main loop would be rather quirky.

busslave

Stupid, stupid me!!
My intuition led me to try and randomly change the value of command_buff[2] (the d-pad nibble).
Guess what! The cursor now jumps all over the place like crazy! Man, I'm excited! ;D

Of course, the console assumed I was still holding down the same button, and stoped at that place. I think this is normal behaviour, at least on the BIOS settings screen, which is where I'm at.

Also, a synch loss shouldn't be an issue. It is now, because I'm powering the MCU from the Arduino, not the Saturn. So, shutting down the console causes synch to be lost, because a MCU reset never occurs.

Basically, I just needed to do what you have been telling me all along. In particular, waiting for TH and TR to become both high is mandatory.

Man, I can never thank you enough!