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