一种基于MCU的神经网络模型在线更新方案之MCU实战篇

cathy的头像

本篇将是系列文章的最后一篇,在MCU上进行实际的部署。
小编就不再给大家复习历史了,直接开始正题。如果想温习前两部分内容,请点击< a href="http://mcu.eetrend.com/content/2022/100562111.html">此处和此处
首先给大家介绍一下我们将要使用的离线Flash烧写工具,名为JFlashLite,他有一个兄弟叫JFLash,既然是Lite就是轻量版本的JFlash工具。

当然,需要大家自行下载Segger Jlink工具包。是的,我们需要一条JLink。随后找到JFlashLite工具,他长这样(请忽略这里的1176,我们还没有配置):

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

比起下图他的哥哥JFlash,是不是很清爽的界面:

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

当然,我们今天的主角是JFlashLite,稍后会介绍如何进行配置并下载

程序编写

工具介绍完了,下面正式开始编写配套程序,所使用的平台是i.MX RT1060, 开发板是IMXRT1060EVKB:

● 划分FLASH以及sdram区域,sdram区域负责加载flash中内容,加速内存访问:我们的开发板上有一个8MB的Flash,内存映射地址为0x60000000,首先保留1MB的头部区域以存储代码,其作用后续会提到。那么我们就剩下7MB大小的空间,由于数据一般较大,划分4MB作为数据存储区,剩下的3MB存储模型。同样的,在sdram区域,内存映射地址为0x80000000也同样开辟这样大的两块内存区,不过,为了防止数据溢出,我们仅针对于sdram区域,在数据区和模型区中间插入256B的数据保护区,并填充0xdeadbeef,最终的内存布局如下:

Flash上内存分配

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

Sdram内存分配

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

● 利用JflashLite工具进行烧写:

打开JFlashLite工具点击…选择器件为1062xxxxA:

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

选择对应的数据文件以及模型进行烧写,烧写地址要依据上述定义的分配,即模型数据烧写到0x60100000,图像数据烧写到0x60400000:

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

选择好之后,点击Program Device即可进行烧写;针对于模型数据,yao注意将以.tflite结尾的模型文件,重命名为.bin文件。

● Scf文件编写,主要考虑到,运行时,将flash中的数据,拷贝到sdram中,以提高运行速度,这里声明两个区域负责存储ER_tflite_model以及ER_test_data

#define m_text_start                   0x80000400
#define FLASH_LOAD 7 * 1024 * 1024
LR_m_text m_interrupts_start m_text_start+m_text_size-m_interrupts_start {   ; load region size_region
  VECTOR_ROM m_interrupts_start FIXED m_interrupts_size { ; load address = execution address
    * (.isr_vector,+FIRST)
  }
  ER_m_text m_text_start FIXED m_text_size - FLASH_LOADER_SIZE { ; load address = execution address
    * (InRoot$$Sections)
    .ANY (+RO)
  }
 
#if (defined(FLASH_LOAD))
  ER_ test_data +0 EMPTY 4 * 1024 * 1024 {}
  ER_PLACEHOLDER1 +0 EMPTY FILL 0xdeadbeef 256{} 
  ER_tflite_model +0 EMPTY 3 * 1024 * 1024 {}  
  ER_PLACEHOLDER2+0 EMPTY FILL 0xdeadbeef 256{}
  ER_EMPTY m_text_start + m_text_size EMPTY 0{}
#endif

● 主代码编写,针对于边界溢出检测代码,简单起见,只检测首地址处值,不同则表示溢出,死循环等待

typedef struct {
	uint32_t n, h, w, c;
	uint8_t data[0];
}data_t;
// use a split area, total 7MB:
	// tflite model 3MB
	// img_data 4MB
	#define FLASH_BASE (0x60100000)
	#define MB(x)  (x * 1024 * 1024)
	
	#define lr_model_data_len (MB(3))
	#define lr_model_data  FLASH_BASE
	#define lr_model_data_end (lr_model_data + lr_model_data_len)

	#define lr_img_data_len  (MB(4))
	#define lr_img_data  (lr_model_data_end)  // 0x60200000
	#define lr_img_data_end (lr_img_data + lr_img_data_len)
	
	// declare the sdram memory
	extern uint8_t Image$$ER_tflite_model$$ZI$$Base[];
	#define model_data Image$$ER_tflite_model$$ZI$$Base
	
	extern uint8_t Image$$ER_test_data$$ZI$$Base[];
	#define img_data Image$$ER_test_data$$ZI$$Base

extern uint8_t Image$$ER_PLACEHOLDER1$$ZI$$Base[];
#define PLACEHOLDER1 Image$$ER_PLACEHOLDER1$$ZI$$Base

extern uint8_t Image$$ER_PLACEHOLDER2$$ZI$$Base[];
#define PLACEHOLDER2 Image$$ER_PLACEHOLDER2$$ZI$$Base
		
#define DO_MEMCPY(name) memcpy((void*)name, (void*)lr_##name, lr_##name##_len)
#define PRE_INIT() \
    do{ \
	DO_MEMCPY(model_data);\
	DO_MEMCPY(img_data); \
            while(0xdeadbeef != *(uint32_t*) PLACEHOLDER1) ; \  
                         while(0xdeadbeef != *(uint32_t*) PLACEHOLDER2) ; \         
    }while(0);

定义好了一些宏之后,就是模型的初始化函数:

void tflite_engine_init(){
	#ifdef FLASH_LOAD
	PRE_INIT()
	#endif
	SysTick_Config(CLOCK_GetCoreSysClkFreq() / 1000);
	MODEL_AllocateTensor((void*)tensorArena, sizeof(tensorArena));
	if (MODEL_Init(model_data) != kStatus_Success)
    {
        PRINTF("Failed initializing model");
        for (;;) {}
    }
}

测试数据如何获取呢,利用我们刚才定义的data_t结构体:

data_t *image = (data_t*)img_data;
	image_data_ptr = image->data;

这样,我们就拿到了存储在flash并且已经被搬运到了sdram上的数据了,接下来就是编译运行了。

实际运行与测试

测试时候,要注意首先确保我们的开发板已经是XIP启动,即从Nor Flash启动,并且保证在flash的头部,烧写过一个完整的可执行镜像,比如hello_world程序,其中会包含Flash的一些配置信息,这一步小编就不再举例,还请大家自行准备。

这样,我们的BootRom会据此帮我们配置好Flash,程序中就不用手动调用Flash的初始化代码了。

还要注意,代码要全部运行在SDRAM或是其他介质上,因为我们已经将flash据为己有了。

下面是内存镜像的样子,首先是model:

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

再者是测试数据,前四个uint32类型的数据刚好是小编这里定义的数据长度100张128*128*3的rgb彩色图:

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

连上板子和PC,并打开串口控制台即可查看输出结果,小编所选用的模型是一个水果识别的模型,下面是最后一组数据的输出结果,证明我们的程序运行成功!

“一种基于MCU的神经网络模型在线更新方案之MCU实战篇"

展望

当然,小编给大家分享的这个方法,不仅可以应用在神经网络AI推理上,可以当作一个低配版的flashloader,以供大家灵活地更新静态数据资源。

相关阅读:

一种基于MCU的神经网络模型灵活更新方案之先行篇

一种基于MCU的神经网络模型在线更新方案之数据处理篇

来源:恩智浦MCU加油站
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。