飞思卡尔MC9S12G系列单片机flash擦写
最近在学习飞思卡尔MC9S12G系列单片机底层驱动,看了很多,目前理解还不深,处于入门菜鸟级别,仅在此记录下学习心得,后续会在实操中强化底层驱动开发能力,不能老是处于光说不练,搞些假把式。好了,言归正传。本文主要记录笔者基于MC9S12G系列单片机flash擦写,还望各路大神路过时,可指点一番。
Flash本身是非易失性存储,可以通过编程的方式擦写其中的内容,掉电后其内容不会丢失,一般是单片机的程序存储位置。单片机运行时先将Flash中下一条运行的程序读出,然后执行其内容,再读出下一条指令,再执行循环往复。通过擦写flash能够实现单片机应用程序自动更新。
G128系列单片机,查阅芯片手册,获得数据如下:
• 128Kbytes of P-Flash (Program Flash) memory
• 4 Kbytes of EEPROM memory
可以看出,G128系列单片机的Flash存储大小有128KB, 其全局地址范围为: 0x2_0000 - 0x3_FFFF。在这里可通过以下链接,熟悉并理解MC9S12G128内存映射。
MC9S12G128内存映射(本地地址,逻辑地址,全局地址)
第一张图:Flash模块功能框图
上图直接告诉我们,对flash操作其实不是直接对每个Flash存储区进行操作,而是通过控制Flash Interface进行的。具体就是操作Flash Interface的寄存器,再进而操作Flash存储区。那么寄存器有哪些?具体怎么操作?看第二张图。
第二张图:寄存器描述
Flash模块有20个控制和状态寄存器。上图是从芯片手册截取的一部分。但对于Flash模块而言关键寄存器就是图中圈住的几个。下面依次来介绍一下:
FCLKDIV(Flash时钟分配寄存器)
先上图(截取自芯片手册)
上图告诉我们:有三个寄存器需要处理(FDIVLD,FDIVLCK,FDIV):
FDIV寄存器:有六位,通过分频将总线时钟频率分频至1MHz以下从而使Flash控制器可以正常工作。FDIV的值与总线时钟频率有关,根据总线频率的大小确定FDIV的值(见上表),假如当前我的总线频率为16MHz,查表中16MHz在15.6与16.6之间,那么FDIV的值就是0x0F了。当确定好Flash控制器的频率后,需要将其写保护,以防误操作修改了分频寄存器,那么对FDIVLCK写1,当FDIVLCK写入1后,除非重启,否则FDIV的值不能被修改,重启后FDIVLCK的值将重新归零。
由此总结我们Flash控制器的时钟设置步骤如下:
- 根据总线频率设置分频FDIV
- 对分频进行保护,将FDIVLCK置为1
FCCOBIX(Flash CCOB索引寄存器)
先上图(截取自芯片手册)
上图告诉我们,这个寄存器是FCCOB的索引寄存器,CCOBIX[2:0] 只有三位,即8种状态。具体如何索引,看下图:
The CCOBIX bits are used to select which word of the FCCOB register
array is being read or written to.
FCCOBIX的两位决定当前读取或者写入FCCOB的是哪一个word(一般而言一个word在单片机中指两个字节),也就是说FCCOB是一个多word的寄存器,通过FCCOBIX来确定当前操作或者读取的是哪一位。具体如何操作,我们要来看看FCCOB寄存器。FCCOBIN和FCCOB这两个寄存器得结合起来看。
FCCOB(Flash指令寄存器)
上图告诉我们:FCCOB是一个六个word长度的数组,通过FCCOBIX寄存器来定位当前操作的是哪个word。一个word是16位的,前[7:0]位Low,后[15:8]为High。
由于FCCOB只有6个word长,所以FCCOBIX范围为0x00-0x05,也就是说当FCCOBIX = 0x00时,读写FCCOB寄存器操作第0个word的内容,当FCCOBIX = 0x01时,读写FCCOB寄存器操作第1个word的内容,以此类推。也就是说我们操作FCCOB实际内容是由FCCOBIX这个索引寄存器来控制的
FSTAT(Flash状态寄存器)
先上图(截取自芯片手册)
上图告诉我们:
CCIF:用来显示一个Flash指令是否已经执行完成
若CCIF = 0 则表示Flash控制器正在执行指令
若CCIF = 1则表示Flash控制器当前空闲,前一条指令已经执行完毕。
除此以外,CCIF的重要作用就是让Flash控制器开始执行指令,由于Flash控制器的指令内容需要在开始前先行准备到Flash指令寄存器中,当指令准备完成后,通过将CCIF位置1就可以使Flash控制器执行当前写入在指令寄存器中的指令。需要注意的是,当Flash需要执行下一条指令前,务必要检查前一条指令是否已经执行完毕,即CCIF是否为1,若CCIF = 1则再写入1方可执行指令寄存器中的内容。
第三张图:Flash擦写操作流
先上图(截取自芯片手册)
上图告诉我们:
1.FCLKDIV时钟分频寄存器初始化,确定分频因子
2.检查命令缓冲区(FSTAT_CCIF)是否为空
3.对读写错误(FSTAT_ACCER)标志位清零(写1清零)
4.对保护区编程错误(FSTAT_PVIOL)标志位清零(写1清零)
5.对索引寄存器(FCCOBIX)赋值0x00,写入FCCOB编程命令0x06
6.对索引寄存器(FCCOBIX)赋值0x01,写入FCCOB全局地址(写入的首地址)
7.将编程的数据赋值给要编程的Flash地址
8.检查是否还有新的数据需要写入Flash,如有转到第5步
9.等待,直到命令完成标志位(FSTAT_CCIF)置位
Flashde的擦除操作流类似,即将第5步改为如下,去掉第7、8步:
5.对索引寄存器(FCCOBIX)赋值0x00,写入FCCOB擦除命令0x0A
Flash的擦写注意事项
由于在Flash的擦除和写入中,Flash是不能读的,故擦除和写入Flash的程序要放到RAM中去,即:在Flash擦除或写入之前,要把擦除和写入的可执行代码复制到RAM中去,并让程序在RAM中执行。如果没有注意此事项,程序会跑飞。如何解决这个问题,查找资料获知如下:
使用#pragma关键字,配合codewarrior的.prm文件
#pragma CODE_SEGFLASH_RAM //在.prm文件中将FLASH_RAM定义到RAM区中
//对flash进行操作的代码
#pragma CODE_SEG DEFAULT
关于codewarrior的.prm文件的结构可参考如下博主的文章,描述的很清晰:
飞思卡尔MC9S12(X)系列的内存资源分配和.prm文件的结构
编码实现
prm文件设置
NAMES END
SEGMENTS/* Register space *//* IO_SEG = PAGED 0x0000 TO 0x03FF; intentionally not defined */
/* RAM */
RAM = READ_WRITE 0x2000 TO 0x3BFF; CODE_RAM = READ_WRITE 0x3C00 TO 0x3FFF; /*1 kB for flash read and write*/
/* D-Flash */
DFLASH = READ_ONLY 0x000400 TO 0x0013FF;
/* non-paged FLASHs */
ROM_1400 = READ_ONLY 0x1400 TO 0x1FFF;
ROM_BOOT = READ_ONLY 0x4000 TO 0x43FF;
ROM_FLASH = READ_ONLY 0X4400 TO 0x47FF RELOCATE_TO 0x3C00; // 1KB for necessary flash operation
ROM_C000 = READ_ONLY 0xC000 TO 0xFEFF;
/* VECTORS = READ_ONLY 0xFF00 TO 0xFFFF; intentionally not defined: used for VECTOR commands below */
//OSVECTORS = READ_ONLY 0xFF80 TO 0xFFFF; /* OSEK interrupt vectors (use your vector.o) */
/* paged FLASH: 0x8000 TO 0xBFFF; addressed through PPAGE */
PAGE_08 = READ_ONLY 0x088000 TO 0x08BFFF;
PAGE_09 = READ_ONLY 0x098000 TO 0x09BFFF;
PAGE_0A = READ_ONLY 0x0A8000 TO 0x0ABFFF;
PAGE_0B = READ_ONLY 0x0B8000 TO 0x0BBFFF;
PAGE_0C = READ_ONLY 0x0C8000 TO 0x0C93FF;
PAGE_0C_A000 = READ_ONLY 0x0CA000 TO 0x0CBFFF;
PAGE_0E = READ_ONLY 0x0E8000 TO 0x0EBFFF;
/* PAGE_0D = READ_ONLY 0x0D8000 TO 0x0DBFFF; not used: equivalent to ROM_4000 */
/* PAGE_0F = READ_ONLY 0x0F8000 TO 0x0FBEFF; not used: equivalent to ROM_C000 */
END
PLACEMENT /* here all predefined and user segments are placed into the SEGMENTS defined above. */
_PRESTART, /* Used in HIWARE format: jump to _Startup at the code start */
STARTUP, /* startup data structures */
ROM_VAR, /* constant variables */
STRINGS, /* string literals */
VIRTUAL_TABLE_SEGMENT, /* C++ virtual table segment */
//.ostext, /* OSEK */
NON_BANKED, /* runtime routines which must not be banked */
COPY /* copy down information: how to initialize variables */
/* in case you want to use ROM_4000 here as well, make sure
that all files (incl. library files) are compiled with the
option: -OnB=b */
INTO ROM_C000/*, ROM_1400, ROM_4000*/;
BOOTLOADER INTO ROM_BOOT;
FLASH_CODE INTO ROM_FLASH;
USER_APP INTO PAGE_08;
TEST_AREA INTO PAGE_09; /* physical address from 0x2_4000 */
DEFAULT_ROM INTO PAGE_0A, PAGE_0B, PAGE_0C, PAGE_0C_A000, PAGE_0E;
//.stackstart, /* eventually used for OSEK kernel awareness: Main Stack Start */
SSTACK, /* allocate stack first to avoid overwriting variables on overflow */
//.stackend, /* eventually used for OSEK kernel awareness: Main-Stack End */
DEFAULT_RAM INTO RAM;
//.vectors INTO OSVECTORS; /* OSEK */
END
ENTRIES /* keep the following unreferenced variables */
/* OSEK: always allocate the vector table and all dependent objects */
//_vectab OsBuildNumber _OsOrtiStackStart _OsOrtiStart
END
STACKSIZE 0x100
VECTOR 0 _Startup /* reset vector: this is the default entry point for a C/C++ application. */
//VECTOR 0 Entry /* reset vector: this is the default entry point for an Assembly application. */
//INIT Entry /* for assembly applications: that this is as well the initialization entry point */
VECTOR ADDRESS 0xFFD6 SCI0_INT_receive
Flash擦写头文件
在这里插入代码片
Flash擦写源文件
在这里插入代码片
ROM中程序复制到RAM中函数
在这里插入代码片