Funktionsweise Luna-Bootloader

Zusammenfassung aus dem Beitrag zur Erläuterung der Bootloader-Funktionalität im Luna-Forum.

Ein sogenannter Bootloader ist ein kleines Programm, dass durch setzen verschiedener Fusebits im Avr-Controller vor dem eigentlichen Anwenderprogramm ausgeführt wird. Ein Bootloader ist ebenfalls im gleichen Flash-Speicher des Controllers gespeichert wie das Anwenderprogramm, jedoch mit dem Unterschied, dass es kurz vor dem Ende des Flashspeichers auf bestimmten Grenzadressen abgelegt wird. Die Grenzadresse ist der Einspringpunkt, den der Controller nach einem Hardwarereset anspringt, wenn das Fusebit BOOTRST aktiviert wurde. Welcher der zumeist vier verschiedenen Grenzbereiche angesprungen wird, bestimmen die Fusebits BOOTSZ0 und BOOTSZ1. Das besondere daran ist, dass aus dem Bootloaderbereich der interne Flashspeicher beschrieben werden kann (bis auf den Bereich in dem der Bootloader selbst liegt).

Je nach Controller legen die beiden Fusebits BOOTSZ0 und BOOTSZ1 fest, wie groß der Bootloaderbereich sein soll. Die Größe des Bootloaderprogramms bestimmt hier welche Größe zu wählen ist. Die verfügbaren Größen unterscheiden sich zwischen den verschiedenen Controllern, daher ist es notwendig die Werte im Datenblatt nachzuschlagen.

Ist ein Bootloader auf den Controller hochgeladen, sieht es im Flash-Speicher des Controllers dann beispielsweise so aus:

S----------------------------------------|-----|xxxxx|xxx|x--E
                                         :     :     :   :
                                         :     :     :   +FIRSTBOOTSTART
                                         :     :     +SECONDBOOTSTART
                                         :     +-THIRDBOOTSTART
                                         +FOURTHBOOTSTART
S = Start Flashspeicher
E = Ende Flashspeicher
| = Bootloadergrenzen
x = Bootloaderprogramm

Wenn der Bootloader normal geflasht ist, startet der Controller ab der Eingestellten Adresse, in diesem Fall wie aus dem Screenshot oben mit aktiviertem Fusebit „BOOTSZ1“ THIRDBOOTSTART.

Der Luna-Bootloader prüft 10x mit sehr kurzen Pausen ob ein Kommando eingetroffen ist das ihn in den Flashmodus versetzt, andernfalls springt der Bootloader mittels Kommando „RESET“ zu Adresse 0x0000 und damit zu einem dort abgelegten Anwenderprogramm. Wenn dort keins zu finden ist, wandert der ProgrammCounter durch den gesamten Flash bis zum Bootloader, wo er wieder ausgeführt wird. Dies deshalb,weil beim Flashen in die nicht genutzten Bereiche der Wert 0xff geschrieben wird, das ist der Maschinenbefehl „nop“ (tue nichts).

Ab sofort darf man sein Anwenderprogramm nun nicht mehr mit z.Bsp. AvrDude auf den Controller hochladen, da sonst der Bootloader gelöscht/überschrieben würde. Man lädt nun das Anwenderprogramm z.Bsp. mit dem bootUploader auf den Controller. Ist dies geschehen, sieht es im Speicher anschließend so aus:

Spppppppppppppppppppppppppp--------------|-----|xxxxx|xxx|x--E
                                         :     :     :   :
                                         :     :     :   +FIRSTBOOTSTART
                                         :     :     +SECONDBOOTSTART
                                         :     +-THIRDBOOTSTART
                                         +FOURTHBOOTSTART
S = Start Flashspeicher
E = Ende Flashspeicher
| = Bootloadergrenzen
x = Bootloaderprogramm
p = Anwenderprogramm

Wenn man nun aus dem Anwenderprogramm einen Neustart durchführen möchte, darf man dabei nicht zur Adresse 0x0000 springen (geschieht mit dem Luna-Befehl reset). Dies würde nur das Anwenderprogramm selbst neu starten, sonst nichts. Es ist notwendig erst den Bootloader anzuspringen, damit dieser auch ausgeführt wird: jump THIRDBOOTSTART

