/*************************************************************************************
     标         题: Flash的读写操作
* 项  目  名: W-R_FLASH_TEST
* 开发环境：ChipON IDE
* 使用芯片：KF8F4156
* 作          者：上海芯旺微电子技术有限公司
* 功能简述:  如程序所示，对相应的地址进行操作，操作结果通过断电 使用PRO读取。
*
* 跳线接法：只需给单片机供电即可，为了观看数据操作是否完成，可以连接P12和J21 ，当D4灯闪烁时表示操作完成。可断电读取数据。
*
* 特殊说明： 程序会占用不确定的FLASH地址，因此你需要确定用于自写的块或确保当前使用地址为非程序区
* 			当前芯片的可自写范围：800H~1FDFH，
* 			实现方法在该项目目录中建立occupancy.txt的文件，添加地址占用描述使编译器跳过该段区域的实现
* 			格式：
*			一行一条记录，一条记录由3部分组成标识、起始地址、结束地址。
*			文件可以包含任意多条记录
*			标识用于标记占用数据段还是代码段，可以是code或者是data。
*			起始地址结束地址使用十六进制表示。
*			占用包含起始和结束地址。
*
*			例如
*			data 0x180 0x190
*			code 0x800 0x810
*************************************************************************************/
#include <KF8F4156.h>
//====================////////////////////FLASH相关声明/////////////////==============//////////
				/*参数传递说明见函数实现位置*/
#define			FLASH_BUFFER_MAX	32		// 数据的大小与单次操作量相关，这里int型，16,32，除非不用写操作或不关心一些数据。
#define         LED  P1LR2
unsigned int 	FLASH_BUFFER[FLASH_BUFFER_MAX];

unsigned int    FLASH_READ_BUF;	//当单字读函数使用非全嵌汇编模式时，使用该值获取读取结果值
unsigned int 	FLASH_READ_ONE	(unsigned int address);	//读一个字
void 			FLASH_READ_FUN	(unsigned int address,unsigned char length);//读多个数据到数据缓存区，建议最多量为1个页即32个数据，同时建议不跨页操作，与写函数对应
void 			FLASH_WRITE_ONE	(unsigned int address,unsigned int value);  // 过程封装
void 			FLASH_WRITE_FUN	(unsigned int address,unsigned char length); //将缓存区数据写到对应位置，从第一个开始
///=========================================================================================///
void init_mcu()
{
	OSCCTL = 0x60;                           //设置为8M
	/*********端口初始化***********/
	TR0 = 0x04;				                 //设置P0端口 	1100 1111
	TR1 = 0x00;				                 //设置P1端口	0000 0000
	TR2 = 0x00;				                 //设置P2端口	0011 1111
	P0 = 0x00;
	P1 = 0x80;			//P1.6 P1.7 指示运行结束灯
	P2 = 0xF0;
}

void main()
{
	unsigned char i;
	unsigned int temp=0;
	init_mcu();
//--- 准备待写入的数据0x0000 0x1111 0x2222...0xFFFF  0x1110  0x2221  0x3332 ...0xFFFE 0x110F
	for (i = 0; i < FLASH_BUFFER_MAX; i++)
	{
		FLASH_BUFFER[i] = temp;
		temp += 0x1111;
	}

//---setp 1 		写入到0x1E60 和0x1E80的FLASH空间 (页的首地址)
	FLASH_WRITE_FUN(0x1E60,FLASH_BUFFER_MAX);
	FLASH_WRITE_FUN(0x1E80,FLASH_BUFFER_MAX);


//--- setp 2 		读取 0x1E71，并写入到0x1EB5
	// 读取所需到缓存
	temp=FLASH_READ_ONE(0x1E71);
	// 非需要段的任意数据填充
	for (i = 0; i < 16; i++)
	{
		FLASH_BUFFER[i] = 0x0000;
	}
	//应该擦除0x1EA0页的数据 以便后续以0x1EB0开始的块写，如果确定该页为0xFFFF，可以跳过
	FLASH_WRITE_FUN(0x1EA0,16);
	// 修改要写入的值
	FLASH_BUFFER[5]=temp;
	FLASH_BUFFER[6]=0xFF33; //验证 用 0x1FB6
	// 执行0x0F98块的写入
	FLASH_WRITE_FUN(0x1EB0,16);


//--- setp 3	将OX0F69中的数据修改为0X55AA
#if 1	// 0 或  1
	FLASH_WRITE_ONE(0x1E69,0x55AA);
#else
	FLASH_READ_FUN(0x1E60,FLASH_BUFFER_MAX);	//缓存必须满足操作对应大小，不满足整页时，后续数据被删空
	FLASH_BUFFER[9]=0x55AA;
	FLASH_WRITE_FUN(0x1E60,FLASH_BUFFER_MAX);
#endif


//----------------------------------------- setp 4  end display
	while(1)
	{
		temp++;
		if(temp>30000)
		{
			temp=0;
			LED = !LED;
		}

	}
}
/***********************************************************************************
 * 函数名     ：FLASH_READ_ONE
 * 函数功能：以设定地址开始，读取一个数的FLASH数据并返回结果
 * 入口参数：起始地址
 * 返回          ：无
 **********************************************************************************/
