Routine zur Programmierung des im MCF51x integrierten Flashes

Zusammenfassung und Inhaltsverzeichnis

In diesem Dokument wird das Vorgehen zur Programmierung des in einem MCF51xx Prozessors integrierten Flash-ROMs dokumentiert.

Allgemeines

Der die Microcontroller der MCF51xx-Familie bieten den Programmierkomfort eines Coldfire-Prozessors (und damit fast eines Motorola 68000) kombiniert mit der elektronischen Anspruchslosigkeit eines normalen Microcontrollers. Ferner sind die beiden integrierten UART-ähnlichen Schnittstellen im Projekteinsatz sehr praktisch.

Leider ist seitens der Firma Freescale die Dokumentation gerade bei der Programmierung des integrierten Flashes sehr dünn und ein einer Stelle leider mehrdeutig. Leider kommt man heute ab und an in die Verlegenheit ein Software-Update durch den Anwender ermöglichen zu müssen, was ein (teilweises) Neuprogrammieren des Flash-Inhalts impliziert. Daher wird im Folgende versucht den in einem Projekt genutzten Weg zum Programmieren des Bausteins näher zu beschreiben. Bitte im Folgenden keine fertige "Backmischung" erwarten, hier sollen nur die wichtigsten Fußangeln mit deren Konsequenzen beschrieben werden.

Funktionsbeschreibung

Es empfiehlt sich, die Routine zur Neuprogrammierung des Flashes in einem Bereich des Flashes abzulegen, welche niemals durch den User überschreieben werden darf. Dies kann z.B. im Rahmen eines sog. Bootloaders geschehen, hierzu ist bereits eine gewisse Auswahl im Opensource bzw. kommerziellen Umfeld erhältlich. Im folgenden wird nur auf die Routine zum Schreiben des Flashes eingegangen.

Initialisieren des Flash-Moduls

Wahrscheinlich wurde um Chipfläche zu sparen seitens Freescale auf die Implementation eines eigenent Taktgenerators für das Flash verzichtet. Daher ist unbedingt darauf zu achten, die Taktfrequenz des Flash-Moduls richtig einzustellen.

1. Stolperfalle: Die Taktfrequenzen


Die die Warnung auf Seite 4-22 des Dokuments 1 ist durchaus ernst zu nehmen: "Program and erase command execution time will increase proportionally with the period of FCLK. Programming or erasing the flash memory with FCLK < 150 kHz must be avoided. Setting FCDIV to a value such that FCLK < 150 kHz can destroy the flash memory due to overstress. Setting FCDIV to a value such that FCLK > 200 kHz can result in incomplete programming or erasing of the flash memory cells.". Also im Klartext: Immer daran denken, den Takt-Vorteiler des Flashes richtig einzustellen. Dazu sind folgende Punkte zu beachten: Hierzu ein Rechenbeispiel, ohne näher auf die gewaltigen Parametersätze des MCG einzugehen:
Quarzfrequenz 16MHz
PLL-Vorteiler * 1/16
PLL-Input = 1 MHz
PLL-Multiplier* 24
PLL-Output = 24 MHz
BDIV * 1/1
CPU-Takt (MCGOUT)= 24 MHz
Fester Bus-Vorteiler* 1/2
Busfrequenz (BUSCLK)= 12MHz
Flash-Frequenz-Vorteiler PRDIV8 (aus):* 1/1
Flash-Zwischenfrq. (PRDCLK)= 12MHz
Flash-Frequenz-Vorteiler FDIV=60:* 1/60
Flash-Takt (FCLK)= 200 kHz

2. Stolperfalle: Flash-Vorteiler ist nur ein mal schreibbar:

Nach einem Reset ist der Flash-Vorteiler nur ein einziges mal beschreibbar. Daher: Aufpassen beim setzen

Assembler-Listing der Initialisierung

