Categories
Uri_ba's pit

standalone Cougar TQS (Throttle) – Part II

In the last part We’ve went over the hardware side of building the DIY TQS adapter.

In this section we will discuss a bit on the firmware side, and the problems it creates.

The Formware

Getting things started.
There are two basic ways to get USB HID out of an avr. Software implementation like the V-USB framework provieds or hardware impementation hardcoded into atmel’s ATMEGA U chips (like the 32u4 driving the Arduino Micro) – those can be harnessed by using the LUFA library.

For the ICP i’ve initially tried the V-USB solution, but it was too much for my programming skills. so I decided to give the LUFA a try, and it was much simpler to implement (for me that is).

So when I approached this little project, I’ve went straight for thhe LUFA (with a 6$ ebay Arduino ProMicro and it’s 32u4 chip). I’ve also used Arduino IDE to check and debug my code chunks and bits (using serial output).

Another bit of very important is the Arduino pin mapping for the 32u4, it allows a quick translation between working Arduino sketch and AVR C commands.

Enter the matrix

The first hurdle I had to cross was reading the button matrix.
Once I’ve figured out how it worked implementing the code as pretty straight forward.

uint16_t pullMatrix(void) {
  uint16_t buttons = 0; // create a 16bit variable to store the button data
  digitalWrite(4, LOW); // pull line 4 down
  bitWrite(buttons, 1, ~(digitalRead(7))); // read T1 (cursor enable)
  bitWrite(buttons, 6, ~(digitalRead(6))); // read T6 (uncage)
  digitalWrite(4, HIGH); // bring line 4 back up

  digitalWrite(2, LOW); // pull pin 2 down
  bitWrite(buttons, 2, ~(digitalRead(6))); // T2
  bitWrite(buttons, 3, ~(digitalRead(7))); // T3
  bitWrite(buttons, 4, ~(digitalRead(8))); // T4
  bitWrite(buttons, 5, ~(digitalRead(9))); // T5
  digitalWrite(2, HIGH); // bring pin 2 back up

  digitalWrite(3, LOW); // pull pin 3 down
  bitWrite(buttons, 7, ~(digitalRead(6))); // T6
  bitWrite(buttons, 8, ~(digitalRead(7))); // T7
  bitWrite(buttons, 9, ~(digitalRead(8))); // T8
  bitWrite(buttons, 10, ~(digitalRead(9))); // T9
  digitalWrite(3, HIGH); // bring pin 3 abck up

  return (buttons >> 1);
}

in Arduino code it’s easy, so I’ve translated everything to AVR C commands (also work on arduino) to test everything will work.
but I had some issues setting the bits, so I had to use if statements, it’s more “expensive” but it works. I’ll probably revise it a bit down the road, but for now it’s OK).

uint16_t pullMatrix(void) {
  uint16_t buttons = 0;
  PORTD &= ~(1 << PD4); // pull "pin 4" down
  delay(1);
  // T6
  if ((PIND & _BV(7))) {
    buttons &= ~(1 << 6);
  } else {
    buttons |= (1 << 6);
  }
  // T1
  if (PINE & _BV(6)) {
    buttons &= ~(1 << 1);
  } else {
    buttons |= (1 << 1);
  }
  PORTD |= (1 << PD4); // bring "pin 4" back up
  // poll toggles	T2-5
  PORTD &= ~(1 << PD1); // pull "pin 3" down
  delay(1);
  //T2
  if ((PIND & _BV(7))) {
    buttons &= ~(1 << 2);
  } else {
    buttons |= (1 << 2);
  }
  // T3
  if (PINE & _BV(6)) {
    buttons &= ~(1 << 3);
  } else {
    buttons |= (1 << 3);
  }
  //T4
  if ((PINB & _BV(4))) {
    buttons &= ~(1 << 4);
  } else {
    buttons |= (1 << 4);
  }
  // T5
  if (PINB & _BV(5)) {
    buttons &= ~(1 << 5);
  } else {
    buttons |= (1 << 5);
  }
  PORTD |= (1 << PD1); // bring "pin 3" back up
  // poll toggles	T7-10
  PORTD &= ~(1 << PD0); // pull "pin 3" down
  delay(1);

  //T7
  if ((PIND & _BV(7))) {
    buttons &= ~(1 << 7);
  } else {
    buttons |= (1 << 7);
  }
  // T8
  if (PINE & _BV(6)) {
    buttons &= ~(1 << 8);
  } else {
    buttons |= (1 << 8);
  }
  //T9
  if ((PINB & _BV(4))) {
    buttons &= ~(1 << 9);
  } else {
    buttons |= (1 << 9);
  }
  // T10
  if (PINB & _BV(5)) {
    buttons &= ~(1 << 10);
  } else {
    buttons |= (1 << 10);
  }

  PORTD |= (1 << PD0); // bring "pin 3" back up

  return (buttons >> 1);
}

