⇐ zurück zur Hauptseite

Zunächst muss die Speicheraufteilung und der Programmstart nach einem Reset geregelt werden:
.section .init0
;-------------------------------------------------------
; Interrupt-Vekroren
;-------------------------------------------------------
; Beim Laden eines Programms achtet der Uploader darauf,
; dass diese Interrupt-Vektoren erhalten bleiben

.ORG 0x0000
   rjmp BootLoaderStart    ; Power-On-Reset: Springe zum Bootloader

.ORG 0x001a
   rjmp    __vector_13     ; USI Start Condition
   rjmp    __vector_14     ; USI Counter Overflow


.ORG 0x001e                ; Platz für die Interrupt-Vektoren lassen

;--------------------------------------------------------------------
Init:                      ; C-Programme landen hier
    rjmp Init              ; Endlos-Schleife, wenn kein Programm geladen ist.

Für diese Aufgabe gibt GCC die Section "init0" vor. An der Speicher-Adresse 0x0000 sind zwei Byte für eine Sprunganweisung reserviert, die beim Power-On-Reset ausgeführt wird. Hier wird zum Bootloader (Marke: BootloaderStart) gesprungen. C-Programme, die mit gcc übersetzt wurden, springen an eine Adresse direkt hinter den anderen Interrupt-Vektoren, hier mit "Init" deklariert. Wenn der Bootloader merkt, dass er nicht gefordert ist, wird hierhin gesprungen.

Damit dieser Mechanismus erhalten bleibt, muss der Uploader dafür sorgen, dass der Sprung zum Bootloader und die Interrupt-Vektoren erhalten bleiben. Außerdem muss er prüfen, ob das zu ladende Programm auch wirklich mit einem Sprung zu 0x001e (Init) beginnt. Wenn nicht handelt es sich nicht um ein Standard-C-Programm und die Logik, wie der Bootloader an das eigentliche Programm weitergibt, wird nicht funktionieren.
;------------------------------------------------------
; Bootloader
;------------------------------------------------------
.ORG BOOTLOADERSTART, 0xFF
    rjmp I2CSlave_Initialize
    rjmp I2CSetAddress

Mit .ORG BOOTLOADERSTART wird der Speicherbereich des Bootloaders festgelegt. Der Parameter 0xFF bewirkt, das die übersprungenen Bytes mit 0xFF vorbelegt werden. 0xFF ist der Wert eines unprogrammierten Flash-Bytes.

Der eigentliche Code beginnt hier allerdings noch nicht. Zunächst ist ein Sprung zur Initialisierungs-Routine des I²C-Slave-Treibers codiert. BOOTLOADERSTART hat einen bekannten Wert. Die eigentliche Funktion jedoch nicht. Die wird vom Linker festgelegt und hängt von vielen nicht beinflussbaren Parametern ab. Die über BOOTLOADERSTART bekannte Sprunganweisung kann von den geladenen Programmen genutzt werden, um den Treiber zu initialisieren. Analog funktioniert der Sprung zur Adressänderungsfunktion. Beide Sprünge werden über "Interface.h" und "Interface.o" mit den Bezeichnungen "I2C_Initialize" und "I2C_SetAddress" veröffentlicht.

BootLoaderStart:
.global BootLoaderStart
;------------------------------------------------------
; Initialisierung des Stackpointer
;------------------------------------------------------
   ldi temp,HIGHBYTE(RAMEND);Stackpointer auf letzte
   out SPH,temp              ;RAM-Adresse setzen
   ldi temp,LOWBYTE(RAMEND)
   out SPL,temp

   rcall BootLoader          ; Sprung zum BootLoader-Code(in C codiert)

   rjmp Init                 ; Sprung ins geladene Programm.
                             ; Diese Stelle wird nur dann erreicht,
                             ; wenn der Bootloader erkennt, dass
                             ; nichts geladen werden soll.

.global BootLoaderStart veröffentlicht die Marke, so dass im C-Code darauf zugegriffen werden kann.

Die ersten vier Anweisungen initialisieren den Stack. rcall BootLoader vezweigt in den in C geschriebenen Teil des Bootloaders. Der Funktionsaufruf per rcall hat den Vorteil, dass die aufgerufene C-Funktion einfach nur eine return-Anweisung auszuführen braucht um zurückzukehren. Das tut sie dann auch, wenn der Bootloader nicht aktiv werden soll (der aktive Bootloader jedoch verweilt bis zum nächsten Reset in einer Endlosschleife).

Der Sprung zur Marke Init bewirkt die Ausführung des regulären Programms, wenn der Bootloader erkennt, dass er nicht aktiv werden soll.


.section boot
PI2CSlave_TransmissionStart: .word 0
PI2CSlave_DataRequest:       .word 0
PI2CSlave_DataReceived:      .word 0
I2CSlave_DeviceAddress:      .byte 0
USI_TWI_Overflow_State:      .byte 0  
.global PI2CSlave_TransmissionStart
.global PI2CSlave_DataRequest
.global PI2CSlave_DataReceived
.global I2CSlave_DeviceAddress
.global USI_TWI_Overflow_State

Der I²C-Slave-Treiber benötigt acht Bytes für die Speicherung von Adressen und Status-Informationen. Diese werden reserviert und veröffentlicht. Es sind die Adressen für die Eventhandler, die Geräte-Adresse und ein Status-Byte. Nähers hierzu findet man an den ensprechenden Stellen.

Das Besondere ist, dass für diesen Speicherbereich eine eigene Section ("boot") vergeben wurde. Hierdurch kann der Adressbereich genau vorgegeben werden. Das geladene Programm kennt dadurch den reservierten Speicherbereich und muss sicherstellen, dass er nicht überschrieben wird!

Die Section "boot" wird an den Anfang des RAMs gelegt. Das Ende ist zum einem vom Prozessortyp abhängig und zum anderen durch den Stack belegt. Der Stack wird von Standard-Bibliotheken initialisiert. Hier etwas zu ändern würde bedeuten, dass bei jeder neuen Version der Bibliotheken die Anpassung nachgefahren werden müsste. Ich kenne keinen Weg, dieses mit erträglichem Aufwand zu bewerkstelligen.

Der Anfang des RAMs wird vom GCC mit der Section ".data" belegt. Diese muss nach hinten verschoben werden. Im AVR-Studio sieht das wie folgt aus: Memory Settings im AVR-Studio

Die entsprechenden Schalter für den Linker sind: "-section-start=boot=0x800060" und "-section-start=.data=0x800068".

Geladene Programme müssen natürlich nur die Section ".data" verlegen. "boot" kennen sie nicht.

Zu beachten ist, das die Startsequenz der Standarbibliothek nicht eingebunden werden darf. Die entsprechenden Schalter für den Linker sind: "-nostartfiles" und "-nostdlib".

⇐ zurück zur Hauptseite