Flash module reference

Header :
#include <flash.h>
Makefile : flash is already included by default

Description

This module gives access to the non-volatile Flash memory embedded in the microcontroller, where the static data and the code are stored.

The total size of Flash memory is device-dependant (from 128kB to 512kB) but the array is always divided in pages of 512 bytes. On the 256kB model used by Carbide, this means 512 pages in total. The granularity when writing or erasing data in the Flash is one page : even when changing a single byte, the whole page has to be erased and written back.

The Flash memory is a special kind of non-volatile memory that only allows the bit to be changed from 1 to 0. By default, all the bits are set to 1 (the array is filled with 0xFF bytes) and data can be written by changing some of them to 0. In order to revert the state of the bits and write another value, the whole page must be reset.

The Flash memory is accessed at address 0x00000000 in the global memory map. Reading data from Flash is done by using the simple read() and readPage() functions, which will directly access the array. However, because of the reasons detailed above, writing data is a bit more complex. The data is not written directly in the array but goes into a dedicated Page Buffer, which can later be copied into the main array. All of this is done automatically by the writePage() function.

To sum up, here are the two most common scenarii :

  • To read a simple byte, use read()
  • To write one or more byte, first read the entire page into a buffer previously allocated in RAM, change the values as required, then write the whole page back :
    uint32_t address = 0x4242;
    uint32_t buffer[Flash::FLASH_PAGE_SIZE_WORDS];
    Flash::readPage(address, buffer);
    // Modify buffer...
    Flash::writePage(address, buffer);
    If the data you want to write spans over multiple pages, you have to repeat this process for each page.

User Page

Beside the main Flash array, there is an additional page, called the User Page, that can be used to store any data, such as a serial number or configuration data. This page is not erased when the rest of the array is erased, which is convenient when prototyping. The user page is accessed using a dedicated set of functions : readUserPage(), eraseUserPage() and writeUserPage(). The first 2 words (8 bytes) of this page are reserved for the configuration of some other modules and should not be overwritten (see the datasheet §14.11.2 First Word of the User Page and 14.11.3 Second Word of the User Page; be careful as the "second word" is actually at offset 0 and the "first word" is at offset 4). The recommended procedure to write the User Page is to perform a Read-Erase-Write sequence without modifying the first 2 words.

General-purpose fuses

The Flash also offers another memory space outside the main array in the form of a 32-bit register used to store fuses. A fuse is simply a non-volatile bit that can be set or reset at will to store an information. Out of these 32 fuses, 16 are used by the Flash controller to lock some parts of the array and prevent modifications, while the other 16 are called General-Purpose Fuses and can be freely used by your program. However, keep in mind that some of these fuses are already used by the library, especially the bootloader, and should not be overwritten. Take a look at the BOOTLOADER_N_RESERVED_FUSES constant for more information.

Special considerations

Accessing the Flash memory array from your program can be extremely useful in certain applications, but remember that this memory is slower and more fragile than the RAM. Even when running at the full 48MHz, programming or erasing a page takes around 5ms. The endurance ratings indicate only 100 000 page write minimum (see §42.8 Flash Characteristics in the datasheet), which can be reached quickly if your program constantly writes into the array. Also, make sure not to overwrite any useful data when writing pages, such as your code (this would lead to really strange behaviour).