I’ve also had to add delay after pulling the line down, as digital write is slower, and I had to wait for the current to drain out correctly.

Next bit after that was getting the axis

Getting internal ADC polled

The micro has 4 exposed ADC pins (actually more are available on the 32u4, but those are shared with digital pins I’m using so I had to skip those).

the internal ADC is 10 bits, i.e 0-1023.
I’ve decided to use that for the rotaries and the mini-stick,
the rotaries are very straight forward. plain “analogRead” gave the full range off the bat. nothing to see here.

the microstick, was a whole different story.
when looking at the raw output, range was between ~300 and 800, but it was different values between the two axis of the microstick,  and it was very very jittery.

So I’ve decided to map the values of the usable range down to 8 bit values, forcing a reduction of the jitter. the microstick, doesn’t actually need so many values. so it kinda summed up to something like:

uint8_t readMicrostick (uint8_t adcChannel) {
  return map(adcChannel,250,850,0,255);
}

That gave me a good range on two throttles, allowing minimum range loss and less jitter.

which leaves us with the Throttle itself…

MCP3202 SPI ADC

The MCP3202 is a 12 bit dual channel ADC, we actually only need one channel for the throttle.

It’s a simple device to read,
you pull CS down, send a start bit. then issue a control order, and get the output. all very well documented in the data sheet.
when pulled into the 12 bit variable I’ve assined to the throttle, it worked like a charm. but here too the throttle has an annoying dead-zone. the 12 bit range is 0-4095, but the throttle moves only in the 450 to 3550 range. when I plugged the numbers into the map function, things began to get weird, it had the distinct characteristics of an integer overflow. So I had to dig into the map function, I’ve initially discovered that there is a very large number being multiplied inside, much greater then the 32 bit number limit. So I created a spin off of the original Arduino Map function.

The original function first multiplies, then divides, that overcomes the problem of integer divisions randomly rounding the number up or down. which overflows almost instantly when you have 12 bit range. I’m creating a relatively small number (around 1000) which can then be multiplied.

int32_t mapLargeNumbers(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max) {
  #define FACTOR 10000
  int ratio = ((((float)out_max - (float)out_min) / ((float)in_max - (float)in_min)) * FACTOR);
  return (((x - in_min) * (ratio)) / FACTOR) + out_min;
}

that worked on sterile tests, but gave me off, outputs when using the raw data, looking at it, it just acted funny, giving me output values way above the 12 bit output i was expecting. looking at the raw ADC output, it looked like there is sometimes data in the higher, unused bits of the higher byte of data. so I’ve added a small bitwise and to make sure the noise is suppressed, giving me a nice clean output.

uint16_t Readthrottle(void) {
  uint16_t buff = 0;
  digitalWrite(10, LOW);
  SPI.transfer(0x01);
  buff = ((uint16_t)((SPI.transfer(0xa0) & ~(0xF0)) << 8)) | ((uint16_t)SPI.transfer(0));
  digitalWrite(10, HIGH);
  return mapLargeNumbers(buff, 400, 3650, 0, 4095);
}

that closes the whole how to get the values dilemma.

Now I can plug everything into the LUFA code, and see how windows responds.

spoiler alert.. It works 😉

Leave a Reply