VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm planning to try and implement the Joystick in my x86EMU emulator, but the documentation from Bochs' ioport.lst is somewhat confusing:

0201 r read joystick position and status bit 7 status B joystick button 2 / D paddle button bit 6 status B joystick button […]
Show full quote

0201 r read joystick position and status
bit 7 status B joystick button 2 / D paddle button
bit 6 status B joystick button 1 / C paddle button
bit 5 status A joystick button 2 / B paddle button
bit 4 status A joystick button 1 / A paddle button
bit 3 B joystick Y coordinate / D paddle coordinate
bit 2 B joystick X coordinate / C paddle coordinate
bit 1 A joystick Y coordinate / B paddle coordinate
bit 0 A joystick X coordinate / A paddle coordinate

w fire joysticks four one-shots

What is the difference between those left and right(left and right of the /) information? Those paddle button vs joystick button/coordinate?
What does the "joysticks four one-shots" do?
What does the A/B/C/D mean? Does this mean four joysticks on one port? Or is this one joystick?

Edit: After looking at http://www.epanorama.net/documents/joystick/pc_joystick.html, it seems it needs some time(which should be emulated) to empty the capacitors in order to measure the current x and y axis of the joystick. I'm currently using 65535 steps for resolution on the x and y axis. (signed short converted to unsigned short in c++ by adding -32768 to it).
When the port is one-shot, it starts timing the steps down linearly. This results in the counters (timeoutx and timeouty) running down over time. When they are 0, the corresponding bit in the status register is cleared, it's set while it's counting down.

Is this correct?

//A bit of length for the timeout to decrease 1 step out of 65536 steps, in nanoseconds! Use 0.5ms for full decay(1.5ms for full fetch in 3 steps)?
//according to http://www.epanorama.net/documents/joystick/pc_joystick.html
//Rewritten to a different interval based on Duke Nukem(about 0.28 seconds per input).
#define JOYSTICK_TICKLENGTH (5000000.0/USHRT_MAX)

struct
{
byte buttons[2][2]; //Two button status for two joysticks!
sword Joystick_X[2]; //X location for two joysticks!
sword Joystick_Y[2]; //Y location for two joysticks!
uint_32 timeoutx[2]; //Keep line high while set(based on Joystick_X when triggered)
uint_32 timeouty[2]; //Keep line high while set(based on Joystick_Y when triggered)
} JOYSTICK;

void setJoystick(byte joystick, byte button1, byte button2, sword analog_x, sword analog_y) //Input from theuser!
{
if (joystick&0xFE) return; //Only two joysticks are supported!
//Set the buttons of the joystick!
JOYSTICK.buttons[joystick][0] = button1?1:0; //Button 1
JOYSTICK.buttons[joystick][1] = button2?1:0; //Button 2
JOYSTICK.Joystick_X[joystick] = analog_x; //Joystick x axis!
JOYSTICK.Joystick_Y[joystick] = analog_y; //Joystick y axis!
}

double joystick_ticktiming = 0.0;
void updateJoystick(double timepassed)
{
//Adlib timer!
joystick_ticktiming += timepassed; //Get the amount of time passed!
if (joystick_ticktiming >= JOYSTICK_TICKLENGTH) //Enough time passed?
{
for (;joystick_ticktiming >= JOYSTICK_TICKLENGTH;) //All that's left!
{
if (JOYSTICK.timeoutx[0]) --JOYSTICK.timeoutx[0];
if (JOYSTICK.timeouty[0]) --JOYSTICK.timeouty[0];
if (JOYSTICK.timeoutx[1]) --JOYSTICK.timeoutx[1];
if (JOYSTICK.timeouty[1]) --JOYSTICK.timeouty[1];
joystick_ticktiming -= JOYSTICK_TICKLENGTH; //Decrease timer to get time left!
}
}
}

byte joystick_readIO(word port, byte *result)
{
INLINEREGISTER byte temp;
switch (port)
{
case 0x201: //Read joystick position and status?
temp = 0; //No input yet!
//bits 8-7 are joystick B buttons 2/1, Bits 6-5 are joystick A buttons 2/1, bit 4-3 are joystick B Y-X timeout timing, bits 1-0 are joystick A Y-X timeout timing.
temp |= JOYSTICK.buttons[1][1]?0:0x80; //Not pressed?
temp |= JOYSTICK.buttons[1][0]?0:0x40; //Not pressed?
temp |= JOYSTICK.buttons[0][1]?0:0x20; //Not pressed?
temp |= JOYSTICK.buttons[0][0]?0:0x10; //Not pressed?
temp |= JOYSTICK.timeouty[1]?0x08:0; //Timing?
temp |= JOYSTICK.timeoutx[1]?0x04:0; //Timing?
temp |= JOYSTICK.timeouty[0]?0x02:0; //Timing?
temp |= JOYSTICK.timeoutx[0]?0x02:0; //Timing?
*result = temp; //Give the result!
return 1; //OK!
Show last 36 lines
		default:
break;
}
return 0; //Not an used port!
}