Keep in mind that this memory is primarily designed to keep your program code and static data (such as default values), so if your program needs to retain large amounts of data even when the power is lost (for example, if you are building a data logger), consider using a dedicated external EEPROM or Flash memory with higher capacity and better endurance ratings. And in any case, try to design caching algorithms adapted to your specific application and access pattern in order to minimise write cycles (e.g. for a data logger, don't write each sensor value as it comes, fill up a buffer in RAM and write a whole page of data at once).

API

bool isReady()
Return true if the memory controller is currently executing an operation, such as a page write or page erase.
uint32_t read(uint32_t address)
Read a single word from the array.
uint32_t readPage(int page, uint32_t data[])
Read a page from the array. data[] must be at least FLASH_PAGE_SIZE_WORDS long.
void erasePage(int page)
Erase a page from the array, resetting it to 0xFF.
void clearPageBuffer()
Clear the current page buffer. This is used internally by writePage().
uint32_t writePage(int page, const uint32_t data[])
Write a page into the array. data[] must be at least FLASH_PAGE_SIZE_WORDS long.
uint32_t readUserPage(uint32_t data[])
Read the user page from the Flash memory. data[] must be at least FLASH_PAGE_SIZE_WORDS long.
void eraseUserPage()
Erase the user page from the array, resetting it to 0xFF.
uint32_t writeUserPage(const uint32_t data[])
Write the user page into the Flash memory. data[] must be at least FLASH_PAGE_SIZE_WORDS long. As explained in the module description, be careful with the first word of this page as it contains configuration data for the microcontroller.
void writeFuse(Fuse fuse, bool state)
Write the given fuse. fuse must be between 0 and 47, however, some fuses might already be used by the bootloader : to be safe, the fuses that you should consider available start after BOOTLOADER_N_RESERVED_FUSES.
bool getFuse(Fuse fuse)
Get the status of the given fuse.
void enableHighSpeedMode()
Set the flash controller into high speed mode. This is used automatically by the library when the power scaling setting is set to PS2.
void disableHighSpeedMode()
Set the flash controller into normal speed mode. This is used automatically by the library when the power scaling setting is set from PS2 to PS0.

Hacking

The Flash memory controller is quite complex and there are a few advanced features that are not implemented:

  • A caching mecanism, called PicoCache, is available. For some intensive applications, enabling it and customizing its configuration could optimize execution speed and power consumption.
  • A Quick Page Read command is available (as well as Quick User Page Read), which allows to quickly check if a page is reset by ANDing all its bits and providing the 1-bit result in FSR.QPRR.
  • For intensive applications, the wait state and high speed settings could be customized.
  • Some fuse and security bits can be written in a special memory register. This allows, among other things, to lock some parts of the array to prevent overwriting.
  • An Error Correcting mecanism is available. Although mostly automatic, its status could be checked for detecting aging problems and preventing failures.

Code

Header

#ifndef _FLASH_H_
#define _FLASH_H_

#include <stdint.h>

// This module gives access to the Flash memory embedded in the chip
namespace Flash {

    // Config registers memory space base address and flash memory base address
    const uint32_t FLASH_BASE = 0x400A0000;
    const uint32_t FLASH_ARRAY_BASE = 0x00000000;
    const uint32_t USER_PAGE_BASE = FLASH_ARRAY_BASE + 0x00800000;
    const int FLASH_PAGE_SIZE_BYTES = 512; // bytes
    const int FLASH_PAGE_SIZE_WORDS = 128; // words
    // N_FLASH_PAGES is a preprocessor word defined in the library Makefile which depend on CHIP_MODEL
    const int FLASH_PAGES = N_FLASH_PAGES; // pages

    // Register offsets
    const uint32_t OFFSET_FCR =         0x00; // Flash Control Register
    const uint32_t OFFSET_FCMD =        0x04; // Flash Command Register
    const uint32_t OFFSET_FSR =         0x08; // Flash Status Register
    const uint32_t OFFSET_FPR =         0x0C; // Flash Parameter Register
    const uint32_t OFFSET_FVR =         0x10; // Flash Version Register
    const uint32_t OFFSET_FGPFRHI =     0x14; // Flash General Purpose Fuse Register Hi -- not implemented
    const uint32_t OFFSET_FGPFRLO =     0x18; // Flash General Purpose Fuse Register Lo
    const uint32_t OFFSET_CTRL =        0x408; // PicoCache Control Register
    const uint32_t OFFSET_SR =          0x40C; // PicoCache Status Register
    const uint32_t OFFSET_MAINT0 =      0x420; // PicoCache Maintenance Register 0
    const uint32_t OFFSET_MAINT1 =      0x424; // PicoCache Maintenance Register 1
    const uint32_t OFFSET_MCFG =        0x428; // PicoCache Monitor Configuration Register
    const uint32_t OFFSET_MEN =         0x42C; // PicoCache Monitor Enable Register
    const uint32_t OFFSET_MCTRL =       0x430; // PicoCache Monitor Control Register
    const uint32_t OFFSET_MSR =         0x434; // PicoCache Monitor Status Register
    const uint32_t OFFSET_PVR =         0x4FC; // Version Register
    
    // Constants
    const uint32_t FSR_FRDY = 0;
    const uint32_t FSR_HSMODE = 6;
    const uint32_t FCMD_CMD = 0;
    const uint32_t FCMD_CMD_NOP = 0;
    const uint32_t FCMD_CMD_WP = 1;
    const uint32_t FCMD_CMD_EP = 2;
    const uint32_t FCMD_CMD_CPB = 3;
    const uint32_t FCMD_CMD_LP = 4;
    const uint32_t FCMD_CMD_UP = 5;
    const uint32_t FCMD_CMD_EA = 6;
    const uint32_t FCMD_CMD_WGPB = 7;
    const uint32_t FCMD_CMD_EGPB = 8;
    const uint32_t FCMD_CMD_SSB = 9;
    const uint32_t FCMD_CMD_PGPFB = 10;
    const uint32_t FCMD_CMD_EAGPF = 11;
    const uint32_t FCMD_CMD_QPR = 12;
    const uint32_t FCMD_CMD_WUP = 13;
    const uint32_t FCMD_CMD_EUP = 14;
    const uint32_t FCMD_CMD_QPRUP = 15;
    const uint32_t FCMD_CMD_HSEN = 16;
    const uint32_t FCMD_CMD_HSDIS = 17;
    const uint32_t FCMD_PAGEN = 8;
    const uint32_t FCMD_KEY = 0xA5 << 24;

    // General-purpose fuses
    using Fuse = uint8_t;
    const int N_FUSES = 16;

    // These fuses are used by the bootloader and are reserved if the bootloader is enabled
    // They need to be defined here for the Core::resetToBootloader() helper function
    const Fuse FUSE_BOOTLOADER_FW_READY = 0;
    const Fuse FUSE_BOOTLOADER_FORCE = 1;
    const Fuse FUSE_BOOTLOADER_SKIP_TIMEOUT = 2;
    const int BOOTLOADER_N_RESERVED_FUSES = 3;


    // Module API
    bool isReady();
    uint32_t read(uint32_t address);
    void readPage(int page, uint32_t data[]);
    void erasePage(int page);
    void clearPageBuffer();
    void writePage(int page, const uint32_t data[]);
    void readUserPage(uint32_t data[]);
    void eraseUserPage();
    void writeUserPage(const uint32_t data[]);
    void writeFuse(Fuse fuse, bool state);
    bool getFuse(Fuse fuse);
    void enableHighSpeedMode();
    void disableHighSpeedMode();

}


#endif

Module

#include "flash.h"

namespace Flash {

    bool isReady() {
        return (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FSR)) & (1 << FSR_FRDY);
    }

    uint32_t read(uint32_t address) {
        // Wait for the flash to be ready
        while (!isReady());

        // Return the word at the specified address
        return (*(volatile uint32_t*)(FLASH_ARRAY_BASE + address));
    }

    void readPage(int page, uint32_t data[]) {
        // Wait for the flash to be ready
        while (!isReady());

        // Copy the buffer to the page buffer
        for (int i = 0; i < FLASH_PAGE_SIZE_WORDS; i++) {
            data[i] = (*(volatile uint32_t*)(FLASH_ARRAY_BASE + page * FLASH_PAGE_SIZE_BYTES + i * 4));
        }
    }

    void erasePage(int page) {
        // Wait for the flash to be ready
        while (!isReady());

        // FCMD (Flash Command Register) : issue an Erase Page command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_EP << FCMD_CMD   // CMD : command code to issue (EP = Erase Page)
            | page << FCMD_PAGEN        // PAGEN : page number
            | FCMD_KEY;                 // KEY : write protection key
    }

    void clearPageBuffer() {
        // Wait for the flash to be ready
        while (!isReady());

        // FCMD (Flash Command Register) : issue a Clear Page Buffer command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_CPB << FCMD_CMD  // CMD : command code to issue (CPB = Clear Page Buffer)
            | FCMD_KEY;                 // KEY : write protection key
    }

    void writePage(int page, const uint32_t data[]) {
        // The flash technology only allows 1-to-0 transitions, so the 
        // page and the buffer must first be cleared (set to 1)
        erasePage(page);
        clearPageBuffer();

        // Wait for the flash to be ready
        while (!isReady());

        // Copy the buffer to the page buffer
        for (int i = 0; i < FLASH_PAGE_SIZE_WORDS; i++) {
            (*(volatile uint32_t*)(FLASH_ARRAY_BASE + page * FLASH_PAGE_SIZE_BYTES + i * 4)) = data[i];
        }

        // FCMD (Flash Command Register) : issue a Write Page command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_WP << FCMD_CMD   // CMD : command code to issue (WP = Write Page)
            | page << FCMD_PAGEN        // PAGEN : page number
            | FCMD_KEY;                 // KEY : write protection key
    }

    void readUserPage(uint32_t data[]) {
        // Wait for the flash to be ready
        while (!isReady());

        // Copy the buffer to the page buffer
        for (int i = 0; i < FLASH_PAGE_SIZE_WORDS; i++) {
            data[i] = (*(volatile uint32_t*)(USER_PAGE_BASE + i * 4));
        }
    }

    void eraseUserPage() {
        // Wait for the flash to be ready
        while (!isReady());

        // FCMD (Flash Command Register) : issue an Erase User Page command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_EUP << FCMD_CMD  // CMD : command code to issue (EUP = Erase User Page)
            | FCMD_KEY;                 // KEY : write protection key
    }

    void writeUserPage(const uint32_t data[]) {
        // The flash technology only allows 1-to-0 transitions, so the 
        // page and the buffer must first be cleared (set to 1)
        eraseUserPage();
        clearPageBuffer();

        // Wait for the flash to be ready
        while (!isReady());

        // Copy the buffer to the page buffer
        for (int i = 0; i < FLASH_PAGE_SIZE_WORDS; i++) {
            (*(volatile uint32_t*)(USER_PAGE_BASE + i * 4)) = data[i];
        }

        // FCMD (Flash Command Register) : issue a Write User Page command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_WUP << FCMD_CMD  // CMD : command code to issue (WUP = Write User Page)
            | FCMD_KEY;                 // KEY : write protection key
    }

    void writeFuse(Fuse fuse, bool state) {
        // Wait for the flash to be ready
        while (!isReady());

        // Check the fuse number
        if (fuse > N_FUSES) {
            return;
        }

        // The usable fuses are actually between 16 and 31, because the first 16 bits are lock bits
        fuse += 16;

        // FCMD (Flash Command Register) : issue a Write User Page command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = (state ? FCMD_CMD_WGPB : FCMD_CMD_EGPB) << FCMD_CMD  // CMD : command code to issue (WGPB = Write General Purpose Bit)
            | fuse << FCMD_PAGEN        // PAGEN : fuse number
            | FCMD_KEY;                 // KEY : write protection key
    }

    bool getFuse(Fuse fuse) {
        // Wait for the flash to be ready
        while (!isReady());

        // Check the fuse number
        if (fuse > N_FUSES) {
            return false;
        }

        // The usable fuses are actually between 16 and 31, because the first 16 bits are lock bits
        fuse += 16;
        bool high = false;
        if (fuse >= 32) {
            high = true;
            fuse -= 32;
        }

        return !(((*(volatile uint32_t*)(FLASH_BASE + (high ? OFFSET_FGPFRHI : OFFSET_FGPFRLO))) >> fuse) & 0x01);
    }

    void enableHighSpeedMode() {
        // FCMD (Flash Command Register) : issue a High Speed Enable command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_HSEN << FCMD_CMD  // CMD : command code to issue (HSEN = High Speed Enable)
            | FCMD_KEY;                  // KEY : write protection key

        // Wait untile HS mode is enabled
        while (!((*(volatile uint32_t*)(FLASH_BASE + OFFSET_FSR)) & (1 << FSR_HSMODE)));
    }

    void disableHighSpeedMode() {
        // FCMD (Flash Command Register) : issue a High Speed Disable command
        (*(volatile uint32_t*)(FLASH_BASE + OFFSET_FCMD))
            = FCMD_CMD_HSDIS << FCMD_CMD // CMD : command code to issue (HSEN = High Speed Disable)
            | FCMD_KEY;                  // KEY : write protection key
    }

}