Nun sieht die Konfiguration des Flash-Moduls so in Assembler aus:
mcf_flash_init:
        /** Allocate stack for three registers**/
        lea.l -(3*4)(sp), sp
        /** Push d0,a2 and a1 onto the stack **/
        movem.l d0/a2-a1,(sp)

        /******* Check if setup hasn't already been done **************/
        /* Load Flash clock divider (status) register */
        move.l #_FCDIV, %d0
        /* Test if FDIVLD (Clock Divider Load Control)-Bit is set */
        btst.l #7, %d0
        /* If bit is set a write has already taken place and we
         * might not be able to write it again, so give up here.
         * If (FDIVLD==1) SR[Z]=0 else SR[Z]=1
         * => Branch if SR[Z]==0 (NE)
         **/
        bne .mcf_flash_init_impossible

        /********* Init clock divider ***********************************/
        /** Setup Flash Clock Divider Register.
          *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
          *! WARNING:                                                       !
          *!  Adapt to current bus clock frequency!!! Otherweise it may kill!
          *!  the Flash!                                                    !
          *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
          *
          *
          * Bit Name     Value
          *  7  FDIVLD =  1    Leave FCDIV writeable
          *  6  PRDIV8 =  0    Don't predivide Fbus/32. So PRDCLK=Fbus
          * 5:0 FDIV   = 60 (decimal)
          *             \__/
          *               +-> 0xBC
          * Calculation of FDIV=CEIL( PRDCLK[kHz] / 200).
          *                    =CEIL( 12000 /200 ) = 60
          * Note: CEIL is used to round up which will yield a more conservative
          *       divider
          **/
        move.l #0xBC, %d0
        move.b %d0, _FCDIV

/***********************************************************************
  * Missing: Relocate flash-helper, see below.                         *
  * Was hier fehlt ist der Flash-Relocator, siehe naechste Stolperfalle*
  ***********************************************************************/


.mcf_flash_init_impossible:
        /** Pop from stack **/
        movem.l (sp),d0/a2-a1
        /** Free stack from three registers**/
        lea.l (3*4)(sp), sp
        /* Return gracefully */
        rts

3. Stolperfalle: Nicht aus dem Flash-Laufen wenn es beschäftigt ist

Auch hier ist das Handbuch mal wieder nicht ganz leicht zu interpretieren "It is not possible to read from a flash block while any command is executing on that specific flash block." (aus [1], Seite 4-14). So könnte man auf die Idee kommen, dass nur des aktuell programmierende bzw. löschende Sektor während der Operation nicht lesbar ist. Leider ist dem nicht so, der Satz "The MCF51AC256 series flash memory is organized as two 16-bit wide blocks interleaved to yield a 32-bit data path." gibt einen Hinweis auf die eigentliche Bedeutung: Es ist praktisch kaum möglich den Programmieralgorithmus aus dem Flash auszuführen ohne sich den Ast auf dem man sitzt selbst abzusägen.

Daher: Während das Flash arbeitet muss der Prozessor sein Programmfutter wo anders finden. Dazu dient eine Hilfsroutine im SRAM des Controllers die man zunächst vom Compiler im Codesegment (also dem Flash) erstellen lässt und die man vorher ins RAM kopiert. Da die Branch-Befehle immer mit relativen Angaben ausgestattet sind kann man die Routine einfach "manuell verschieben" (Der Anglizismus lautet relocaten).

Die Hilfsroutine sendet einen Befehl an das Flash (also Erase, Write, etc.) und wartet im RAM auf dessen Fertigstellung. Dabei bleiben die Interrupts für diese Zeit deaktiviert denn deren Vektortabelle liegt ja am Anfang des Flashes. Die Hilfsroutine sieht wie folgt aus:

/************************** Execute flash command ***************************/
/** Relocated into mcf_flash_do_cmd within ram to avoid messing with (fetching,
  * etc.) the flash while it is busy.
  * Relocation by mcf_flash_init.
  *
  * Register usage
  * External
  * %d0 : Input: Command to send to flash/Return: Flash-status
  **/
