Open Flash Loader - 简单搞定JLINK Ozone flash下载

JLINK和Ozone基本算是MacOS上嵌入式开发的唯一选择了,虽然基于CMSIS-DAP的pyocd在MacOS上跑得也挺欢,但是这个对于新产品支持并不友好,并且需要额外的芯片和一整套软件的移植,对于我来说还是有些复杂。因此我还是选择Segger公司的一套工具好了。

因为嵌入式芯片种类繁多,对于我这样总是研究新产品的人来说,等官方支持我们的产品明显是不现实的,所以我需要有一个可以比较自由定制的调试工具链,以前我都是通过Ozone的方式进行调试,程序基本也都是运行在RAM上面,但是有些应用对于RAM的需求很大,我很难在新产品上跑比较大型的应用,如果通过Bootloader下载程序,每次都要条线也是繁琐的要命。所以这时候有个直接Ozone能够支持的flashloader就非常完美了,正是因为基于这种需求我找到了Segger官方支持的一个Open Flash Loader,这个flashloader可以支持用户自己定义新产品的flash下载,具体实现可以参考官方wiki.

Flash Loader的基本原理

Flash Loader是调试工具用来将用户的应用加载到芯片内部Flash的一个程序,我们知道芯片内部的Flash是无法直接支持写操作的,所以就要借助Flash loader完成这一操作。Flash Loader实际上是运行在芯片RAM的一个Flash操作代码,调试工具本身是支持RAM的直接访问的,所以可以直接Halt CPU然后通过写RAM的方式将程序下载到RAM,下载完成后Debugger可以通过控制程序PC的位置实现不同的函数调用,从而完成Flash擦除和写入的操作,比如在需要下载一段程序的时候,Debugger可以先调用擦除Sector的函数将Flash进行擦除,然后将待下载的程序分片写入RAM中,然后调用Flash loader的写入函数将数据从RAM写入到Flash中,从而完成下载操作。

flashloader_memory.PNG

从上面的描述中,我们可以看出,如果需要让Debugger和Flash Loader协同操作,Flash Loader需要有一定的格式,并且要实现必要的函数,也就是遵循一定的框架。今天我们要讲的Open Flash Loader实际上就是Segger家Flash loader遵循的一套框架。

Open Flash Loader

Segger官方给出了一个Open Flash Loader的模板工程,工程用到的开发环境也是Segger公司的一套IDE,真是不遗余力的推广呀,好在编译个Flash Loader还是免费的,所以还算良心。工程模板可以通过https://wiki.segger.com/images/4/4e/OpenFlashloader_CortexM_Template_EmbeddedStudio.zip 进行下载。

Open Flash Loader移植

这个工程还是非常简单的,我们移植的时候基本就是按照我们的Flash参数修改相应的参数,然后编写擦除和写入的两个函数就好了。

首先我们看一下设备定义的FlashDev.c,这个文件主要就是一个结构体:

struct FlashDevice const FlashDevice __attribute__ ((section ("DevDscr"))) =  {
  ALGO_VERSION,              // Algo version
  "Internal flash", // Flash device name
  ONCHIP,                    // Flash device type
  0x00000000,                // Flash base address
  0x00080000,                // Total flash device size in Bytes (256 KB)
  16,                       // Page Size (number of bytes that will be passed to ProgramPage(). May be multiple of min alignment in order to reduce overhead for calling ProgramPage multiple times
  0,                         // Reserved, should be 0
  0xFF,                      // Flash erased value
  100,                       // Program page timeout in ms
  6000,                      // Erase sector timeout in ms
  //
  // Flash sector layout definition
  //
  0x00002000, 0x00000000,   // 16 *  8 KB =  128 KB
  0xFFFFFFFF, 0xFFFFFFFF    // Indicates the end of the flash sector layout. Must be present.
};

注意DevDscr的section属性很重要,别乱改,这里我们只需要修改Flash大小, page大小和最后的Flash结构。

另外一个需要修改的就是FlashPrg.c这个文件包含Flash操作的各种函数,这里也就是实现两个函数就好了,首先我们看看基础的一些宏定义:

#define PAGE_SIZE_SHIFT (4)    // The smallest program unit (one page) is 8 byte in size
#define SECTOR_SIZE_SHIFT (13) // 4096 sector size
//
// Some flash types require a native verify function as the memory is not memory mapped available (e.g. eMMC flashes).
// If the verify function is implemented in the algorithm, it will be used by the J-Link DLL during compare / verify
// independent of what verify type is configured in the J-Link DLL.
// Please note, that SEGGER does not recommend to use this function if the flash can be memory mapped read
// as this may can slow-down the compare / verify step.
//
#define SUPPORT_NATIVE_VERIFY (0)
#define SUPPORT_NATIVE_READ_FUNCTION (0)
#define SUPPORT_BLANK_CHECK (0)
#define SUPPORT_ERASE_CHIP (0)
#define SUPPORT_SEGGER_OPEN_Program (1)
#define SUPPORT_SEGGER_OPEN_ERASE (1)

这个部分也是根据需要修改,后面的几个为0的support如果改成1就要实现相应的函数,这里因为我们Flash一般都支持编址形式的访问(类似NOR flash)所以一般都是不用实现的,除非你搞成类似EMMC那样块操作的形式,那么是需要实现verify,read和check的函数的。这里我们根据Page和sector大小修改相应的shift就可以了。

下面我们看看Init函数,这个函数在每次flash操作之前调用,可以进行必要的初始化,我这为了省事,直接在这里关了WDOG,严谨软件不建议关🐶哈。对应的UnInit这里我就不写了,因为里面没什么需要恢复的。

int Init(U32 Addr, U32 Freq, U32 Func) {
(void)Addr;
(void)Freq;
(void)Func;
//
// Init code
//
disable_wdog();

return 0;
}
下面就来到比较重要的擦除函数了,这个调用的时候有地址参数,直接实现就好了

int EraseSector(U32 SectorAddr) {
  //
  // Erase sector code
  //
  *(volatile U32 *)(0x40020018) = SectorAddr; // FADDR
  *(volatile U32 *)(0x40020010) = 0x42;       // FCMD
  *(volatile U32 *)(0x40020000) = 0x80;       // FSTAT
  while (!(*(volatile U32 *)(0x40020000) & 0x80))
    ;
  if ((*(volatile U32 *)(0x40020000) & 0x01))
    return 1; // FAIL
  _FeedWatchdog();
  return 0;
}

然后就是写Flash的函数,没啥好说的,相应的Flash命令填上去就好了,这些函数可以在main里面直接Debug。

int ProgramPage(U32 DestAddr, U32 NumBytes, U8 *pSrcBuff) {
  volatile U32 *pSrc;
  U32 NumPages;
  int r;

  r = -1;
  //
  // RAMCode is able to program multiple pages
  //
  NumPages = NumBytes >> PAGE_SIZE_SHIFT;
  //
  // Program page-wise
  //
  pSrc = (U32 *)pSrcBuff;
  if (NumPages) {
    r = 0;
    do {
      _FeedWatchdog();
      //
      // Program one page
      //
      *(volatile U32 *)(0x40020018) = DestAddr;
      *(volatile U32 *)(0x40020020) = pSrc[0];
      *(volatile U32 *)(0x40020024) = pSrc[1];
      *(volatile U32 *)(0x40020028) = pSrc[2];
      *(volatile U32 *)(0x4002002c) = pSrc[3];
      *(volatile U32 *)(0x40020010) = 0x24;
      *(volatile U32 *)(0x40020000) = 0x80; // FSTAT
      while (!(*(volatile U32 *)(0x40020000) & 0x80))
        ;
      if ((*(volatile U32 *)(0x40020000) & 0x01))
        return 1; // FAIL
      pSrc += 4;
      DestAddr += 4;
    } while (--NumPages);
  }
  return r;
}

上面两个函数我们都可以在Main函数里面调用调试,并不影响FlashLoader,因为Debugger并不会调用Main函数。

搞定函数之后就可以编译(Release)得到elf文件了,这个文件就是我们Debugger需要的Flash Loader了。

向Ozone和JLINK里面添加Device

有了Flash Loader之后我们还要让调试工具知道怎么去找flash loader,当然我们还可以为我们的产品起一个响亮的名字,这个时候我们就需要到JLink或者ozone安装目录下面找一个JLinkDevices.xml的文件了,打开文件,在文件的末尾依照格式添加device吧!比如我们搞一个:

<Device>
    <ChipInfo Vendor="Wukong" Name="West" Core="JLINK_CORE_CORTEX_M0" WorkRAMAddr="0x20000000" WorkRAMSize="0x4000" />
    <FlashBankInfo Name="Internal Flash" BaseAddr="0x00000000" MaxSize="0x20000" Loader="Devices/Wukong/flash_loader_west.elf" LoaderType="FLASH_ALGO_TYPE_OPEN" AlwaysPresent="1"/>
  </Device> 

这个里面Vendor就是企业的名称,Name是产品的名称,CORE是产品核心的名称,然后根据需要修改RAM的地址和大小,Flash Loader的路径就可以了,保存之后打开Ozone或者J-Flash Lite就可以选我们自己定义的Device了,然后debugger会根据elf文件内容自动加载Flashloader下载,非常完美的。

发表新评论