Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Got it talking, but still unrecognized? #17

Open
timboldt opened this issue May 25, 2019 · 12 comments
Open

Got it talking, but still unrecognized? #17

timboldt opened this issue May 25, 2019 · 12 comments

Comments

@timboldt
Copy link
Contributor

I got a chance to experiment with this again on my STM32F103.

I used this repo as of May 24 (not sure if that was a good choice, but I needed to include the commit that I submitted in April, which isn't on crates.io yet).

It still fails, but in a much more promising way.

Here are the first few hundred bytes I see being exchanged. The devices seem to be talking, but the library doesn't seem to recognize the device (the slave keeps responding with E7 7F). My device is a LynxMotion PS/2 V4.

Either that, or I did something facepalm-worthy again. :-)

cmd | data
0x80 | 0xFF
0x42 | 0xE7
0x00 | 0x7F
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x80 | 0xFF
0xC2 | 0xE7
0x00 | 0x7F
0x80 | 0xFF
0x00 | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0x80 | 0xFF
0x22 | 0xE7
0x00 | 0x7F
0x80 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0x80 | 0xFF
0xB2 | 0xE7
0x00 | 0x7F
0x00 | 0xFF
0x80 | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0x80 | 0xFF
0x02 | 0xE7
0x00 | 0x7F
0x00 | 0xFF
0x40 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0x80 | 0xFF
0xF2 | 0xE7
0x00 | 0x7F
0xFF | 0xFF
0xFF | 0xFF
0xC0 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0x80 | 0xFF
0xC2 | 0xE7
0x00 | 0x7F
0x00 | 0xFF
0x00 | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0xFF | 0xFF
0x80 | 0xFF
0x42 | 0xE7
0x00 | 0x7F
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
0x00 | 0xFF
@timboldt
Copy link
Contributor Author

Ah, my logic analyzer has the bit pattern reversed, so 0x80 0x42 is actually 0x01 0x42. Time to pull out the oscilloscope and look at this more carefully. I'll be back.

P.S. The official spec for the LynxMotion controllers is here, so this is what I should be seeing:
http://www.lynxmotion.com/images/files/ps2cmd01.txt

@RandomInsano
Copy link
Owner

RandomInsano commented May 25, 2019 via email

@RandomInsano
Copy link
Owner

Just circling back here too, try lifting code chunks out of the console example as it's just pushing raw bytes. The library does the same, it just overlays the response bytes into an array and maps a union on top of it. I originally wrote it because I was having problems with the SPI bus on the NTC CHIP and Odroid C1+ computers (both had problems).

Just an aside, Rust 1.35.0 should also allow for dynamically sized arrays on the stack now and I plan to make that huge amount of bytes transferred dynamic based on what comes from the response code from the controller.

@timboldt
Copy link
Contributor Author

My SPI bus is running at 140.8kHz and there doesn't seem to be a way to change it, even though the stm32f1xx-hal tries to set it correctly.

The edges all look clean in an oscilloscope, and a real Playstation runs at 250kHz, so I don't think this is the issue.

I also verified my results with and without a 1K pull-up, and it appears it is only necessary when the LynxMotion wireless adapter is not plugged in. With it plugged in, it actively pulls the DATA line high.

The device identifies itself as either 0x83 0xFE (default config when I turn it on) or 0xE7 0xFE (if I press the mode button, which turns on a red led). This suggests that I have connectivity working end to end.

The raw data (ControllerData.data) is all 0xFF, except for the last 3 bytes, which are 0x00.

I guess the next thing to try is to get out my old Arduino Uno and try their sample code with that to confirm that I don't have a faulty device.

@RandomInsano
Copy link
Owner

RandomInsano commented May 26, 2019 via email

@timboldt
Copy link
Contributor Author

I figured it out: it is a timing issue with the wireless controller. If I slow down the CPU from 72MHz to 8MHz, it results in enough of a gap (12.5usec in my case) between bytes that it works. I may do some experiments to see if adding a few delays in your code will let me run the CPU at full speed. (The Lynxmotion fork of the Arduino code also has a few extra delays to deal with the quirks of wireless controllers.)

And by the way, it wasn't a question of SPI bus speed. I was easily able to run it at 250kHz, as long as there were gaps between bytes.

