Hello,
About a year ago I made a cable to connect my TX to the parallel port for use under FMS.
Since I also wanted to use the TX as a joystick in other programs, I started looking for suitable drivers. I didn't found a solution, so I used what I found to create a bridge between the IOCTL PPJoy virtual joystick driver and the parallel port.
For the case that I ever might switch to winXP I programmed the parallel port using a dll found at
I tested the program and it works fine under win98 (not tested on win2000 / xp but should work)
There seem to be some issues with the PPJoy channel assignment, therefore I rearranged channel mapping so that all four channels are available under windows.
Since I have a 5 channel TX, I hardcoded the channels in the code, with channel 5 configured as a switch. (you need at least 1 switch to calibrate your joystick under windows).
I'll attach the code here (I used BCC32 free compiler since I don't have an IDE on my PC since I havn't programmed in 5 years) so maybe someone else might want to use it.
The rest of the instructions you'll find in the code comments.
If someone makes a better version, integrates an installer or compiles this into the PPJoy driver, please send me the result.
greets,
Koen
PS: this should also solve the issue that FMS can not use the parallel port interface under win2000 / xp
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <winioctl.h>
#include "ppjioctl.h"
#define NUM_ANALOG 8 /* Number of analog values which we will provide */
#define NUM_DIGITAL 1 /* Number of digital values which we will provide */
/* Definitions in the build of inpout32.dll are: */
/* short _stdcall Inp32(short PortAddress); */
/* void _stdcall Out32(short PortAddress, short data); */
/* prototype (function typedef) for DLL function Inp32: */
typedef short _stdcall (*inpfuncPtr)(short portaddr);
typedef void _stdcall (*oupfuncPtr)(short portaddr, short datum);
#pragma pack(push,1) /* All fields in structure must be byte aligned. */
typedef struct
{
unsigned long Signature; /* Signature to identify packet to PPJoy IOCTL */
char NumAnalog; /* Num of analog values we pass */
long Analog[NUM_ANALOG]; /* Analog values */
char NumDigital; /* Num of digital values we pass */
char Digital[NUM_DIGITAL]; /* Digital values */
} JOYSTICK_STATE;
#pragma pack(pop)
int main (int argc, char **argv)
{
HANDLE h;
char ch;
JOYSTICK_STATE JoyState;
DWORD RetSize;
DWORD rc;
long *Analog;
char *Digital;
char *DevName;
HINSTANCE hLib;
inpfuncPtr inp32;
oupfuncPtr oup32;
// following variables are not used in neither the io dll or the ioctl joystick interface
// they just beloing to the PPM demodulation routine
short current_data;
short glitches_in_a_row;
short previous_data;
short current_channel_number =0;
short still_waiting_for_last_pulse;
short max_channel_number_needed;
long channel_value[10]; // provide some headroom to not have to start from 0
LARGE_INTEGER Frequency;
LARGE_INTEGER PerformanceCount;
LARGE_INTEGER Startingtime;
long Pulseduration;
long three_milliseconds;
long one_millisecond;
// this is the standard name on installation, if you chose something else, change it
DevName= "\\\\.\\PPJoyIOCTL1";
/* Load the library for parallel communications*/
hLib = LoadLibrary("inpout32.dll");
if (hLib == NULL) {
printf("LoadLibrary Failed.
");
return -1;
}
/* get the address of the function for parallel input communication*/
inp32 = (inpfuncPtr) GetProcAddress(hLib, "Inp32");
if (inp32 == NULL) {
printf("GetProcAddress for Inp32 Failed.
");
return -1;
}
oup32 = (oupfuncPtr) GetProcAddress(hLib, "Out32");
if (oup32 == NULL) {
printf("GetProcAddress for Oup32 Failed.
");
return -1;
}
/* Open a handle to the control device for the first virtual joystick. */
/* Virtual joystick devices are names PPJoyIOCTL1 to PPJoyIOCTL16. */
h= CreateFile(DevName,GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
/* Make sure we could open the device! */
if (h==INVALID_HANDLE_VALUE)
{
printf ("CreateFile failed with error code %d trying to open %s device
",GetLastError(),DevName);
return 1;
}
/* Initialise the IOCTL data structure for joystick communication*/
JoyState.Signature= JOYSTICK_STATE_V1;
JoyState.NumAnalog= NUM_ANALOG; /* Number of analog values */
Analog= JoyState.Analog; /* Keep a pointer to the analog array for easy updating */
JoyState.NumDigital= NUM_DIGITAL; /* Number of digital values */
Digital= JoyState.Digital; /* Digital array */
// purpose of this program:
//
// Using the inpout32.dll and the PPJoy virtual joystick driver with IOCTL control,
// this program allows to connect a RC transmitter to the parallel port and model
// the transmitter's channels towards windows as a joystick
// Connect the TX ground to pin 10 and the TX PPM signal via a resistor and a
// zener diode to pin 18 (I used this since I already made the cable for FMS)
//
// installation notes:
//
// install PPJoy virtual joystick driver (important note for win98 users: don't perform
// all of the "post win98 steps" mentioned in the PPJoy instructions, only add to the
// control panel the "Parallel port joystick bus and forget about the other ones for LPTx)
// after installing the inpout32.dll and a compiled and linkes version of this program
// in a directory, start the program and you can configure your virtual joystick
// PPM signal spec (free interpretation):
//
// we'll see a number of active HIGH pulses
// pulse duration 1ms means channel position is 0%
// pulse duration 2ms means channel position is 100%
// 1st pulse is channel1, 2nd channel2 and so on
// max number of pulses allowed is 9
// after last pulse, min. 2ms LOW functions as reset signal,
// indicating pulse 1 will follow again
// tell the routines how many channels you want to monitor
// don't make this bigger then 9, actually 8 is the max. number for
// this routine to work reliable. Also don't make it higher then
// the actual number of channels your TX supports or it won't work
// the lower the number, the more time the routine has to call the joystick driver,
// the less chance we have for glitches in the PPM decoding
max_channel_number_needed = 6;
//check just how fast our internal HW timer is in units per second
QueryPerformanceFrequency (&Frequency);
// do some calculation before we start
one_millisecond = Frequency.QuadPart / 1000;
three_milliseconds = one_millisecond * 3;
// run the program forever. You can always terminate by pressing Ctrl + C
while (1!=2)
{
// read stuff from the parallel port (this allows for the simplest adaptor to the TX)
// On my win98 machine I could call the hardware directly, but since the
// library is fast enough I used that to benefit from win 2000 / win xp compatibility
current_data = (inp32)(0x379);
if (previous_data != current_data)
{
// make variable ready for next check;
previous_data = current_data;
// check if pin 18 is pulled low by the TX,
// assume other pins are high, better would be to mask them out
// if your TX is giving a active low signal, change "!=" to "==" below
if (current_data != 104)
{
// so it's a PPM HIGH
// check our watch to see what time it is
QueryPerformanceCounter(&PerformanceCount);
// calculate the pulse duration
Pulseduration = PerformanceCount.QuadPart - Startingtime.QuadPart;
// reinitialise for next measurement
Startingtime = PerformanceCount;
// see if it was the reset pulse that just ended
// if there are more then 3 ms between two highs
// assume we just ended the reset pulse.
// note: this deviates from the spec but works ok
// if TX is not sending more then 8 channels in the frame
// and it allows us to get away without checking on low signals
if (Pulseduration > three_milliseconds)
{
// yess, we have a winner: the reset pulse just ended
current_channel_number = 1;
still_waiting_for_last_pulse = 1;
}
else
{
// quess what: the pulse for the next channel is starting
// let's assume this is also the end of the previous pulse
// put the value found in the array
// if this is not the last channel, just continue to the next one
if ( max_channel_number_needed > current_channel_number)
{
channel_value[current_channel_number] = Pulseduration;
// indicate we now wait for the next channel
current_channel_number++;
}
else
{
if (still_waiting_for_last_pulse == 1)
{
// the still_waiting part is to avoid that in case we look for e.g. 6
// pulses and the signal contains 8 that we will write signals 7&8 in channel6
still_waiting_for_last_pulse = 0;
channel_value[current_channel_number]= Pulseduration;
// at this moment in the PPM signal, possibly more pulses need
// to follow but they don't interest us anymore. After them,
// the reset pulse will follow. Since we are now at the least
// time-critical period in the signal do the time-consuming joystick stuff now.
// joystick driver starts counting from 0, not 1 like I prefer
// figure out the mathwork yourselves: pulse is between 1 and 2 ms +
// about 100 us of the pause between two pulses
Analog[0]= PPJOY_AXIS_MIN +
((PPJOY_AXIS_MAX - PPJOY_AXIS_MIN) *
(channel_value[1] - one_millisecond
- (one_millisecond/10) ) /
one_millisecond);
Analog[1]= PPJOY_AXIS_MIN +
((PPJOY_AXIS_MAX - PPJOY_AXIS_MIN) *
(channel_value[2] - one_millisecond
- (one_millisecond/10) ) /
one_millisecond);
Analog[3]= PPJOY_AXIS_MIN +
((PPJOY_AXIS_MAX - PPJOY_AXIS_MIN) *
(channel_value[3] - one_millisecond
- (one_millisecond/10) ) /
one_millisecond);
Analog[4]= PPJOY_AXIS_MIN +
((PPJOY_AXIS_MAX - PPJOY_AXIS_MIN) *
(channel_value[4] - one_millisecond
- (one_millisecond/10) ) /
one_millisecond);
if (((channel_value[6] - one_millisecond
- (one_millisecond/10) ) / one_millisecond) > 0.5 )
Digital[0]= 1;
else
Digital[0]= 0;
// now write all joystick values back to the driver
DeviceIoControl(h,IOCTL_PPORTJOY_SET_STATE,&JoyState,
sizeof(JoyState),NULL,0,&RetSize,NULL);
}
}
}
}
else
{
// so it's a PPM LOW
// let's do nothing on low signals
// this saves us some calls to the timer routines,
// maybe resulting in less chances on glitches
}
}
}
// although we never arive here due to the endless loop, the code below could provide a clean exit
//CloseHandle(h);
//FreeLibrary(hLib);
//return 0;
}