Close
0%
0%

RPdeck

RP2040 based device for Windows per-application volume control

Similar projects worth following
328 views
0 followers
Volume controller for Windows that can select and control the volume of specific applications.

(Project description is a work in progress)
A DIY USB Volume controller and macro pad, powered by an RP2040. 

Per-application volume control with the current profiles/applications in mind:

  • Master volume
  • Current window
  • Spotify
  • Discord
  • Web browser
  • Video games

Game application names must be added in config file

OLED display for monitoring current volume and currently selected profile

This project is inspired by the Maxmix (https://maxmixproject.com/) and deej (https://github.com/omriharel/deej).

  • Log 5.2:

    neatloaf76 days ago 0 comments

    I think I will move forward with this case design, I think the extra thickness looks good.

  • Log 5.1: New enclosure

    neatloaf77 days ago 0 comments

    I made a new 2 piece enclosure with the upper piece acting as the top plate. 

    I am currently making a "high profile" enclosure, where the bottom of the keycaps will be about flush with the top of the enclosure, as opposed to the floating key design shown above. I will see which one looks better and then hopefully begin wiring everything up. 

  • Log 5: Prototype Enclosure

    neatloaf703/13/2025 at 02:44 0 comments

    I printed out a test enclosure to check fitment on everything and it turned out pretty well. I got alot of warping on the top plate but it still fit. The entire assembly is larger than i'd like it to be so I'll see how I can save space on the next design iteration.

    I want to avoid cutting the perfboard since I'm not very good at it, it looks like I can save some space by seating the pcbs directly against the enclosure wall, or even by having them sit slightly inside the wall. I need to figure out a good way to make a two piece enclosure so I can sandwich the plate between the two pieces. I have alot of space so I should be able to wire everything fairly easily. I am going to solder in the female headers to allow the pro micro and screen to stand on their own, and then will start routing wires. I may have to switch some pins around depending on how well I can utilize the space. The hardest things to wire will be the key switches but I can do that last. 

  • Log 4.75: Physical layout

    neatloaf703/07/2025 at 20:54 0 comments

    I used some packing foam to mockup some possible layouts.

    1
    2
    3

    Layout 1 is the most compact, but would require a board of similar size or smaller. My desktop tower is on the right side of my desk so I kind of want the board to be on the right side, like layout 2. Layout 3 is an option but I probably won't do that one. The advantage of layout 2, besides having the board where I prefer, is that there is room to accommodate larger boards like the pico. I have a piece of perf board that is the exact length for a pico, which is convenient.

    Since I will be using the pro micro, there will be some extra space on the bottom right if I use this layout. Keeping the encoders and pro micro close is helpful since I can just solder some headers onto the encoders and stick them into the perf board. I'll have to see the height I get using female headers soldered into the perf board and see if it gives enough clearance for the right most key switch, otherwise I'll have to do some cutting. 

    The switch plate is super floppy, I'll need to stiffen it along the transverse axis somehow. I've read some people 3d print thick switch plates, and reduce it to 1.5mm thick (the standard switch plate thickness) where the switches click into the plate. I could also just add some stiffening elements along the top and bottom edges and add foundations to support the middle of the switch plate. Thinking about it, having the plate sandwiched between a top and bottom enclosure piece should stiffen the plate pretty well since its so small.

    The hardest part will be wiring the screen and key switches in a neat way. I think the 5v pin is on the right side of the board so I'll need to experiment with wiring layouts. Ideally the wiring will be on the bottom of the perf board so everything can be hidden under a top plate. 

    I'll need to find a good way to secure the screen. The Adafruit Macropad case I made has a separate piece that sits on top of the pcb, and the screen rests on top of it, with the ribbon cable coming from a slot underneath. It would be nice to have the top of the enclosure all one piece, but that might not be feasible for 3D printing. If I position the switch plate to be at the height of the encoders, I could possibly have a top piece that takes care of the display, encoders, and switch plate. I need another plate that covers the perf board and is about flush with the top of the female headers that will accept the pro micro. There will likely be a gap between the two plates unless the length of the female headers matches the height of the encoder module below the input shaft, in which case I can use male header pins for the encoder without using the female ones on the perf board. I could also hand wire the encoders but that sounds like it would be a lot of work. Usually I enjoy the CAD portion of projects but this one sounds not as fun. I'll need to look for existing models of all my components, but having cheap no name Amazon parts makes that a bit tougher.

    The more I look at layout 1 though, the more I like the full use of space and larger encoder spacing. I'll have to do some more thinking.

    This is a situation that calls for learning how to design PCBs but this project has technically been in progress since last year so I might just do that for something else. 

  • Log 4.5: More graphics, next steps

    neatloaf703/06/2025 at 23:04 0 comments

    I added in a mute functionality that removes the volume digit display groups and displays a unique mute group. The speaker icon also changes depending on volume level. Right now profile, volume, and mute are all stored and controlled locally. The final device will need to receive information about these from the host software, and can either store the values or only update the screen when a serial message is received. Although the current code is mostly a graphics test, it was good to be able to flesh out the display groups I will be using and how they will behave. I also should add a profile for my internet browser but I forgot. 

    Thinking about the serial communication, I will have to determine if it is better to have only one message type sent that contains information about profile, volume, and mute, or have discrete messages for each value. A bulk message would be simpler, but the device would need to store information about the previous value states to avoid updating the whole screen. Separate messages would possibly be a little more complicated, but the device would be able to react without knowing its previous state. The only issues I foresee would happen if multiple actions happen at once, such as accidentally bumping both knobs or an accidental volume knob turn at the same time as a mute. 

    For the host software, I will need to be able to enable and disable profiles depending on what applications are open at the time. I will also need to figure out how to handle two games being open at once, as I am intending to use a general "games" profile rather than a profile for each game I play. I could also try rendering text depending on the game open, making sure the host software knows to increment through applications as well as profiles. I could also implement a sort of application whitelist, so that the "current window" volume doesn't try to change the volume of file explorer or text editors, skipping over the current window profile when changing through profiles.

    I haven't thought much about the physical design yet but the break out boards for the encoders and display are huge. I will try to angle the screen like on my Adafruit Macropad so I'll have to devise a way to hide the board given the short ribbon cable length. I want to use perfboard for wiring so I can solder in some female headers to slot the dev board into. Designing in some extra perfboard length and width where the MCU goes would make it easier for someone to copy this project with a different board but I'll have to see how much room I have. I only have the pico clone and pro micro to test with, but I think most RP2040 boards fall in between those sizes, or are smaller. I think the perfboard will be sitting right under the encoder breakout boards, so I'll either have to do some trimming or possibly connect the boards directly with some male header pins. 

  • Log 4: Graphics, simple encoder control

    neatloaf703/06/2025 at 04:46 0 comments

    I finished creating most of the graphics. I wasn't sure what to put in the top right corner so I just have pusheen there for now. I have separate groups for the left half of the screen, the top right, and a few for the bottom right to split up the hundreds, tens, and ones digits, as well as the speaker icon. I used the minecraft font for the digits.

    I have one encoder set to loop through the profiles in the left display group, and the other encoder increments the volume by 2. Everything updates on the display fairly quickly. Next I will need to implement the simple 4 frame pusheen animation, link the speaker display state to volume increments, and add a separate mute state for the volume display. Once I have the display groups mostly figured out, I will need to start writing a program that interfaces with the windows core audio api. I should first try to setup basic two way serial communication. 

  • 3.5 Displayio stuff, pixel art

    neatloaf703/01/2025 at 18:00 0 comments

    I spent most of my free time last week trying to make 1-bit pixel art, which appears to be harder than learning python. I made some simple screen animations for my Adafruit Macropad.

    I have a main 6 frame loop that refreshes the screen once a second. The firmware saves the current frame state, so switching between the game and numpad profiles does not restart the animation. I also have some 'mixup' animations that play every 3 loops. These are 8 frames long, and the refresh rate is updated to be 8 fps during these animations. I pick one animation to play instead of the starting loop frame.

    I have everything in a 30 frame long animation, and export it into a bmp that is 2 screens tall and 30 screens wide. I then call the specific frame to be displayed while the animation loop runs.

    I decided to not use the MacroPad library. Although the library streamlines alot of configuration, you cannot call the displayio object directly,  so more complex display behavior is not possible. The 'board' library initializes the display for you as board.DISPLAY. 

    As for the RPDeck, I bought some larger 1.3 inch displays using the SH1106 driver. The driver supports up to 132x64 screens, but the actual screen included is 128x64. As is, loading images into the driver memory begins at column zero, but the display starts at column 2. For the RPDeck, I will need to make sure to create 132Px wide images with a 2px buffer on each side. The screens are blue which is cool

    old vs new screen

    I also got some RP2040 boards in a pro micro form factor to use in the final build. I wanted to have the board exposed, and I thought the pico (or actually a VCC-GND YD) was too big. They were pretty cheap and have 16MB of flash memory.

  • 3: CircuitPython

    neatloaf702/27/2025 at 17:08 0 comments

    I have decided to switch to CircuitPython for creating the device firmware. I initially wanted to use QMK since I had spent a few days learning it to configure the Adafruit Macropad. However, I figured that most of the loafdeck (name WIP) functions won't be keycodes so there isn't a pressing reason for me to use QMK. One QoL improvement is the ability to import local .bmp images directly into bitmap objects, rather than needing to export, convert to byte array, and paste into firmware. This can be done either through the displayio or adafruit_imageload libraries.  I've begun trying to teach myself how to make 1-bit pixel art and uploading iterations of the screen art work will be more streamlined now. 

    I am using the Adafruit_displayio_ssd1306 library to display images and text. The displayio.TileGrid class is helpful for using sprite sheets, which will reduce the number of image files needed to be uploaded. A sprite sheet of arbitrary size can be uploaded as a "grid," and each grid can be split into "tiles" for easily calling the image you want. As a simple test, I went in GIMP to create an image with twice the height of my oled dimensions, 128x128 in this case. The top 128x64 of the image is one screen state, and the bottom half is a second screen state. I import the bitmap as a 1x2 tile grid, with each tile being the size of the screen. In circuit python, you can easily switch between which tile is being shown on the display. This is a very rudimentary test as grids and tiles can be any size and located anywhere on the screen. 

    Another good feature of displayio are the palette and pixel_map features, specifically being able to designate specific colors as transparent. This was useful for me as I was trying to create sprites for the left hand and right hand of the screen separately. The sprites I was using took up the whole screen, so the sprites would draw over each other rather than showing something on both halves of the screen. Setting black to transparent allows for this to work, although I may look into having smaller tiles instead. 

    Serial communication is handled by the usb_cdc library. The rp2040 actually has 2 serial ports available, with one disabled by default. The normally used serial port, identified as usb_cdc.console, is typically used for the REPL. This port would be usable for two way communications, but data can be interrupted and only one program can access the port at a time. I am experimenting with the usb_cdc.data port which is normally disabled, so is unaffected by the REPL data stream. I have gotten the device to output a message to serial if the first encoder is turned one direction. I used PuTTY to confirm the device was sending serial data that could be read. 

    I am very inexperienced with circuitpython and python in general, but I have attached my test code for reference. I pieced it together by taking from the few examples available and trying to decipher the documentation. I figure for someone with as little knowledge about (circuit)Python as I had 24 hours ago (no knowledge), this example could be helpful for starting projects that require two-way communication and updating a display accordingly.

    import board
    import rotaryio
    import digitalio
    from adafruit_hid.keyboard import Keyboard
    from adafruit_hid.keycode import Keycode
    import usb_hid
    import time
    import displayio
    import busio
    from adafruit_displayio_ssd1306 import SSD1306
    from adafruit_display_text import label
    import terminalio
    import usb_cdc
    import adafruit_imageload
    
    #create object named 'serial' connected to the unused serial data port
    serial = usb_cdc.data
    
    # Release any existing displays
    displayio.release_displays()
    
    # Initialize I2C
    i2c = busio.I2C(scl=board.GP3, sda=board.GP2)
    
    # Create the display bus
    display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
    
    # Create the SSD1306 display object
    display = SSD1306(display_bus, width=128, height=64)
    
    # Create a display group 'splash'
    splash = displayio.Group()
    ...
    Read more »

  • 2: QMK

    neatloaf702/24/2025 at 18:04 0 comments

    I recently picked up an Adafruit macropad kit and have been learning to use QMK. After reading through the documentation, I learned of the Raw HID feature that allows for two-way HID reporting with the host. I figured QMK is already so feature rich and somewhat easy to create firmware with, so I will be trying to use QMK for this project. 

    I will be trying to implement some sort of image-based indicator for the sound level along with its numerical value, sort of like how the windows volume mixer tray icon works. I also hope to incorporate a few application icons such as for spotify or discord. I have decided to switch to the RP2040 as the much larger flash memory should help me avoid any memory issues down the line. My code for the firmware probably not be well optimized given my lack of code experience. 

    The way I have been drawing to the Adafruit macropad OLED is using byte arrays, which works  since I only have 2 layers for now. I imagine the volume indicator image will have 4-5 different states depending on volume level and mute status. I'll assume for now about 4 different unique sound icons (spotify, discord, games, and master volume). That is alot of permutations for what images can be shown on the screen and that doesn't include all the text descriptors. I believe QMK allows for writing characters directly to the screen, so hopefully I can populate the images first and then draw the text on after. 

    The oled is 128x64, so about a 8200 length byte array. However, HID reports are only 32 bytes in length I believe. Sending a whole byte array to draw to the OLED would take a minimum of 256 reports. I've read that the report rate reaches about 1kHz with the rp2040. It sounds like it could be potentially slow to update if you are quickly switching through apps, so I will probably have all the image arrays baked into the firmware. I am hoping to have arrays for the application logo on the left of the screen and arrays for the volume level image on the right, and combine the two before drawing to the screen. Lastly I will draw the application name and volume level. 

    The app switching encoder can either be mapped null in the keymap and instead directly send HID reports, or I could map them to hot keys that the windows application will be listening for. Outgoing HID reports would be pretty simple, probably only needing one or two bytes of information to update the windows application. 

    Volume control will be through QMK standard key codes. Switching output devices is taken care of with the windows app called SoundSwitch which I have mapped to a hot key already. 

    Incoming HID reports will be a little more complex. The windows app will need to report information on the current volume level, mute status, and selected application. Volume level and mute can be 3 bytes in the report. As for selected application, I can either send the name over, or have a precompiled list of applications in the firmware and just send a number associated with the application. I could also do a combination of the two; on startup, have the windows app report names of all applications configured and store them on the RP2040 and associate each application name to a number. Having a preset list is the simplest, but would require reflashing the board each time a new application (probably a new game) is configured in the windows application.

    The windows application will probably give me the most trouble. At the least I need to monitor volume level and mute status for what application is currently selected. There will need to be a way to filter out any applications I don't want to control. I believe windows has an API with this functionality but I'll need to do more research. 

    While I wait for the RP2040 to arrive, I am using a pro micro to test out building firmware from scratch. 

    It was sort of a headache but I at least got the oled and encoders working. The static image took up a not insignificant...

    Read more »

  • Log 1: Design parameters, early prototyping

    neatloaf701/09/2024 at 00:16 0 comments

    Before I flesh out the physical design, I am going to establish some design parameters so I (hopefully) don't add too many features that I'll never use, and to see if what I want to make can be bought somewhere for cheaper. The Adafruit MacroPad kit runs 50 dollars as of right now, so I am hoping I can keep my design under that price. I was thinking about buying the Adafruit kit for this project, but couldn't justify having 12 switches for what ultimately amounts to being a fancy volume knob. I made a rough sketch of the features I want, with a little wiggle room for additional functions down the road.

    This is a poorly drawn out representation of the functions I would like to implement. At the very least the OLED will report the current application and its volume level. It would be nice to show the previous and next applications that can be scrolled to, and animate the application names scrolling. What would be cool to add is a little speaker icon that shows the relative volume level, sort of like what you'd see from the Windows volume icon in the task bar tray. Maybe it also does a little volume increase/decrease animation when changing the volume. If memory becomes an issue I will probably switch to an RP2040 based board. 

    I was thinking of using two rotary encoders with a push button for controlling the app selection and volume. The push button on the volume control knob will be a mute, but I am not sure exactly what to use the other encoder push button for. I don't want the project to be too large physically, so I am thinking of only including 2 or 3 macro buttons, and using the remaining encoder push button to swap between button profiles. If I do go this route, I'll need the OLED to also report what button profile it is on. For the macro buttons I'll use some cherry-MX profile switches that I have extras of. I have an extra set of keycaps not being used, but it might be nice to buy some PBT blanks.

    When looking at the Maxmix and the deej, they both use a separate windows application to interface with the arduino. I will have to learn how to write, or at least modify windows applications. Functionality wise, I think the maxmix software is pretty complete in regards to the features I want. YaMoef's fork of deej is also similar to how I'd approach building off the deej software.

    For now, I am going to learn how to use the OLED screen and look at the code of the other two projects I mentioned earlier. When I first came up with this idea I didn't anticipate needing to write software, but I suppose this is an easy project to learn on.

View all 10 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates