Monday, February 9, 2015

picobootSTK500: an Arduino compatible atmega bootloader in 216 bytes


picoboot was the first bootloader I wrote.  Although it hasn't been beat for size, the fact that it uses a proprietary protocol is a big drawback.  I soon found out that despite Avrdude being open source, there is no open and public process for contributing to it.  So I published a version of avrdude with picoboot support, but since I had emailed Joerg with my patches instead of posting to the avrdude mailing list, support for picoboot in the official avrdude release was not going to happen.

With Arduino Pro Mini boards available from China for only $2, there is no longer a significant cost advantage of using individual DIP AVR MCUs.  Many of these boards come with the stock Arduino bootloader, which is 2KB.  Optiboot was what I flashed to my pro mini boards to replace the stock bootloader.  Since optiboot doesn't wait long after reset for a flash download, and my PL-2303hx USB-TTL modules don't break out the DTR line, I developed a zero-wire auto-reset for the boards.  But since I couldn't get my patch added to avrdude, it would be hard for other people to use it.

My solution was to write a tiny bootloader that, like Optiboot, is compatible with the Arduino bootloader already supported by avrdude.  Like picoboot, I decided to write it in AVR assembler.  Optiboot is small enough to fit into the 512-byte minimum boot size of a mega328, but too big for the 256-byte minimum bootsize of a mega168.  Another design goal was for the bootloader to wait indefinitely, to avoid having to juggle the timing of pressing the reset button and uploading code to flash.  I didn't want to use the watchdog reset the way Optiboot does, since it makes it impossible for application code to detect a watchdog reset.  Lastly, if space allowed, I wanted to support eeprom writing (Optiboot only supports flash writes).

After a few months of on-and-off development, I have a working release.  There's no eeprom write support yet, but at 216 bytes of code, I still have 40 bytes to use for eeprom support and still have it fit in 256 bytes.  Besides being the smallest arduino-compatible bootloader, it has a unique reset-handling functionality.  Pressing the reset button once will go into bootloader mode, waiting with the LED dimly lit.  Pressing the reset button a second time will do a normal reset.  Due to delayed re-enabling of the read-while-write (RWW) section, it should be slightly faster than Optiboot.  Like my original picoboot bootloader, a basic blink program is included with picobootSTK500.

I have included two pre-built versions in the repository; one for the mega328 and another for the mega168.  I only have mega328's for testing, so I'd appreciate someone with a mega168 testing it out and dropping a note in the comments.  Lastly I'd like to thank Optiboot maintainer Bill Westfield for his help.  He is one of those pleasant programmers where his abilities exceed his ego - something I may yet achieve in the future. :-)

