New 2.x Configurable Keyboard/Gamepad Firmware

I’ve pushed a new firmware binary to GitHub which includes the following changes:

  • Alt1 / Alt2 modifier keys to support soft switching, with independent toggle/hold choices for each
  • Volume Up/Down control over serial using commands + or -
  • Gamepad Z axis, just in case you can’t get enough axis
  • Minor bugfixes and tweaks

An example binding for n64 support is: a 220 221 222 223 230 231 232 233 234 235 236 237 253 239 240 252 0 0 0 0 0 0 0 0 242 243 0 244 245 0 0 0 0 0 0 0 0 0 250 251 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

This sets the gamepad up on all buttons, the 4 right-most buttons on the top become alternate gamepad buttons when holding the left-side front-most button. When holding the right-side front-most button the joystick becomes vol up/down.

Note: I’ve been testing this in Mupen64plus, ran across PEBKAC errors and eventually got it working by reading the manual. Whee!

I tried sdl-jstest from here ( GitHub - Grumbel/sdl-jstest: Simple SDL joystick test application for the console ) to confirm buttons were being read- it works fine in SDL 1.2, but for some completely insane reason SDL 2.0 emits console warnings about not supporting certain keycodes whenever a Picade Gamepad button is hit.

I’ve put together a firmware which makes the Picade PCB pretend to be a 2 axis, 16 button HID gamepad in addition to a keyboard. We demoed it ( sort of ) on BilgeTank:

You can dynamically map Picade buttons to either gamepad axis/buttons, keyboard keys or volume control.

This may or may not be useful to users looking to get the Picade working with the N64 emulator, which doesn’t appear to readily accept keyboard input.

You can find it named picade_2016.hex in the Picade firmware update folder on GitHub: https://github.com/pimoroni/Picade-Sketch/tree/master/update

I have improved the update script so you can simply run:

./update picade.hex

(Note this used to be ./update picade_2016.hex, but the configurable firmware is now stable and part of the regular release.)

And everything should be taken care of.

Editing the config with Python

The easy way!

You can load a config dump easily Python using PySerial, which is auto-installed by the Picade updater ( to reset the Picade ):

import serial
picade = serial.Serial('/dev/ttyACM0',9600,timeout=1.0)
picade.write("a 218 217 216 215 128 130 32 129 122 120 115 99 176 177 250 251 105 111 112")
print(picade.readline().strip())

Or you can use the picade.py library available here: https://github.com/pimoroni/Picade-Sketch/tree/master/Config

import picade

picade.bind([picade.GAMEPAD_UP, picade.GAMEPAD_DOWN, picade.GAMEPAD_LEFT, picade.GAMEPAD_RIGHT])

Editing the config manually

The painful way!

When you connect to the new firmware over serial at 9600 baud, you can send commands to rebind the keys and all sorts of other goodness. At the moment this is most easily accomplished using the Serial Monitor in the Arduino IDE or in PuTTY using “Local Line Editing” and “Local Echo” which you can find in the options under “Terminal”.

Commands are as follows:

h - List all available buttons and actions
b <button> <keycode> - Bind a button ( from 0 to 18 ) to a specific action
a <keycode> <keycode> ... - Bind all keys, or any number of keys, in order ( up to 19 values )
d - Dump current binding
s - Save current config to EEPROM
l - Load config from EEPROM, replacing current config
k - Load a quick keyboard preset ( picade defaults )
g - Load a quick gamepad preset

When you type h you’ll see something like this:

Buttons are the joystick axis and buttons on the Picade. MOSI, MISO and SCLK being the 3 additional pins on the 6-pin programming header which you can use with a bit of hackery.

Keycode can map to either a gamepad axis, a gamepad button or an action like volume up/down.

<button> should be one of:
0  = Joy U    1  = Joy D    2  = Joy L    3  = Joy R
4  = Btn 1    5  = Btn 2    6  = Btn 3    7  = Btn 4    8  = Btn 5    9  = Btn 6
10 = Start    11 = Coin     12 = Enter    13 = Escape
14 = Vol Up   15 = Vol Down
16 = MOSI     17 = MISO     18 = SCLK

