Unbricking my Pro Micro

My workbench of shame. An arduino being used as an ISP programmer for a promicro plugged into a breadboard attached to a completely unrelated board.

QMK is an extraordinarily versatile open source customizable firmware for input devices such as custom keyboards, mice, etc. Unfortunately, I did not get to do anything fun with QMK because I accidentally corrupted my firmware, so that’s what we’ll be talking about today.

In this post, I’ll go over my debugging process for getting a working firmware back onto a Pro Micro that wasn’t working as intended, and fixing its bootloader using an Arduino as an ISP programmer.

For context, I am an absolute firmware newb and have not done AVR programming before this, though I have interfaced with chips as blackboxes before such as with hobby electronics or breadboarding chips together.

This post is now fixed with some new information I learned on 2021-10-24. I highly recommend reading this newer post before re-flashing your bootloaders, if it turns out you need to do that.

Environment

My “legacy” OSX 10.13 appeared to have some compatibility issues with running QMK, so I waited until recently when I was willing to upgrade to 10.14 (I’ve been avoiding 10.15 for developer incompatibility reasons).

In the meantime, I’ve been tweaking my keymap with VIA, an extremely user friendly piece of software that doesn’t have as many features as QMK but still lets you do quite a bit of customization including basic Mod-Tap for multiplexing tapped and held keycodes.

So, I bricked my controller

I tried reflashing a default firmware build onto my controllers with QMK Toolbox and instantaneously bricked the right-hand side. While the left Pro Micro was still flashable, the right could not be flashed over USB and also stopped functioning as a keyboard.

Debug checklist

Other useful things to check on the keyboard PCB:

Hypothesis: the bootloader is corrupted.

It became clear that, for whatever reason, I was not going to be able to write to this board directly over USB any more.

I received a recommendation to use an ISP programmer to reflash the bootloader on the Pro Micro. Digging through mine and my housemates’ hobby electronics piles for plausible ISP programming hardware, we came up with a very old Sparkfun Bus Pirate and an Arduino Mega 2560. For some reason I wasn’t able to get the Bus Pirate to work, and ended up using the Arduino instead.

Reflashing it with an ISP programmer

ISP stands for In System Programming, so “ISP Programmer” suffers from a similar acronymic issue to “ATM Machine”.

My rough understanding of an ISP programmer is that it’s any device capable of re-flashing the firmware on chips which are able to receive firmware updates while installed in a complete system. I know this sounds a bit tautological.

Hobby electronics platforms such as Arduino obscure this by shipping with a firmware which allows you to load special Arduino code directly over USB, and run that code without changing the firmware.

Materials

brew install avr-gcc avrdude lsusb

(avr-gcc takes a while)

Which bootloader?

While I was sorting all this out, Drashna (an extremely helpful core QMK contributor) recommended that, if I was going to be reflashing my bootloader anyway, I should switch bootloaders. keeb.io summary of Drashna’s online bootloader spiel.

Apparently, Caterina, the default Pro Micro bootloader, frequently overwrites itself. DFU is another bootloader which is much more robust. The two flavors relevant to this post are atmel-dfu and qmk-dfu.

  1. atmel-dfu is the generic DFU bootloader, which you can download a hex file of directly from the QMK Github repo.

  2. qmk-dfu is a fork maintained by QMK contributors with some convenience features for QMK users. qmk-dfu allows you to modify the EEPROM in place, for example to set certain user firmware settings such as handedness, without clearing and reflashing the chip. See this doc for instructions on generating a production file with the qmk-dfu bootloader.

Switching bootloaders from Caterina to some flavor of DFU is optional, but highly recommended if you are already here, as it could save you a lot of future pain.

(Look at the large central chip under a magnifying glass to double check what processor your board uses. I have a Pro Micro with an Atmega32u4 processor.)

If using Sparkfun Bus Pirate as ISP programmer

This was how I interpreted the wiring recommendation. There are multiple versions of this board, and sometimes the header can apparently be flipped, so make absolutely sure that the wires are emerging in the order you expect.

buspirate +5V <> promicro VCC
buspirate GND <> promicro GND
buspirate CS <> promicro RST
buspirate CLK <> promicro 15 (SCLK)
buspirate MISO <> promicro 14 (MISO)
buspirate MOSI <> promicro 16 (MOSI)

Unfortunately this did not work for me, and I’m not sure why.

If using Arduino as ISP programmer

Things are going to get a bit confusing with multiple Arduinos being used for different purposes. I will refer to one of them as the ISP programmer, and the keyboard microcontroller which you are attempting to reflash with it as the “target board”.

First, turn your extra Arduino into an ISP programmer using the appropriate arduino.cc instructions for your board. Here is an additional Sparkfun guide on using an Arduino to burn a new bootloader. It can also help to dig up the pinouts for both your Arduino-as-ISP and your target board.

Safety emphases

The capacitor needs to be wired between RST and GND in the POWER section after you have turned the Arduino into an ISP programmer, but before you wire it to your target board.

For safety, keep the power unplugged while you are in the process of wiring the board, and any time you are plugging/unplugging the target board.

If using Arduino Mega 2560 as programmer

