{PLC,IOT}>ESP32 可执行文件加载

        可执行文件的加载运行,是计算机操作系统一个很古老、很基础的问题,这个问题早已经很好的被解决了,基本上以ELF(Executable and Linkable Format)为标准。我们在计算机上理所当然的启动可执行文件,没有觉得有什么稀奇的。如果我们“理所当然”的期望其他操作系统也支持可执行文件加载运行,心里的落差是相当巨大的。

       在嵌入式软件领域,应用的场景差异很大,软件的组成部分不像个人电脑或者服务器那样有区分明确的系统软件和应用软件,通常有三种情形:1、以嵌入式Linux(安卓、WinCE或者Vxworks)等成熟的操作系统为平台,运行应用领域的应用软件;2、微型操作系统FreeRTOS、UCOS内核作为领域应用软件的支持库,和应用软件一起编译成二进制文件在板子上运行;3、没有操作系统,应用软件把需要做的所有事情全做了。在IOT应用领域,网络通信是必不可少的基础,所以1、2的情况居多,结合成本因素,第2种情况最为普遍。在有网络通信的基础上,远程升级维护的需求突显重要,系统和应用一起编译的现状令人苦恼。

       网络上关于 FreeRTOS或者ESP32实现ELF文件加载运行的信息较少,有一篇博文Elf Loader (ourembeddeds.github.io)把ELF在MCU上加载运行的原理和方法做了清晰的介绍。

按照文章所讲的方法做,也可以达成目标,不过程序的开发调试要点工作量。我们采取了开发平台软件和板上系统软件各做一部分的方式实现ELF文件的加载运行。具体就是在电脑上调用python 程序将 elf文件进行一次预处理,提取入口地址、程序段(.text)地址和容量、数据段(.data和.rodata)地址和容量、以及bss段的地址和容量,保存为新的二进制文件,有PLC的基础系统软件加载到ESP32的内存中运行。处理各段的内容的python代码:

    def tobin(self):
        fo = open("plc.bin","wb")
        a = bytearray(4)
        n = elf.elf32_Ehdr.e_entry
        a[0] = n&0xFF
        a[1] = (n>>8)&0xFF
        a[2] = (n>>16)&0xFF
        a[3] = (n>>24)&0xFF
        fo.write(a)
        ss =['.rom.data','.sram1.text' , '.psram.data']
        for s in ss:
            sec = elf.getSectionByName(s)
            if sec is None:
                continue
            n = sec.sh_addr
            a[0] = n&0xFF
            a[1] = (n>>8)&0xFF
            a[2] = (n>>16)&0xFF
            a[3] = (n>>24)&0xFF
            fo.write(a)
            n = sec.sh_size
            a[0] = n&0xFF
            a[1] = (n>>8)&0xFF
            a[2] = (n>>16)&0xFF
            a[3] = (n>>24)&0xFF
            fo.write(a)
            elf.f.seek(sec.sh_offset, 0)
            fo.write(elf.f.read(n))
        fo.close()

各段的内存起始地址和占用空间,PLC基础系统软件和PLC的应用程序需要保持一致。在PLC基础系统软件中预留出对应的存储空间,在PLC应用程序中使用这些存储空间。这些需要在编译链接的过程中明确指定。

MEMORY
{
  rom_seg (RX)   : org = 0x00000000, len = 0x80000
  iram_seg (RX)  : org = 0x400A0000, len = 0x10000
  dram_seg (RW)  : org = 0x3FFD8000, len = 0x10000
  eh_seg         : org = 0xc0000000, len = 0x10000
}

PLC基础系统软件和PLC的应用程序之间有相互调用的函数入口需要在加载的时候明确指定。

void* fp_app[]={
__init,
__run
};
void entry(void* p){
PLC_VARS* vp = (PLC_VARS*)p;
void** ftbl;
    if(p==NULL){
        return;
    }
    vp->common_ticktime = common_ticktime__;
    vp->vars          = vars;
    vp->vars_n     = sizeof(vars)/sizeof(VARP);
    vp->inputList  = inputVarList;
    vp->input_n    = sizeof(inputVarList)/sizeof(VARP);
    vp->outputList = outputVarList;
    vp->output_n   = sizeof(outputVarList)/sizeof(VARP);
    vp->pous         = pous;
    vp->pous_n       = sizeof(pous)/sizeof(VARP);
    vp->prgs          = prgs;
    vp->prg_sz     = prg_sz;
    vp->prgs_n     = sizeof(prgs)/sizeof(VARP);
    vp->cfg        = global_cfg;
    vp->cfg_n             = sizeof(global_cfg)/sizeof(char*);
    
    ftbl = (void**)vp->fp_os;
    FB_init             = ftbl[0];
    FB_body             = ftbl[1];
    get_time_fp    = ftbl[2];
    remind_fp      = ftbl[3];
    retain_fp         = ftbl[4];
    append_chain_fp= ftbl[5];
    
    vp->fp_app        = fp_app;
}

       完成这些基础的工作以后,PLC应用程序的编译就可以和PLC基础系统软件分开,需要更新PLC应用的时候,编译PLC应用程序就好,然后通过网络或者下载接口把二进制文件传到板子上,由PLC基础系统软件加载运行即可。即便包含了MQTT 接收、发送模块的PLC应用程序,容量也在64K字节以内。

编译的输出为:

[1/6] Building C object CMakeFiles/plc.dir/plc_main.c.obj
[2/6] Building C object CMakeFiles/plc.dir/Config0.c.obj
[3/6] Building C object CMakeFiles/plc.dir/VARIABLES.c.obj
[4/6] Building C object CMakeFiles/plc.dir/main.c.obj
[5/6] Building C object CMakeFiles/plc.dir/POUS.c.obj
[6/6] Linking C executable plc
[+] Section Header Table:
  #      Name                    Type            Addr            Offset          Size    ES      Flg     Lk      Inf     Al      
  [   1] .rom.data               PROGBITS        0x0             0x1000          0xb3a   0       3       0       0       4       
  [   2] .sram1.text             PROGBITS        0x400a0000      0x3000          0x22fc  0       7       0       0       4       
  [   3] .psram.data             PROGBITS        0x3ffd8000      0x2000          0x558   0       3       0       0       8       
  [   4] .psram.bss              NOBITS          0x3ffd8558      0x2558          0x68    0       3       0       0       8       
  [  15] .bss                    NOBITS          0x3ffd85c0      0x2558          0x9e8   0       3       0       0       8       

       当前的容量大约为10K字节,用网络远程传输就可以在大约2秒内完成,所期望的远程升级维护特性就比较实用。 

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值