The NanoCore12C32 is a 24-pin DIP microcontroller module sold by Technological Arts for about $50. It is a pre-built module designed around the Motorola/Freescale MC9S12C32 microcontroller, and it features 32 KiB of on-chip non-volatile flash memory. This document aims to serve as an introduction to writing to the 9S12's on-chip flash memory.
It is assumed that the reader is familiar with assembly-language programming for the MC9S12C32.
CAUTION: Before you start mucking around with the flash memory, be aware that the serial monitor (the program that allows you to program the NanoCore12 over the serial port) and the interrupt vectors (which are responsible for calling the serial monitor code) are also located in flash memory. If you corrupt the serial monitor or the interrupt vectors, it will probably become impossible to re-program your NanoCore12 module using the serial port. I've heard that the serial monitor supposedly locks its own area of flash so that you can't accidentally erase it, but I didn't feel like testing it on my hardware, so I can't really say for sure. Therefore, I repeat: You might damage your hardware by following these instructions. Use these instructions at your own risk!
Flash memory is similar to read-only memory (ROM). When you read from some address, you get the data stored there, and when you write to that address, nothing appears to happen -- the old values remain. Although flash memory can be written to, you need to follow a special procedure to modify the data in flash memory.
Flash memory is divided into sectors, which are themselves divided into words. If you want to write to flash, you first need to erase the sector you're interested in. Once you've erased a sector, all the words in the sector may be programmed. However, after being erased, each word in a sector is only allowed to be programmed once; If you want to re-program a word, you must again erase the entire sector.
On the MC9S12C32, sectors are 512 bytes each and words are 2 bytes each.
The default memory map for the NanoCore12 module (assuming the Motorola Serial Monitor is intact) is as follows:
Address range | Description |
---|---|
$0000-$03FF | Reserved (Device registers) |
$3800-$3FBF | Usable RAM (when INITRM is set to $39) |
$3FC0-$3FFF | Reserved (used by the serial monitor when debugging) |
$4000-$7FFF | Bottom 16 KiB flash region (usable) |
$8000-$BFFF | 16 KiB Page Window (see below) |
$C000-$F5FF | Top 16 KiB flash region (usable) |
$F600-$FFFF | Reserved for bootloader/monitor (do not touch) |
The address range $8000-$BFFF is actually a mirror of either the bottom flash region or the top flash region, depending the current value of the PPAGE register. For the purposes of this tutorial, it is recommended that you avoid using this address range.
You may have seen a demo program that starts with the following line:
org $8000 ; Start of program memory
In your programs, you should replace the above line with a line like this:
org $C000 ; Start of program memory
Also: You may need to to update your interrupt vectors, if you had any of them hard-coded to $8000.
This will cause your program to be placed in the top region of flash. As a result, you will be free to use the entire bottom region of flash ($4000-$7FFF) for your own purposes, without having to worry about overwriting either your program code or the bootloader/monitor code.
The following register addresses may be useful to you:
Address | Name | Description |
---|---|---|
$0100 | FCLKDIV | Flash clock division register |
$0105 | FSTAT | Flash status register |
$0106 | FCMD | Flash command register |
To read a value in flash memory, you don't have to do anything special; Just read the data the same way you would read any other value from memory. However, you cannot read flash at the same time as you are erasing/programming flash. Trying to do so will cause your program to crash. The implications of this limitation will be discussed in more detail below.
Before you can erase or program the flash, you must set the FCLKDIV register so that the flash core runs between 150kHz and 200kHz. Be careful here. The manufacturer's documentation states that if the flash clock is too slow (divided too much), you will damage the flash memory module.
If your PLL clock is running at 24 MHz, you can use the value $51, which divides the clock down to about 175 MHz. However, since you can damage your hardware if you're not careful, it is recommended that you read the manufacturer's documentation and determine the correct value for yourself.
In any case, this is the code that corresponds to the value $51:
movb #$51, FCLKDIV ; Set flash clock to approx. 175 kHz
Before you can program the flash, you must first erase the sector you're interested in. To do this, perform the following steps:
Once you've erased the sector, you can program the 16-bit word you're interested in. Note that the word must be aligned on 16-bit boundaries. (i.e. It can be a 16-bit word starting at $4000, $4002, $4004, etc., but not at $4003.) Then, perform the following steps:
Your flash programming routine must be in RAM, not in flash. An easy way to accomplish this is to copy your flash programming routine to RAM and execute it there, rather than executing the routine from flash directly.
The problem with executing your flash programming routine from flash is that as soon as you write to the FSTAT register, the CPU will try to read the next instruction -- from flash. Since the flash cannot be read while it is being programmed, this read will fail and the CPU will crash.
FCLKDIV equ $0100 FSTAT equ $0105 FCMD equ $0106 org $C000 ; Start of program memory ; ... Do your own initialization here, including setting the system clock ; ... to 24 MHz ; Set flash clock to approx. 175 kHz movb #$51, FCLKDIV ; Initialize ProgramFlash jsr CopyProgramFlashRoutine
;;;;;;;;;;;;;;;; ; CopyProgramFlashRoutine - Copy ProgramFlash_ROM to ProgramFlash CopyProgramFlashRoutine: ldab #(ProgramFlash_ROM_END-ProgramFlash_ROM) ldx #ProgramFlash_ROM ldy #ProgramFlash __CopyProgramFlashRoutine_loop: ldaa 0,X staa 0,Y inx iny dbne B,__CopyProgramFlashRoutine_loop rts
;;;;;;;;;;;;;;;; ; ProgramFlash_ROM - Re-programs the non-volatile variable ; Note: This function clobbers the registers ; NOTE: This function MUST be copied to some non-flash storage (e.g. RAM) and ; executed from there. Otherwise, the CPU tries to fetch instructions ; from flash while the flash controller is erasing/programming, which ; doesn't work and causes the CPU to do weird things (like resetting!). ; NOTE: If you change this function, you may need to adjust how much memory ; is reserved for ProgramFlash. ProgramFlash_ROM: ; Erase sector std flash_counter ldaa #$40 ; erase staa FCMD ldaa #$80 ; clear CBEIF staa FSTAT ; Execute ; wait for completion __ProgramFlash_ROM_wait1: ldaa FSTAT anda #$40 beq __ProgramFlash_ROM_wait1 ; Program word ldd counter std flash_counter ldaa #$20 ; program staa FCMD ldaa #$80 ; clear CBEIF staa FSTAT __ProgramFlash_ROM_wait2: ldaa FSTAT anda #$40 beq __ProgramFlash_ROM_wait2 rts ProgramFlash_ROM_END:
org $3800 ; RAM ProgramFlash: rmb $30 ; Space reserved in RAM for the flash writing routine counter: rmb 2 ; Space reserved in RAM for the counter variable
org $4000 ; Flash memory flash_counter: rmb 2 ; Space reserved in flash for the counter variable
I would like to thank Exequiel Rarama from Technological Arts, for providing me with information for the memory map section; John Palmarin, for being a guinea pig for this tutorial, and for providing example source code in C; and Mark Anderson, for helping to come up with this code in the first place.
John Palmarin has provided some example source code in C to complement this tutorial.