With the CPU slowed down, here is what I see (which matches expectations, I think):

Time [s], Analyzer Name, Decoded Protocol Result
0.453431187500000,SPI,MOSI: 0x01;  MISO: 0xFF
0.453473937500000,SPI,MOSI: 0x42;  MISO: 0x41
0.453516687500000,SPI,MOSI: 0x00;  MISO: 0x5A
0.453559437500000,SPI,MOSI: 0x00;  MISO: 0xFF
0.453602187500000,SPI,MOSI: 0x00;  MISO: 0xFF
....
0.454924000000000,SPI,MOSI: 0x01;  MISO: 0xFF
0.454966750000000,SPI,MOSI: 0x43;  MISO: 0x41
0.455009437500000,SPI,MOSI: 0x00;  MISO: 0x5A
0.455052187500000,SPI,MOSI: 0x01;  MISO: 0xFF
0.455094937500000,SPI,MOSI: 0x00;  MISO: 0xFF
0.455137687500000,SPI,MOSI: 0xFF;  MISO: 0xFF
...

@timboldt
Copy link
Contributor Author

timboldt commented May 26, 2019

So, for posterity, the workaround I did was to lower the clock speed from 72MHz to 48MHz as follows.

    let clocks = rcc
        .cfgr
        .use_hse(8.mhz())
        .sysclk(48.mhz())
        .pclk1(24.mhz())
        .freeze(&mut flash.acr);

This worked when compiled for debug with opt-level = 2 (needed because flash is at a premium).

Given the nature of this workaround, there is no guarantee it will work in release mode, nor will it necessarily fail in debug mode with optimizations turned off.

Most likely the correct fix is to have the user pass in a delay function that supports microsecond-level delays, and then add a 4us delay between bytes. Adding a delay is what most of the C/C++ libraries do.

Specifically, a common Arduino PS2 library has these values:
https://github.com/madsci1016/Arduino-PS2X/blob/master/PS2X_lib/PS2X_lib.h#L95

And the Lynxmotion fork of it changed the 3 to a 4:
madsci1016/Arduino-PS2X@451b911

@RandomInsano
Copy link
Owner

RandomInsano commented May 26, 2019 via email

@timboldt
Copy link
Contributor Author

timboldt commented May 27, 2019

Actually, the delay I was talking about was between individual bytes. The real PS2 uses hardware ACKs which result in short (<4usec) gaps between bytes. I believe the lack of a delay there was causing the issue. You can see the gap in the oscilloscope dumps of real hardware, at Curious Inventor. Most software implementations introduce an intentional delay between SPI transfer-byte calls, to compensate for the lack of a hardware ACK.

The easiest solution would be a (optional) "delay between bytes" function that I could supply.

P.S. It occurred to me that under Linux, the kernel transition might be slow enough that it introduces just enough delay to avoid the issue. Even on my Blue Pill microcontroller, changes in CPU speed were enough to introduce a small delay. The problem is that it is pretty much guaranteed to fail on faster MCUs like the F7, without introducing an intentional delay.

@timboldt
Copy link
Contributor Author

Looking closer, I see you are using SPI::transfer to exchange the entire buffer in one go. This would definitely be the right way to do it, assuming that the PS2 had a standard SPI implementation.

If my theory is right, this won't work if the SPI device is able to string bytes together fast enough without the multi-microsecond gap between bytes that the PS2 devices expect. For example, it is probably guaranteed to fail if the MCU uses DMA with its SPI device.

On the plus side, this gives me an idea: I can create a virtual SPI device that does byte-by-byte transfers and injects delays between bytes (or even waits for a signal on the PS2 hardware ACK line).

However, the best solution (from the perspective of a user of your general-purpose UI) would be to have the option of doing the SPI transfer byte by byte, with a user-supplied wait() function between bytes. The user-supplied wait function could then busy-wait, yield the CPU to another task, look for a hardware ACK, or whatever was appropriate for the platform. If you prefer this option, I could put together a PR for discussion.

@timboldt
Copy link
Contributor Author

I also see why it works on a Raspberry Pi. The Pi kernel injects a one clock-cycle delay between bytes on the SPI bus (making each byte take 9 clock cycles). At 100kbit, that's 10usec.

@RandomInsano
Copy link
Owner

RandomInsano commented May 31, 2019 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants