The Code
Now that we have defined the device and prepared responses for any queries from the PC, the only two things we have left to do is the code for our inputs and the code for our LED patterns. We use two files in the library teensy3 cores directory named usb_xinput.c and usb_xinput.h to set up functions allowing us to send button packets and receive LED packets. We will start with explaining the header file first:
#ifndef USBxinput_h_
#define USBxinput_h_
#include "usb_desc.h"
#if defined(XINPUT_INTERFACE)
#include <inttypes.h>
// C language implementation
#ifdef __cplusplus
extern "C" {
#endif
int usb_xinput_recv(void *buffer, uint32_t timeout);
int usb_xinput_available(void);
int usb_xinput_send(const void *buffer, uint32_t timeout);
#ifdef __cplusplus
}
#endif
// C++ interface
#ifdef __cplusplus
class usb_xinput_class
{
public:
int available(void) {return usb_xinput_available(); }
int recv(void *buffer, uint16_t timeout) { return usb_xinput_recv(buffer, timeout); }
int send(const void *buffer, uint16_t timeout) { return usb_xinput_send(buffer, timeout); }
};
extern usb_xinput_class XInput;
#endif // __cplusplus
#endif // XINPUT_INTERFACE
#endif // USBxinput_h_
As you can see, this is simply the header for the source file we mentioned previously. We check that the correct things are defined and then we start with some forward declarations for the functions we will be using in the C source file. Once those forward declarations are completed, we are going to create a class, usb_xinput_class that will allow us to bundle these functions together for easier access. We label them with shorter names and then declare an external variable XInput to use this class in our source. This allows us to type XInput.available(), XInput.recv(), and XInput.send() instead of their complete names. As you can see, this creates a much more straight forward approach to the functions we will be using.
#include "usb_dev.h"
#include "usb_xinput.h"
#include "core_pins.h" // for yield(), millis()
#include <string.h> // for memcpy()
//#include "HardwareSerial.h"
#ifdef XINPUT_INTERFACE // defined by usb_dev.h -> usb_desc.h
#if F_CPU >= 20000000
//Function receives packets from the RX endpoint
//We will use this for receiving LED commands
int usb_xinput_recv(void *buffer, uint32_t timeout)
{
usb_packet_t *rx_packet;
uint32_t begin = millis();
while (1) {
if (!usb_configuration) return -1;
rx_packet = usb_rx(XINPUT_RX_ENDPOINT);
if (rx_packet) break;
if (millis() - begin > timeout || !timeout) return 0;
yield();
}
memcpy(buffer, rx_packet->buf, XINPUT_RX_SIZE);
usb_free(rx_packet);
return XINPUT_RX_SIZE;
}
//Function to check if packets are available
//to be received on the RX endpoint
int usb_xinput_available(void)
{
uint32_t count;
if (!usb_configuration) return 0;
count = usb_rx_byte_count(XINPUT_RX_ENDPOINT);
return count;
}
// Maximum number of transmit packets to queue so we don't starve other endpoints for memory
#define TX_PACKET_LIMIT 3
//Function used to send packets out of the TX endpoint
//This is used to send button reports
int usb_xinput_send(const void *buffer, uint32_t timeout)
{
usb_packet_t *tx_packet;
uint32_t begin = millis();
while (1) {
if (!usb_configuration) return -1;
if (usb_tx_packet_count(XINPUT_TX_ENDPOINT) < TX_PACKET_LIMIT) {
tx_packet = usb_malloc();
if (tx_packet) break;
}
if (millis() - begin > timeout) return 0;
yield();
}
memcpy(tx_packet->buf, buffer, XINPUT_TX_SIZE);
tx_packet->len = XINPUT_TX_SIZE;
usb_tx(XINPUT_TX_ENDPOINT, tx_packet);
return XINPUT_TX_SIZE;
}
#endif // F_CPU
#endif // XINPUT_INTERFACE
You will notice we have some includes at the top to include our header and to give us a few of the useful functions we need such as yield, memcpy, etc. We then check that our xinput_interface is defined and that the CPU clock is greater than or equal to 20MHz. The first function in this source file is usb_xinput_recv. This function grabs a packet if its ready and uses memcpy to copy it over to rx_packet. We use memcpy because its an array of bytes and memcpy allows us to copy the whole array in memory without having to index through the array. The first part of this function starts in a while 1 indefinite loop. If the usb is not configured then the function breaks, returning a -1. If the usb is configured it then uses the USB library function usb_rx to grab a packet from the endpoint we defined as XINPUT_RX_ENDPOINT earlier. If the packet does not equal 0 the while loop breaks. However, if it does equal 0 we will begin using millis to check if we have reached the timeout value we specified when calling the function. If we have not timed out yet we yield and then start the while loop over looking for a new non zero packet. If we do timeout the while loop breaks and the function returns 0. Once a nonzero packet has been received, we copy it to the pointer buffer that we passed to the function and free up rx_packet using the function from the USB library usb_free. Finally we return the predefined value of XINPUT_RX_SIZE stating that we used memcpy to copy that many bytes.
The next function is a simple one that returns a count of how many bytes are available to receive from the endpoint XINPUT_RX_ENDPOINT using the USB library function usb_rx_byte_count(). We will use this function to check if LED pattern data is available to receive before calling usb_xinput_recv().
The last function in this source file is the one that we will use to send button packets to the host, usb_xinput_send(). Similarly to the receive function, we start off in an infinite while loop. If the USB is not configured we return a -1. This line is extremely important in both these functions when you are not running on host power. We do not want to be trying to send or receive packets on the USB bus if we are not configured and enumerated completely with the host. Next we use the function usb_tx_packet_count and pass it our define XINPUT_TX_ENDPOINT to check and make sure that the packet count is less than our TX_PACKET_LIMIT. We do not want overflows or to capture partial packets. If we are less than the packet limit we then use the function usb_malloc() to allocate memory for our packet and pass the pointer to the variable tx_packet. If tx_packet is greater than 0 we break from the while loop, if not we use millis to check if we have reached our timeout. If we have timed out the function returns 0, but if it hasn’t timed out we will yield and then start the while loop over. Once we are out of the while loop, we use memcpy to copy the pointer to the variable we passed that contains our button packet into tx_packet. We then set the length of tx_packet to the predefined value of XINPUT_TX_SIZE. Finally, we use the USB library function usb_tx() to transmit our button packet to the endpoint defined as XINPUT_TX_ENDPOINT and return the value defined as XINPUT_TX_SIZE.
These USB functions were based directly on the examples included in the Teensy installation. They are simple once broken down, but can be confusing at first because of the need and use of pointers by the USB library. Passing the pointers around lets these functions store the received data right where we want it and allows the USB stack to transmit our data directly from where we put it. This creates less convoluted code and a speedier execution. If you aren’t catching on you can attempt to read this a few more times, look up the meaning of some of these functions (memcpy, malloc, etc), or just trust that they work. As always, you can contact me and I can attempt at a better explanation.
//General Declarations
#define MILLIDEBOUNCE 20 //Debounce time in milliseconds
#define NUMBUTTONS 14 //Number of all buttons
#define NUMBUTTONSONLY 10 //Number of just buttons
#define interval 150 //interval in milliseconds to update LED
#define USB_TIMEOUT 12840 //packet timeout for USB
//LED STYLE DEFINES
#define NO_LED 0
#define ONBOARD_LED 1
#define EXTERNAL_LED 2
//LED Pattern Defines
#define ALLOFF 0x00
#define ALLBLINKING 0x01
#define FLASHON1 0x02
#define FLASHON2 0x03
#define FLASHON3 0x04
#define FLASHON4 0x05
#define ON1 0x06
#define ON2 0x07
#define ON3 0x08
#define ON4 0x09
#define ROTATING 0x0A
#define BLINK 0x0B
#define SLOWBLINK 0x0C
#define ALTERNATE 0x0D
//BUTTON MASK DEFINES
#define R3_MASK 0x80
#define L3_MASK 0x40
#define BACK_MASK 0x20
#define START_MASK 0x10
#define DPAD_RIGHT_MASK 0x08
#define DPAD_LEFT_MASK 0x04
#define DPAD_DOWN_MASK 0x02
#define DPAD_UP_MASK 0x01
#define Y_MASK 0x80
#define X_MASK 0x40
#define B_MASK 0x20
#define A_MASK 0x10
#define LOGO_MASK 0x04
#define RB_MASK 0x02
#define LB_MASK 0x01
//Byte location Definitions
#define BUTTON_PACKET_1 2
#define BUTTON_PACKET_2 3
#define LEFT_TRIGGER_PACKET 4
#define RIGHT_TRIGGER_PACKET 5
#define LEFT_STICK_X_PACKET_LSB 6
#define LEFT_STICK_X_PACKET_MSB 7
#define LEFT_STICK_Y_PACKET_LSB 8
#define LEFT_STICK_Y_PACKET_MSB 9
#define RIGHT_STICK_X_PACKET_LSB 10
#define RIGHT_STICK_X_PACKET_MSB 11
#define RIGHT_STICK_Y_PACKET_LSB 12
#define RIGHT_STICK_Y_PACKET_MSB 13
//Pin Declarations
#define pinUP 5 //Up on stick is pin 5
#define pinDN 6 //Down on stick is pin 6
#define pinLT 7 //Left on stick is pin 7
#define pinRT 8 //Right on stick is pin 8
#define pinB1 9 //Button 1 is pin 9 (Start of top row and across)
#define pinB2 10 //Button 2 is pin 10
#define pinB3 11 //Button 3 is pin 11
#define pinB4 12 //Button 4 is pin 12
#define pinB5 14 //Button 5 is pin 13 (Start of second row and across)
#define pinB6 15 //Button 6 is pin 14
#define pinB7 16 //Button 7 is pin 15
#define pinB8 17 //Button 8 is pin 16
#define pinST 18 //Start Button is pin 17
#define pinSL 19 //Select Button is pin 18
#define pinOBLED 13 //Onboard LED pin
//Position of a button in the button status array
#define POSUP 0
#define POSDN 1
#define POSLT 2
#define POSRT 3
#define POSB1 4
#define POSB2 5
#define POSB3 6
#define POSB4 7
#define POSB5 8
#define POSB6 9
#define POSB7 10
#define POSB8 11
#define POSST 12
#define POSSL 13
The next file we will go over is the header for the actual project. I used a lot of defines in my code and really wanted these outside of the INO file. It starts to make it extremely unruly and lengthy for no reason when these are included in the actual project source file. As you can see we start off with a few more generic definitions. We want to define things like the amount of time we use to debounce the buttons, the number of all inputs, how many of them are only buttons, the interval at which the LED is updated, and the length of time we will use for packet timeout in our USB functions. We then have 3 defines that are used when selecting what style of LED you want to use. The only options I have code for are NO_LED and ONBOARD_LED. This gives you the option of not having any LED or at least using patterns on the single onboard LED to indicate what player the current controller is. The 3rd option for EXTERNAL_LED is meant to eventually be used if you would like to have your own 4 LED ring like an actual controller has. I have not coded this only because my fightstick case has no spot for an LED setup like this and I couldn’t really find any small prototype boards that were cheap and had 4 lights in a circular shape. The next section of defines are all for the LED patterns that are sent from the PC Host. Using these defines allows me to give more meaningful names to these patterns instead of trying to remember which pattern corresponded to 08H. The defines under Button Mask defines are used when setting buttons. These correspond solely to the position of that bit in one of the button packets. For example, DPAD Left is held in the first button packet and is in the 3rd bit position from the right. This means that we want to store it here xxxxxXxx, which corresponds to 0x04 in hex. So when we OR the bit mask with the packet that stores DPAD Left it will set the correct bit to a 1.
The byte location definitions allow us to refer to the index of the bytes in the packet array in a more friendly manner. Instead of making us remember that BUTTON_PACKET_1 is the 3rd byte in the array (0, 1, 2) we can just define it as 2 here and use BUTTON_PACKET_1 from now on. Next up are pin defines. These were discussed previously when wiring up the board and basically just map readable words to these pin numbers. The final portion of defines are used when trying to reference a button in our button status array. We use this array to hold all the current statuses of the buttons as we read them from our controller and then use the array again when setting the correct bits in the packets going to the host. Hopefully those of you who are programming proficient didn’t stop reading after I painfully described some first day lessons on defines. Also I am hoping that those of you who are not proficient now have a better understanding of why defines and header files are used.
/*
Mechanical Squid Factory presents to you:
Open Source Fight Stick of Awesomesauce
Compatible w/ PC
Developer: Zack "Reaper" Littell
Email: [email protected]
www.zlittell.com
Cheap and awesome were the goals so hopefully that works out
Uses the Teensy-LC
This tricks the computer into loading the xbox 360 controller driver.
Then it sends button reports in the same way as the xbox 360 controller.
Which means this is an example of making the teensy into a XINPUT device :).
This is used in a box with a joystick, 8 buttons on top, and a start select on the front.
Also used in a similar setup but with a "hit box" layout.
Press start and select together for xbox logo.
This should work with steam big picture and stuff.
Attempted to save PWM capable pins for LEDs if needed in the future.
But some were sacrificed for ease of layout
Would love to eventually move to the FreeScale KDS and off the arduino IDE
Credit where credit is due:
Paul Stoffregen - for the awesome teensy and all the awesome examples he has included
Hamaluik.com - for allowing me to not murder the arduino "IDE" out of frustration of hidden "magic"
BeyondLogic.org - for a great resource on all things USB protocol related
BrandonW - I contacted him a long time ago for a different project to get log files from his
- beagle usb 12 between the 360 and controller. I used them again for verification
- and understanding during this project. (brandonw.net)
free60.org - for their page on the x360 gamepad and its lusb output plus the explanations of the descriptors
Microsoft - Windows Message Analyzer. It wouldn't have been possible at times without this awesome message
- analyzer capturing USB packets. Debugged many issues with enumerating the device using this.
Also one final shoutout to Microsoft... basically **** you for creating xinput and not using HID to do so.
XINPUT makes signing drivers necessary again, which means paying you. Also you have ZERO openly available
documentation on the XUSB device standard and I hate you for that.
*/
//Includes
#include <Bounce.h>
#include "FightStick.h"
//LED STYLE
//NO_LED = Do not use LED
//ONBOARD_LED = use on board LED
//EXTERNAL_LED = in the future add option to use outputs for 4 LEDS
const int LEDSTYLE = ONBOARD_LED;
//Global Variables
byte buttonStatus[NUMBUTTONS]; //array Holds a "Snapshot" of the button status to parse and manipulate
uint8_t TXData[20] = {0x00, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //Holds USB transmit packet data
uint8_t RXData[3] = {0x00, 0x00, 0x00}; //Holds USB receive packet data
//LED Toggle Tracking Global Variables
uint8_t LEDState = LOW; //used to set the pin for the LED
uint32_t previousMS = 0; //used to store the last time LED was updated
uint8_t LEDtracker = 0; //used as an index to step through a pattern on interval
//LED Patterns
uint8_t patternAllOff[10] = {0,0,0,0,0,0,0,0,0,0};
uint8_t patternBlinkRotate[10] = {1,0,1,0,1,0,1,0,1,0};
uint8_t patternPlayer1[10] = {1,0,0,0,0,0,0,0,0,0};
uint8_t patternPlayer2[10] = {1,0,1,0,0,0,0,0,0,0};
uint8_t patternPlayer3[10] = {1,0,1,0,1,0,0,0,0,0};
uint8_t patternPlayer4[10] = {1,0,1,0,1,0,1,0,0,0};
//Variable to hold the current pattern selected by the host
uint8_t patternCurrent[10] = {0,0,0,0,0,0,0,0,0,0};
//Setup Button Debouncing
Bounce joystickUP = Bounce(pinUP, MILLIDEBOUNCE);
Bounce joystickDOWN = Bounce(pinDN, MILLIDEBOUNCE);
Bounce joystickLEFT = Bounce(pinLT, MILLIDEBOUNCE);
Bounce joystickRIGHT = Bounce(pinRT, MILLIDEBOUNCE);
Bounce button1 = Bounce(pinB1, MILLIDEBOUNCE);
Bounce button2 = Bounce(pinB2, MILLIDEBOUNCE);
Bounce button3 = Bounce(pinB3, MILLIDEBOUNCE);
Bounce button4 = Bounce(pinB4, MILLIDEBOUNCE);
Bounce button5 = Bounce(pinB5, MILLIDEBOUNCE);
Bounce button6 = Bounce(pinB6, MILLIDEBOUNCE);
Bounce button7 = Bounce(pinB7, MILLIDEBOUNCE);
Bounce button8 = Bounce(pinB8, MILLIDEBOUNCE);
Bounce buttonSTART = Bounce(pinST, MILLIDEBOUNCE);
Bounce buttonSELECT = Bounce(pinSL, MILLIDEBOUNCE);
//void Configure Inputs and Outputs
void setupPins()
{
//Configure the direction of the pins
//All inputs with internal pullups enabled
pinMode(pinUP, INPUT_PULLUP);
pinMode(pinDN, INPUT_PULLUP);
pinMode(pinLT, INPUT_PULLUP);
pinMode(pinRT, INPUT_PULLUP);
pinMode(pinB1, INPUT_PULLUP);
pinMode(pinB2, INPUT_PULLUP);
pinMode(pinB3, INPUT_PULLUP);
pinMode(pinB4, INPUT_PULLUP);
pinMode(pinB5, INPUT_PULLUP);
pinMode(pinB6, INPUT_PULLUP);
pinMode(pinB7, INPUT_PULLUP);
pinMode(pinB8, INPUT_PULLUP);
pinMode(pinST, INPUT_PULLUP);
pinMode(pinSL, INPUT_PULLUP);
pinMode(pinOBLED, OUTPUT);
//Set the LED to low to make sure it is off
digitalWrite(pinOBLED, LOW);
}
//Update the debounced button statuses
//We are looking for falling edges since the boards are built
//for common ground sticks
void buttonUpdate()
{
if (joystickUP.update()) {buttonStatus[POSUP] = joystickUP.fallingEdge();}
if (joystickDOWN.update()) {buttonStatus[POSDN] = joystickDOWN.fallingEdge();}
if (joystickLEFT.update()) {buttonStatus[POSLT] = joystickLEFT.fallingEdge();}
if (joystickRIGHT.update()) {buttonStatus[POSRT] = joystickRIGHT.fallingEdge();}
if (button1.update()) {buttonStatus[POSB1] = button1.fallingEdge();}
if (button2.update()) {buttonStatus[POSB2] = button2.fallingEdge();}
if (button3.update()) {buttonStatus[POSB3] = button3.fallingEdge();}
if (button4.update()) {buttonStatus[POSB4] = button4.fallingEdge();}
if (button5.update()) {buttonStatus[POSB5] = button5.fallingEdge();}
if (button6.update()) {buttonStatus[POSB6] = button6.fallingEdge();}
if (button7.update()) {buttonStatus[POSB7] = button7.fallingEdge();}
if (button8.update()) {buttonStatus[POSB8] = button8.fallingEdge();}
if (buttonSTART.update()) {buttonStatus[POSST] = buttonSTART.fallingEdge();}
if (buttonSELECT.update()) {buttonStatus[POSSL] = buttonSELECT.fallingEdge();}
}
//ProcessInputs
//Button layout on fight stick
// SL ST
//5 6 7 8
//1 2 3 4
//X360 Verson
// BK ST
//X Y RB LB
//A B RT LT
void processInputs()
{
//Zero out button values
//Start at 2 so that you can keep the message type and packet size
//Then fill the rest with 0x00's
for (int i=2; i<13; i++) {TXData[i] = 0x00;}
//Button Packet 1 (usb data array position 2)
//SOCD cleaner included
//Programmed behavior is UP+DOWN=UP and LEFT+RIGHT=NEUTRAL
//DPAD Up
if (buttonStatus[POSUP]) {TXData[BUTTON_PACKET_1] |= DPAD_UP_MASK;}
//DPAD Down
if (buttonStatus[POSDN] && !buttonStatus[POSUP]) {TXData[BUTTON_PACKET_1] |= DPAD_DOWN_MASK;}
//DPAD Left
if (buttonStatus[POSLT] && !buttonStatus[POSRT]) {TXData[BUTTON_PACKET_1] |= DPAD_LEFT_MASK;}
//DPAD Right
if (buttonStatus[POSRT] && !buttonStatus[POSLT]) {TXData[BUTTON_PACKET_1] |= DPAD_RIGHT_MASK;}
//Button Start OR Select OR Both (XBOX Logo)
if (buttonStatus[POSST]&&buttonStatus[POSSL]) {TXData[BUTTON_PACKET_2] |= LOGO_MASK;}
else if (buttonStatus[POSST]) {TXData[BUTTON_PACKET_1] |= START_MASK;}
else if (buttonStatus[POSSL]) {TXData[BUTTON_PACKET_1] |= BACK_MASK;}
//Button Packet 2 (usb data array position 3)
//Button 1
if (buttonStatus[POSB1]) {TXData[BUTTON_PACKET_2] |= A_MASK;}
//Button 2
if (buttonStatus[POSB2]) {TXData[BUTTON_PACKET_2] |= B_MASK;}
//Button 5
if (buttonStatus[POSB5]) {TXData[BUTTON_PACKET_2] |= X_MASK;}
//Button 6
if (buttonStatus[POSB6]) {TXData[BUTTON_PACKET_2] |= Y_MASK;}
//Button 7
if (buttonStatus[POSB7]) {TXData[BUTTON_PACKET_2] |= RB_MASK;}
//Button 8
if (buttonStatus[POSB8]) {TXData[BUTTON_PACKET_2] |= LB_MASK;}
//Triggers (usb data array position 4 and 5)
//0xFF is full scale
//Button 3
if (buttonStatus[POSB3]) {TXData[LEFT_TRIGGER_PACKET] = 0xFF;}
//Button 4
if (buttonStatus[POSB4]) {TXData[RIGHT_TRIGGER_PACKET] = 0xFF;}
}
//Select pattern
//Examines USB packet and sets the correct pattern
//according to the 3rd byte
/*
Process the LED Pattern
0x00 OFF
0x01 All Blinking
0x02 1 Flashes, then on
0x03 2 Flashes, then on
0x04 3 Flashes, then on
0x05 4 Flashes, then on
0x06 1 on
0x07 2 on
0x08 3 on
0x09 4 on
0x0A Rotating (1-2-4-3)
0x0B Blinking*
0x0C Slow Blinking*
0x0D Alternating (1+4-2+3)*
*Does Pattern and then goes back to previous
*/
void LEDPatternSelect()
{
//All blinking or rotating
if((RXData[2]==ALLBLINKING)||(RXData[2]==ROTATING))
{
//Copy the pattern array into the current pattern
memcpy(patternCurrent, patternBlinkRotate, 10);
//Reset the index to beginning of pattern
LEDtracker = 0;
}
//Device is player 1
else if ((RXData[2]==FLASHON1)||(RXData[2]==ON1))
{
//Copy the pattern array into the current pattern
memcpy(patternCurrent, patternPlayer1, 10);
//Reset the index to beginning of pattern
LEDtracker = 0;
}
//Device is player 2
else if ((RXData[2]==FLASHON2)||(RXData[2]==ON2))
{
//Copy the pattern array into the current pattern
memcpy(patternCurrent, patternPlayer2, 10);
//Reset the index to beginning of pattern
LEDtracker = 0;
}
//Device is player 3
else if ((RXData[2]==FLASHON3)||(RXData[2]==ON3))
{
//Copy the pattern array into the current pattern
memcpy(patternCurrent, patternPlayer3, 10);
//Reset the index to beginning of pattern
LEDtracker = 0;
}
//Device is player 4
else if ((RXData[2]==FLASHON4)||(RXData[2]==ON4))
{
//Copy the pattern array into the current pattern
memcpy(patternCurrent, patternPlayer4, 10);
//Reset the index to beginning of pattern
LEDtracker = 0;
}
//If pattern is not specified perform no pattern
else
{
//Copy the pattern array into the current pattern
memcpy(patternCurrent, patternAllOff, 10);
//Pattern is all 0's so we don't care where LEDtracker is at
}
}
//Cycle through the LED pattern
void LEDPatternDisplay(void)
{
//Grab the current time in mS that the program has been running
uint32_t currentMS = millis();
//subtract the previous update time from the current time and see if interval has passed
if ((currentMS - previousMS)>interval)
{
//Set the led state correctly according to next part of pattern
LEDState = patternCurrent[LEDtracker];
//update the previous time
previousMS = currentMS;
//increment the pattern tracker
LEDtracker++;
//write the state to the led
digitalWrite(pinOBLED, LEDState);
}
//if we increased ledtracker to 10, it needs to rollover
if (LEDtracker==10) {LEDtracker=0;}
}
//Setup
void setup()
{
setupPins();
}
void loop()
{
//Poll Buttons
buttonUpdate();
//Process all inputs and load up the usbData registers correctly
processInputs();
//Check for bootloader jump
if (buttonStatus[POSUP] & buttonStatus[POSB1] & buttonStatus[POSB5] & buttonStatus[POSST] & buttonStatus[POSSL])
{
_reboot_Teensyduino_();
}
//Update Buttons
XInput.send(TXData, USB_TIMEOUT);
//Check if packet available and parse to see if its an LED pattern packet
if (XInput.available() > 0)
{
//Grab packet and store it in RXData array
XInput.recv(RXData, USB_TIMEOUT);
//If the data is an LED pattern command parse it
if(RXData[0] == 1)
{
LEDPatternSelect();
}
}
//Process the LED pattern if the style is onboard LED
if (LEDSTYLE == ONBOARD_LED)
{
LEDPatternDisplay();
}
}
We finally made it! We are now ready to go over the code that actually utilizes all the libraries and makes the controller function. The first thing you will see, after my lengthy commented introduction, is a couple of includes. One of them includes Bounce.h so we can use some nice functions to debouce our inputs and the other includes our header file so that we can utilize our nifty definitions. Then we need to set the style of LED we want using a constant integer LEDSTYLE. There might be later releases that do this on a pin, but sadly there’s a lot of extras we could add and not a whole lot of pins to add them to. I didn’t want to have to open the stick up and add any wires, so I just kept it a variable for now. Next up we have an array to hold a snapshot of the current button states, an array we will use for our transmit data, and an array for our received data. The TX array is preloaded with a 0x14 in the 1st index telling the host the size of the packet and a 0x01 in the 2nd index indicating that this is a button packet. The next 3 variables are for our LED control functionality. LEDState is used to hold the state of the LED, previousMS holds the time the LED was last updated, and LEDtracker is used to step through the array holding our pattern. I have simplified the patterns used for the onboard LED to a pattern for off, blinking rotate, and one for each possible player the controller can be set as. With this controller always being plugged in there was no reason to include the patterns the show low battery or that the controller is not connected to/searching for a console. We also need an array to hold the pattern we are currently displaying. The button debouncing section sets up variables of a class known as bounce. There is a variable for each controller input and we simply call the bounce function and pass it the corresponding pin and the millisecond debounce time that we decided on earlier. This allows us to take advantage of arduino’s built-in pin debouncing library by calling the update subroutine of each button class.
The first function in our ino file is setupPins(). This function does exactly as it is titled and uses pinMode to set all of the button pins to input with internal pullups activated. I also set the pin for the onboard LED to output and then use digitalWrite to make it output 0. Next up is the function buttonUpdate, in this function we go through all the bounce variables we set up earlier. For each button we call its update subroutine, which reads the pin and updates its status. If the update routine returns true that means the input has changed and then we set the correct index in buttonStatus to the value returned from calling the fallingEdge() subroutine. Falling edge is used because the buttons are common ground and we want to detect the transition to ground as a 1. Once we have an updated array of button states, we need to reflect those button states in our transmit packets. This activity is done in the next function, processInputs(). First, we start off with a for loop that starts at 2 and goes through our TXData array. This zeroes out everything after the initial bytes that state size of packet and message type. Once the data has been zeroed we start to bitmask in the values of the buttons. We check to see if each button is a 1 and if it is we apply the appropriate bitmask to the byte the holds that buttons status. The only differences here is that for the guide button we check for both start and select being pressed. Also the triggers are in their own packets and are represented with analog values. We will set them either to 0x00 to indicate not pressed or 0xFF to indicate full scale.
The function LEDPatternSelect() is called to check the RXData and copy the matching pattern into the patternCurrent variable. This function takes into account the multiple possibilities of patterns being called and combines them into a smaller subset of patterns for use with the onboard LED. The memcpy instruction is used here because of the need to easily copy the entire pattern array from one area to another. We then set LEDtracker to 0 to ensure that we begin stepping through the pattern at its beginning. LEDtracker will be used as an index for displaying the pattern array. LEDPatternDisplay is used to actually display whichever pattern has been loaded into patternCurrent. We use an interval constant and the millis() function to detect how much time has occurred since we last updated the LED’s state. So we take the current time in milliseconds and subtract it from the last changed time and if it is greater than the interval chosen the pattern is advanced a step. LEDState is set to the value held in patternCurrent at the index of LEDtracker, the previous time is updated to the current time, the index is increased, and finally a digitalWrite puts whatever value is in the LEDstate variable on to the onboard LED pin. The last statement of this function checks if LEDtracker is equal to 10 and then if true resets it back to 0 to start the pattern over again. This function uses these methods so that there is no need to use a delay of any sort. This allows our main loop of code to continue to run as fast as possible. As long as we constantly call this function the LED will display the appropriate pattern.
The only two functions we have left to discuss are standard in an arduino sketch. The only thing that setup() does in this sketch is make a call to setupPin(). The loop() function is where all the magic happens. This function will loop infinitely and will be used to place calls to things we want to do constantly as the program runs. We start off by polling the buttons to update their statuses. The next step is to call processInputs to prepare the TX packets for sending. I have included an if statement that allows us to achieve a bootloader jump when UP, Button 1, Button 5, Start, Select are all pressed. This was a problem once we began testing code inside the fightstick cases. You had to open the bottom up and hit the little button on the teensy to get it to restart into the bootloader. The beta testers were not fond of doing that more than once or twice. Next we call our send subroutine and pass it our previously prepared TXData array and the constant USB_TIMEOUT. We have now, in theory, sent the host our button update packet. We now need to check if data is available to be received. The available subroutine is used for this and we check if the value returned is greater than 0. If it is greater than 0, we use the recv subroutine and pass it the array to store the received bytes in, RXData, and our USB_TIMEOUT constant. We then check if the first byte (index 0) is equal to 1 which indicates that this is an LED pattern command from the host. If it is a one we will call LEDPatternSelect to handle copying the respective pattern into currentPattern. Finally, the last portion of our loop function checks to see if the LEDSTYLE we defined is set to ONBOARD_LED. If the style is set to onboard, we then call LEDPatternDisplay to handle displaying the pattern on the LED. However, if the style is not onboard, we just disregard the LED pattern and continue on. So in essence the loop function just continues to update the buttons, set up our TX packets, check for bootloader jump, send our data, receive data, update the current LED pattern, and display the current LED pattern.