unsigned int FLASH_READ_ONE(unsigned int address)
{
#if  1  	// 1 选用C语言表达  0 选用嵌汇编表达，嵌汇编效率更高，但提示传递参数未使用和函数无返回，可以忽略

	// FLASH_READ_BUF;  需要定义全局的该变量，获取值用于返回，或直接使用
	//; 参数的使用
	NVMADDRH=(unsigned char)(address>>8);
	NVMADDRL=(unsigned char)address;
	__asm
		;//备份中断使能寄存器
			BANKSEL _INTCTL
			MOV		R1,_INTCTL
		;//关闭中断，操作不可打断
			CLR		_INTCTL,_AIE
			JNB		_INTCTL,_AIE
			JMP		$-2
		;//时钟频率备份及降频操作,建议降频到1M,此时中断已被关
			MOV		R0,#0x30
			MOV		R2,_OSCCTL
			MOV		_OSCCTL,R0
		;//硬件使能读操作
			BANKSEL _NVMCTL0
			MOV 	R5,#0x81
			MOV 	_NVMCTL0,R5
			NOPZ
			NOPZ
			NOPZ
			NOPZ
		;//时钟与中断使能的还原
			MOV		_OSCCTL,R2
			AND 	R1,#0xC0	;//中断使能仅关系高2位
			ORL 	INTCTL,R1
		;//操作结果赋值到变量，用于返回
			BANKSEL	_NVMDATAL
			MOV	R6,_NVMDATAL
			MOV	R7,_NVMDATAH
			BANKSEL _FLASH_READ_BUF
			MOV (_FLASH_READ_BUF),R6
			MOV (_FLASH_READ_BUF+1),R7
	__endasm;
	return 	FLASH_READ_BUF;
#else
	// 参数的传递使用  编译器自动变量 STK00  和 R0 ，其中R0为高位，返回使用STK00  和 R0 ，其中R0为高位
	__asm
	;//传递操作地址
		BANKSEL _NVMADDRH
		MOV		_NVMADDRH,R0
		BANKSEL STK00
		MOV		R0,STK00
		BANKSEL _NVMADDRL
		MOV		_NVMADDRL,R0
	;//备份中断使能寄存器
		BANKSEL _INTCTL
		MOV		R1,_INTCTL
	;//关闭中断，操作不可打断
		CLR		_INTCTL,_AIE
		JNB		_INTCTL,_AIE
		JMP		$-2
	;//时钟频率备份及降频操作,建议降频到1M,此时中断已被关
		MOV		R0,#0x30
		MOV		R2,_OSCCTL
		MOV		_OSCCTL,R0
	;//硬件使能读操作
		BANKSEL _NVMCTL0
		MOV 	R5,#0x81
		MOV 	_NVMCTL0,R5
		NOPZ
		NOPZ
		NOPZ
		NOPZ
	;//时钟与中断使能的还原
		MOV		_OSCCTL,R2
		AND 	R1,#0xC0	;//中断使能仅关系高2位
		ORL 	_INTCTL,R1
	;//操作结果提供形式 整型数据返回结果使用  编译器自动变量 STK00  和 R0 ，其中R0为高位
		BANKSEL	_NVMDATAL
		MOV		R0,NVMDATAL
		BANKSEL STK00
		MOV		STK00,R0

		BANKSEL	_NVMDATAH
		MOV		R0,NVMDATAH
	__endasm;
#endif

}
/***********************************************************************************
 * 函数名     ：FLASH_READ_FUN
 * 函数功能：以设定地址开始，读取一定个数的FLASH数据放置到数值FLASH_BUFFER[x]中
 * 入口参数：起始地址，操作个数
 * 返回          ：无
 **********************************************************************************/
