Switch Dual Shock adapter interlude: Bootloading
In this series of posts, I’m attempting to make a Dual Shock to Switch controller adapter. It will plug into the Switch Dock’s USB port.
This is a bit of an interlude from actually working on the project: in this post, I put a serial bootloader onto the ATmega8A I’m using, to make development more efficient.
In part 5 of this series, I started using my ISP programmer - which I’ve been using since part 1 - to do In-System Programming ‘for real’. Instead of removing the ATmega from the breadboard every time I wanted to upload a new program to it, I connected wires from the programmer to the breadboard so that I could upload to it at any time - ‘in-system’.
This worked great for a long time. But when I started to get ready for connecting the Dual Shock, which meant changing some pin usage, something went badly wrong. When I removed the LED previously connected to pin 19 - also the ‘clock’ line for ISP programming - disaster!
Programming stopped working, and the old program didn’t run after the failure. At first, I thought I just had a flaky connection or something - but after went through three ATmegas trying to work out what was happening I realized the chips were bricked.
The problem is obvious in retrospect.
The ISP programming shield I set up in part 1 runs on 5V. But the ATmega on the breadboard, in order to be compatible with USB signalling, runs at 3.3V.
I was connecting the programmer that represented a ‘high’ as close to 5V to a chip that expected no more than 3.3v. The ATmega8A data sheet has an “Absolute Maximum Ratings” section that lists “Vcc + 0.5V” as the maximum that should be applied to a pin - so 3.8V in this case. Significantly less than 5. Ick.
Why wasn’t this a problem until I removed the LED from the serial clock line? I’m not entirely sure - but I guess that somehow it was soaking up enough current that the 5V didn’t damage the chip? Or the LED and resistor to ground were somehow clamping the voltage? And why didn’t the data transmission line from the programmer to the ATmega, which didn’t have any of this accidental LED “protection”, cause any issues?
Whatever the cause, without some reasonably elaborate level-shifting circuit, I couldn’t keep using the programmer ‘in-system’.
I had become really become accustomed to not having to remove the chip form the breadboard though. I’d like to continue to be able to do that…
Time to set up a proper bootloader!
A bootloader is a small piece of code that runs when the chip first powers on. It looks for any incoming programs to flash, then jumps to the already-flashed program if it doesn’t receive any
There are a few bootloaders that integrate V-USB and allow uploading through USB without extra hardware - most notably USBaspLoader. Unfortunately it can’t run at 12.8MHz - the 12.8MHz-tuned code is too big to fit in the limited area of flash that bootloaders are allowed to reside in. And, besides that, even the versions that do fit take up a whopping 2kB of the total 8kB of program space.
While it would be cool to allow firmware upgrade of the final version of this project via USB, these two caveats mean I didn’t want to use it.
So, no native USB bootloading.
PlatformIO and can natively set up the OptiBoot serial bootloader, which comes with MiniCore1. OptiBoot allows programs to be flashed to the ATmega over a serial link. I already had a USB <-> Serial convertor on the breadboard for debug logging - and it can also be used directly by OptiBoot.
Usually, with a minimum of setup, you can just run
pio run -t bootloader and it will upload OptiBoot to your ATmega. It wasn’t quite that easy for me though - the bootloader runs before any of our USB-based oscillator tuning happens, so it needs to run at 8MHz - but PlatformIO thinks that we’re running at 12.8MHz and gets confused.
The solution was to add a new ‘environment’ that can be explicitly used to set up the bootloader, set to 8MHz.
Here’s the resulting
Fuses are set when burning the bootloader, so I moved the fuse settings into the new environment, and adjusted them to set up the ATmega correctly for a 512 byte bootloader. I also set up the
upload_protocol appropriately differently in each section.
To get the bootloader onto a new ATmega before first use I still use the ISP programmer. I put the fresh chip in it and click “Burn Bootloader” in the “ATMega8Bootloader” section of the PlatformIO sidebar in VS Code - or run
pio run -e ATmega8Bootloader -t bootloader. PlatformIO then sets the fuses and uploads the bootloader.
After the bootloader is installed the ATmega can be transferred onto the breadboard - and stay there.
I needed to make one change to the breadboard circuit: I connected a 0.1µf capacitor between the ‘DTR’ line on the serial convertor and pin 1 - reset - on the AVR. When programming is about to start, the system will pull the DTR line low. This causes the reset line to get intermittently pulled low via the capacitor, which resets the ATmega, causing the bootloader to run just in time to receive the program.
When all this is set up, uploading a change to the program is just one step. Click “Upload” in the status bar or in the “ATMega8Bootloader” section of PlatformIO pane - or run
pio run -t upload. The ATmega resets, the program gets transferred, then it resets again and runs the new program.
I can now leave the ATmega in the breadboard while programming, and what’s more, uploading the program with the bootloader is slightly faster than using ISP programming.
Here’s how the breadboard looks now - I’ve removed the ISP jumpers, and moved the USB serial convertor over to tidy things up, but with the exception of the new capacitor I mentioned above the circuit is exactly the same.
Now I can get back to connecting the Dual Shock!
You can follow the code for this series on GitHub. The
main branch there will grow as this series does. The
blog_interlude_1 tag corresponds to the code as it is (was) at the end of this post.
All the posts in this series will be listed here as they appear.