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); } |