mcf_flash_do_cmd_orig:
        /** Block interrupts as they would be vectored into the busy
          * flash **/
        move.w #0x2700,%sr

        /** Send command */
        move.b %d0, _FCMD
        /** Execute the command by setting FSTAT (p. 4-25)
          *
          * Bit Name     Value
          *  7  FCBEF  =  1   Reset Command buffer flag to zero by writing a 1 !
          *  6  FCCF   =  0   Don't care, can't be written
          *  5  FPVIOL =  1   Reset Violation flag to zero by writing a 1!
          *  4  FACCERR=  1   Reset Access Error flag to zero by writing a 1
          *  3         =  0   Reserved, must be cleared
          *  2  FBLANK =  0   Don't care, can't be written
          * 1:0          00   Reserved, must be cleared.
          *             \_/
          *              +-> 0xB0
          **/
        move.l #0xb0, %d0
        move.b %d0, _FSTAT
.mcf_flash_wait:
        /** Load Flash-Status  **/
        move.b  _FSTAT, %d0
        /** Test if bit 6 (FCCF=Flash Command Complete) is set **/
        btst #6, %d0

        /** If bit is clear (=not ready) Zero flag will be set,
          * so branch
          **/
        beq .mcf_flash_wait

        /** Enable interrupts again **/
        move.w #0x2000,%sr
        /** Return into flash **/
        rts
Die Relocation-Hilfsroutine welche ich der Übersichtlichkeit halber in mcf_flash_init ausgelassen habe sieht wie folgt aus:
/* Paste into mcf_init_flash */
        /** Load helper into SRAM **/
        /** Prepare source pointer **/
        lea.l mcf_flash_do_cmd_orig, %a0
        /** Prepare destination pointer **/
        lea.l mcf_flash_do_cmd, %a1
        /** Clear %d0 fully **/
        clr.l %d0
        /** Start copying **/
.mcf_flash_init_copy:
        /** Load from source **/
        move.w (%a0)+, %d0
        /** Write to destination **/
        move.w %d0, (%a1)+
        /** Test if opcode RTS (return) found, then
          * we should have copied the whole routine
          **/
        cmpi.l #0x4E75, %d0
        /** If the end of sequence opcode was not found
          * loop on
          **/
        bne .mcf_flash_init_copy
/* End paste into mcf_init_flash */

Löschen eines Sektors

Vor dem Bescheiben einer Zelle sollte ein Sektor gelöscht werden. Das ist in der Theorie nicht so komplex wenn die Anwendung vollständig im RAM läuft, wobei davon gerade bei dem doch begrenzten RAM der MCF51x-Serie nicht immer auszugehen ist. Also: Stolperfalle 3 beachten!

Das Löschen eines Sektors erfolgt entsprechend des Diagramms 4-13 auf Seite 4-41 in 1. Es wird zunächst ein Dummy-Wort in den zu löschenden Sektor geschrieben. Anschließend wird der Lösch-Befehl (hier 0x40) per RAM-Hilfsroutine an das Flash gesandt.

/************************** ERASE SECTOR ************************************/
/** \function mcf_flash_sector_erase
  * Erases sector at given address
  * Registers used (externally)
  * %a1 : Address of sector to erase
  * Registers used internally:
  * %d0 : Scratch: 1. Dummy for write, 2. Flash-Command, 3. Flash status
  * %a1 : 1. Destination address, 2. Flash-Command-Register
  **/
mcf_flash_sector_erase:
        /** Allocate stack for one register**/
        lea.l -(8)(sp), sp
        /** Push d1 onto the stack **/
        movem.l d0/a1,(sp)
        /** Perform a (dummy) write to the given flash location **/
        move.l %d0, (%a1)
        /** Load command-word into %d1 **/
        moveq.l #0x40, %d0
        /** Send command and wait for it to finish */
        jsr mcf_flash_do_cmd

        /** Pop %d1/%a1 back **/
        movem.l (sp), d1/a1
        /** Free stack pointers **/
        lea.l (8)(sp), sp
        /** Return **/
        rts

Schreiben eines Sektors