byte joystick_writeIO(word port, byte value)
{
switch (port)
{
case 0x201: //Fire joystick four one-shots?
//Set timeoutx and timeouty based on the relative status of Joystick_X and Joystick_Y to fully left/top!
JOYSTICK.timeoutx[1] = (uint_32)(SHRT_MIN+JOYSTICK.Joystick_X[1]);
JOYSTICK.timeoutx[0] = (uint_32)(SHRT_MIN+JOYSTICK.Joystick_X[0]);
JOYSTICK.timeouty[1] = (uint_32)(SHRT_MIN+JOYSTICK.Joystick_Y[1]);
JOYSTICK.timeouty[0] = (uint_32)(SHRT_MIN+JOYSTICK.Joystick_Y[0]);
if (JOYSTICK.Joystick_X[0] != 0)
{
dolog("Joystick","timeoutx:%i->%u",JOYSTICK.Joystick_X[0],JOYSTICK.timeoutx[0]);
}
if (JOYSTICK.Joystick_Y[0] != 0)
{
dolog("Joystick", "timeouty:%i->%u",JOYSTICK.Joystick_Y[0],JOYSTICK.timeouty[0]);
}
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

void joystickInit()
{
register_PORTIN(&joystick_readIO); //Register our handler!
register_PORTOUT(&joystick_writeIO); //Register our handler!
}

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 1 of 10, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

Looks approximately correct to me.

Write to port (data is ignored) will start the timing of resistances. The bit is set to one during timing, and zero when it completes. If there is no resistance connected (joystick disconnected or broken), the timing will never complete so the bit stays one. I think it will only start channels that are currently not timing, so it won't restart timing on channels that have not completed the timing yet. This should be double-checked from NE558 datasheet.

The joystick pot resistances are usually about 100k or 120k (there are exceptions), so to emulate the original hardware and many sound cards emulating the original hardware, the internal resistor 2.2k is added to pot resistance and this charges a 10nF capacitor. Timing ends when voltage of capacitor is 0.63 * 5 volts, and as with any RC circuit, the voltage rise is not linear but exponential. But fortunately, the time the capacitor takes to charge to 0.63% is linear in function of resistance.

So basically, if you have a modern digital joystick and the values you get are something like 0% for far left and 100% for far right, it can be linearly converted to time between min and max lengths. To support all games, the min value is set by internal resistance and capacitor (RC time constant) while max value is set by pot plus internal resistance summed together, times capacitance. Use could set internal R/C values and pot max value for games that need special timings.

To support also throttle handles, pedals or joystick hats, they should not be considered as only X and Y axes, but just generic timing pots and button bits. It would be cool to emulate also digital joysticks/gamepads like Logitech Wingman Extreme Digital that internally sample their pot values and send the data as burst packets on the four digital button bits.

Reply 2 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

To what value should I set the full right timing(fully pulled to the right/down)? It's currently set to 5ms(5000000ns) for being 100% pulled right/down on the x/y axes(the define at the start divides this linearly for conversion from unsigned 16-bit word).

Duke Nukem 1 sees the 2 buttons, but the axis has no effect?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 10, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

5ms could be too much for normal joystick. The game could limit the polling time to some sane value and it is possible it time outs before 5ms thinking the joystick is not there.

IBM manuals say 24.2 microseconds minimum (because of the 2.2kohm resistor), and then pot resistance * 0.011 microseconds more. Assuming a 100kohm pot, thats 24.2 minimum and 1100+24.2=1124.2 microseconds maximum.

If the component values in the joystick schematics are used, and 0.63% is close enough to 0.632% (RC time constant), then the values would be 22 to 1022 microseconds (the pot itself adding exactly 0 to 1000 us).

Reply 4 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

So I should make the bits stay 1 for (24.2+((horizontalunsigned16bitvalue/65535.0)*1100.0)*1000.0 nanoseconds? After that, report 0?

Edit: I've modified the code. Now duke from Duke Nukem 1 (which I'm using to test the Joystick) uses ~574208.39... nanoseconds for a neutral position(x and y axis being 0). Is this correct?

#include "headers/types.h" //Basic types!
#include "headers/hardware/ports.h" //Port I/O support!

//Time until we time out!
#define CALCTIMEOUT(position) ((24.2+(((double)position/65535.0)*1100.0))*1000.0)

struct
{
byte buttons[2][2]; //Two button status for two joysticks!
sword Joystick_X[2]; //X location for two joysticks!
sword Joystick_Y[2]; //Y location for two joysticks!
double timeoutx[2]; //Keep line high while set(based on Joystick_X when triggered)
double timeouty[2]; //Keep line high while set(based on Joystick_Y when triggered)
} JOYSTICK;

void setJoystick(byte joystick, byte button1, byte button2, sword analog_x, sword analog_y) //Input from theuser!
{
if (joystick&0xFE) return; //Only two joysticks are supported!
//Set the buttons of the joystick!
JOYSTICK.buttons[joystick][0] = button1?1:0; //Button 1
JOYSTICK.buttons[joystick][1] = button2?1:0; //Button 2
JOYSTICK.Joystick_X[joystick] = analog_x; //Joystick x axis!
JOYSTICK.Joystick_Y[joystick] = analog_y; //Joystick y axis!
}

void updateJoystick(double timepassed)
{
//Joystick timer!
if (JOYSTICK.timeoutx[0]>0.0) JOYSTICK.timeoutx[0] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeouty[0]>0.0) JOYSTICK.timeouty[0] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeoutx[1]>0.0) JOYSTICK.timeoutx[1] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeouty[1]>0.0) JOYSTICK.timeouty[1] -= timepassed; //Add the time to what's left!
}