Wiring Arduino Mega 2560 as ISP programmer, to Pro Micro as target:

mega +5V <> promicro VCC
mega GND <> promicro GND
mega 50 (MISO) <> promicro 14 (MISO)
mega 51 (MOSI) <> promicro 16 (MOSI)
mega 52 (SCK) <> promicro 15 (SCLK)
mega 53 (SS) <> promicro RST

If using Pro Micro as programmer

You can also use a spare Pro Micro as the ISP programmer for a target Pro Micro. I ended up wiring this into a breadboard later after acquiring spares, because it made my ISP setup significantly less bulky than the Mega2560 with all the jumper cables.

Pro Micro wired as the ISP programmer for a target Pro Micro board.

There are two things you need to change before setup to make this work.

1. Add promicroisp settings to your Arduino application.

Locate your Arduino application settings. On OSX 10.14, I found them in /Applications/Arduino.app/Contents/Java/.

Within that folder, add the following to hardware/arduino/avr/programmers.txt.

promicroisp.name=Pro Micro as ISP
promicroisp.communication=serial
promicroisp.protocol=arduino
promicroisp.speed=19200
promicroisp.program.protocol=arduino
promicroisp.program.speed=19200
promicroisp.program.tool=avrdude
promicroisp.program.extra_params=-P{serial.port} -b{program.speed}

Thanks to the Arduino forum and Arduino GH issue #1182 for documenting this.

2. Change the RESET pin in the ArduinoISP sketch

In the ArduinoISP sketch, find the line that says:

#define RESET SS

and change it to:

#define RESET 10

Also from the Arduino forum.

You can then follow the same ArduinoISP setup instructions as previously linked.

Wiring Pro Micro to Pro Micro

Since they’re both Pro Micros, the wiring is 1:1 except for the reset pin; wire pin 10 on the ArduinoISP to the target board’s RST pin.

promicroISP +5V <> promicro VCC
promicroISP GND <> promicro GND
promicroISP 14 (MISO) <> promicro 14 (MISO)
promicroISP 16 (MOSI) <> promicro 16 (MOSI)
promicroISP 15 (SCK) <> promicro 15 (SCLK)
promicroISP 10 (RST) <> promicro RST

It’s avrdude time

Identify your port

I did ls /dev/cu* before and after plugging in the USB to check my port name. Mine ended up being either /dev/cu.usbmodem14101 or /dev/cu.usbmodem14201, but yours may differ.

Sanity check that the devices are talking

Sanity check that you can talk to your board with avrdude -c <programmer type> -P <USB port name> -p <target board name>. You also might need to set your baud rate with -b. My output ended up looking like this:

$ avrdude -c arduino -P /dev/cu.usbmodem14101 -p ATmega32u4 -b 19200

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e9587 (probably m32u4)

avrdude: safemode: Fuses OK (E:CB, H:D8, L:FF)

avrdude done.  Thank you.

Are your fuse bits correct?

If you are changing bootloaders, you will need to change the fuses too. If not, you can ignore this section.

Fuse bits are stored configuration bits for AVR microcontrollers which control low-level settings, such as the internal oscillator multiplier. See AVR fuse bits explained - Hackaday.

You can permanently brick a board by setting the fuse bits incorrectly, so make absolutely sure you have picked the correct bits before doing this, and then triple check that they are typed correctly. I got the appropriate fuse bits from this section of the QMK ISP flashing guide.

For a DFU bootloader on a 5V 16MHz atmega32u4, I added -U lfuse:w:0x5E:m -U hfuse:w:0x99:m -U efuse:w:0xC3:m to my avrdude command. Again, these are not going to be the same for some other combination of bootloader and chip, so please triple check that you have the correct ones for your hardware & firmware.

Final flashing process

In summary, to fix the messed up bootloader on my 5V 16MHz atmega32u4 using an Arduino as an ISP programmer, and replace it with a DFU bootloader, I did this. (Do NOT copy these exact commands for any other combination of chips/bootloaders – you will probably brick your hardware.)

  1. Sanity check that the right flags are being passed and the board is responsive.
    avrdude -c arduino -P /dev/cu.usbmodem14101 -p ATmega32u4 -b 19200
    

    This should list a nonzero device signature and say something like “Fuses OK”.

  2. Set the correct fuse bits for a DFU bootloader on this chip. (ONLY IF SWITCHING BOOTLOADER)
    avrdude -c arduino -P /dev/cu.usbmodem14101 -p ATmega32u4 -b 19200 -v -e -U lfuse:w:0x5E:m -U hfuse:w:0x99:m -U efuse:w:0xC3:m
    
  3. Flash production-ready .hex file of choice, e.g. firmware containing a bootloader.
    avrdude -c arduino -P /dev/cu.usbmodem14101 -p ATmega32u4 -b 19200 -v -e -U flash:w:bootloader_atmega32u4_1.0.0.hex
    

At this point, you should be able to flash with qmk again.

Conclusion

I learned a lot about AVR programming during this process; mostly that I find it unbelievably stressful and despairing. My sincerest hope is that this blog post will save you from days of sobbing into your firmware toolchain like I did. Good luck!