Picotroller, GPIO or RPi Pico over Serial custom controller driver.
-
While making portable emulation units with my Pis, I found that I was rapidly running out of GPIO, taken up by buttons. This lead me to find a way to use the serial port to off load buttons inputs. At the same time, I was working on a project to adjust volume with the controller buttons. I was getting annoyed, however, that I couldn't interrupt button presses being sent when I was holding a combo for adjusting volume. This is when I decided to combined the two projects together. The first version of PicoTroller only worked for emulating a joystick using a raspberry pi pico connected to the serial port, and in version two, I rewrote the uinput handler to allow specifying a custom monitor, and rewrote the pico module to be as a monitor. I then wrote a monitor for GPIO as well so that either could be used. You could clone picotroller and create your own monitor, and modify main.cpp to use it instead, maybe you will make a wifi enabled controller. You can find the repo here: https://github.com/The-Next-Guy/picotroller
Gripes with Debouncing
I decided to go a bit above and beyond for button debouncing. It is handled in the monitor but implemented in both the GPIO and the PICO monitor. I personally found this method to be the absolute best possible, feeling as close to SNES responsiveness and feel as possible. So much so that I recommend this program over GPIOnext. The first two projects I made, I used GPIOnext as the joystick emulator. It works great, and is very user friendly and easy to setup. I would notice at times though that buttons would double press. Its especially annoying if you are selecting a console, as sometimes it would also select the first game on the list. Or maybe you are rapid pressing directional buttons to scroll down to your game and it scrolls one extra even though you didn't press it.
Why it matters
It all depends on how you debounce your button presses. The goal of debounce is to eliminate the electromechanical noise generated when pushing or releasing two contacts together. Since our program runs fast enough, we could end up sampling an IO pin during a transition. If you ever smacked a bouncy ball against a wall, attempting to hold it against the wall the whole time, you will feel the ball bounce vibrate in your hand for a short while after the initial collision. In smaller scales, releasing can cause a sort of vibration as well. During that transition from low to high, or high to low, it takes time for voltage to fully transition from say 3.3v, to gnd. This means that if we sample at the right time, we might get an intermediate voltage that is not defined as high or low logic, causing an unpredictable state.
Methods
The simplest debounce just limits the response time of a button press, saying once a state change is detected, the controller must wait a timeout before the next state change can be detected. The timeout could start at a constant time, or when the button is pressed. If its a constant time, you only need one timeout for all buttons, and all update are just read after the delay and sent as one event. If started on a button press, you would want a timeout for each button, and an event for each button would have to be dispatched.
Problem 1
The issue with method one, is that if the frame lands on a transition between frame 0 and 1, frame 1 will may report a different state than frame 0 when the button was pressed on frame 0 to 1. If during a push, frame 0 could report a 1, frame 1 report a 0, and frame 2 to report a 1. This is what I think is happening with GPIOnext.
Problem 2
The issue with method two, is that if the game or emulator samples the controller faster than you can report updates between user presses, actions that require buttons the be pressed at the "same" time, will have a larger chance of it not working. This probably isn't common, but could happen.
The Solution
What the pico and gpio monitors of Picotroller use, is a hybrid between both. Controller updates are sent at a constant time, however, the button states are sampled as fast as possible and an on-counter, or off-counter for each button is incremented. At the end of a frame, the on and off counters are compared and reset. The state of the button is set to whichever counter had the most. The frame length is 15ms for gpio, and 20ms for pico, though it could also be set to 15ms. This was chosen because that is roughly the frame length of the SNES when reading the controller.
Gripes with Overlays
Not only did I want to be able to adjust volume with the controller buttons, but I also wanted to be able to display the current volume and have a nice indicator between 0 and 100. At first I tried using dispmanx libraries to draw on top of EmulationStation, but I could not get it to work. I tried another method by editing the framebuffer directly, but it only showed up in console, never while in ES. That's when I thought of modifying the display driver I was originally using, fbcp-ili9431. I added a class to copy from a second frame buffer stored in a Shared Memory Asset (SMA), using inspiration from
low_battery.cpp
. This allows an application to draw to the screen directly, instead of indirectly going through the GPU. If you copyoverlay.h
,overlay.cpp
andshared_memory.h
from Picotroller into your projects, you can draw custom overlays too. You do not need this to use Picotroller but, you can find my custom fbcp-ili9431 driver repo here: https://github.com/The-Next-Guy/fbcp-nexus
Contributions to the project are always appreciated, so if you would like to support us with a donation you can do so here.
Hosting provided by Mythic-Beasts. See the Hosting Information page for more information.