Beispiel

Dieses Beispiel beinhaltet den Bootloader, angepasst für den Atmega168, sowie einem passenden Anwenderprogramm. Das Controllerboard benötigt dann eine serielle Verbindung zum PC.

Die notwendigen zu aktivierenden Fusebits sind:

[x] BOOTRST
[x] BOOTSZ0
[x] BOOTSZ1
Der Einspringpunkt (bzw. Bootloaderadresse) ist in diesem Fall dann FOURTHBOOTSTART.

Vorgehensweise

  • Fusebits lesen und obige Fusebits aktivieren (Häkchen gesetzt).
  • Fusebits schreiben.
  • Bootloader kompilieren und in den Controller hochladen.
  • Testprogramm nur kompilieren.
  • bootUploader starten, kompiliertes Testprogramm wählen, Schnittstelle & Baudrate einstellen und Verbinden.
  • Mit „Upload Flash“ das Testprogramm hochladen.

Dateien

bootloader-m168.luna
'******************************************************************************
' Author      : rgf, avr@myluna.de, http://avr.myluna.de
' Title       : bootloader example
' Last updated: 09.02.2015
' Protocol    : v1.0
' Description : flash and eeprom write with crc check, retry and error handling
' Tools       : Uploader software "bootUploader" http://avr.myluna.de
'******************************************************************************
' Compiler    : LunaAVR 2015r1 or newer
'******************************************************************************
avr.device = atmega168
avr.clock = 20000000
avr.stack = 64
 
#library "Library/Crc8.interface"
'******************************************************************************
'*** CHANGE CODE START ADDRESS TO BOOT LOADER SECTION *************************
'******************************************************************************
'Luna Bootloader requires 2 kb bootloader section
avr.CodeStartAddr	= nrww_start_addr
const MAX_PAGES		= nrww_start_addr/pagesize
'******************************************************************************
 
uart.baud = 19200
uart.Recv.enable
uart.Send.enable
 
#define SLED as portd.6			'status led
#define WLED as portd.7			'write action led
 
SLED.mode = output,low		'status led
WLED.mode = output,low		'write led
 
 
'******************************************************************************
'luna bootloader protocol (universal)
'******************************************************************************
' input commands
const PROTO_RDY		= 0x01		'access bootloader and keep alive when accessed
const PROTO_CBV		= 0x02		'read controller base values, answer:
const PROTO_EFP		= 0x04		'erase flash page, command: <word:pagenum>, answer: <ack>
const PROTO_WFP		= 0x05		'erase and write flash page, command: <word:pagenum><bytes:data><byte:crc8>, answer: <ack>
const PROTO_EEP		= 0x06		'erase eeprom page, command: <word:pagenum>, answer: <ack>
const PROTO_WEP		= 0x07		'erase and write eeprom page, command: <word:pagenum><bytes:data><byte:crc8>, answer: <ack>
'	long	flashsize_bytes
'	long	bootloaderStartAddr_bytes
'	word	eepromSize_bytes
'	word	pagesizeFlash_bytes
'	word	pagesizeEeprom_bytes
'	byte	crc8
'	byte	<ack>
const PROTO_END		= 0xff		'end of processing, reset controller
' input/output commands
const PROTO_ACK		= 0xfd		'acknowledge
const PROTO_NAK		= 0xfe		'not acknowledge
' memory types
const MEMTYPE_FLASH		= 0
const MEMTYPE_EEPROM	= 1
' misc values
const COMMAND_TIMEOUT	= 10000				'command timeout in ms
const PAGE_BYTES		= PageSize*2		'page size flash in bytes
const PAGE_BYTES_EE		= 8					'page size eeprom in bytes (do not change!)
 
 
'******************************************************************************
'******************************************************************************
' main program
'******************************************************************************
dim i,cnt,cmd,buffer(PAGE_BYTES-1),type as byte
dim ctcnt,page,eadr as word
dim rtcnt as long
 
main()
 
