μBoot-AvrTiny – bootloader for ATTiny13

Napisano dnia 2.09.2019 r. o godzinie 21:10
Autor: Piotr Sperka

Introduction

Recently I’ve been working on bootloaders for microcontrollers based on the ARM architecture. In my free time I decided to write a bootloader for ATTiny13, which is one of the smallest MCUs in the AVR family. Unlike larger AVRs, ATTiny13 doesn’t have hardware support for booting from the bootloader sector, it doesn’t have the ability to reallocate the interrupt table. The MCU does not even have a hardware UART, which I decided to use as a communication interface. μBoot-AvrTiny (as I called my bootloader) in its simplest form takes only 80 words (160 bytes) of FLASH and allows to use of the UART routines in the user program.

Communication

For communication with the computer I’ve decided to use UART. Mainly because of the huge popularity of the interface and its ease of use from virtually any computer, tablet or smartphone with a cheap USB adapter. As I mentioned, ATTiny13 isn’t equipped with hardware UART. However, many years ago Atmel released the AVR305 note, which describes optimised implementation of software UART. So I used the code from that note with a slight modification allowing using only one pin for half-duplex transmission. That kind of connection also provides a “hardware” echo (everything we send via the Tx line automatically returns to via the Rx line). With a 1.2MHz clock, a rate of 9600 baud/s is easily achieved in the standard 8N1 configuration. Bit rate can be adjusted using the baud constant at the beginning of the code. The formula for its calculation can be found in mentioned AVR305 note.

Reading and writing memories

AVR microcontrollers allow self-programming, which is feature that is necessary to make a bootloader. Remember that this feature must be activated in fuse-bits, and isn’t active in factory default settings. Programming FLASH memory must be done by pages, in addition, before saving data on the page, you must clear it – this is due to the way FLASH memory works. For ATTiny13, the page has 32 bytes. EEPROM doesn’t have such restrictions and programming can be done both page by page (in this case page is 4 bytes long) or byte by byte.

Reading and writing FLASH

The programming of each page of the FLASH memory contains:

  • Loading the temporary buffer with 32 bytes of data.
  • Clearing the page.
  • Writing data from the buffer to the page.

An appropriately commented code snippet responsible for writing a FLASH page looks like this:

_PROGRAM_PAGE:
  ldi		R25, 0x10 ; 16 words to write into flash
  ldi		R24, 0x01 ; New value for SPMCSR register
  rcall	getchar ; Get more significant part of Z register
  mov		R31, Rxbyte
  rcall	getchar ; Get less significant part of Z register
  mov		R30, Rxbyte
_PP_GDL:
  rcall	getchar ; Get Data Loop, get more significant byte of word
  mov		R0, Rxbyte
  rcall	getchar ; Get less significant byte of word
  mov		R1, Rxbyte
  out		SPMCSR, R24
  spm		; Write word to temp buffer
  dec		R25
  breq	_PP_ERASE
  inc		R30 ; Increment PCWORD by 2 (Z reg)
  inc		R30
  rjmp	_PP_GDL

; Erase page
_PP_ERASE: 
  ldi		R24, 0x03 ; New value for SPMCSR
  out		SPMCSR, R24
  spm

; Write flash
  ldi		R24, 0x05 ; New value for SPMCSR
  andi	R30, 0xE0 ; Zero PCWORD in Z
  out		SPMCSR, R24
  spm

; Send response
  ldi		Txbyte, 'Y'
  rcall	putchar

Reading data from the FLASH memory is much simpler. The following code should explain itself:

_READ_FLASH:
  ldi		R24, LOW(2*(FLASHEND + 1))
  ldi		R25, HIGH(2*(FLASHEND + 1))
  clr		R30
  clr		R31

_RFL:
  lpm		Txbyte, Z+
  rcall	putchar
  sbiw	R24, 1
  brne	_RFL

Reading and writing EEPROM

In a more extensive version of the bootloader it is also possible to read and write EEPROM. The code is much simpler than in the case of program memory:

_READ_EEPROM:
  clr		R25
