• Firmware update, read key from data flash

    biemster03/31/2023 at 10:12 0 comments

    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!

  • Initial version of firmware on git

    biemster03/20/2023 at 15:55 0 comments

    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:

    • Left key: '1'
    • Middle key: '2'
    • Right key: '3'
    • Press knob: 's'
    • Turn knob clockwise: 'r'
    • Turn knob counterclockwise: 'l'

    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:

    1. The USB HID code disables the automatic jump-to-bootloader in ch55xduino, so every time you want to reflash the ch552, you'll have to open up the keypad and connect the testpads connected to P3.6.
    2. The ch55xduino IDE changed the bootloader access method from P1.5 to GND, to P3.6 to 3v3. This is unfortunate, because programming the chip using P1.5 would be possible without opening up the pad since the right key is connected to the pin adjacent to P1.5, and pulls that to GND when pressed.
    3. Ch55xduino has examples where you can use both USB CDC and USB HID together (comport + keyboard), but that does not seem to work on my ch552. Only the HID shows up when I connect it to my linux machine.
    4. The jump-to-bootloader function does not seem to work, which is odd because I use exactly the same code as the USB CDC of ch55xduino, which does work properly also on my chip. So more research is needed here.
    5. Bootloader access is also needed to write to the EEPROM, which will be used to reconfigure which characters are returned on keypress.
    6. Debounce is not yet implemented, although I did not notice any bouncing yet.

    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.

  • Reading the rotary encoder

    biemster03/09/2023 at 11:22 0 comments

    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;
    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:
      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!

  • Controlling the RGB LEDs

    biemster03/08/2023 at 17:48 0 comments

    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
    __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);
      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);
      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);

    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.

  • Programming with CH55xduino, mapping the 3 keys

    biemster03/08/2023 at 17:03 0 comments

    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:



    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() {
    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;

    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!

  • Initial findings

    biemster03/07/2023 at 13:27 0 comments

    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



    The keyboard seller provided the following link for the Windows software:


    And Reddit provided some more: