基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)

需要的软件:
Keil
STM32CubeMX
J-Flash

参考文档:
方法1:在Keil中点击Help→uVision Help,然后再搜索框中输入FLM,点击列出主题,可以看到生成下载算法的大致步骤:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第1张图片

方法2:在ARM Keil官网,搜索KAN333,可以找到生成算法说明的PDF文档以及例程源码。链接
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第2张图片

方法3:在Keil安装路径下Keil_v5\ARM\Flash_Template,找到Abstract.txt,打开后有一个生成算法说明的文档。链接
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第3张图片
先说一下大致原理,生成的算法文件也就是FLM文件,实际上会先下载进你板子的RAM中,然后在板子的RAM中运行,进而去写外部Nor Flash或者片内Flash。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第4张图片
参考上面任意一个说明文档都可以,但是不要完全按照上面描述的来,我之前是按照第二个方法下载的pdf来的,但是折腾了两天才有结果,下面是具体实现的方法。

1.复制Keil安装路径下Keil_v5\ARM\Flash_Template文件夹,并重命名为自己的算法工程名,我这以STM32L4xx_W25Q64为例。
2.将文件夹的只读属性取消,并将keil工程的名字改为自己的工程名。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第5张图片
3.打开工程,选择魔术棒,在Device一栏中选择自己板子上对应的MCU,在Target一栏中ARM Compiler保持默认V5就好,不用像PDF中描述的一样改成V6的,两个版本用的编译方法不一样,V6版本的生成的FLM文件小一点,但是好像没办法使用MicroLIB,会报错。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第6张图片
在Output一栏中改为自己工程的名字
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第7张图片
User一栏中不要动,cmd.exe /C copy “Objects%L” "[email protected]"在工程编译后会执行。
C/C++和Asm中确认Read-Only Position Independent和Read-Write Position Independent勾选,表明这个是与位置无关的算法,因为要放在RAM中执行。C/C++一栏中Define中对应填入需要的宏,代码量不大的话编译器优化等级可以设置为0,后边会提到代码量具体有什么限制。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第8张图片
Linker一栏中使用自定义分散加载文件,保持默认就好。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第9张图片
4.打开Manage Run-Time Environment,勾选如下内容,并点击绿色按钮,启动STM32CubeMX。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第10张图片
5.在STM32CubeMX中配置工程,先配置为自己需要的时钟频率,不要像PDF中一样保持默认,不然的话时钟频率只有4M。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第11张图片
然后配置好SPI,GPIO等,如果有需要的话也可以配置一下串口用于输出Log,我是配置了两个LED灯,一个用于指示擦除,一个用于指示编写。
在Project Manager中,Project一栏中不用像PDF中描述的勾选Do not generate the main(),这样会生成main函数,可以用于测试生成的代码是否可用,不用自己写。
Code Generator一栏中按如下勾选:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第12张图片
Advanced Settings一栏保持函数调用。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第13张图片
然后点击Generate Code生成代码。

6.在STM32L4xx_W25Q64\RTE\Device\STM32L431CCTx\STCubeGenerated\MDK-ARM路径下,打开生成的的工程,然后可以在里面测试一下生成的工程,添加一下自己的读写外部Nor Flash的代码,能正常操作就行。

7.回到生成算法的工程里面,把main.c中的main函数屏蔽掉,否则在生成算法文件的时候会报错。此外也不要像PDF中描述的重新定义HAL_InitTick(),HAL_Delay()等几个函数,因为使用HAL库的话用到的一些超时检测什么的都会有影响。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第14张图片

8.在Device目录右键Options for Component Class ‘Device’,选择启动文件,取消勾选Include in target build。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第15张图片
9.新建一个目录,添加自己操作外部Nor Flash的代码和System文件。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第16张图片
10.实现FlashDev.c,这个是设备描述文件,改为自己合适的就好。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第17张图片
Device Type根据实际设置。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第18张图片
Device Start Address可以直接从0x00000000开始,这样就不用在FlashPrg.c中额外处理地址。

Programming Page Size根据自己的外部Nor Flash设置,比如W25Q64每页256字节,设置为256的话一个扇区就需要多次调用ProgramPage()函数,所以可以直接设置成一个扇区大小(4096),这样调用ProgramPage()函数的时候直接写一个扇区。

Specify Size and Address of Sectors指的是扇区划分,比如有的MCU有不同大小的扇区,第一个参数就是扇区大小,第二个参数就是扇区起始地址;比如W25Q64,每个扇区都是16个页,每个页256字节,所以第一个参数就是0x001000(4096),第二个表示从0地址开始,如果有其他的扇区大小类型,在SECTOR_END之前添加。

Erase Sector Timeout为擦除超时时间,全片擦除也受这个参数影响,W25Q64全片擦除需要14s左右,所以适当设置大一点。

10.实现FlashPrg.c,必须要实现的函数是Init,UnInit,EraseChip,EraseSector,ProgramPage。可根据需要实现的函数Verify,BlankCheck。

Init函数需要初始化时钟,GPIO,SPI等,正确执行返回0,执行错误返回1。不要像PDF中一样关中断,因为需要用到滴答定时器中断。

int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
  int ret = 0;
  volatile int i;
  volatile unsigned char * ptr = (volatile unsigned char * )&hspi2;

  for (i = 0; i < sizeof(hspi2); i++) {
    *ptr++ = 0U;
  }
  SystemInit();
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_InitLed();
  if(W25Q64_Init() != W25Q64_ID)
  {
	ret = 1;
  }
  return (ret);                                  // Finished without Errors
}

UnInit函数可以什么都不做,因为我在擦除和写的时候翻转LED,所以添加了关LED操作。

int UnInit (unsigned long fnc) {

  HAL_GPIO_WritePin(Prog_Led_GPIO_Port,Prog_Led_Pin,GPIO_PIN_RESET);
  HAL_GPIO_WritePin(Erase_Led_GPIO_Port,Erase_Led_Pin,GPIO_PIN_RESET);
  return (0);                                  // Finished without Errors
}

擦除分为全片擦除和擦除扇区,流程图如下:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第19张图片
EraseChip函数直接调用自己的全片擦除函数。

int EraseChip (void) {
	
  HAL_GPIO_TogglePin(Erase_Led_GPIO_Port,Erase_Led_Pin);
  W25Q64_Erase_Chip();
  return (0);                                  // Finished without Errors
}

EraseSector 函数直接调用自己的擦除扇区函数。

int EraseSector (unsigned long adr) {
	
  HAL_GPIO_TogglePin(Erase_Led_GPIO_Port,Erase_Led_Pin);
  W25Q64_Erase_Sector((uint32_t)adr);
  return (0);                                  // Finished without Errors
}

ProgramPage函数直接调用自己的写扇区函数。要写的大小跟FlashDev.c中的Programming Page Size大小一致。写Flash流程图如下:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第20张图片

int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {

  HAL_GPIO_TogglePin(Prog_Led_GPIO_Port,Prog_Led_Pin);
//	W25Q64_Write_Page(buf,(uint32_t)adr,(uint16_t)sz);
  W25Q64_Write_Sector(buf,(uint32_t)adr,(uint16_t)sz);
  return (0);                                  // Finished without Errors
}

Verify和BlankCheck函数可以不实现。
Verify函数作用是判断写成功了多少字节数据,这个值跟FlashDev.c中Device Start Address值有关,如果Device Start Address值不为0,那么就是Device Start Address加上写成功了多少字节。校验函数流程图:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第21张图片

unsigned long Verify (unsigned long adr, unsigned long sz, unsigned char *buf) {
  uint32_t i;
  W25Q64_ReadData(W25Q64_BufferA,(uint32_t)adr,(uint16_t)sz);
  for (i = 0; i < sz; i++) 
  {
	if (W25Q64_BufferA[i] != buf[i]) 
	{
	  return (adr + i); 
	}
  }
  return (adr + sz); 
}

BlankCheck函数作用是在擦除块的时候判断一下这个块需不需要擦除,实际上就是比较块内的值是不是都是FlashDev.c中定义的Initial Content of Erased Memory值一样,如果有一个字节不是,说明块需要擦除。

int BlankCheck (unsigned long adr, unsigned long sz, unsigned char pat) {
  uint32_t i, j;
  uint32_t read_len = 0;
  if((adr + sz) > W25Q64_TOTAL_NUM)
  {
	return (1);
  }
  for (i = 0; i < sz; i += W25Q64_SECTOR_NUM) 
  {
	if(sz >= W25Q64_SECTOR_NUM)
	{
		read_len = W25Q64_SECTOR_NUM;
	}
	else
	{
		read_len = sz;
	}
	W25Q64_ReadData(W25Q64_BufferA,(adr+i),read_len);
	for(j = 0; j < read_len; j++)
	{
	  if (W25Q64_BufferA[j] != pat) 
	    return (1);
	}
	sz -= read_len;
  }
  return (0);                           // Memory is blank
}

然后编译之后就会生成下载算法文件FLM。注意不要添加太多没用的调试代码,否则会导致生成的代码量太大,就不得不提高优化等级,还可能导致生成的算法文件使用时报错。可以在编译之后通过map文件确认一下代码占用的空间,不能超过RAM的大小,因为算法是需要在RAM中运行的。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第22张图片
如果算法代码量太大,可能会导致报错RAM area configured for this target is too small.
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第23张图片

11.将生成的下载算法文件复制到Keil_v5\ARM\Flash路径下,就可以在KEIL中看到。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第24张图片
12.在J-link安装目录下SEGGER\JLink_V640\Devices中新建一个文件夹,比如命名为W25Qxx,然后把下载算法文件复制过来。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第25张图片
13.在SEGGER\JLink_V640路径下打开JLinkDevices文件,在末尾添加如下内容。

  <!--         -->
  <!-- W25QXX  -->
  <!--         -->
<Device>
<ChipInfo Vendor="W25QXX" Name="STM32L4xx_W25Q64" Core="JLINK_CORE_CORTEX_M4" WorkRAMAddr="0x20000000" WorkRAMSize="0x0000C000"/>
<FlashBankInfo Name="SPI Flash" BaseAddr="0x00000000" MaxSize="0x00800000" Loader="Devices/W25Qxx/STM32L4xx_W25Q64.FLM" LoaderType="FLASH_ALGO_TYPE_OPEN" AlwaysPresent="1"/>
</Device>

WorkRAMAddr和WorkRAMSize就是自己MCU的RAM参数,BaseAddr和MaxSize设置的和FlashDev.c中一样。

之后就可以使用J-Flash直接把数据烧写进外部Nor Flash中。

测试:
使用J-Flash新建工程,选择下载算法文件。
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第26张图片
全片擦除:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第27张图片
擦除扇区:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第28张图片
写入:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第29张图片
Flash为空:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第30张图片
Flash不为空:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第31张图片
写入并校验:
基于Keil生成外部Nor Flash下载算法,并使用J-Flash直接烧录(以W25Q64为例)_第32张图片

你可能感兴趣的:(单片机,STM32,C语言,单片机,stm32,Nor,Flash)