Uri_ba's pit

Standalone Cougar TQS (Throttle) – Part III

In the last two posts we worked with the hardware and basic code for debugging. In this post we will go over the basic changes done in the actual code.

Everything is already available in the download page.
You can browse the code and download all the stuff you need (including a pre-compiled Hex, ready to be uploaded to your device – incl. a simple script to help with it for those of you who only want it to work :))

Here is a small clip showing how it looks in windows.

Now let’s dig into some technical stuff (only if you want to) 🙂

I’ll start from the Arduino sketch we used on the previous post.
In that code we have separated each part into a little unit, so we can debug each part individually. but that is very inefficient.
For example:
We are not using interrupt polling of ADC. instead, we are using a single run with busy loop, to poll each of them. Which means readying an axis is a blocking action. nothing can happen while we are converting. the action itself of course is pretty quick, about 200ns (that is 1/5th of a ms).

If you also remember, the button polling function had delays built in, because the line didn’t drop quick enough. another blocking action.

So I immediately combined the two, and used the ADC polling as the delay.

	// poll toggles	T7-10
	PORTD &= ~(1 << PD0); // pull "pin 3" down
	JoystickReport->RNG = (((ADC_GetChannelReading(7|ADC_RIGHT_ADJUSTED | ADC_REFERENCE_AVCC)*2)+AxisLastRun.RNG)/3); // run ADC conversion as delay
	if ((PIND & _BV(7))) {
		buttonbuffer &= ~(1 << 7);
		} else {
		buttonbuffer |= (1 << 7);
	// T8
	if (PINE & _BV(6)) {
		buttonbuffer &= ~(1 << 8);
		} else {
		buttonbuffer |= (1 << 8);
	if ((PINB & _BV(4))) {
		buttonbuffer &= ~(1 << 9);
		} else {
		buttonbuffer |= (1 << 9);
	// T10
	if (PINB&_BV(5)) {
		buttonbuffer &= ~(1 << 10);
		} else {
		buttonbuffer |= (1 << 10);
	PORTD |= (1 << PD0); // bring "pin 3" back up

Another thing I’ve added (as you can see in the snippet above), it a crude Jitter-suppression trick. I’m storing the last Axis value, and then averaging it with the current value, to help smooth out the output.

In the case of the microstick (which is horrible), I took it a step further, and I’m using the last three runs with a running average.

JoystickReport->Y =  (((ReadMinistick(5,0)*4)+(MicrostickHistory[0].Y*3)+(MicrostickHistory[1].Y*2)+MicrostickHistory[2].Y)/10); // run ADC conversion as delay, do average reduce jitter

with the throttle I even went further, and if the change in the value is minor (less then 5 – out of possible 4096 values), just use the old value.

	JoystickReport->Z = (((readSPIADC()*2)+AxisLastRun.Z)/3);
	if (abs(JoystickReport->Z - AxisLastRun.Z) < 5) {
		JoystickReport->Z = AxisLastRun.Z;

all in all those changes make the throttle less prone to Jitter, while keeping the responsiveness very high.

Another small tweak in the code, is that all values are “Falcon normalized”. (rather then being “native”. that means that when you set the axis in Falcon, you will not need to check and think which axis needs the “invert” flag. It’s just gives falcon exactly the output it needs.

That’s about it…
have fun!

2 replies on “Standalone Cougar TQS (Throttle) – Part III”

What an amazing job you’ve done!
I just yesterday found my old thrustmaster gear in storage and was wondering if this would also work on my F-16 TQS (pre-cougar).. the buttons seem all the same, I’m determined now thank you!
Again what a great job!!

Hi David,
I’m pretty sure the pinout is different in the old TQS, and will require some changes in the code to support it.
From the photos I’ve seen, it looks like the throttle is “old school” and uses Keyboard pass-through. If it’s indeed so, my best guess is that it would be easier to open the throttle, and replace the PCB completely. If it’s like the cougar, there is a 22pin ribbon cable from the throttle handle, and another 3 pin connector from the throttle POT. I would put a 4021BE Shift register and capture 8 buttons, and directly poll another 2 via the pins. and polling the Analog axis should be the same, Just finding the correct pin.

Leave a Reply