1. 目的
本文主要探讨在 MCUXprosso IDE 中 ,KW36 的工程中 ,如何将 Flash 烧写程序放在 RAM 中并运行 。
2. 存储规格
图 1 KW36 存储分布
如图 1 ,对于 Flash ,分为 PFlash 和 FlexNVM 两个块,FlexNVM 可做为仿真 EEPROM 的备份区 ,Flash 每个扇区为 2KB ,每次擦除最少一个扇区 ,Flash 每页可为 8B ,所以每次写入要 8 字节写入 ,地址 8 对齐 。
FlexRAM 可配置为仿真 EEPROM (8KB),对齐可配置为 1、2、4字节对齐 。
3. 代码
首选选择一个 led 灯例程 ,右击工程->属性->MCU setting->Memory details 中 ,看 Flash 和 RAM 的分区情况 ,如图 2 ,其中 RAM3~5 是不可动的 ,所以只能看 RAM 和 RAM2 情况 ,在此我选择的是RAM2 ; 然后在相对应的工程下->Debug(没有的先编译一下)->xxx_Debug.ld(xxx 表示工程名) 中看分段情况 ,如图 3 ,选择 .ramfunc.$SRAM1 段 。
图 2 分区情况
图 3 RAM2/SRAM1 分段情况
当然 ,如果自己想要去分配 RAM 也可以 ,但记得在 startup->startup_mkw36z4.c->ResetISR 中 ,将代码从 Flash 复制到 Ram ,如下代码 :
SectionTableAddr = &__data_section_table;
while (SectionTableAddr < &__data_section_table_end) {
LoadAddr = *SectionTableAddr++;
ExeAddr = *SectionTableAddr++;
SectionLen = *SectionTableAddr++;
data_init(LoadAddr, ExeAddr, SectionLen);
}
其中 __data_section_table 和 __data_section_table_end 在刚才打开的 ld 文件中 ,可以看到载入的 .data_RAM2 与图 3 一致 ,说明在 ResetISR 中 ,已经将代码复制到 RAM 中 。
__data_section_table = .;
LONG(LOADADDR(.data));
LONG( ADDR(.data));
LONG( SIZEOF(.data));
LONG(LOADADDR(.data_RAM2));
LONG( ADDR(.data_RAM2));
LONG( SIZEOF(.data_RAM2));
LONG(LOADADDR(.data_RAM3));
LONG( ADDR(.data_RAM3));
LONG( SIZEOF(.data_RAM3));
LONG(LOADADDR(.data_RAM4));
LONG( ADDR(.data_RAM4));
LONG( SIZEOF(.data_RAM4));
LONG(LOADADDR(.data_RAM5));
LONG( ADDR(.data_RAM5));
LONG( SIZEOF(.data_RAM5));
__data_section_table_end = .;
确定好这些前提条件之后 ,就可以开始写代码 ,首先需要确定擦写代码里面所调用的函数也是需要在 RAM 里面的 ,不然一调用就需要读取 Flash 里面的指令 ,跟我们的期望不符合 。基于此 ,就写一个最简单的烧写程序 。
在 source 下面创建 flash_driver.h 和 flash_driver.c 文件 ,flash_driver.h 文件内容如下 :
#include "fsl_device_registers.h"
#define FLASH_ALIGN 8 // 64bit , 对齐字节
#define FLASH_SECTOR 2048 // 2KB , 扇区大小
#define FLASH_MAX_RANGE 0x80000 // 512KB , Flash 最大范围
void __attribute__((section(".ramfunc.$SRAM1"))) kw36_flash_init(void);
/*
* 擦除 flash
* start_add : flash 要擦除的起始地址 ,要 2KB(2048字节) 对齐
* lengthInSectors : 要擦除的扇区个数
* */
void __attribute__((section(".ramfunc.$SRAM1"))) kw36_flash_erase(uint32_t start_addr, uint32_t lengthInSectors);
/*
* 向 flash 中写入数据
* start_add : flash 要 program 的起始地址 ,要 8字节对齐
* src : 要写入数据的指针 ,元素有效个数是 2 的倍数
* lengthInBytes : 要写入的数据长度 ,要是 8 的倍数
* */
void __attribute__((section(".ramfunc.$SRAM1"))) kw36_flash_program(uint32_t start_addr, uint32_t *src, uint32_t lengthInBytes);
其中 __attribute__((section(".data.$SRAM1"))) 将此函数制定到 .ramfunc.$SRAM1 段中 。
flash_driver.c 代码如下 :
static volatile uint32_t *const kFCCOBx = (volatile uint32_t *)&(FTFE->FCCOB3);
/*
* 擦除 flash
* start_add : flash 要擦除的起始地址 ,要 2KB(2048字节) 对齐
* lengthInSectors : 要擦除的扇区个数
* */
void __attribute__((section(".ramfunc.$SRAM1"))) kw36_flash_erase(uint32_t start_addr, uint32_t lengthInSectors){
uint8_t flash_status=0; // flash 状态
if( 0==lengthInSectors ){ // 无擦除
return ;
}
// 超过最大范围
if( (start_addr+lengthInSectors*FLASH_SECTOR) > FLASH_MAX_RANGE ){
return ;
}
if( 0!=(start_addr%FLASH_SECTOR) ){ // 地址不是 2KB 对齐 ,不可 erase
return ;
}
while(lengthInSectors>0){
kFCCOBx[0]= (uint32_t)( ((uint32_t)(0x09<<24)) | ((uint32_t)(start_addr&0xFFFFFF)) ) ; // 写入命令
FTFE->FSTAT=0x70; // 清除错误标志
start_addr+=FLASH_SECTOR; //
lengthInSectors--; //
FTFE->FSTAT=0x80; // 执行命令
flash_status=0;
while( !(0xF0&flash_status) ){ // 是否执行完毕
flash_status=FTFE->FSTAT;
}
if(0x20&flash_status){ // 判断返回的状态值
return ;
FTFE->FSTAT=0x80; // 清除错误标志
}else if(0x10&flash_status){
return ;
FTFE->FSTAT=0x80; // 清除错误标志
}else if(0x1&flash_status){
return ;
}else{
}
}
}
/*
* Kai Chen
* 向 flash 中写入数据
* start_add : flash 要 program 的起始地址 ,要 8字节对齐
* src : 要写入数据的指针 ,元素有效个数是 2 的倍数
* lengthInBytes : 要写入的数据长度 ,要是 8 的倍数
* */
void __attribute__((section(".ramfunc.$SRAM1"))) kw36_flash_program(uint32_t start_addr, uint32_t *src, uint32_t lengthInBytes){
uint8_t flash_status=0; // flash 状态
if( (NULL==src) || (0==lengthInBytes) ){ // 无数据写入 ,不可 program
return ;
}
if( (start_addr+lengthInBytes) > FLASH_MAX_RANGE ){ // 超过最大范围
return ;
}
if( (0!=(start_addr%FLASH_ALIGN)) || (0!=(lengthInBytes%FLASH_ALIGN)) ){ // 不是64位对齐,不可 program
return ;
}
while(lengthInBytes>0){
kFCCOBx[0]= (uint32_t)( ((uint32_t)(0x07<<24)) | ((uint32_t)(start_addr&0xFFFFFF)) ) ; // 写入命令
kFCCOBx[1]= (*src); // 写入数据
#if (FLASH_ALIGN==8)
src++;
kFCCOBx[2]= (*src); // 写入数据
#endif
FTFE->FSTAT=0x70; // 清除错误标志
start_addr+=FLASH_ALIGN; //
src++; //
lengthInBytes-=FLASH_ALIGN; //
FTFE->FSTAT=0x80; // 执行命令
flash_status=0;
while( !(0xF0&flash_status) ){ // 是否执行完毕
flash_status=FTFE->FSTAT;
}
if(0x20&flash_status){ // 判断返回的状态值
FTFE->FSTAT=0x80; // 清除错误标志
}else if(0x10&flash_status){
FTFE->FSTAT=0x80; // 清除错误标志
}else if(0x1&flash_status){
}else{
}
}
}
在上面 ,之所以每次执行完的状态检查中,一检查到错误情况就写入 FTFE->FSTAT=0x80 是因为一旦出错 ,FSTAT->CCIF 必须写第一次清除错误情况 ,第二次写入才会清零启动命令 。
3. 参考资料
(1) NXP :《MKW36A512RM.pdf》
下载链接 :
评论