So I didn’t get a chance to take a video of the controller in action, but I do have a picture of me playing the original Zelda using my soldered up controller prototype. The Raspberry Pi and LCD is hooked up to one battery bank and the controller is hooked up to a separate one. Going through this test I noticed a few things that can be improved.
For starters, I had a hellish time getting enough power to both the Raspberry Pi and LCD. For whatever reason the power supply didn’t seem to be able to give enough juice to keep the Raspberry Pi from rebooting. The display has two micro USB ports on it that are used to power the screen and provide touchscreen feedback to the Pi. However since I currently don’t need to use the touchscreen I just powered the screen directly from the battery bank. To circumvent these power issues in the future I’ll need to see if I can get the Pi to provide more current from the USB ports. If that doesn’t work I can use one of the micro USB ports for touchscreen feedback and cut the red power wire to prevent the screen from drawing power from that port and use the second USB port to power the screen directly from the battery bank.
Another issue I noticed with my controller is the lack of menu buttons. The RetroPie interface requires using Start and Select buttons for navigation, so I had to map those to an attached keyboard since I only have four action buttons and four directional buttons on my prototype. Additionally I’ll also want shoulder buttons since the later consoles all have at least two and the PS1 has four.
The next step for the controller interface is designing the PCB!
The next step in getting the controller to work as a RetroPie controller is writing a driver so the console interprets the serial data coming in from the Bluetooth module the same way it would interpret a USB controller.
I got this idea from petRockBlog who mapped their custom, two-player joystick to two separate virtual controllers in RetroPie. Their code is available on GitHub. After a quick review, I saw that their code is using a C user input emulation library called uinput. In the interest of simplicity I found a Python uinput library and decided to use that to emulate my controller driver.
Below is a full snippet of the code from the GitHub repository followed by an explanation of how it works:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This is the definition of the Controller class. A static variable called NumButtons is used to show the maximum number of buttons a Controller has. A Controller instance is initialized with a path to the Bluetooth serial device which it then connects to. The initialization also creates a uinput Device that it will use to simulate a virtual controller for providing input to the console.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
An array of keys is created to represent the possible input keys that will be passed into the system. Right now I’m just using the letters of the alphabet. RetroPie offers full controller customization so the actual keys used don’t really matter. I still plan on changing the controller driver to emulate a joystick with analog controls, however, so this method will need to be updated in the future.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This array will store the device names for all of the Bluetooth serial consoles. At some point I’d like to autodetect all of the serial consoles from the controllers but for now a hardcoded array will work.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
The program creates a controller for each serial console in the ControllerNames array and stores them in the Controllers array. It also resets the serial input buffer so old input data doesn’t bog down the driver and it can start processing the most current data.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
My driver operates on button transitions (unpressed->pressed and pressed->unpressed) rather than operating on the button state. This is to be consistent with keyboards which work in the same way. Because of this transition-based processing, the previous input packet needs to be stored so the previous packet can be compared to the current packet and differences between the two can be detected. Here the previous packet is instantiated as a dictionary and is initialized to a known, totally unpressed state.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This is just an infinite loop which iterates over the list of controllers and processes each of them.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
A line is read from the serial port and split at the colon. The left side will be the key character which reflects the type of data and the right side will be the data itself.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This if statement detects which key is seen and executes the appropriate code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This is the code for the key ‘K’ which is the only type of data I currently have. It iterates over each character in the eight bytes of data and compares it against the data from the previous packet. If the value went to ‘1’ from ‘0’ it emits an event to signal that the key was pressed and if the value went to ‘0’ from ‘1’ it emits an event to signal the opposite.
As a follow-up to my last post, I wanted to go over the Arduino firmware I used for my primary controller test. Essentially what it does is read the states of multiple buttons and print a sequence of 1’s or 0’s (1 for unpressed and 0 for pressed) to the Bluetooth module.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
The whole program is embedded above and I’ll go through each section individually and explain what it does below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
These enums serve the purpose of defining human-readable names for the pins that are hooked up to the buttons. That way their usage in the code is immediately obvious upon reading.
Nasty literals!
This isn’t strictly necessary due to the simplicity of this program, but the general best practice is to not use integer literals in code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This is the pin mode setup in the Arduino setup() function. The buttons are hooked up directly to ground so that when the button is pressed, the pin sees 0V. When not pressed the pin is floating so here I’m enabling the Arduino’spullup resistors so that when the button isn’t pressed the pin sees 5V.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Here the state of the buttons is read and printed to the Bluetooth module over UART. Because of the way pullups are handled, ‘1’ corresponds to unpressed and ‘0’ corresponds to pressed. The printing of the button states is preceded by a “K:” to act as a key-value pair. This means that when the controller driver on the Raspberry Pi sees a “K” it knows it’s about to get a list of digital buttons. I’m planning on using this in the future to enable more complex data than just a few buttons. For example, I could use the keys “JX” and “JY” to denote the two axis values of an analog joystick. The last serial print is a Serial.println() function call to print a newline character. This is an easy way to segment the data so the controller driver can differentiate between multiple packets.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Lastly is the delay that sets the update rate of the program. With a delay of 10 milliseconds at the end of the loop, new button states will be sent to the driver at an update rate of roughly 100Hz. Right now this rate was arbitrarily decided, but in the future I’ll have to tweak it to balance several factors.
First off is the bandwidth of the Bluetooth connection. As far as I can tell, the HC-05 module uses Bluetooth 2.0 which has a data throughput of of 2.1 Mbit/s or 275251.2 bytes per second. Divide this by the update rate and you get the total amount of data that can be sent in one cycle of the program. This also assumes that the connection is perfect with no lost packets. Bluetooth’s serial port profile uses re-transmission to verify that packets sent are received. This means that wireless noise can cause the connection to slow down while the Bluetooth module is attempting to resend a bad packet. Additionally, if I go back to the BLE module and am able to get it working, BLE only has a throughput of 35389.44 bytes per second, or 13% of Bluetooth 2.0. So while I’d be saving power I’d be restricting the update rate and amount of data I can send from the controller.
Another thing to consider is the update rate of the controller driver running on the Raspberry Pi. Serial ports have buffers to store incoming data. This means that if the driver is updating slower than the controller, the driver won’t be able to process all of the data coming in and the buffer will overflow. This problem will exhibit itself as lag in the controls. The driver needs to be updating at the same rate, or faster, than the controller.
The last consideration is debouncing. This is a topic that has been extensively covered elsewhere, but suffice it to say that if the controller updates too fast it will accidentally send phantom bounces in the switch contacts to the driver and cause erroneous button presses.
Once everything has come together and is working to a degree I’m happy with, I’ll come back to these small issues and tweak everything until it works perfectly. Until then, my next step is writing the controller driver! As always, my code is available on my GitHub in the PiGRRL_Switch repository.