_REL:		
  sbic	EECR, EEPE ; Wait for completion of previous write
  rjmp	_READ_EEPROM
  out		EEARL, R25 ; Set up address (r17) in address register
  sbi		EECR,EERE ; Start eeprom read by writing EERE
  in		Txbyte, EEDR ; Read data from data register
  rcall	putchar
  inc		R25
  cpi		R25, (EEPROMEND + 1) ; EEPROM is 64 bytes, so we need only one register to count
  brne	_REL

  rjmp	_READ_LOOP

_WRITE_EEPROM:
  sbic	EECR, EEPE ; Wait for completion of previous write
  rjmp	_WRITE_EEPROM
  ldi		R16, (0<<EEPM1)|(0<<EEPM0) ; Set Programming mode
  out		EECR, R16
  rcall	getchar ; Set up address (r17) in address register
  out		EEARL, Rxbyte
  rcall	getchar ; Write data (r16) to data register
  out		EEDR, Rxbyte
  sbi		EECR, EEMPE ; Write logical one to EEMPE
  sbi		EECR, EEPE ; Start eeprom write by setting EEPE

; Send response
  ldi		Txbyte, 'Y'
  rcall	putchar

  rjmp	_READ_LOOP

Communication protocol

Our microcontroller is equipped with only 1kB of program memory, hence I put great emphasis on the smallest size of the bootloader. This in turn leads to the conclusion that the communication protocol must be really simple. To read the entire FLASH memory, send the “R” character. In response, you will receive 1024 bytes corresponding to the contents of the FLASH. Writing is a bit more complex. First, send the “P” character followed by the frame:

Z = (FLASH_PAGE_NUMBER << 5) & 0xFFFF

Success is confirmed by the “Y” character sent by AVR. If microcontroller doesn’t respond, it means something went wrong (ex. you sent too little data).

When EEPROM support is compiled in, reading works similarly as for FLASH memory. After receiving the “E” character, the device will send all 64 bytes of EEPROM. Writing can be done by sending the “F” character, and then sending two more characters: address (0-63) and data (0-255). Success will be confirmed by the returned “Y” character.

Because bootloader uses software UART, the AVR is not able to receive new bits when processing previous data. The result is that sending data from the computer in the form of a byte array (which produces data with no time gaps between bytes) usually results in transmission errors. The remedy for this problem is sending data byte by byte, calling the flush function each time.

How to use bootloader?

Above, I have provided an example schematic. The diode connected to PB3 is only for testing purposes, while C1 and R2 can be theoretically eliminated. To test bootloader operation and make it easier to use, I wrote a very simple Python program. This program allows reading the contents of both memories to a binary file, as well as writing to memory from binary files. Hex files are not currently supported, however, firstly, programs converting hex to bin are available, and secondly, you can add such a function if you need.

The program also takes care to not overwrite the bootloader with the uploaded binary, it also takes care of splitting the binary file into appropriate pages and sending data to the bootloader. It also ensures that the only necessary change in the binary file is made – it modifies the rjmp instruction at 0x00 (the address from which the microcontroller starts after reset) so that microcontroller jumps to the bootloader. Normally, we place the jump instruction to the beginning of the program here.

To start the bootloader, short the data line to ground (Boot button), reset the microcontroller (Reset button), release the Reset button, and finally release the Boot button. Restarting in normal mode is done by resetting the microcontroller with the data line in a high state.

Known limitations

μBoot-AvrTiny is very simple, and although it fulfills its function, you must be aware of certain restrictions. First, the program must start at 0x0A (directly after the interrupt table), because the bootloader jumps to this address to start the user application. This can of course be changed by compiling your bootloader version. Secondly, it is possible to overwrite (damage) the bootloader code by attempting to write the pages on which it is located. It’s on the computer side (ex. mentioned Python script) to make sure that only those pages that are intended for it are overwritten. It is also on the computer side to modify the binary the way it jumps to the bootloader after reset.

Summary

To sum up, I was able to write a simple but fully functional bootloader for the ATTiny13 microcontroller, containing only 80 words of code. In addition, part of the bootloader (routines for sending and receiving data via UART) can be used in the user application. If the topic interested you and you want to try it out, go to my GitHub. There you will find sources and compiled versions of the bootloader along with test applications (LED flashing and the use of the UART functions from the bootloader) and Python script: https://github.com/PiotrSperka/avrTinyBootloader.

Finally, if you have any comments, questions or suggestions, please contact me. See you in the next article!