18 comments:

  1. Wow, that's in impressive reduction in size,along with extra functionality!

    One variant I'd like to see is changing the baud rate to 76800; that seems to be a great rate for Arduino Pro Mini's, which often have a (cheap) ceramic resonator. With a 16 MHz clock, 115200 is just too close to the error limits for reliable async, and it's worse at 8MHz. 76800 baud on the ATMegas however is very close to the right baud rate with either clock speed, and a number of people have reported that it is more reliable (more reliable than 57600 as well). It's usually even good enough for the internal RC oscillator.

    So what I'd like to use is your picoboot, with 8MHz and 16MHz versions using 76800 baud. (boards.txt also needs entries which specify this baud rate). EG see http://yveaux.blogspot.com/2014/07/changing-arduino-bootloader-to-optiboot.html

    I am not familiar with assembling and linking ATMega assembly language. Could you give some hint on how to make the needed changes, then to create the HEX file? (and/or provide downloadable versions - you may want to check out 76800 too!).

    Thanks!

    Very interesting blog, lots of good stuff - thanks for that too.

    ReplyDelete
    Replies
    1. picobootSTK500 uses a UBRR value of 16 (0x10), which gives 115.2kbps @16Mhz and 57.6Mhz at 8Mhz, which gives a timing error margin of 2.1%
      http://wormfood.net/avrbaudcalc.php?postbitrate=9600&postclock=8&u2xmode=1
      Ceramic resonators like the kind used on some pro mini boards are accurate to within 0.5%
      http://www.mouser.com/ProductDetail/Murata-Electronics/CSTCE16M0V53-R0/?qs=sGAEpiMZZMukHu%252bjC5l7YcAOEttiSxncqZKMMcyVO5c%3d
      So the total error is 2.6%; well within the ~5.8% margin of error before the timing error for 8N1 approaches 1/2 bit-time on the last bit.

      If you really want to do 76800, change line 86 from:
      ldi Temp, 16
      to:
      ldi Temp, 25

      then just run make to generate the .hex file.

      Delete
    2. p.s. I have considered making a version for 8Mhz running on the internal RC oscillator, which would include OSCCAL tuning based on the received bit timing. I might even be able to include auto-baud detection between 19,2 and 57.6kbps.

      Delete
    3. Thanks for the tip. I look forward to any future development you do on this.

      As for error - yes, if the other serial port's baud rate is dead on and the signals have good edges and no noise, 57600 or 115200 ought to work fine in theory. Sometimes it doesn't work reliably enough in practice tho (at least with cheap clone APM's), and the extra accuracy provided by 76800 baud has been useful.

      Delete
  2. Oh, one other thing. I've been wanting to serialize my Arduinos, so I can track which device has what code and is used where - including noting problems and being able to trace back to which batch/vendor I used. The best I can think of is to include a short serial number (even 1 byte - I have way less than 255 of them!) in a fixed location in the bootloader. In most cases I would only burn the new bootloader once; if I have to reburn I would just be careful to use the same serial. I can read and report the serial number in program code (unless prohibited by the fuses). Or I think it can be fetched using AVRDUDE by dumping the whole bootloader. I'll keep a spreadsheet with the details for each serial number. I was just going to put the serial number and its complement in the last bytes of the boot sector by tweaking the HEX, as there were a few zero bytes at the end of Optiboot. Maybe this can be done more gracefully within PicoBoot? What do you think?

    ReplyDelete
    Replies
    1. The undocumented signature row contents should give you a way to generate a unique id.
      http://www.avrfreaks.net/forum/undocumented-signature-row-contents

      Delete
    2. Thanks for that pointer, I was unaware of these extended sig bytes.

      I see that I can read them in my firmware using boot_signature_byte_get() from

      Is there any way to read them using AVRDUDE? Does PicoBoot implement anything its STK500 subset protocol which would return the extended signature row - not just bytes 0,2,4 for the normal signature?

      Thanks!

      Delete
    3. The signature is set at compile time (based on the Makefile DEVICE= line), so that it returns the same signature (i.e. m328p) for a regular m328 or 328p since the Arduino IDE expects the 328p, even though the m328 is fully code compatible.

      Delete
    4. Hi, Ralph. I don't think I was clear enough about what I was asking.

      AVRDUDE in serial mode (as used by the Arduino IDE) apparently that the device signature is as expected (unless over-ridden) by requesting those bytes from the bootloader (eg: PicoBoot, OptiBoot. That's three bytes out of the "signature row" (offsets 0, 2 and 4).

      By reading bytes 14 to 30 of that signature row one can get a unique ID (thanks for that tip). And those bytes CAN be read and reported by a program in flash - either a short sketch that does only that, or as a small part of newly written code that mostly does other things.

      Sometimes there is an already programmed ATMega whose ID I would like to know. So the question was: Is there any way for AVRDUDE or another program on the PC side to read those bytes (signature row 14..30) via the serial bootloader protocol? Does the serial protocol you implemented only allow AVRDUDE to read the three signature bytes from the row, or would it allow reading the other bytes of the signature row?

      If so, I could potentially get a unique ID for a programmed ATMega chip through the serial protocol without needing to flash a new program for that purpose, and then reflash the original program.

      Thanks for your insights; I hope you don't mind the discussion.

      Delete
    5. In the comments of the picobootSTK500 source I refer to the following protocol trace:
      http://baldwisdom.com/bootloading/

      The stk500 protocol does not specify which signature bytes to retrieve - the response to the get signature command is just the 3 "normal" signature bytes.

      If you were to use a USBasp, and avrdude terminal mode (-t option), you may be able to send a series of four-byte commands to retrieve arbitrary signature bytes ($30 $00 $addrLSB $00).

      Delete
    6. Thanks, that answers my question well.

      Presumably with the USBasp, one could write a program to fetch the ID (eg: which can be integrated into a shell script or make file).

      Is there any obvious & simple way to extend the serial protocol to fetch those bytes? (Since the 328p seems to be about as cheap as the 168, for that chip there's still 300+ bytes to burn in the minimum boot sector, right?)

      Delete
    7. With a USBasp and avrdude you could write a script that makes a pipe connection to avrdude in terminal mode.

      In the STK500 protocol, commands are just single ASCII characters followed by a space (the end-of-packet character). You could add a custom command to the bootloader, and have it respond with your custom unique ID.

      You are correct about 512 bytes being the minimum bootsector size on the m328 (vs 256 bytes on the 168).

      Delete
  3. Third paragraph should say "apparently CHECKS that the device signature is as expected"

    ReplyDelete
  4. hi guys

    i am very interested in making unique ID for every chip
    have u achieved that?
    if so can u show me the results

    ReplyDelete
    Replies
    1. I just gave Zeph some suggestions, but haven't had a reason to try it myself.
      Another option I thought of would be to generate a UUID in the makefile, and append it to the bootloader before flashing.

      Delete
    2. The route I am pursuing is using the (apparently) unique ID already embedded in AVR uC's, in the signature row.

      The signature row contains the three processor ID bytes (at offsets 0,2,4) and the calibration byte (at offset 1). These are documented in the datasheet, but the same commands can read other offsets from the signature row, very similar to reading from Flash (ie: one command for low bytes, one command for high bytes). There is a string at offsets 14-19, and some fabrication info in offsets 20-30; I think it has to do with dates or runs, wafers, and position on wafer; combined with the 3 processor ID byte it seems to be unique. The advantage here is that this unique ID is already present from the factory, and is preserved even if you burn a new bootloader (much less a sketch). One disadvantage is that it's longer - more bytes to store and compare against (I was going to use a single byte sequential ID on my processors, as I don't expect to exceed 256 of them).

      There are two ways to read these extended signature row bytes:
      1) Internally, from a program in the uC Flash (ie: a sketch or a bootloader) you can use the boot_signature_byte_get() function.
      2) Externally using the six pin ISP header, you can use commands 0x30 and 0x38 to read even and odd bytes respectively (analogous to how you read Flash). 0x30 is documented in the datasheet for the three byte processor ID, and 0x38 for the calibration byte, but they work for higher addresses too.

      However, these extra signature row bytes are not available over any of the normal bootloading protocols (eg: STK500 and variants) that operate over a serial port. There IS a command to fetch just the 3 processor ID bytes at fixed offsets, but no command that fetches more, or that allows the other end to specify the offsets.

      So one can fetch the unique ID using method 1 above by temporarily uploading a sketch that prints it out once before loading your working sketch (or embedding that function in your working sketch).

      Or you can connect another Arduino to the 6 pin ISP header (same as you would for the ArduinoISP sketch) and run a sketch which fetches the unique ID bytes.

      Delete
    3. Suppose you wanted to fetch it via Serial port instead, without modifying all your sketches or temporarily loading a fetcher sketch as described above.

      Given that a bootloader firmware can use method 1, it would be possible to extend the STK500 protocol to include fetching bytes from the signature row at spedified offsets, just as Flash or EEPROM can be read. Then one could add that to a bootloader firmware; Optiboot doesn't have room for this within a 512 byte minimum boot size, but Ralph's picoboot variant described on this page should have room.

      Then you could modify AVRDUDE to have an additional feature of dumping the signature row, using the above extended STK500 protocol. Or write another program on the PC/Mac/Unix end to do that.

      Or you could modify one of the ISP programmers to fetch the unique ID via the ISP header. This could be done most easily from ArduinoISP. Again, you'd need to modify AVRDUDE or write another program to make use of this.

      (I think the ATtiny based ISP external programmer might be able to handle this without modification; in one exploration it looked like it allowed the PC software - eg: AVRDUDE - to send it the 4 byte ISP commands it would send to the target system, so this might work with the commands mentioned above under #2 - but you'd have to modify the controlling program, like AVRDUDE).

      Let me be clear about something that would have helped me at the start. The processor registers and instructions would allow a bootloader firmware to access the unique ID, but the standard protocols for communicating with it have no commands to transfer it. And the ISP commands built into the chip would allow an external ISP programmer to access it, but again there's no place in the protocols used to communicate with those. Digging into this one finds layers and layers of protocols and command sets.

      Whew. That's a dump of all I've learned (in snippets of time) in one place.

      Delete
    4. By contrast, you could assign your own ID number, stored in Flash. Of course it could be in your sketch, but that's a bit dicey with sketches being changed, and the same sketch potentially going into multiple chips. So putting it at the end of a bootloader firmware (my origional approach) is still possible. Optiboot has a few spare bytes at the end, and the picoboot derivative has lots of room.

      This has the advantage that your ID can be read by a sketch (lock bits permitting), or you could use standard STK500 derived protocols (and in particular the stock bootloader firmware, or external ISP programmers, and stock tools like AVRDUDE to fetch the Flash contents.

      At this point I'm almost ready to begin an inventory of my Arduinos. Varioius Unos (R2, R3, chinese variants with CH340 or other chips), Pro Mini's at various speeds and voltages, Mega2560 clones, Nano's, Leonardo. I would keep a table of which is what, including the source for each one (that I can still decipher from my note and old orders).

      I'm not yet sure if I'll modify Ralph's picoboot for this purpose or not. This is something I spend a little time on once in a while. I'm about to set it aside for other aspects of life again, I just happened to check in here and saw your interest.

      Thanks to Ralph for helping me begin to sort this stuff out, in any case. Hope this helps you too.

      Delete