byte joystick_readIO(word port, byte *result)
{
INLINEREGISTER byte temp;
switch (port)
{
case 0x201: //Read joystick position and status?
temp = 0; //No input yet!
//bits 8-7 are joystick B buttons 2/1, Bits 6-5 are joystick A buttons 2/1, bit 4-3 are joystick B Y-X timeout timing, bits 1-0 are joystick A Y-X timeout timing.
temp |= JOYSTICK.buttons[1][1]?0:0x80; //Not pressed?
temp |= JOYSTICK.buttons[1][0]?0:0x40; //Not pressed?
temp |= JOYSTICK.buttons[0][1]?0:0x20; //Not pressed?
temp |= JOYSTICK.buttons[0][0]?0:0x10; //Not pressed?
temp |= (JOYSTICK.timeouty[1]>0.0)?0x08:0; //Timing?
temp |= (JOYSTICK.timeoutx[1]>0.0)?0x04:0; //Timing?
temp |= (JOYSTICK.timeouty[0]>0.0)?0x02:0; //Timing?
temp |= (JOYSTICK.timeoutx[0]>0.0)?0x01:0; //Timing?
*result = temp; //Give the result!
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

byte joystick_writeIO(word port, byte value)
{
Show last 36 lines
	switch (port)
{
case 0x201: //Fire joystick four one-shots?
//Set timeoutx and timeouty based on the relative status of Joystick_X and Joystick_Y to fully left/top!
//First joystick timeout!
if (JOYSTICK.timeoutx[1]<=0.0)
{
JOYSTICK.timeoutx[1] = CALCTIMEOUT(JOYSTICK.Joystick_X[1]+(-SHRT_MIN));
}
if (JOYSTICK.timeouty[1]<=0.0)
{
JOYSTICK.timeouty[1] = CALCTIMEOUT(JOYSTICK.Joystick_Y[1]+(-SHRT_MIN));
}
//Second joystick timeout!
if (JOYSTICK.timeoutx[0] <= 0.0)
{
JOYSTICK.timeoutx[0] = CALCTIMEOUT(JOYSTICK.Joystick_X[0]+(-SHRT_MIN));
}
if (JOYSTICK.timeouty[0] <= 0.0)
{
JOYSTICK.timeouty[0] = CALCTIMEOUT(JOYSTICK.Joystick_Y[0]+(-SHRT_MIN));
}
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

void joystickInit()
{
register_PORTIN(&joystick_readIO); //Register our handler!
register_PORTOUT(&joystick_writeIO); //Register our handler!
JOYSTICK.timeoutx[0] = JOYSTICK.timeouty[0] = JOYSTICK.timeoutx[1] = JOYSTICK.timeouty[1] = 0.0; //Init timeout!
}

It seems Duke keeps moving left, until at some point I press right, after which he keeps moving right? It's like a giant delay of new input?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 5 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just tried using the Joy Test v2.0 by Rich Weeds Nagel(1996) from Good DOS joystick test program? .
When I pull the joystick left (by putting the application in gaming mode) and pressing IJKL for moving the analog stick and 2/6 for first and second button(as it's mapped in gaming mode, although an actual joystick does the same, like a PSP or XBox 360 controller(now supported!) connected to the PC). Pressing J(pulling the (left XBox) joystick left) makes the X axis of Joystick A go to 0(from 53(=Center)). Pressing K/L(pulling right/down) has no effect(stays at 53). Strangely, pulling the joystick left/up decreases the other X and Y axis of both joysticks by 1(as well as making the X/Y axis pulled become 0, which is correct)?

So when I don't pull the joystick(centered), all four axis(Joystick A&B) are 53. When I pull it fully left(center vertically), Joystick A's horizontal value gives 0, while all other measurements(Joystick A's Y axis and Joystick B's X&Y axis) becomes 52? When I pull fully up(centered horizontally) Joystick A's vertical value becomes 0, while all other values(Joystick A's X axis and Joystick B's X&Y axis) become 52?
You can see the same when you try it with the PSP or PSP mapped PC input(Input in Joystick Gaming mode(as set in the BIOS Settings menu's input menu), have the program running and press I,J,K,L to pull the virtual joystick in the four + directions(or use the left analog stick of the connected XBox 360 in the current x86EMU commit(make sure to have the controller plugged in when starting the emulator. This will make it used until the emulation is terminated(SDL2 builds should detect the XBox 360 controller plugged in/out, which overwrites the default PSP mapping from the keyboard to use the XBox 360 controller instead. SDL1.2.15 builds will only check this when the emulation is (re)started using either automatic reboot(debug modes) or manual reboot(the reboot emulator(and open BIOS menu) options execute this check as well).

You can switch between the two input devices (currently) by opening the BIOS Settings menu(Using the current input method(Keyboard or XBox 360 controller), unmount the mouse(when in direct input mode), plug in(to use the XBox input) or out(to use the Keyboard PSP Mapping) the XBox controller and using the mouse to click on either reboot option to restart the emulator and apply(redetect) the current input device(XBox vs Keyboard in non-Direct Input mode). Or simply close the emulator and start it again.

Simply said, current results:
Input 0,0 becomes AX=53, AY=53, BX=53. BY=53
Input -16384,0 becomes AX=0, AY=52, BX=52, CX=52
Input 0,-16384 becomes AX=52, AY=0, BX=52, CX=52
Input 16384,0 becomes AX=52, AY=52, BX=52, CX=52(no effect/hanging data)
Input 16384,16384 becomes AX=52, AY=52, BX=52, CX=52(no effect/hanging data)

Can you see what's going wrong?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 6 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just fixed the code. Now the analog stick works(looked at the Dosbox code and rewritten/optimized the used timing):

#include "headers/types.h" //Basic types!
#include "headers/hardware/ports.h" //Port I/O support!

//Time until we time out!
//R=(t-24.2)/0.011, where t is in microseconds(us). Our timing is in nanoseconds(1000us). r=0-100. R(max)=2200 ohm.
//Thus, t(microseconds)=(R(ohms)*0.011)+24.2 microseconds.
#define OHMS (120000.0/2.0)
#define POS2OHM(position) ((((double)(position+1))/65535.0)*OHMS)
#define CALCTIMEOUT(position) (((24.2+(POS2OHM(position)*0.011)))*1000.0)

struct
{
byte buttons[2]; //Two button status for two joysticks!
sword Joystick_X[2]; //X location for two joysticks!
sword Joystick_Y[2]; //Y location for two joysticks!
double timeoutx[2]; //Keep line high while set(based on Joystick_X when triggered)
double timeouty[2]; //Keep line high while set(based on Joystick_Y when triggered)
byte timeout; //Timeout status of the four data lines(logic 0/1)! 1 for timed out(default state), 0 when timing!
} JOYSTICK;

void setJoystick(byte joystick, byte button1, byte button2, sword analog_x, sword analog_y) //Input from theuser!
{
if (joystick&0xFE) return; //Only two joysticks are supported!
//Set the buttons of the joystick!
JOYSTICK.buttons[joystick] = (button2?0x0:0x2)|(button1?0x0:0x1); //Button 1&2, not pressed!
JOYSTICK.Joystick_X[joystick] = analog_x; //Joystick x axis!
JOYSTICK.Joystick_Y[joystick] = analog_y; //Joystick y axis!
}

void updateJoystick(double timepassed)
{
//Joystick timer!
if (JOYSTICK.timeout) //Timing?
{
if (JOYSTICK.timeout&1) //AX timing?
{
JOYSTICK.timeoutx[0] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeoutx[0]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~1; //Finished timing, go logic 1(digital 0)!
}
}
if (JOYSTICK.timeout&2) //AY timing?
{
JOYSTICK.timeouty[0] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeouty[0]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~2; //Finished timing, go logic 1(digital 0)!
}
}
if (JOYSTICK.timeout&4) //BX timing?
{
JOYSTICK.timeoutx[1] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeoutx[1]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~4; //Finished timing, go logic 1(digital 0)!
}
}
if (JOYSTICK.timeout&8) //BY timing?
{
Show last 55 lines
			JOYSTICK.timeouty[1] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeouty[1]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~8; //Finished timing, go logic 1(digital 0)!
}
}
}
}

byte joystick_readIO(word port, byte *result)
{
INLINEREGISTER byte temp;
switch (port)
{
case 0x201: //Read joystick position and status?
//bits 8-7 are joystick B buttons 2/1, Bits 6-5 are joystick A buttons 2/1, bit 4-3 are joystick B Y-X timeout timing, bits 1-0 are joystick A Y-X timeout timing.
temp = (JOYSTICK.buttons[1]<<6); //Not pressed joystick B?
temp |= (JOYSTICK.buttons[0]<<4); //Not pressed joystick A?
temp |= JOYSTICK.timeout; //Timed out?
*result = temp; //Give the result!
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

byte joystick_writeIO(word port, byte value)
{
switch (port)
{
case 0x201: //Fire joystick four one-shots?
//Set timeoutx and timeouty based on the relative status of Joystick_X and Joystick_Y to fully left/top!
//First joystick timeout!
JOYSTICK.timeoutx[1] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_X[1]-SHRT_MIN);
JOYSTICK.timeouty[1] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_Y[1]-SHRT_MIN);
//Second joystick timeout!
JOYSTICK.timeoutx[0] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_X[0]-SHRT_MIN);
JOYSTICK.timeouty[0] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_Y[0]-SHRT_MIN);
JOYSTICK.timeout = 0xF; //Start the timeout on all channels. Multivibrator output goes to logic 0.
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

void joystickInit()
{
register_PORTIN(&joystick_readIO); //Register our handler!
register_PORTOUT(&joystick_writeIO); //Register our handler!
JOYSTICK.buttons[0] = JOYSTICK.buttons[1] = 3; //Not pressed!
JOYSTICK.timeoutx[0] = JOYSTICK.timeouty[0] = JOYSTICK.timeoutx[1] = JOYSTICK.timeouty[1] = 0.0; //Init timeout to nothing!
JOYSTICK.timeout = 0x0; //Default: all lines timed out!
}

There's only one strange thing left: The analog stick works fully, mapping from 1 to 63 in joytest and also working correctly in Duke Nukem, making duke movable:)
The two buttons used are a bit strange though:
- Joytest 2.0 reports the two buttons mapped correctly, so does TMScope.
- The third application(joycheck.exe) doesn't work with x86EMU(strangely crashes).

One thing I've noticed and tested is that Duke Nukem (1) reverses the two buttons when selecting button 1 to fire or button 1 to jump? Does this happen with Dosbox as well? The bit positions are identical in both Dosbox and x86EMU, so Dosbox should have the same problem? Or is this a CPU bug of x86EMU?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 7 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've found some information about the joystick extensions that were used for different joysticks:
http://atrey.karlin.mff.cuni.cz/~vojtech/joystick/specs.txt

However, nothing is said about how the joysticks switch between analog(compatibility) and their digital modes? Anyone can tell me how this is done?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 8 of 10, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

However, nothing is said about how the joysticks switch between analog(compatibility) and their digital modes? Anyone can tell me how this is done?

Carefully timed write sequence to game port is used to put Logitech Wingman Extreme Digital into digital mode. It would be a neat joystick to emulate, in digital mode it has 6 buttons, POV hat, throttle bar, in addition to the XY stick. The joystick can sense the pot measurement voltage so it knows when the PC starts the timing. So it is used as very low speed data channel towards joystick, which is a neat trick. The last time I checked the Linux joystick driver had the timing sequence. I had this joystick when it was new, but I never tried to put it into digital mode as I only discovered the sequence much later. I wonder if I still can find it..

After the joystick is put into digital mode, I think the PC triggers the sending of the packet periodically by writing to game port to start pot measurements, and the joystick has some kind of timeout mechanism to fall back to analog mode if too much time passes after last pot measurement.

Reply 9 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

I think I've found it:
https://github.com/torvalds/linux/blob/master … /joystick/adi.c

/*
* adi_init_digital() sends a trigger & delay sequence
* to reset and initialize a Logitech joystick into digital mode.
*/

static void adi_init_digital(struct gameport *gameport)
{
int seq[] = { 4, -2, -3, 10, -6, -11, -7, -9, 11, 0 };
int i;

for (i = 0; seq[i]; i++) {
gameport_trigger(gameport);
if (seq[i] > 0)
msleep(seq[i]);
if (seq[i] < 0) {
mdelay(-seq[i]);
udelay(-seq[i]*14); /* It looks like mdelay() is off by approx 1.4% */
}
}
}

Is that it? After this initialization call it seems to read a packet(which might contain information about the device's capabilities(some kind of Identification packet)?)? Then delays for ADI_INIT_DELAY milliseconds, then starts polling normal data from the port(at less than 40us per bit)?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 10 of 10, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've tried to implement the Logitech Wingman Extreme on top of the existing emulation(it uses the setJoystick_other instead of the setJoystick function to provide more information instead):

#include "headers/types.h" //Basic types!
#include "headers/hardware/ports.h" //Port I/O support!
#include "headers/support/fifobuffer.h" //FIFO buffer support for detecting Digital Mode sequence!
#include "headers/hardware/joystick.h" //Our own type definitions!

//Time until we time out!
//R=(t-24.2)/0.011, where t is in microseconds(us). Our timing is in nanoseconds(1000us). r=0-100. R(max)=2200 ohm.
//Thus, t(microseconds)=(R(ohms)*0.011)+24.2 microseconds.
#define OHMS (120000.0/2.0)
#define POS2OHM(position) ((((double)(position+1))/65535.0)*OHMS)
#define CALCTIMEOUT(position) (((24.2+(POS2OHM(position)*0.011)))*1000.0)

//The size of the sequence to be supported!
#define MAXSEQUENCESIZE 10

struct
{
byte model; //What joystick type are we emulating? 0=Normal PC-compatible input, >0=Digital joystick(number specified which one)!
byte enabled[2]; //Is this joystick enabled for emulation?
byte buttons[2]; //Two button status for two joysticks!
sword Joystick_X[2]; //X location for two joysticks!
sword Joystick_Y[2]; //Y location for two joysticks!
double timeoutx[2]; //Keep line high while set(based on Joystick_X[0-1] when triggered)
double timeouty[2]; //Keep line high while set(based on Joystick_Y[0-1] when triggered)
byte timeout; //Timeout status of the four data lines(logic 0/1)! 1 for timed out(default state), 0 when timing!

//Digital mode timing support!
FIFOBUFFER *digitalmodesequence; //Digital sequence to check when pulsing when model=0.
double lasttiming; //Last write digital timing counter!
double digitaltiming; //Digital timing accumulated!
double digitaltiming_step; //The frequency of the digital timing(in ns, based on e.g. 100Hz signal).
byte buttons2[6]; //Extended 6 buttons(digital joysticks)!
byte hats[4]; //Extended 4 hats(digital joysticks)!
sword axis[6]; //Extended 3 x/y axis(digital joysticks)!
uint_64 packet; //A packet to be read for digital joysticks, based on previous data! The Wingman Extreme Digital's Buttons field is reversed!
uint_64 bitmaskhigh; //High data bit mask!
uint_64 bitmasklow; //Low data bit mask!
byte extensionModel; //What model are we?
} JOYSTICK;

//WingMan Digital sequence from Linux: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/adi.c
//More information about it's data packets: http://atrey.karlin.mff.cuni.cz/~vojtech/joystick/specs.txt
int WingManDigitalSequence[9] = { 4, 2, 3, 10, 6, 11, 7, 9, 11 }; //WingMan digital mode sequence in milliseconds(milliseconds since last pulse)! Make negative values positive for correct handling!

void setJoystickModel(byte model)
{
JOYSTICK.extensionModel = (model==1)?1:0; //Set the model to use!
}

void enableJoystick(byte joystick, byte enabled)
{
if (joystick&0xFE) return; //Only two joysticks are supported!
JOYSTICK.enabled[joystick] = enabled?((enabled<4)?enabled:1):0; //Enable this joystick(with special status disabling the analog part being status 2, only x analog being status 3)?
}

//This one is used for normal joystick(joystick A only), Gravis Gamepad and Gravis Analog Pro(joystick A+B combined).
void setJoystick(byte joystick, byte button1, byte button2, sword analog_x, sword analog_y) //Input from theuser!
{
if (joystick&0xFE) return; //Only two joysticks are supported!
//Set the buttons of the joystick!
Show last 286 lines
	JOYSTICK.buttons[joystick] = (button2?0x0:0x2)|(button1?0x0:0x1); //Button 1&2, not pressed!
JOYSTICK.Joystick_X[joystick] = analog_x; //Joystick x axis!
JOYSTICK.Joystick_Y[joystick] = analog_y; //Joystick y axis!
}

//Below is used with extended digital joysticks!
void setJoystick_other(byte button1, byte button2, byte button3, byte button4, byte button5, byte button6, byte hatleft, byte hatright, byte hatup, byte hatdown, sword analog_x, sword analog_y, sword analog2_x, sword analog2_y) //Input from theuser!
{
//Set the buttons of the joystick in their own buffers(seperated)!
JOYSTICK.buttons2[0] = button1?1:0;
JOYSTICK.buttons2[1] = button2?1:0;
JOYSTICK.buttons2[2] = button3?1:0;
JOYSTICK.buttons2[3] = button4?1:0;
JOYSTICK.buttons2[4] = button5?1:0; //Unsupported!
JOYSTICK.buttons2[5] = button6?1:0; //Unsupported!
JOYSTICK.hats[0] = hatleft?1:0;
JOYSTICK.hats[1] = hatright?1:0;
JOYSTICK.hats[2] = hatup?1:0;
JOYSTICK.hats[3] = hatdown?1:0;
//Buttons are data streamed instead, so store them seperately!
JOYSTICK.Joystick_X[0] = analog_x; //Joystick x axis for compatibility!
JOYSTICK.Joystick_Y[0] = analog_y; //Joystick y axis for compatibility!
JOYSTICK.Joystick_X[1] = analog2_x; //Joystick x axis!
JOYSTICK.Joystick_Y[1] = analog2_y; //Joystick y axis!
}

void updateJoystick(double timepassed)
{
//Joystick timer!
if (JOYSTICK.timeout) //Timing?
{
if (JOYSTICK.model==0) //Analog model? Use compatibiliy analog emulation!
{
if (JOYSTICK.enabled[0]) //Joystick A enabled?
{
if ((JOYSTICK.timeout&1) && (JOYSTICK.enabled[0]&1)) //AX timing?
{
JOYSTICK.timeoutx[0] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeoutx[0]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~1; //Finished timing, go logic 1(digital 0)!
}
}
if ((JOYSTICK.timeout&2) && (JOYSTICK.enabled[0]==1)) //AY timing?
{
JOYSTICK.timeouty[0] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeouty[0]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~2; //Finished timing, go logic 1(digital 0)!
}
}
}
if (JOYSTICK.enabled[1]) //Joystick B enabled?
{
if ((JOYSTICK.timeout&4) && (JOYSTICK.enabled[1]&1)) //BX timing?
{
JOYSTICK.timeoutx[1] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeoutx[1]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~4; //Finished timing, go logic 1(digital 0)!
}
}
if ((JOYSTICK.timeout&8) && (JOYSTICK.enabled[1]==1)) //BY timing?
{
JOYSTICK.timeouty[1] -= timepassed; //Add the time to what's left!
if (JOYSTICK.timeouty[1]<=0.0) //Finished timing?
{
JOYSTICK.timeout &= ~8; //Finished timing, go logic 1(digital 0)!
}
}
}

//Add to the digital timing sequence for detecting the activation sequence!
JOYSTICK.digitaltiming += timepassed; //Apply timing directly to the digital timing for activation sequence detection!
}
else //Digital mode? Use packets according to the emulated device!
{
//Set the current packet, based on the data and timing!
switch (JOYSTICK.model) //What model?
{
default: //Unknown?
case MODEL_LOGITECH_WINGMAN_EXTREME_DIGITAL: //Logitech WingMan Extreme Digital?
JOYSTICK.digitaltiming += timepassed; //Add the time!
if (JOYSTICK.digitaltiming>=JOYSTICK.digitaltiming_step && JOYSTICK.bitmasklow) //To step the counter?
{
for (;JOYSTICK.digitaltiming>=JOYSTICK.digitaltiming_step;) //Stepping?
{
JOYSTICK.digitaltiming -= JOYSTICK.digitaltiming_step; //We're stepping!
//Give the 1 and 0 bits on the two channels! Buttons 2&3 carry the lower half, Buttons 1&2 carry the upper half!
//Lower half!
if (JOYSTICK.packet&JOYSTICK.bitmasklow) //Bit is 1?
{
JOYSTICK.buttons[1] ^= 2; //The upper bit changes state!
}
else //Bit is 0?
{
JOYSTICK.buttons[1] ^= 1; //The lower bit changes state!
}
//Upper half!
if (JOYSTICK.packet&JOYSTICK.bitmaskhigh) //Bit is 1?
{
JOYSTICK.buttons[0] ^= 2; //The upper bit changes state!
}
else //Bit is 0?
{
JOYSTICK.buttons[0] ^= 1; //The lower bit changes state!
}
JOYSTICK.bitmaskhigh >>= 1; //Check the next bit, if any!
JOYSTICK.bitmasklow >>= 1; //Check the next bit and update status, if any!
}
}
break;
}
}
}
}

byte joystick_readIO(word port, byte *result)
{
INLINEREGISTER byte temp;
switch (port)
{
case 0x201: //Read joystick position and status?
//bits 8-7 are joystick B buttons 2/1, Bits 6-5 are joystick A buttons 2/1, bit 4-3 are joystick B Y-X timeout timing, bits 1-0 are joystick A Y-X timeout timing.
temp = 0xFF; //Init the result!
if (JOYSTICK.enabled[1]) //Joystick B enabled?
{
temp &= 0x33|(((JOYSTICK.buttons[1]<<6)|JOYSTICK.timeout)&0xCC); //Clear joystick B bits when applied!
}
if (JOYSTICK.enabled[0]) //Joystick A enabled?
{
temp &= 0xCC|(((JOYSTICK.buttons[0]<<4)|JOYSTICK.timeout)&0x33); //Set joystick A bits when applied!
}
*result = temp; //Give the result!
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

byte joystick_writeIO(word port, byte value)
{
#include "headers/packed.h"
union
{
sword axis;
word data;
} axisconversion;
#include "headers/endpacked.h"
byte entry;
byte sequencepos;
switch (port)
{
case 0x201: //Fire joystick four one-shots?
//Set timeoutx and timeouty based on the relative status of Joystick_X and Joystick_Y to fully left/top!
//First joystick timeout!
if (JOYSTICK.enabled[1]) //Joystick B enabled?
{
if (JOYSTICK.enabled[1]&1) //X axis used?
{
JOYSTICK.timeoutx[1] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_X[1]-SHRT_MIN);
}
if (JOYSTICK.enabled[1]==1) //Y axis used?
{
JOYSTICK.timeouty[1] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_Y[1]-SHRT_MIN);
}
}
//Second joystick timeout!
if (JOYSTICK.enabled[0]) //Joystick A enabled?
{
if (JOYSTICK.enabled[0]&1) //X axis used?
{
JOYSTICK.timeoutx[0] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_X[0]-SHRT_MIN);
}
if (JOYSTICK.enabled[0]==1) //Y axis used?
{
JOYSTICK.timeouty[0] = CALCTIMEOUT((int_32)JOYSTICK.Joystick_Y[0]-SHRT_MIN);
}
}

//Check for activation of the WingMan digital mode!
if (JOYSTICK.model==0) //Analog mode?
{
switch (JOYSTICK.extensionModel) //What extension model?
{
default: //Unknown?
break; //Don't handle model extension!
case MODEL_LOGITECH_WINGMAN_EXTREME_DIGITAL: //Logitech WingMan Extreme Digital?
if (fifobuffer_freesize(JOYSTICK.digitalmodesequence)==0) //No space left?
{
readfifobuffer(JOYSTICK.digitalmodesequence,&entry); //Discard an entry!
}
if ((JOYSTICK.digitaltiming/1000000.0f)>=256.0) //Timeout?
{
writefifobuffer(JOYSTICK.digitalmodesequence,0xFF); //Simply time out!
}
else //Within range?
{
writefifobuffer(JOYSTICK.digitalmodesequence,(byte)(JOYSTICK.digitaltiming/1000000.0f)); //The time since last pulse!
}
JOYSTICK.digitaltiming = 0.0f; //Reset the timing?
fifobuffer_save(JOYSTICK.digitalmodesequence); //Save the position for checking!
for (;fifobuffer_freesize(JOYSTICK.digitalmodesequence)<(MAXSEQUENCESIZE-NUMITEMS(WingManDigitalSequence));) //We need the buffer items from the point of the sequence most recently given!
{
readfifobuffer(JOYSTICK.digitalmodesequence,&entry); //Discard an entry!
}
for (sequencepos=0;sequencepos<NUMITEMS(WingManDigitalSequence);) //Check for the sequence!
{
if (!readfifobuffer(JOYSTICK.digitalmodesequence,&entry)) break; //Not enough entries yet?
if (entry!=WingManDigitalSequence[sequencepos]) break; //Only count when the sequence is matched!
++sequencepos; //Counted!
}
fifobuffer_restore(JOYSTICK.digitalmodesequence); //We're finished, undo any reads!
if (sequencepos==NUMITEMS(WingManDigitalSequence)) //Sequence pattern matched?
{
JOYSTICK.model = MODEL_LOGITECH_WINGMAN_EXTREME_DIGITAL; //Enter digital WingMan Digital's digital mode!
JOYSTICK.digitaltiming_step = 1000000000.0f/(100000.0f*2.0f); //We're a signal going 1-0 or 0-1 at 100kHz!
}
break;
}
}
else //Give a packet in the current device format!
{
switch (JOYSTICK.model) //What model are we?
{
default: //Unknown?
case 1: //Logitech WingMan Extreme Digital?
JOYSTICK.bitmaskhigh = (1ULL<<41); //Reset to the high 21st bit!
JOYSTICK.bitmasklow = (1<<20); //Reset to the low 21st bit! We're starting to send a packet, because we're not 0! Once we shift below bit 0, becoming 0, we're finished!
JOYSTICK.digitaltiming = 0.0f; //We're starting to send a packet, reset our timing!
//Formulate a packet from the current data!
/*
Bits Meaning
0 .. 3 - Hat (4 bits)
4 .. 9 - Buttons (6 bits)
10 .. 17 - Axis 2 (Twist) (8 bits)
18 .. 25 - Axis 1 (Y) (8 bits)
26 .. 33 - Axis 0 (X) (8 bits)
34 .. 41 - 0x00 (8 bits)
*/
JOYSTICK.packet = 0; //Initialize the packet!
//Now, fill the packet with our current information!
//First, hats!
JOYSTICK.packet |= JOYSTICK.hats[0]|(JOYSTICK.hats[1]<<1)|(JOYSTICK.hats[2]<<2)|(JOYSTICK.hats[3]<<3);
//Then buttons(reversed)!
JOYSTICK.packet |= JOYSTICK.buttons2[5]|(JOYSTICK.buttons2[4]<<1)|(JOYSTICK.buttons2[3]<<2)|(JOYSTICK.buttons2[2]<<3)|(JOYSTICK.buttons2[1]<<4)|(JOYSTICK.buttons2[0]<<5);
//Axis2(Twist) 8-bits converted!
axisconversion.axis = JOYSTICK.Joystick_X[1]; //Twist!
JOYSTICK.packet |= (((axisconversion.data>>8)&0xFF)<<10); //Twist converted!
//Axis 1(Y) 8-bits converted!
axisconversion.axis = JOYSTICK.Joystick_Y[0]; //Y!
JOYSTICK.packet |= (((axisconversion.data>>8)&0xFF)<<18); //Twist converted!
//Axis 0(X) 8-bits converted!
axisconversion.axis = JOYSTICK.Joystick_X[0]; //Y!
JOYSTICK.packet |= (((axisconversion.data>>8)&0xFF)<<26); //Twist converted!
//The upper bits are 0x00!
break;
}
}
JOYSTICK.timeout = 0xF; //Start the timeout on all channels, regardless if they're enabled. Multivibrator output goes to logic 0.
return 1; //OK!
default:
break;
}
return 0; //Not an used port!
}

void joystickInit()
{
register_PORTIN(&joystick_readIO); //Register our handler!
register_PORTOUT(&joystick_writeIO); //Register our handler!
JOYSTICK.buttons[0] = JOYSTICK.buttons[1] = 3; //Not pressed!
JOYSTICK.timeoutx[0] = JOYSTICK.timeouty[0] = JOYSTICK.timeoutx[1] = JOYSTICK.timeouty[1] = 0.0; //Init timeout to nothing!
JOYSTICK.timeout = 0x0; //Default: all lines timed out!
JOYSTICK.model = 0; //Default: analog model(compatibility)!
JOYSTICK.digitaltiming = 0.0f; //Reset our digital detection(during analog mode)/step(during digital mode) timing!
JOYSTICK.digitalmodesequence = allocfifobuffer(MAXSEQUENCESIZE,0); //We use a simple buffer with 10 8-bit entries, unlocked!
JOYSTICK.lasttiming = 0.0f; //Last write digital timing delay!
JOYSTICK.extensionModel = 0; //Default: no extension specified!
}

void joystickDone()
{
free_fifobuffer(&JOYSTICK.digitalmodesequence); //Release our buffer, we're finished with it!
}

Is this correct?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io