procedure main()
  if CheckBootloaderAccess() then
    ack()
    do
      cmd=uart.GetByte
      if cmd then
        clr ctcnt
        select case cmd
        case PROTO_RDY
          ack()
        case PROTO_CBV
          uart.writelong avr.flashsize
          uart.writelong avr.CodeStartAddr*2
          uart.writeword avr.eramsize
          uart.writeword PAGE_BYTES
          uart.writeword PAGE_BYTES_EE
          crc8.Start
          crc8.AddLong avr.flashsize
          crc8.AddLong avr.CodeStartAddr*2
          crc8.AddWord avr.eramsize
          crc8.AddWord PAGE_BYTES
          crc8.AddWord PAGE_BYTES_EE
          uart.WriteByte crc8.value
          ack()
        case PROTO_EFP
          ack()
          type=MEMTYPE_FLASH
          ErasePage()
        case PROTO_WFP
          ack()
          type=MEMTYPE_FLASH
          EraseWritePage()
        case PROTO_EEP
          ack()
          type=MEMTYPE_EEPROM
          ErasePage()
        case PROTO_WEP
          ack()
          type=MEMTYPE_EEPROM
          EraseWritePage()
        case PROTO_END
          reset
        default
          clr ctcnt
          nak()
        end select
      end if
      delay(1)
      incr ctcnt
    loop until ctcnt=COMMAND_TIMEOUT
  end if
  'jump to main program
  reset
endproc
 
'******************************************************************************
'******************************************************************************
'sub routines
'******************************************************************************
'******************************************************************************
procedure delay(a as byte)
  waitms a
endproc
 
'******************************************************************************
'check if bootloader called
function CheckBootloaderAccess() as byte
  for i=0 to 9
    SLED.toggle
    if uart.GetByte=PROTO_RDY then
      return true
    end if
    delay(50)
  next
  SLED = 1
  delay(1000)
  SLED = 0
  return false
endfunc
 
'******************************************************************************
'******************************************************************************
'erase/write page
'******************************************************************************
'******************************************************************************
procedure ErasePage()
  page=uart.readword
  if MAX_PAGES>=page then  'prevent overwriting bootloader section
    decr page									'page number for writing is zero-based
    WLED=1
    if type=MEMTYPE_FLASH then
      for i=0 To buffer().Ubound
        buffer(i) = 0xff
      next
      flash.PageWrite buffer(),page			    'write the page data to flash
    else
      eadr=page*PAGE_BYTES_EE
      for i=0 To PAGE_BYTES_EE-1
        eeprom.bytevalue(eadr) = 0xff
        incr eadr
      next
    endif
    ack()
    WLED=0
    return
  end if
  nak()
endproc
 
procedure EraseWritePage()
  page=uart.readword
  if MAX_PAGES>=page then  'prevent overwriting bootloader section
    crc8.start									'initialize crc calculation
    crc8.AddWord page							'add page number to crc stream
    if type=MEMTYPE_FLASH then
      cnt=buffer().Ubound
    else
      cnt=PAGE_BYTES_EE-1
    end if
    for i=0 To cnt								'read page data
      buffer(i) = uart.ReadByte				  'add to buffer
      crc8.AddByte buffer(i)					  'add to crc stream
    next
    if uart.ReadByte = crc8.value then			'equate received and calculated crc
      decr page								  'page number for writing is zero-based
      WLED=1									  'indicate write
      if type=MEMTYPE_FLASH then
        flash.PageWrite buffer(),page			  'write the page data to flash
      else
        eadr=page*PAGE_BYTES_EE
        for i=0 To PAGE_BYTES_EE-1
          eeprom.bytevalue(eadr) = buffer(i)
          incr eadr
        next
      endif
      WLED=0
      ack()
      return
    end if
  end if
  nak()
endproc
 
procedure ack()
  uart.WriteByte PROTO_ACK				'say OK to transmitter
endproc
procedure nak()
  uart.WriteByte PROTO_NAK				'failed
endproc
testprogramm.luna
'
'Testprogramm fuer Bootloader-Aufrufe aus dem Programm heraus
'
'compiler:
'---------
'luna 2015r1 or newer
'
'history:
'--------
'2013-02-12 demel start
'2015-02-09 Anpassung an 2015r1 or newer
'
'references:
'-----------
'Luna Bootloader:         http://avr.myluna.de/doku.php?id=de:bootloader
'TaskMgr:               http://avr.myluna.de/doku.php?id=de:taskmgr
'ATmega88/168/328/32 doku:    http://www.atmel.com
'LunaAVR doku:           http://avr.myluna.de/doku.php?id=de:download
'
 
