My adventures in fixing an atmega328p for Arduino on Linux

2019-02-27

(Arduino,Minipro,GRBL)

I purchased a bunch of atmega328p chips from banggood.com to experiment with using these microcontrollers without an arduino board. I have a minipro TL866 that I was using to flash the programs to, and after much experimentation managed to get something working. However in the process I managed to flash one of the avr's that came with an Arduino, thus wiping the bootloader. I could just throw the board away and buy a new Arduino but it seems wasteful - surely it's possible to restore the bootloader to the chip and get it working again!

The process took a very long time, mainly because I didn't really know anything about the atmega328p nor exactly how an Ardunio works. I managed to learn a lot in the process though. In the end these were the steps I used to get this chip back into a working Ardunio state:

Install minipro

Clone the minipro repo and compile it. When you use the -l flag with this program to list the supported devices it launches a "pager" (less). I do not like this behaviour so I delete the first block of code in the function print_devices_and_exit() and recompile to force the program to print to stdout. For me this behaves much more like a normal *nix utility (if I want to pipe to less I can just do that). Go free software!

Get the optiboot bootloader

The Ardunio IDE contains bootloaders in the installation directory (you can also download the bootloaders from github). For the atmega328p the bootloader file is "optiboot_atmega328.hex". This file is an ASCII text file containing HEX codes. It turns out that this is an Intel HEX file format "... that conveys binary information in ASCII text form". This can be converted to binary format using avr-objcopy (apt-get install binutils-avr):

avr-objcopy -I ihex optiboot_atmega328.hex -O binary optiboot_atmega328.bin

Once converted the binary is 512 bytes. This needs to be padded to the full memory size of the chip (32xxx bytes), otherwise minipro will refuse to write it. Initially I placed the bootloader at the beginning of memory assuming that is where the chip would start executing instructions on boot. Which is true, unless certain fuses are set. More on the fuses later. For now the following commands will pad the binary appropriately so the bootloader occupies the last 512 bytes of the image:

dd if=/dev/zero bs=1 count=32256 | tr "\000" "\377" > image.bin
dd if=optiboot_atmega328.bin bs=1 count=512 >> image.bin

The pipe to "tr" is to turn 0x00's into 0xff's. I am not sure if this is needed but I saw it mentioned somewhere that this was a good idea. This can now be written to the chip via minipro:

minipro -p ATMEGA328P -w image.bin

The fuses were something I struggled with a lot. The atmega328p has a mode where instead of starting execution at address zero, on reset the progam counter will jump to a specific starting address. This is very useful because you can load regular programs at address zero, but when you first start the microcontroller it will jump to this bootloader address where it can do some prelimiary work. I searched around a lot for information about the default Arduino fuse values and in the end the following values worked. Create a regular text file called config.txt and put this in it:

fuses_lo = 0x00ff
fuses_hi = 0x00de
fuses_ext = 0x00fd
lock_byte = 0x00ff

This can be written to the chip using the "-c config" option of minipro:

minipro -e -c config -w config.txt

It turns out the "-e" here is very important. By default minipro will erase the chip every time it writes. This includes resetting fuse values to their defaults and writing program memory with 0xff. For a long time I would write the program without "-e" which would reset the fuses to the default values. Then I would write the fuses without "-e" which would erase the program memory. For hours I went back and forward like this.

Once I had the chip working again I created a new image of GRBL, padded the space between GRBL and the bootloader start with 0xff and appended the bootloader. I then wrote the binary without "-e" as I had already set the fuses. However the verification step failed! When I dumped program memory into hexdump and looked at it, seemingly some of the bytes were randomly different. I wrote the binary again without "-e" so it erased everthing and write the fuse bits again. This worked, so just a word of warning that you can't just always have "-e" set on all minipro operations.