void FLASH_READ_FUN(unsigned int address,unsigned char length)
{
#if 1  	   //  1时效率比0小   1 使用传递参数   0 按编译器全嵌汇编实现，但编译提示参数未使用，可以忽略
	//; 参数的使用
	NVMADDRH=(unsigned char) (address>>8);
	NVMADDRL=address;

	__asm
	;//备份中断使能寄存器
		BANKSEL _INTCTL
		MOV		R1,_INTCTL
	;//关闭中断，操作不可打断
		CLR		_INTCTL,_AIE
		JNB		_INTCTL,_AIE
		JMP		$-2
	;//时钟频率备份及降频操作,建议降频到1M,此时中断已被关
		MOV		R0,#0x30
		MOV		R2,_OSCCTL
		MOV		_OSCCTL,R0
	;//读取结果的存放起始RAM地址，即数组缓存区  FLASH_BUFFER[x]
		MOV		R3,#_FLASH_BUFFER
	__endasm;

	while(length--)	// 仅使用R0，不改变R1
	{
		__asm
		;//硬件读
		BANKSEL _NVMCTL0
		MOV 	R5,#0x81
		MOV 	_NVMCTL0,R5
		NOPZ
		NOPZ
		NOPZ				;//至少2条
		NOPZ
		;//读结果处理
		BANKSEL	_NVMDATAL
		MOV	R6,_NVMDATAL
		MOV	R7,_NVMDATAH

		BANKSEL _FLASH_BUFFER
		ST		[R3],R6
		INC		R3
		ST		[R3],R7
		INC		R3
		;//指向下一操作地址，建议每次操作内容在一个块中，此时不需要处理_BADDRL进位的_BADDRH+1操作
		BANKSEL _NVMADDRL
		INC		_NVMADDRL
		JNB		_PSW,_Z
		INC		_NVMADDRH
		__endasm;
	}

	__asm
		;//时钟与中断使能的还原
		BANKSEL _OSCCTL
		MOV		_OSCCTL,R2
		AND 	R1,#0xC0	;//中断使能仅关系高2位
		ORL 	_INTCTL,R1
	__endasm;
#else
	// 参数1的传递使用  编译器自动变量 STK00  和 R0 ，其中R0为高位
	// 参数2的传递使用  编译器自动变量 STK01
	// 整体实现的嵌汇编会提示参数未被使用，可以忽略
__asm
	;//传递操作地址
	BANKSEL _NVMADDRH
	MOV		_NVMADDRH,R0
	BANKSEL STK00
	MOV		R0,STK00
	BANKSEL _NVMADDRL
	MOV		_NVMADDRL,R0
	;//备份中断使能寄存器
	BANKSEL _INTCTL
	MOV		R1,_INTCTL
	;//关闭中断，操作不可打断
	CLR		_INTCTL,_AIE
	JNB		_INTCTL,_AIE
	JMP		$-2
	;//时钟频率备份及降频操作,建议降频到1M,此时中断已被关
	MOV		R0,#0x30
	MOV		R2,_OSCCTL
	MOV		_OSCCTL,R0
	;//读取结果的存放起始RAM地址，即数组缓存区  FLASH_BUFFER[x]
	MOV		R0,#_FLASH_BUFFER

FLASH_READ_FUN_LOOP:
	;//硬件读
	BANKSEL _NVMCTL0
	MOV 	R5,#0x81
	MOV 	_NVMCTL0,R5
	NOPZ
	NOPZ
	NOPZ					;//至少2条
	NOPZ
	;//读结果处理
	BANKSEL	_NVMDATAL
	MOV	R6,_NVMDATAL
	MOV	R7,_NVMDATAH

	BANKSEL _FLASH_BUFFER
	ST		[R0],R6
	INC		R0
	ST		[R0],R7
	INC		R0
	;//指向下一操作地址，建议每次操作内容在一个块中，此时不需要处理_BADDRL进位的_BADDRH+1操作
	BANKSEL _NVMADDRL
	INC		_NVMADDRL
	JNB		_PSW,_Z
	INC		_NVMADDRH
	;//读数量的判断
	BANKSEL STK01
	DECJZ	STK01
	JMP		FLASH_READ_FUN_LOOP

	;//时钟与中断使能的还原
	BANKSEL _OSCCTL
	MOV		_OSCCTL,R2
	AND 	R1,#0xC0	;//中断使能仅关系高2位
	ORL 	_INTCTL,R1
__endasm;
#endif
}
/***********************************************************************************
 * 函数名     ：FLASH_WRITE_FUN
 * 函数功能：按块或按页写入数据到FLASH，个数参数只能为16,32 ，地址必须为块的首地址 如十六进制下结尾00 20 40 60 80 A0 C0 E0
 * 			如果地址不是页的首地址，必须确定后续块结果为0xFFFF，或前面操作过块首写，使后续块值被0xFFFF，否则写结果异常。
 * 入口参数：待写地址，待写地址的数据
 * 返回          ：无
 * 写时间说明：除去代码，以整页FLASH为例，操作完第一块需要6ms，另外3块需要3ms。即第一块执行整页的擦除后写自身块，其他块直接写。
 **********************************************************************************/
