M0AGX / LB9MG

Amateur radio and embedded systems

Kinetis E - writing to internal flash

While working on a bootloader for a Kinetis KE06 I obviously ran in to the task of having to write the internal flash memory of the microcontroller. Freescale's driver is over 1500 lines of code, exposes every single bit of the flash controller so the simple operation of erasing a sector and writing to it requires many function calls. I wrote my own driver that is less than 200 lines of code and has everything needed to make a bootloader.

To implement a bootloader (or any kind of permanent storage) basically two functions are required - write data and erase sector (the smallest "chunk" of flash that can be individually erased). Why there is no read operation? Because on all ARM microcontrollers flash is mapped to main memory space so reading is simply a pointer dereference.

For example: to read something from address 0x500 you could write:

1
2
3
uint8_t *some_ptr;
some_ptr = 0x500; //the pointer
printf("%d", *some_ptr/*the pointed value*/);

The driver has just 3 public functions and requires very little configuration. First flash_init() has to be called. It initializes the flash controller. This function requires a proper FCLKDIV value that depends on clock speed of the MCU. If it is too low or too high the chip can be permanently damaged! Check the reference manual for exact values.

All function calls are blocking, ie. don't return until the flash controller is done. Thanks to its architecture Cortex-M can execute code from any part of the address space. Usually it is the flash sometimes execution from RAM is helpful. How can the CPU execute code from flash memory while the flash controller is busy with something? It can't! This leads to some serious implications - while flash is being updated or erased - the CPU can not execute code from it. The exact behavior depends on the microcontroller. Fortunately the Kinetis E has a "stalling" function and can simply halt while flash is busy. This is enabled in flash_execute_cmd. Of course the CPU will not process interrupts or do anything else at that time. It is not a problem for a bootloader but may be a problem if using the flash for configuration storage of the main application.

Another option of the driver is FLASH_APPLICATION_BASE. This defines the lowest address that the driver is allowed to erase. Usually the bootloader is placed at the beginning of flash memory and is executed first by the MCU. The bootloader later starts the application. If the bootloader is damaged, then the device is bricked. This option prevents such problems.

The driver

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#pragma once
#include <stdint.h>

void flash_init(void);
void flash_erase_sector(uint32_t address);
void flash_write(uint32_t target_address, const uint32_t *data, uint32_t length_words);

#define FLASH_SECTOR_SIZE 512
#define FLASH_WRITE_WORD_SIZE_BYTES 8

#define FLASH_APPLICATION_BASE 20480 //first 20KB of flash is occupied by the bootloader
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <flash.h>
#include <MKE06Z4.h>

#define debugf(...) //TODO: define your own debug macro if needed

#ifndef DEFAULT_BUS_CLOCK
#error "Bus clock not defined"
#endif

#define FLASH_CMD_ERASE_SECTOR 0x0A
#define FLASH_CMD_PROGRAM 0x06

#define FLASH_ERR_BASE              0x3000                                      /*!< FTMRE error base */
#define FLASH_ERR_SUCCESS           0                                                   /*!< FTMRE sucess */
#define FLASH_ERR_INVALID_PARAM     (FLASH_ERR_BASE+1)      /*!<  invalid parameter error code*/
#define EEPROM_ERR_SINGLE_BIT_FAULT (FLASH_ERR_BASE+2)  /*!<  EEPROM single bit fault error code*/
#define EEPROM_ERR_DOUBLE_BIT_FAULT (FLASH_ERR_BASE+4)  /*!<  EEPROM double bits fault error code*/
#define FLASH_ERR_ACCESS            (FLASH_ERR_BASE+8)              /*!< flash access error code*/
#define FLASH_ERR_PROTECTION        (FLASH_ERR_BASE+0x10)       /*!<  flash protection error code*/
#define FLASH_ERR_MGSTAT0           (FLASH_ERR_BASE+0x11)           /*!<  flash verification error code*/
#define FLASH_ERR_MGSTAT1           (FLASH_ERR_BASE+0x12)           /*!<  flash non-correctable error code*/
#define FLASH_ERR_INIT_CCIF         (FLASH_ERR_BASE+0x14)       /*!<  flash driver init error with CCIF = 1*/
#define FLASH_ERR_INIT_FDIV         (FLASH_ERR_BASE+0x18)       /*!<  flash driver init error with wrong FDIV*/

static void flash_execute_cmd(void);
static uint16_t flash_write_2_words(uint32_t address, uint32_t word0, uint32_t word1);


void flash_init(void){
#if DEFAULT_BUS_CLOCK == 20000000u
    FTMRE->FCLKDIV = 0x13;
#else
#error "FCLKDIV not defined for this bus speed! Consult reference manual for proper speed. Otherwise flash will be damaged!"
#endif
}