Das Flash kann in jeweils 32-bit Worten programmiert werden. Das Vorgehen entspricht dem Diagramm 4-11 auf Seite 4-27 in 1. Zuerst wird das zu schreibende Wort an seine Zieladresse geschrieben und dann der Schreibbefehl 0x20 mittels der Hilfsroutine im RAM an das Flash übertragen. Da man zumeist einen ganzen (gerade gelöschen) Block geändert zurückschreiben möchte ist braucht man meistens eine Routine zum schreiben eines Sektors. Anbei ein Beispiel (der Burst-Write wird dabei noch nicht verwendet):

************************** WRITE SECTOR ************************************/
/* Writes contents of mcf_flash_sector_base to sector mcf_flash_sector_base.
 * The sector is automatically aligned to a sector-size.
 *
 * NOTE: It is up to the user to assure that the given source and destination
 *       are aligned to a sector boundary.
 *
 * Registers used:
 * External:
 *  %a0 : Source-Address
 *  %a1 : Destination-Address
 * Internal:
 *  %d0 : Current dataword/Status word
 *  %d1 : Loop-counter
 *
 *  %a0 : Read-Pointer from source buffer
 *  %a1 : Write-Pointer in Flash-destination
 **/
mcf_flash_sector_write:
        /** Allocate stack for four register**/
        lea.l -(4*4)(sp), sp
        /** Push four registers onto the stack **/
        movem.l d1-d0/a1-a0,(sp)

        /** Preload addresses into registers **/
        lea.l mcf_flash_sector_buffer, %a0
        /** Use %d1 for trimming dest-address **/
        move.l %a1, %d1
        /** Do the trimming **/
        andi.l #~(SECTOR_SIZE-1), %d1
        move.l %d1, %a1

        /** Initialize loop-counter, we will only write SIZE/4 cells
          * because the flash is organized in 32-bit cells
          **/
        move.l #(SECTOR_SIZE >> 2), %d1

        /** Start writing the sectors  **/
.write_sector_loop:
        /** Make watchdog happy **/
        move.b %d0, _SRS
        /** Do the programming sequence: (See p. 4-27) **/
        /** 1. Wait for flash to come ready **/
.write_sector_wait_rdy:
        /** 1.1. Read status register **/
        move.b _FSTAT, %d0
        /** 1.2. Test if FSTAT[7]=FCBEF (Buffer empty)-Flag is set (see p. 4-20) **/
        btst.l #7, %d0
        /** 1.3. Check if bit form step 1.2 is clear then wait: Z=~FCBEF=1  **/
        beq .write_sector_wait_rdy
        /** 2. Test if there are violations to ack **/
        /** 2.1. Load status register again **/
        move.b _FSTAT, %d0
        /** 2.2. Test if any of these bits FSTAT[5,4] are set by masking it **/
        andi.l #0x30, %d0
        /** 2.3. If none of these is set Z will be set so we can skip clearing the register **/
        beq .write_sector_dont_ack
        /** 2.4. ACK it if needed by writing ones to the bits set (p. 4-20)
          *      as the set bits are still left from the previous and we just copy
          *      the current d0 back
          **/
        move.b %d0, _FSTAT
.write_sector_dont_ack:
        /** 3. Write dataword to be written to flash **/
        move.l (%a0)+, (%a1)+
        /** 4. Write command sequence **/
        move.l #0x20, %d0
        /** 5. Start sequence **/
        jsr mcf_flash_do_cmd

        /** Loop until we finish the sector as the loop counter reaches zero **/
        subi.l #1, %d1
        bne .write_sector_loop

        /** Pop four registers back **/
        movem.l (sp), d1-d0/a1-a0
        /** Free stack pointers **/
        lea.l (4*4)(sp), sp
        /** Return **/
        rts

Literatur

  1. MCF51AC256 ColdFire Integrated Microcontoller Reference Manual: URL http://cache.freescale.com/files/32bit/doc/ref_manual/MCF51AC256RM.pdf?fpsp=1
  2. Freescale Appnote 3521: Using the ColdFire Flash Module with the MCF521x ColdFire Microcontroller : URL http://www.freescale.com/files/32bit/doc/app_note/AN3521.pdf
$Revision: 1.1 $, $Date: 2010/10/12 13:39:18 $