void FLASH_WRITE_FUN(unsigned int address,unsigned char length)
{
#if 1  	   //  1时效率比0小   1 使用传递参数   0 按编译器全嵌汇编实现，但编译提示参数未使用，可以忽略
	//; 参数的使用
	NVMADDRH=(unsigned char) (address>>8);
	NVMADDRL=address;

	__asm
	;//备份中断使能寄存器
		BANKSEL _INTCTL
		MOV		R1,_INTCTL
	;//关闭中断，操作不可打断
		CLR		_INTCTL,_PUIE
		CLR		_INTCTL,_AIE
		JNB		_INTCTL,_AIE
		JMP		$-2
	;//时钟频率备份及降频操作,建议降频到1M,此时中断已被关
		MOV		R0,#0x30
		MOV		R2,_OSCCTL
		MOV		_OSCCTL,R0
	;//读取结果的存放起始RAM地址，即数组缓存区  FLASH_BUFFER[x]
		MOV		R3,#_FLASH_BUFFER
	__endasm;

	while(length--)		// 仅使用R0，不改变R1
	{
		__asm

		;//加载待写数据
		BANKSEL _FLASH_BUFFER
		LD		R6,[R3]
		INC		R3
		LD		R7,[R3]
		INC		R3
		BANKSEL _NVMDATAH
		MOV _NVMDATAH,R7
		MOV _NVMDATAL,R6
		;//硬件写
		MOV 	R5 ,#0x84
		MOV 	_NVMCTL0,R5
		MOV 	R5,#0x69
		MOV 	_NVMCTL1,R5
		MOV 	R5,#0x96
		MOV 	_NVMCTL1,R5
		SET 	_NVMCTL0 , 1	;// 写存在高压，高压还原添加空指令确保后续运行正常
		NOPZ
		NOPZ
		NOPZ
		NOPZ
		NOPZ
		NOPZ						;// 建议10条，至少2条
		NOPZ
		NOPZ
		NOPZ
		NOPZ
		MOV 	R5,#0X80
		MOV 	_NVMCTL0,R5
		;//指向下一操作地址，这里不考虑高位，特性要求只能操作一页内的数据，不能跨页
		BANKSEL _NVMADDRL
		INC		_NVMADDRL
		__endasm;
	}

	__asm
		;//时钟与中断使能的还原
		BANKSEL _OSCCTL
		MOV		_OSCCTL,R2
		AND 	R1,#0xC0	;//中断使能仅关系高2位
		ORL 	_INTCTL,R1
	__endasm;
#else
	// 参数1的传递使用  编译器自动变量 STK00  和 R0 ，其中R0为高位
	// 参数2的传递使用  编译器自动变量 STK01
	// 整体实现的嵌汇编会提示参数未被使用，可以忽略
__asm
	;//传递操作的地址(块首)
	BANKSEL _NVMADDRH
	MOV		_NVMADDRH,R0
	BANKSEL STK00
	MOV		R0,STK00
	BANKSEL _NVMADDRL
	MOV		_NVMADDRL,R0
	;//备份中断使能寄存器
	BANKSEL	_INTCTL
	MOV		R1,_INTCTL
	;//关闭中断，操作不可打断
	CLR		_INTCTL,_PUIE
	CLR		_INTCTL,_AIE
	JNB		_INTCTL,_AIE
	JMP		$-2
	;//时钟频率备份及降频操作,建议降频到1M,此时中断已被关
	MOV		R0,#0x30
	MOV		R2,_OSCCTL
	MOV		_OSCCTL,R0
	;//待写数据的存放起始RAM地址，即数组缓存区  FLASH_BUFFER[x]
	MOV		R0,#_FLASH_BUFFER

FALSH_WRITE_PAGE_LOOP:
	;//加载待写数据
	BANKSEL _FLASH_BUFFER
	LD		R6,[R0]
	INC		R0
	LD		R7,[R0]
	INC		R0

	BANKSEL _NVMDATAH
	MOV _NVMDATAH,R7
	MOV _NVMDATAL,R6

	;//硬件写
	MOV 	R5 ,#0x84
	MOV 	_NVMCTL0,R5
	MOV 	R5,#0x69
	MOV 	_NVMCTL1,R5
	MOV 	R5,#0x96
	MOV 	_NVMCTL1,R5
	SET 	_NVMCTL0 , 1	;// 写存在高压，高压还原添加空指令确保后续运行正常
	NOPZ
	NOPZ
	NOPZ
	NOPZ
	NOPZ
	NOPZ						;// 建议10条，至少2条
	NOPZ
	NOPZ
	NOPZ
	NOPZ
	MOV 	R5,#0X80
	MOV 	_NVMCTL0,R5
	;//指向下一操作地址，这里不考虑高位，特性要求只能操作一页内的数据，不能跨页
	BANKSEL _NVMADDRL
	INC		_NVMADDRL

	BANKSEL STK01
	DECJZ	STK01
	JMP		FALSH_WRITE_PAGE_LOOP

	;//时钟与中断使能的还原
	BANKSEL _OSCCTL
	MOV		_OSCCTL,R2
	AND 	R1,#0xC0	;//中断使能仅关系高2位
	ORL 	_INTCTL,R1
__endasm;
#endif
}

/***********************************************************************************
 * 函数名     ：FLASH_WRITE_ONE
 * 函数功能：向某一地址写入一个字，不建议使用，实际执行从页首读出，经对应地址的缓存修改，在页起始回写，因此地址与页首偏移量需要数组大小满足需求。
 * 建议：         可采用FLASH_WRITE_FUN的传递个数为16,32 对应连续操作地址开始的第一块 第二块
 * 			 但地址必须是某块的开始
 * 入口参数：待写地址，待写地址的数据
 * 返回          ：无
 **********************************************************************************/
void FLASH_WRITE_ONE(unsigned int address,unsigned int value)
{
	// 读出当前页到数据缓存，要求缓存大小必须满足写地址的偏移量要求
	FLASH_READ_FUN(address&0xFFE0,FLASH_BUFFER_MAX);
	// 修改待写数据在按照页排序中位置数据结果
	FLASH_BUFFER[address&0x1F] = value;
	// 整页数据回写
	FLASH_WRITE_FUN(address&0xFFE0,FLASH_BUFFER_MAX);
}