<keycode> should be one of:
220 = Gpad U   221 = Gpad D   222 = Gpad L   223 = Gpad R
230 = Gpad 0   231 = Gpad 1   232 = Gpad 2   233 = Gpad 3
234 = Gpad 4   235 = Gpad 5   236 = Gpad 6   237 = Gpad 7
238 = Gpad 8   239 = Gpad 9   240 = Gpad 10  241 = Gpad 11
242 = Gpad 12  243 = Gpad 13  244 = Gpad 14  245 = Gpad 15

250 = Volume Up   251 = Volume Down
218 = U Arrow 217 = D Arrow 216 = L Arrow 215 = R Arrow
128 = L Ctrl  129 = L Shift 130 = L Alt   131 = L GUI
132 = R Ctrl  133 = R Shift 134 = R Alt   135 = R GUI
178 = Backspc 179 = TAB     176 = Return  177 = ESC
209 = Insert  212 = Delete  211 = Pg Up   214 = Pg Down
210 = Home    213 = End     193 = Caps Lk 13  = Space
194 = F1      195 = F2      196 = F3      197 = F4
198 = F5      199 = F6      200 = F7      201 = F8
202 = F9      203 = F10     204 = F11     205 = F12

33  = !       34  = "       35  = #       36  = $
37  = %       38  = &       39  = '       40  = (
41  = )       42  = *       43  = +       44  = ,
45  = -       46  = .       47  = /       48  = 0
49  = 1       50  = 2       51  = 3       52  = 4
53  = 5       54  = 6       55  = 7       56  = 8
57  = 9       58  = :       59  = ;       60  = <
61  = =       62  = >       63  = ?       64  = @
65  = A       66  = B       67  = C       68  = D
69  = E       70  = F       71  = G       72  = H
73  = I       74  = J       75  = K       76  = L
77  = M       78  = N       79  = O       80  = P
81  = Q       82  = R       83  = S       84  = T
85  = U       86  = V       87  = W       88  = X
89  = Y       90  = Z       91  = [       92  = \
93  = ]       94  = ^       95  = _       96  = `
97  = a       98  = b       99  = c       100 = d
101 = e       102 = f       103 = g       104 = h
105 = i       106 = j       107 = k       108 = l
109 = m       110 = n       111 = o       112 = p
113 = q       114 = r       115 = s       116 = t
117 = u       118 = v       119 = w       120 = x
121 = y       122 = z       123 = {       124 = |
125 = }       126 = ~

A complete dump of the default keyboard config looks like:

a 218 217 216 215 128 130 32 129 122 120 115 99 176 177 250 251 105 111 112
3 Likes

I’d just like to say thanks a lot for this firmware, it took me a moment to fathom it out and understand it, but I have successfully mapped all the buttons as a gamepad but left the side buttons as vol up/down and enter/escape. I used Putty to achieve this via USB serial connection.

The reason I did this is because I use RetroPie, and there are a couple of issues I found with simultaneous keyboard button presses not working reliably in certain libretro cores (you have to hold one button and then press the other to get the desired output, eg. pressing jump+attack to perform a special move in Final Fight), and lr-mame2003 just acts strange because it doesn’t enjoy the default bindings of the Picade keyboard (pressing ctrl triggers ctrl+shift, etc), and some games don’t control correctly as a result. Remapping as a gamepad and configuring Retropie accordingly has solved both of these issues, the only thing it messed up is the Amiga emulator because that requires a keyboard and is hardcoded - but that’s ok. If anyone is planning on doing something similar I had to change the joypad driver to SDL2 in retroarch.cfg (input_joypad_driver = “sdl2”) otherwise the “controller” did not respond in any games. It does produce some SDL warnings on the console output as mentioned above which is stupid, but this can be ignored.

The only issue I have, is volume is not saved to EEPROM on shutdown anymore, and as previously mentioned in another thread you have to tap the volume keys to increase/decrease volume, which isn’t an issue and i’m not fussed about that. Any plans to save the volume to EEPROM on shutdown though like on the stable firmware? As a workaround I set mine to about 70% over serial and saved it, which for me is a nice default on boot.

If anyone is interested, my config looks like:
a 220 221 222 223 230 231 232 233 234 235 238 239 176 177 250 251 105 111 112

Don’t forget to use the + key to increase your volume over serial and save it, otherwise as mentioned above the Picade seems to start at minimum volume. I set mine to 20 (or something like that) so it defaults to this on boot, the max is 24.

Thanks!

Thank you for the new firmware! My setup is a Picade with Raspberry 3, Retropie and Raspbian upgraded to the latest release. The firmware installed fine, and I now have switched the controls over to a gamepad.

Two questions, though:

  1. How can I get a non-zero volume after booting? I assumed that I need to set the volume with the left buttons, and when I am satisfied I send an “s” over serial. However, this doesn’t help. After every reboot or startup the volume is zero. Edit: Even if I don’t use the buttons, but send 20 “+” instead over serial, the volume doesn’t stick. It’s back to zero after a reboot.
    I now remember that I have seen the same with the original firmware. When I first built the Picade I used a Raspberry Pi 2, and there the volume stuck. I later switched to a Raspberry Pi 3, and also had to update Retropie and the underlying Raspbian in the process. Since then, the volume is always zero on boot.

  2. Did you ever understand what happens with “but for some completely insane reason SDL 2.0 emits console warnings about not supporting certain keycodes whenever a Picade Gamepad button is hit.”? I can see the same thing: My console is full of these warnings.

Hi,

To try and answer your questions:

  1. No idea why the latest release doesn’t save the volume on shutdown, however to get a non-zero volume before booting, I sent the “+” command from my keyboard when connected over serial to raise the volume up by a single notch. After the command is sent it should tell you the new volume I believe. I kept resending “+” until I got the volume to around 20ish (max 24), and then I pressed “s” to write the config. Now when I boot my system the volume is always set to 20 by default.

  2. Unfortunately I am just ignoring these console warnings, I get them also. I did read this, not sure if it is related: https://github.com/RetroPie/RetroPie-Setup/issues/1077
    It is safe to ignore the warnings, even though it is annoying. I have no idea how to solve this.

Cheers

Thanks for your quick reply! I am doing a similar thing: With a small Python application, I send a String “++++++++++++++++++s” (actually 20 pluses) to the board. The volume is then fine. However, after a reboot it is back to zero.

Edit: I wonder if something with the headphone detection is different between my Raspi 2 and the newer Raspi 3 setup. When I look at the controller source code, the volume is indeed set to zero when the board thinks that no headphone is connected. I don’t know how the headphone detection works on a hardware level, so I can’t analyze this problem any further.

Edit2: So far I could verify that the volume is read correctly from the EEPROM (I added a few lines to the “D” command in order to also dump the shift state and the volume). The volume gets saved and reloaded fine.

When I press the “Volume Up” button on the Picade, or the “+” key on the console, the volume is set to “1”. So apparently the “volume_target” was set to zero, after the volume was loaded from the EEPROM, and written into volume_target. The only location where this could happen at all is the headphone part. However, I can’t see how this could happen. The code seems robust, and I have absolutely no idea what could go wrong. Even if the headphone detection bounces a few times, the volume_target should always get backed up and then reloaded.

One suggestion:
The “L” command does load the volume (amongst all of the other config values), but it does not set “volume_target”. Therefore the loaded volume does not get applied. I think for consistency this should be changed.

Sorry, my bad… It’s been 20 years since I last touched C, so I forgot about the trailing zero char in a String.
Still, the “default_config” looks odd: It consists of CONFIG_VERSION, plus the key bindings, plus VOL_DEFAULT. The “picade_config” structure contains a shift state between the buttons and the volume. It appears that the VOL_DEFAULT goes to the shift state, and the actual volume remains unspecified.

Old text for reference:
Hello, some more questions… I am trying to understand the firmware code. Unfortunately, I can’t even understand the EEPROM load/save part of the code. This is from the header file:
#define CONFIG_VERSION "Picade v2.2" #define CONFIG_VERSION_SIZE 12

By my count, these are 11 characters, yet the size is declared to be 12. The “picade_config” structure is declared to be 12 chars, and then the actual configuration. The “default_config” struct is declared to be the 11 char version string, and then the actual configuration. Isn’t the entire configuration array now off by one byte? Also, the “valid_config()” routine iterates over 12 chars, so whether or not the last one matches is a bit of a chance.

Or am I misunderstanding something?

Thanks! Martin

Strings in C are terminated by a “\0”, which is the 12th character. Whether or not this is even necessary in this scenario, since it’s a known length, is debatable.

I believe a string will implicitly be null terminated, so when I use CONFIG_VERSION here:

Serial.println(F(CONFIG_VERSION));

It’s actually 12 characters long so Serial.println doesn’t venture off into whatever might be in memory adjacent to the string. But in terms of the version check it’s unnecessary since it’s not using a “strcmp”, but a fixed loop and individual character compare.

You’re also on to something with the Default Config. I added shift state settings and forgot to update the default to account for this. I’ll have to push up a new build that has a sane volume level by default. Well spotted!

Thanks for your reply! With a lot of Serial.print statements I figured out what happens with my Picade’s volume.

Basically, “!digitalRead(HEADPHONE_DETECT)” always reads true, no matter whether or not a headphone is connected.
The last_headphone is initialized with false (no headphone connected). So after a reset, the “if(headphone != last_headphone)” part is executed exactly once, and sets the volume to zero.

Because this part never runs again I can then use the volume buttons to tune up the volume again.

So the question is: Why does the headphone detection not work? Is there a problem with the firmware (e.g. is the headphone detect not on Pin 5 as coded), or is there a problem with my board? Maybe different revisions exist, some supporting a headphone detect, and some not?

Anyway: Because I know what the problem is I can now work around it. The simplest will be to initialize last_headphone with “true”.

Edit: It seems to be at least partially a hardware issue. When I use a Hifiberry DAC+ for the audio, then the headphone detection works. Also, it worked when I used a Raspberry Pi 2 (with the original firmware, though). It doesn’t work when I connect the Raspberry Pi 3’s 3.5mm audio out to the board’s audio in.

Edit2: One (hopefully) last update: I could now fully solve the issue. Two changes were required:

  • In picade.h: Enable pull-up for the headphone pin (pinMode(HEADPHONE_DETECT, INPUT_PULLUP);
  • In picade.ino: Headphone state is now inverted (0:no headphone, 1:headphone). So it is now “boolean headphone = digitalRead(HEADPHONE_DETECT);” (i.e. the “not” is gone).

Hi both,

Most of the coding stuff is a bit over my head so I can’t really add to the above! However generally the firmware works very well for me in terms of mapping the buttons as gamepad inputs. I only have two issues which i’d love to see resolved, do you think the following is possible in a future updated version?

  1. Volume does not save on shutdown any more like it did in the previous firmware
  2. You cannot hold down the volume keys to increase/decrease volume any more, not that this is a massive issue tbh
  3. The headphone jack on the PiCade does not detect when headphones plugged in and route accordingly. I am using a USB audio card out of the Pi, and PiCade plugged into this, not sure if this has an effect? Maybe this is causing an issue. If I manually select the headphones within RetroPie, sounds comes out of the headphones and the speakers at the same time. It’s all a bit messy. However, I can’t verify how it behaved before the firmware upgrade, as I never use headphones. I was just testing it all out. When I get a moment i’ll see how it all behaves when I take the USB audio out of the mix. Any suggestions on this welcome.

Thanks!

  1. I’m definitely going to investigate this one, it’s a bug

  2. This feature was lost when I made volume dynamically re-bindable, but there will be some way to get it back

  3. When you plug in headphones, the Picade should mute speakers regardless of what the audio source is, but we’ve had strange things happen with either non-line-level or line-level audio jacks which might explain what’s going on. Headphones are always physically connected to the audio source. Definitely try without the USB audio- you may be seeing the same strangeness we did. ( unless I’m mis-remembering what we saw :D )

There will definitely be a maintenance release for this firmware and, hopefully, a utility or more documentation about how to configure it to fit your needs.

@T1nue good detective work there, I’ll look into this next week when I can get my development Picade Console set up for testing.

Hi,

Great news on the upcoming volume bugfixes. I agree, a utility or more documentation about how to configure it would be useful, more people will probably upgrade if there is an easier way to rebind the keys.

Regarding the headphones, yes at the moment it misbehaves somewhat - I will remove the USB audio from the equation and revert back to the built in audio, and let you know the behaviour when I plug in headphones then. I should be able to do this late tonight or at the weekend.

Cheers

Hi,

Just following up the above as promised, so I removed USB from the equation and it behaves as follows:

  • If volume is anything above a medium level, when headphones are plugged in, the speaker volume dips a bit and sound is simultaneously delivered to the headphones and speakers. If you wait about 5 seconds or so though, it then successfully switches over to the headphones only. The sound delivered to the headphones is at a set volume, and level cannot be adjusted with vol + and - anymore. When the headphones are removed, volume to the speakers restores back to the default level which is saved on the board (in my case 20).

  • If volume is set to a low level, when headphones are plugged in, it switches over to headphone straight away with no delays. Again the headphones are at the same set volume as above and the level cannot be adjusted. When the headphones are removed, volume to the speakers is restored back to what it was before you plugged the headphones in - rather than reverting to the default.

As you see above, the headphone detection/behaviour seems to differ depending how loud the speakers are set to initially, but ultimately, it does work, just not entirely cleanly. I think the main issue is not being able to adjust the headphone volume at all. I’ve no idea if this was an issue on the stock firmware, I don’t think I ever tested it.

When using USB audio, actually it does work to a degree, sort of similar to the above, but after a few unplugs etc, it can get itself into a right twist and sometimes you lose sound entirely. Also the quality delivered through the headphones is poor. So i’ve left USB off for now and just disabled audio dithering to remove any hiss from using the built in 3.5mm jack, and it’s fine.

That is, unfortunately, something that can’t be fixed since the headphone jack is electrically directly connected to whatever input level you connect. The amp is connected to the same source, and only outputs to the speakers. On the plus side, it’s not a firmware bug :D ( Although this should probably be documented somewhere )

I’d recommend setting a comfortable level for headphones, and then adjusting the speaker amplification accordingly. If you absolutely need headphone volume control ( I wouldn’t be surprised if the volume in emulators was all over the place ) you could use something like: http://www.amazon.co.uk/Pro-Signal-Headphone-Volume-Controller/dp/B0131PQIJM

I don’t know if there’s any way to bind a hotkey in Raspbian to allow the Picade to adjust the system volume control on the fly. Could be worth investigating.

It sounds like something’s gone awry with headphone volume ducking, though. It’s an interesting challenge, since the Amp doesn’t have any kind of bi-directional volume protocol- the Picade never actually knows what volume is set, and restores the volume setting by turning it right down to zero and then stepping it back up again to the desired setting.

Hi,

Thanks for the detailed reply.

Ah, no worries with the headphone jack volume, this is no problem. It’s better to know it isn’t a bug or a firmware issue for sure! Regarding setting a comfortable level headphone volume though, does the headphone output use the same volume that is the default on the board (mine is set to 20, for example and I saved this as the default over serial). I assume it must do. If so I will just knock my default down a couple of notches.

I’m not fussed about an inline volume control or anything, as I doubt i’ll ever use the headphones much, I was just testing everything out.

That’s a good point regarding binding a hotkey in Raspbian to adjust system volume on the fly - if I get chance I might look into this.

Having a quick glance over this today, I’ve already fixed a horrible bounce issue I missed with headphone detect which could really mess things up. I’ve also been working on an experimental dual analog feature- a total distraction from the problem at hand, but I had a support ticket that made me think it would be a good idea.

Cool no worries! Hope the work on the experimental features are working out well - I will keep an eye on the thread for when any further updates/fixes are posted for this 2.x firmware, i’m happy to flash any firmware which is released. Thanks again for the work on this.

This definitely shouldn’t be the case. I haven’t been able to replicate this while the Picade powered-by and connected to a Pi 3. This is following some minor tweaks to the add debouncing to the headphone detect, so perhaps I should publish them for you to try.

After I added the pull-up, I could see the bouncing clearly. Before there was nothing at all when I connected/disconnected the headphone, no matter how quickly or slowly I fiddled with the headphone connector.
Therefore I don’t expect to see a difference with the added debouncing code.

I haven’t tried the simplest thing yet: Unconnecting the audio plug coming from the Pi 3 and see if this makes a difference. I will try this, but it may take a while.

Since there is no software pull-up defined on the port: How is the high or low state defined? Isn’t the port dangling if there is no headphone connected? Or is there maybe a hardware pull-down resistor in place?

In other posts I have seen that the audio-in and the headphone-out are electrically connected. Therefore I guess an electrical change on the Raspberry Pi could propagate to the headphone connector.

It’s an interesting subject… as in I think you might be having a dry joint on the headphone connector. This would also explain the zero level on boot you initially reported.

… I’ll dig my rig later on and post a detailed answer on how you might be able to fix this. That said I will try to see if I can split the subject as I don’t think your issue is software related i.e what this thread started as.