void flash_erase_sector(uint32_t address){
    if (address < FLASH_APPLICATION_BASE){
        return;
    }

    uint16_t u16Err = 0;

    // Check address to see if it is aligned to 4 bytes
    // Global address [1:0] must be 00.
    if(address & 0x03)
    {
        u16Err = 1;
        debugf("Sector address is not 4-byte aligned");
        goto end;
    }
    // Clear error flags
    FTMRE->FSTAT = 0x30;

    // Write index to specify the command code to be loaded
    FTMRE->FCCOBIX = 0x0;
    // Write command code and memory address bits[23:16]
    FTMRE->FCCOBHI = FLASH_CMD_ERASE_SECTOR;// EEPROM FLASH command
    FTMRE->FCCOBLO = address>>16;// memory address bits[23:16]
    // Write index to specify the lower byte memory address bits[15:0] to be loaded
    FTMRE->FCCOBIX = 0x1;
    // Write the lower byte memory address bits[15:0]
    FTMRE->FCCOBLO = address;
    FTMRE->FCCOBHI = address>>8;

    // Launch the command
    flash_execute_cmd();

    // Check error status
    if(FTMRE->FSTAT & FTMRE_FSTAT_ACCERR_MASK)   {
        u16Err |= FLASH_ERR_ACCESS;
        debugf("Err access");
    }
    if(FTMRE->FSTAT & FTMRE_FSTAT_FPVIOL_MASK)   {
        u16Err |= FLASH_ERR_PROTECTION;
        debugf("Err protection");
    }
//  if(FTMRE->FSTAT & FTMRE_FS FTMRE_FSTAT_MGSTAT0_MASK)
//  {
//      u16Err |= FLASH_ERR_MGSTAT0;
//  }
//  if(FTMRE->FSTAT & FTMRE_FSTAT_MGSTAT1_MASK)
//  {
//      u16Err |= FLASH_ERR_MGSTAT1;
//  }
end:
    debugf("status = %d", u16Err);
//  return (u16Err);
}

void flash_write(uint32_t target_address, const uint32_t *data, uint32_t length_words){
    for (uint32_t i = 0; i < length_words/2; i++){
        flash_write_2_words(target_address, data[0], data[1]);
        data += 2;
        target_address += 2*sizeof(uint32_t);
    }
}

static void flash_execute_cmd(void){
    MCM->PLACR |= MCM_PLACR_ESFC_MASK;          /* enable stalling flash controller when flash is busy */
    FTMRE->FSTAT = 0x80;
    while (!(FTMRE->FSTAT & FTMRE_FSTAT_CCIF_MASK)); //wait until command is done
}

static uint16_t flash_write_2_words(uint32_t address, uint32_t word0, uint32_t word1){
    uint16_t u16Err = FLASH_ERR_SUCCESS;

    // Check address to see if it is aligned to 4 bytes
    // Global address [1:0] must be 00.
    if(address & 0x03)
    {
        u16Err = FLASH_ERR_INVALID_PARAM;
        return (u16Err);
    }
    // Clear error flags
    FTMRE->FSTAT = 0x30;

    // Write index to specify the command code to be loaded
    FTMRE->FCCOBIX = 0x0;
    // Write command code and memory address bits[23:16]
    FTMRE->FCCOBHI = FLASH_CMD_PROGRAM;// program FLASH command
    FTMRE->FCCOBLO = address>>16;// memory address bits[23:16]
    // Write index to specify the lower byte memory address bits[15:0] to be loaded
    FTMRE->FCCOBIX = 0x1;
    // Write the lower byte memory address bits[15:0]
    FTMRE->FCCOBLO = address;
    FTMRE->FCCOBHI = address>>8;

    // Write index to specify the word0 (MSB word) to be programmed
    FTMRE->FCCOBIX = 0x2;
    // Write the word 0
    //FTMRE_FCCOB = (word0) & 0xFFFF;
    FTMRE->FCCOBHI = (word0) >>8;
    FTMRE->FCCOBLO = (word0);

    // Write index to specify the word1 (LSB word) to be programmed
    FTMRE->FCCOBIX = 0x3;
    // Write word 1
    FTMRE->FCCOBHI = (word0>>16)>>8;
    FTMRE->FCCOBLO = (word0>>16);

    // Write index to specify the word0 (MSB word) to be programmed
    FTMRE->FCCOBIX = 0x4;
    // Write the word2
    //FTMRE_FCCOB = (word1) & 0xFFFF;
    FTMRE->FCCOBHI = (word1) >>8;
    FTMRE->FCCOBLO = (word1);

    // Write index to specify the word1 (LSB word) to be programmed
    FTMRE->FCCOBIX = 0x5;
    // Write word 3
    //FTMRE_FCCOB = (word1>>16) & 0xFFFF;
    FTMRE->FCCOBHI = (word1>>16)>>8;
    FTMRE->FCCOBLO = (word1>>16);

    // Launch the command
    flash_execute_cmd();

    // Check error status
    if(FTMRE->FSTAT & FTMRE_FSTAT_ACCERR_MASK)
    {
        u16Err |= FLASH_ERR_ACCESS;
    }
    if(FTMRE->FSTAT & FTMRE_FSTAT_FPVIOL_MASK)
    {
        u16Err |= FLASH_ERR_PROTECTION;
    }
//  if(FTMRE->FSTAT & FTMRE_FSTAT_MGSTAT0_MASK)
//  {
//      u16Err |= FLASH_ERR_MGSTAT0;
//  }
//  if(FTMRE->FSTAT & FTMRE_FSTAT_MGSTAT1_MASK)
//  {
//      u16Err |= FLASH_ERR_MGSTAT1;
//  }

    return (u16Err);
}