Custom firmware for the CH552 found in those USB RGB macropads with a rotaty knob
To make the experience fit your profile, pick a username and tell us what interests you.
We found and based on your interests.
So at the very end I did still run into some issues, where this project started so expeditious. The main issue was that I couldn't start the bootloader by pressing a key when connecting to USB, a feature I really wanted to reprogram the keys. The problem was that with CH55xduino I couldn't seem to jump to the bootloader at #3800 from user code.
With the help of @Stefan Wagner I decided to move away from CH55xduino, and base the code off of his implementations in wagiminator/CH552-Macropad-mini and wagiminator/CH552-USB-Knob.
The firmware in the git repository is now a complete rewrite, and includes a nice flashing tool. First time programming still needs a hardware connection to be made between P1.5 and GND, but after that holding the 1st key while connecting USB lands you in the bootloader!
And as an added feature, the keys the pad sends are now stored in EEPROM, since the ROM has an guaranteed write lifetime of only 200 writes. So reprogramming the keys does not count towards that anymore.
This reprogramming the keys requires a bit of fiddling now, with reading the data flash like this:
$ isp55e0 --data-dump flashdata.bin
then change the first 6 bytes of this binary files to your liking (3 keys, plus 3 for the knob), and writing it back:
$ isp55e0 --data-flash flashdata.bin
There are other tools out there to do this, I just happened to have isp55e0 installed. Last thing to do is write a small python script that does this for you, and it's a wrap-up!
The linked github repository now has a very early version of the firmware. It should be compiled with ch55xduino, just like the other code snippets in the other logs. It is not possible yet to configure which keys are send, it his hardcoded (but straightforward to change) like this:
Next up is to make this easily configurable, without reprogramming the firmware. I'm planning to have the values on the internal eeprom, with an easy way to change them.
A couple things that came up when creating this:
Concluded, there are some loose ends still in need of wrapup, but the code in https://github.com/biemster/3keys_1knob is already quite usable.
The rotary encoder is quite straightforward to read out, the internet is full of examples. After some tryouts the pins were found to be connected to P3.0 and P3.1, and the switch that can be activated by pressing the knob is on P3.3. The two outputs of the encoder just go high one after another, depending on which direction the knob is turned:
#include <Serial.h>
uint8_t OUTA = 30;
uint8_t OUTB = 31;
uint8_t SW = 33;
uint8_t sw_prev = 0;
enum {RSTAT_SETTLED=0, RSTAT_A, RSTAT_AB, RSTAT_B, RSTAT_FULL};
uint8_t stat_prev = RSTAT_SETTLED;
uint8_t stat_seen[4] = {0};
void setup() {
pinMode(OUTA, INPUT_PULLUP);
pinMode(OUTB, INPUT_PULLUP);
pinMode(SW, INPUT_PULLUP);
}
void loop() {
uint8_t a = digitalRead(OUTA) ? 0 : 1;
uint8_t b = digitalRead(OUTB) ? 0 : 1;
uint8_t sw = digitalRead(SW) ? 0 : 1;
uint8_t status = (a && b) ? RSTAT_AB:
a ? RSTAT_A:
b ? RSTAT_B:
stat_prev ? RSTAT_FULL:
RSTAT_SETTLED;
if(a && b) {
stat_seen[RSTAT_AB] = 1;
}
else if(a) {
stat_seen[RSTAT_A] = 1;
}
else if(b) {
stat_seen[RSTAT_B] = 1;
}
if(sw_prev != sw) {
if(sw) {
USBSerial_println("Rotary encoder pressed");
}
else {
USBSerial_println("Rotary encoder released");
}
}
if(status == RSTAT_FULL && stat_prev == RSTAT_A && stat_seen[RSTAT_B] && stat_seen[RSTAT_AB]) {
USBSerial_println("Rotary encoder finished clockwise move");
memset(stat_seen, 0, sizeof(stat_seen));
status = RSTAT_SETTLED;
}
else if(status == RSTAT_FULL && stat_prev == RSTAT_B && stat_seen[RSTAT_A] && stat_seen[RSTAT_AB]) {
USBSerial_println("Rotary encoder finished counter clockwise move");
memset(stat_seen, 0, sizeof(stat_seen));
status = RSTAT_SETTLED;
}
else if(status == RSTAT_FULL) {
USBSerial_println("Rotary encoder got back to starting position");
memset(stat_seen, 0, sizeof(stat_seen));
status = RSTAT_SETTLED;
}
stat_prev = status;
sw_prev = sw;
}
There is no debouncing in the code above, so you might get some extra readings when pressing the knob.
This means the whole pad is figured out now (in record time, at least for me), next steps will be to write the USB keyboard code. And some nice way to configure the keys, LEDs and encoder without having to reprogram the ch552 with the Arduino IDE. Onward!
So that was quick! Hot on the heels of the key mapping to the pins, it turns out that the RGB LEDs are WS2812 connected to P3.4, and CH55xduino has some example code for that already!
#include <WS2812.h>
#define NUM_LEDS 3
#define COLOR_PER_LEDS 3
#define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)
__xdata uint8_t ledData[NUM_BYTES];
void setup() {
pinMode(34, OUTPUT);
}
void loop() {
for (uint8_t i = 0; i < NUM_LEDS; i++) {
set_pixel_for_GRB_LED(ledData, i, 255, 0, 0);
neopixel_show_P3_4(ledData, NUM_BYTES);
delay(100);
}
for (uint8_t i = 0; i < NUM_LEDS; i++) {
set_pixel_for_GRB_LED(ledData, i, 0, 255, 0);
neopixel_show_P3_4(ledData, NUM_BYTES);
delay(100);
}
for (uint8_t i = 0; i < NUM_LEDS; i++) {
set_pixel_for_GRB_LED(ledData, i, 0, 0, 255);
neopixel_show_P3_4(ledData, NUM_BYTES);
delay(100);
}
}
So only the rotary knob is left to figure out, and after that the HID stuff (but CH55xduino has plenty of examples on that). Pins left are P1.4 P1.5 P3.0 P3.1 P3.2 and P3.3.
This will be my quickest project ever by a long shot.
After a lot of headaches why I couldn't get to the bootloader, you can see in the previous log I finally succeeded. So next step: get some code running on it.
As I did not want to fiddle with some clip all the time, I decided to go with the Arduino port by Deqing:
https://hackaday.io/project/172143-ch55xduino
https://github.com/DeqingSun/ch55xduino
Not only is it a breeze to upload new firmware with this, it also comes with a ton of example code! The only downside I can think of is that it takes up quite a chunk of available space, but since this macropad should only monitor the buttons, and light the LEDs, I guess it will be fine.
In my great wisdom I decided to screw the whole thing together without tracing what connects to which pins, so that will be trial and error. For the next log. I already trial-and-errored the 3 keys:
#include <Serial.h>
unsigned long prev_ms = 0;
const long interval_ms = 300;
uint8_t BUTTON_LEFT = 11;
uint8_t BUTTON_RIGHT = 16;
uint8_t BUTTON_MIDDLE = 17;
void setup() {
pinMode(BUTTON_LEFT, INPUT_PULLUP);
pinMode(BUTTON_RIGHT, INPUT_PULLUP);
pinMode(BUTTON_MIDDLE, INPUT_PULLUP);
}
void loop() {
const long now_ms = millis();
if(now_ms > prev_ms +interval_ms) {
prev_ms = now_ms;
int buttonState = digitalRead(BUTTON_LEFT) ? 0 : 1;
buttonState += digitalRead(BUTTON_MIDDLE) ? 0 : 2;
buttonState += digitalRead(BUTTON_RIGHT) ? 0 : 4;
USBSerial_println(buttonState);
}
}
Great fun! And would be for sure a couple days more work if not for the work of Deqing. Thanks!
Next up is the rotary encoder, and the LEDs. Pins that are left are: P1.4 P1.5 P3.0 P3.1 P3.2 P3.3 P3.4
My guess is that the encoder is something like a HW-040 and the LEDS are WS2812's or APA102 or the like. But who knows!
Connecting the pad to a PC has it show up in lsusb like this:
ID 1189:8890 Acer Communications & Multimedia
The keyboard ships with Windows software to do a first time programming of the buttons (links added at the end of this log), after which it should be usable on any device (I did not test this though, I opted for first opening it up).
After unscrewing the bottom which is very hacker friendly, it showed its internals which is just a CH552 and some passives + USB-C connector. The board looks very well assembled with the MCU easily accessible for reprogramming.
To reprogram the chip there are several flashers available, I picked isp55e0 from the AUR. I've read somewhere that the chip should be in DFU mode, and to achieve that I have to boot it while holding D+ at 3v3, which means connecting pin 12 and 16 together. And this board has nice test pads placed next to each other for this! So I guess these pads are there for exactly that reason.
On the other hand, connecting USB while holding D+ at 3v3 sounds like a bad idea to me, so I did not try that yet. I'll do further research on how to flash them first, and report on this in a new log.
EDIT: actually, CH55xduino https://hackaday.io/project/172143/instructions indicates it is really as easy as connecting D+ to Vcc (5V in their case), and then connect USB. Let's try! and expect a new log soon.
EDIT2: so the super easy way of getting into the bootloader (D+ to 3v3 using the pads) is not working, probably because the USB is in the way. Luckily, The Rabid Inventor wrote that there is another way, pull pin 3 to ground, and that works! I can access the bootloader now, and reprogram the chip. Woohoo!
ID 4348:55e0 WinChipHead
http://rabid-inventor.blogspot.com/2020/05/getting-started-with-ch55x.html
---
The keyboard seller provided the following link for the Windows software:
And Reddit provided some more:
https://www.reddit.com/r/keyboards/comments/u1bfbn/help_looking_on_software_to_program_this_keypad/
Create an account to leave a comment. Already have an account? Log In.
I've forked the repository and added a feature about gamma correction (to display realistic colors) and the ability to update these colors from a HID raw interface along the keyboard. It's also capable of parsing keyboard led states (Caps / Num lock ...) and has a python script with an example of how to do effects on the macropad
See : https://github.com/mgrenonville/3keys_1knob/tree/serial
That RGB script is an awesome idea, I'm going to have a look at that. Should be straightforward to use it to program the key functions as well right?
Thanks !
Indeed, For now, I'm using the first byte of the USB data to differentiate the message type.
https://github.com/mgrenonville/3keys_1knob/blob/a100ce9d929d5776e31d961fe654aa7121eab3ce/3keys_1knob.c#L257
That was on my todo list (i've added the eeprom_write_byte for that) but I'm waiting CH55x devboards because I have flashed more than 50 times and I don't want to break my keyboard ^^'
Thank you for the sharing, I've successfully flashed my keyboard.
I'm using a forked version of your reposistory to allow Media keys and modifiers ( https://github.com/MisterRager/3keys_1knob.git ).
Using a hexeditor + this HID table https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2 helped to create the suitable configuration:
```
00000000 00 01 A9 01 00 06 01 00 19 00 00 7F 00 00 80 00
00000010 00 81 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
```
In my case, first button is for Media play, second for Ctrl+C, then Ctrl+V, and knob click is Mute, then Vol +/-
Cheers
hehe was just about to write a firmware but gave it another search and found this :D btw on linux you need to add some permissions first to access the bootloader. This should help those having issues:
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="4348", ATTR{idProduct}=="55e0", MODE="666"' | sudo tee /etc/udev/rules.d/99-ch55x.rules
sudo service udev restart
Thanks for making this firmware 👍
Indeed an udev rule would make the usb bootloader accessible for non-root users. Most flashers include this in their install guide, but good to mention here nonetheless!
Thanks so much for your work. Based on your code I got a 6-button model with knob working. It has a slight different microcontroller package (CH552G), but only some minor remapping of pins was needed in the config:
// Pin definitions
#define PIN_KEY1 P15 // pin connected to key 1
#define PIN_KEY2 P34 // pin connected to key 2
#define PIN_KEY3 P11 // pin connected to key 3
#define PIN_KEY4 P30 // pin connected to key 4
#define PIN_KEY5 P16 // pin connected to key 5
#define PIN_KEY6 P17 // pin connected to key 6
#define PIN_ENC_SW P33 // pin connected to knob switch
#define PIN_ENC_A P32 // pin connected to knob outA
#define PIN_ENC_B P14 // pin connected to knob outB
This is the version without LEDs, no wireless, with black keys and black bottom and top plexi plate. I added some code to check the additional 3 buttons. I also kicked out all code related to neopixels, since mine doesn't have leds. There seem to be many manufacturers of this hardware, and I don't know if the pin mapping is the same for each of these, but I hope the above helps someone...
If anyone is struggeling getting the CH552 into bootloader mode with the P1.5 to GND method: I got mine into bootloader pulling up P3.6/UDP to 3V3 using a 10k resistor. A comment on Rabid inventor mentioned this way.
Yes I probably should have been more clear on this, the manufacturer can choose between either these two ways. If P1.5 to GND doesn't work for you, try (the default) P3.6 to 3v3!
i have the 6 button non-rgb and the 6 button 3 layer bluetooth version of these macropads. both use a version of the ch552 and the same terrible software to program it.
i've had both apart to do the same preliminary exploration and dug a little into the same idea of seeing if i could get something - anything - better than just keypresses and more toward real macro's or even just binding to launching a small python script using something like F13-24 or other usually unbound available hid standard keys and additional software - i didn't get anywhere with it really.
i did find some interesting data sheets and some revealing chinese webpages on the devices but it's just too far out of my wheelhouse to make much of it
I just added a log while you typed this, it seems they are fully open for modification. Did you try reprogramming the ch552?
no i didn't - i did see the ch55xduino stuff and found a couple of flash utils a few months ago but wasn't brave enough to possibly brick my new toys - i'm not great with programming arduino and not much better with micro/circuit python
i'm more at a 'i can fiollolw instructions and make minor modifications' kinda level since i'm just getting back into this kinda fun after 20 some years - lots of catching up to do
The ch552 seems very difficult to brick, once you have the arduino core on it it's basically free play. We can discuss this further via PM here on had.io if you like, those macropads are very hacker friendly, much more than I anticipated.
Become a member to follow this project and never miss any updates
I was trying to get it to switch to the bootloader, now all the rgb lights are stuck in a blue-ish white, and the device doesnt appear in lsusb, Did i just fry the mcu...