avr.device = atmega168
avr.clock = 20000000
avr.stack = 64
 
#library "Library/TaskTimer.interface"
#library "Library/TaskMgr.interface"
 
const USE_SERIAL = 1
 
'Die Baudrate muss mit der im Bootloader uebereinstimmen!!
#if USE_SERIAL
  uart.baud = 19200
  uart.Recv.enable
  uart.Send.enable
#endif
 
'Je nach Kontroller START_BOOTLOADER und die Fuses anpassen
#if avr.device = "atmega88"
  const START_BOOTLOADER   = FOURTHBOOTSTART
#endif
#if avr.device = "atmega168"
  const START_BOOTLOADER   = FOURTHBOOTSTART
#endif
#if avr.device = "atmega328p"
  const START_BOOTLOADER   = THIRDBOOTSTART
#endif
#if avr.device = "atmega32"
  const START_BOOTLOADER   = THIRDBOOTSTART
#endif
 
#define STATUS_LED as portd.5   'Status LED gruen, nur zum testen
 
STATUS_LED.mode = output,low
 
'Benutzereingabe RS232 Konsole
Dim chars as string
 
part TaskManager_initialisieren
  'Task usw. fuer TaskMgr definieren
  #define TIMED_TASKS		as 2			'2 Tasks im TaskTimer
  #define TIMED_TIMESLICE	as 1	 		'TaskTimer lauft mit 1 ms
  #define MAXIMUM_TASKS 	as 2			'2 Tasks im TaskManager
 
  const FLASH_TIME1   = 150      '150 * 1ms = 150ms
 
  'TaskMgr initialisieren
  TaskMgr.Init(MAXIMUM_TASKS)
  TaskMgr.TaskAdd(  task_led1_toggle().Addr, FLASH_TIME1)
  'TaskMgr.TaskAdd( task_led_gruen_toggle().Addr, FLASH_TIME2)
 
  'TaskTimer initialisieren mit 1 Task und einem Intervall von 1 ms.
  TaskTimer.Init(Timer2, TIMED_TASKS, TIMED_TIMESLICE) '1 Tasks, 1 ms
 
  'TaskTimer-ISR soll nur die selbst veraenderten Register sichern.
  'Dies weil wir eine Methode aus einer Library setzen, welche ihrerseits ebenfalls
  'ihre veraenderten Register sichert. Dies spart Stack-Speicher.
  TaskTimer.SaveMode = selfused
 
  'Die PollRoutine des TaskMgr in den TaskTimer eintragen
  TaskTimer.Task(0) = TaskMgr.Poll().Addr
endpart
 
' Enable all Interrupt
Avr.Interrupts.Enable
 
#if USE_SERIAL
  print "Test Bootloader"
  print avr.device
#endif
 
do
  'Konsolenfunktion fuer Bootloader-Bearbeitung
  'hier koennen bei Bedarf natuerlich noch mehr Befehle stehen.
  Print "Bootloader Demo > "
  chars = uart.InpStr()
 
  select case chars
  case "reset"
    jump START_BOOTLOADER
  case "cls"
    print 12;
  case "help"
    call help()
  default
    print "Kein gueltiges Komamndo"
  endselect
loop
 
idle
  'Eigenen Programmcode hier einfuegen
  if TaskMgr.Tick then                  '1ms Takt vom TaskManager
    TaskMgr.Schedule()               	'Scheduler aufrufen
  endif
endidle
 
procedure task_led1_toggle()
  STATUS_LED.toggle
  taskmgr.TaskAdd( task_led1_toggle().Addr, FLASH_TIME1 )
endproc
 
procedure Help()
  print " Kommando            Funktion"
  print "--------------------------------------------"
  print " help                Befehlsuebersicht"
  print " cls                 Bildschirm loeschen"
  print " reset               Neustart durchfuehren"
  print
endproc