EV3运行程序探究

本文主要探究Bytecode指令集、EV3应用开发与编译、VM运行时等相关内容。

一、简介

EV3运行程序是指下载到EV3编程块上用于执行的一个文件(rbf文件),rbf是Robot Bytecode File的缩写,这个文件是一个字节码文件,需要由EV3固件程序来加载执行,在官方《EV3固件开发者工具包》文档架构图中,把固件程序(lms2012.elf)称为VM(虚拟机)。

EV3运行程序探究

那么EV3系统设计为什么使用bytecode做为运行程序呢?

一个可能原因是乐高要在EV3上使用图形化编程,看中了NI的LabVIEW,而LabVIEW在设计上就是采用的bytecode方式,给EV3做一个图形化开发环境只需要实现相应的bytecode指令集和VM运行时(固件)就可以了。

从嵌入式系统设计角度来看,我认为通过VM来隔离应用程序与底层程序(系统程序、硬件驱动等)之间的耦合是一个非常优雅的设计方案,因为这样在开发应用软件时可以不用考虑硬件相关特性,而硬件发生变更时(如更新换代,接口调整等)只需要更新VM即可,对应用软件影响较小,当然代价就是对硬件和系统开发的要求更高,如要能运行Linux,VM的开发要涉及Linux底层的驱动开发等等。

正是因为采用VM系统架构和相关资料的开放,使得EV3除了能面向青少年STEM教育外,还可以供我们系统的学习嵌入式系统与计算机原理相关的知识。

本文主要探究Bytecode指令集、EV3应用开发与编译、VM运行时等相关知识。

 

二、Bytecode指令集

EV3的Bytecode指令集采用的是不定长指令设计,指令的第1个字节为操作码(OpCode),共有185条指令。包含:

1.一般操作指令(30条):包含程序、对象、系统等指令

2.数学指令(17条):包含加、减、乘、除等指令

3.逻辑操作指令(11条):包含逻辑与、或、异或、移位等指令

4.内存移动操作指令(18条):包含内存数据移动指令

5.分支操作指令(28条):包含各种分支跳转指令

6.比较操作指令(18条):包含大于、小于、等于、不等于等指令

7.程序选择操作指令(4条)

8.输入端口操作指令(8条):包含对输入端口1-4上设备的操作指令

9.输出端口操作指令(19条):包含对输出端口A-D上设备的操作指令

10.声音操作指令(3条):

11.定时器操作指令(4条)

12.通讯操作指令(12条):包含通讯硬件(USB、蓝牙、WiFi)和MailBox相关的指令

13.内存操作指令(7条):包含对文件和数组进行操作的指令

14.用户界面操作指令(6):包含对LCD显示屏,按钮和指示灯进行操作的指令

各指令的细节请参考《EV3固件开发者工具包》的第4节。

 

三、应用开发与编译

 

3.1 程序开发

 

3.1.1 EV3 Lab

EV3早期指出的应用软件,是集教学、开发调试和EV3编程块管理于一体的软件。应用开发采用的是LabVIEW的Graphicals语言的变体,也被称为EV3-G。

EV3运行程序探究

使用EV3-G进行开发,只需将各类程序模块进行连接组合即可完成编程工作。

由EV3-G开发出来的程序文件后缀为ev3,它其实是一个zip文件,包括程序(Program.ev3p)、图像、声音、我的模块和变量。

 

3.1.2 EV3 Classroom

EV3 Classroom(课堂)是EV3新一代的教学软件,支持Windows, MacOS, 和移动端设备,与Scratch等图形化编程语言类似,程序流为竖向。

EV3运行程序探究

 

3.1.3 LMS专用开发语言

除了上面两种图形化开发语言,EV3还支持使用一种LMS专用语言来开发应用程序,EV3启动后的UI程序就是通过这种专用开发语言编写的。
EV3运行程序探究
Port View.lms主代码

vmthread  MAIN                                                        //  void MAIN(void)
{                                                                     //  {
  DATA32  Timer                                                       //
  DATA16  hModes                                                      //
  DATA8   Run                                                         //
  DATA8   Selection                                                   //
  DATA8   Changing                                                    //
  DATA8   Tmp                                                         //
  DATA8   Init                                                        //
  DATA8   ShowVersion                                                 //
                                                                      //
  UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion)                         //    UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion)
  JR_FALSE(ShowVersion,DontShowVersion)                               //    if (ShowVersion)
                                                                      //    {
  UI_DRAW(FILLRECT,BG_COLOR,4,50,170,28)                              //      UI_DRAW(FILLRECT,BG_COLOR,4,50,170,28)
  UI_DRAW(RECT,FG_COLOR,6,52,166,24)                                  //      UI_DRAW(RECT,FG_COLOR,6,52,166,24)
  UI_DRAW(TEXT,FG_COLOR,13,60,appv)                                   //      UI_DRAW(TEXT,FG_COLOR,13,60,appv)
  UI_DRAW(UPDATE)                                                     //      UI_DRAW(UPDATE)
                                                                      //
ShowVersionWait:                                                      //      do
                                                                      //      {  
  UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion)                         //        UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion)
                                                                      //      }
  JR_TRUE(ShowVersion,ShowVersionWait)                                //      while (ShowVersion)
                                                                      //
  UI_BUTTON(FLUSH)                                                    //      UI_BUTTON(FLUSH)
DontShowVersion:                                                      //    }  
                                                                      //
  UI_DRAW(RESTORE,0)                                                  //    UI_DRAW(RESTORE,0)
  UI_DRAW(TOPLINE,1)                                                  //    UI_DRAW(TOPLINE,1)
  UI_BUTTON(SET_BACK_BLOCK,1)                                         //    UI_BUTTON(SET_BACK_BLOCK,1)
  UI_WRITE(LED,LED_GREEN)                                             //    UI_WRITE(LED,LED_GREEN)
                                                                      //
  ARRAY(CREATE8,MAX_PORTS,hModes)                                     //    ARRAY(CREATE8,MAX_PORTS,hModes)
  ARRAY(FILL,hModes,0)                                                //    ARRAY(FILL,hModes,0)
                                                                      //
  MOVE8_8(1,Run)                                                      //    Run         =  1
  MOVE8_8(0,Selection)                                                //    Selection   =  0
  MOVE8_8(0,Changing)                                                 //    Changing    =  0
  MOVE8_8(0,First)                                                    //    First       =  0
  MOVE8_8(0,TmpMode)                                                  //    TmpMode     =  0
  MOVE8_8(0,Init)                                                     //    Init        =  0
                                                                      //
  Loop:                                                               //    do
                                                                      //    {
  UI_DRAW(FILLWINDOW,BG_COLOR,TOPLINE_HEIGHT,0)                       //      UI_DRAW(FILLWINDOW,BG_COLOR,TOPLINE_HEIGHT,0)
                                                                      //                                        
  CALL(CheckConnections,hModes)                                       //      CheckConnections(hModes)
  CALL(CheckKeys,Run,Selection,Changing)                              //      CheckKeys(Run,Selection,Changing)
  CALL(ShowSelectedConnection,hModes,Selection,Changing)              //      ShowSelectedConnection(hModes,Selection,Changing)
  CALL(ShowAllConnections,hModes,Selection)                           //      ShowAllConnections(hModes,Selection)
                                                                      //
  JR_EQ8(Init,1,Skip)                                                 //      if (Init != 1)
                                                                      //      {
  INPUT_DEVICE(CLR_ALL,0)                                             //        INPUT_DEVICE(CLR_ALL,0)
  MOVE8_8(1,Init)                                                     //        Init    =  1
Skip:                                                                 //      }  
                                                                      //  
  UI_DRAW(UPDATE)                                                     //      UI_DRAW(UPDATE)
                                                                      //
  TIMER_WAIT(UPDATE_TIME,Timer)                                       //      TIMER_WAIT(UPDATE_TIME,Timer)
  TIMER_READY(Timer)                                                  //      TIMER_READY(Timer)
                                                                      //    }
  JR_TRUE(Run,Loop)                                                   //    while (Run)
                                                                      //
  UI_BUTTON(SET_BACK_BLOCK,0)                                         //    UI_BUTTON(SET_BACK_BLOCK,0)
}                                                                     //  }

左边为专有语句,右边注释为伪高级语句,可以看到主要是把一些结构控制语句换成了JR_xxx跳转语句,另外专有语句名称与Bytecode指令操作码是对应的。

 

3.1.4 其他语言

MakeCode

MakeCode for ev3是Microsoft推出的图形化编程软件,支持在线模拟执行。
EV3运行程序探究

EV3Basic

EV3Basic是Microsoft推出的基于Small Basic语言的EV3扩展版本。

EV3运行程序探究
 Small Basic采用基于文本的编程方式,使用简洁的语法和简单直观的命令,使初学者能够快速理解和编写代码。

Robot C

[稍后补充】

3.2 编译

编译是指将程序代码转换成计算机指令的过程,是计算机原理的重要组成部分。在EV3系统中,应用程序需要编译成Bytecode指令后,才能被EV3 VM执行。

3.2.1 图形化程序的编译

前文提到EV3-G图形化程序其实是LabVIEW的变体,编译使用的是LabVIEW的编译系统,因为EV3 Lab并没有开放源代码,无法直接学习其编译过程。

不过通过反编译EV3 Lab的相关程序,可以了解编译的大概过程

EV3运行程序探究
这里不做展开,有兴趣的同学可以自行反编译后查看。

3.2.2 LMS专用语言程序的编译

前文有提到EV3启动后的UI程序是通过LMS专用开发语言编写的,本小节来看下这种专用语言代码(文件后缀lms)的编译。

1. EV3自带的编译程序

打开EV3 固件的源代码目录,进入lmssrc/adk/lmsasm目录,找到assembler.jar,这个文件就是编译lms文件的java程序。

新建一个helloworld.lms文件,内容如下:

vmthread  MAIN
{
  UI_DRAW(FILLWINDOW,0,0,0)              //  清屏
  UI_DRAW(TEXT,1,10,50,'hello world')    //  在10,50 (X,Y)处显示 "hello world"
  UI_DRAW(UPDATE)                               //  更新UI
  UI_BUTTON(WAIT_FOR_PRESS)            //  等待按钮按下
}

通过执行java -jar assembler.jar helloworld来进行编译,helloworld就是xxx.lms,执行命令里不需要指定.lms后缀,编译若无错误会生成helloworld.rbf文件。

EV3运行程序探究

assembler.jar没有开放源码,有兴趣的同学可以自行反编译后查看编译源码。

2. EV3Basic带的编译程序

EV3Basic实际上是将Basic语言代码先转换为LMS代码,再使用自带的LMS编译程序将LMS代码编译为RBF,而且这个编译程序还支持将RBF反编译成LMS专用语言代码。
EV3Basic LMS编译程序源码:
https://github.com/c0pperdragon/EV3Basic/tree/master/LMSAssembler

LMS编译涉及的内容较多,这里就不细述了,后面另开一篇文章讲解。

 

3.3 反编译

前一小节提到了EV3Basic带的LMS编译程序可以反编译,下面就是Helloworld.rbf反编译的内容,可以看到与helloworld.lms的代码是一致的。

EV3运行程序探究

 

四、VM运行时

EV3的VM运行时,就是固件程序,它是一个Linux执行程序,EV3程序块系统启动后,这个固件程序就接管了系统,然后等待用户的操作。

1 固件初始化

打开固件源码文件夹的lms2012.c文件,找到入口main函数

int main(int argc,char *argv[])
{
  RESULT  Result = FAIL;
  UBYTE   Restart;
 
  do
  {
    Restart  =  0;
    Result   =  mSchedInit(argc,argv);
 
    if (Result == OK)
    {
      do
      {
        Result  =  mSchedCtrl(&Restart);
      }
      while (Result == OK);
 
      Result  =  mSchedExit();
    }
  }
  while (Restart);
 
  return ((int)Result);
}

可以看出这是一个较典型的嵌入式系统主程序代码,

  • 1.初始化;
  • 2.死循环执行业务代码;
  • 3.退出循环执行清理代码;

2 运行rbf文件

找到mSchedCtrl函数,下面是精简后的代码

RESULT    mSchedCtrl(UBYTE *pRestart)
{
  RESULT  Result   = FAIL;
  ULONG   Time;
  IP      TmpIp;
 
  if (VMInstance.DispatchStatus != STOPBREAK)
  {
    ProgramInit();
  }
  SetDispatchStatus(ObjectInit());
 
/*** Execute BYTECODES *******************************************************/
 
#ifndef DISABLE_PREEMPTED_VM
  (*VMInstance.pAnalog).PreemptMilliSeconds  =  0;
  while ((VMInstance.Priority) && ((*VMInstance.pAnalog).PreemptMilliSeconds < 2))
#else
  while (VMInstance.Priority)
#endif
  {
      VMInstance.Priority--;
      PrimDispatchTabel[*(VMInstance.ObjectIp++)]();
      VMInstance.InstrCnt++;
  }
/*****************************************************************************/

  VMInstance.NewTime  =  GetTimeMS();

  Time  =  VMInstance.NewTime - VMInstance.OldTime1;

  if (Time >= UPDATE_TIME1)
  {
    VMInstance.OldTime1 +=  Time;
    cComUpdate();
    cSoundUpdate();
  }

  Time  =  VMInstance.NewTime - VMInstance.OldTime2;
  if (Time >= UPDATE_TIME2)
  {
    VMInstance.OldTime2 +=  Time;
    usleep(10);
    cInputUpdate((UWORD)Time);
    cUiUpdate((UWORD)Time);
  }

  if (VMInstance.DispatchStatus == FAILBREAK)
  {
    if (VMInstance.ProgramId != GUI_SLOT)
    {
      if (VMInstance.ProgramId != CMD_SLOT)
      {
        UiInstance.Warning |=  WARNING_DSPSTAT;
      }

      snprintf(VMInstance.PrintBuffer,PRINTBUFFERSIZE,"}\r\nPROGRAM \"%d\" FAIL BREAK just before %lu!\r\n",VMInstance.ProgramId,(unsigned long)(VMInstance.ObjectIp - VMInstance.Program[VMInstance.ProgramId].pImage));
      VmPrint(VMInstance.PrintBuffer);
      ProgramEnd(VMInstance.ProgramId);
      VMInstance.Program[VMInstance.ProgramId].Result          =  FAIL;
    }
    else
    {
      snprintf(VMInstance.PrintBuffer,PRINTBUFFERSIZE,"UI FAIL BREAK just before %lu!\r\n",(unsigned long)(VMInstance.ObjectIp - VMInstance.Program[VMInstance.ProgramId].pImage));
      VmPrint(VMInstance.PrintBuffer);
      LogErrorNumber(VM_INTERNAL);
      *pRestart  =  1;
    }
  }
  else
  {
    if (VMInstance.DispatchStatus == INSTRBREAK)
    {
      if (VMInstance.ProgramId != CMD_SLOT)
      {
        LogErrorNumber(VM_PROGRAM_INSTRUCTION_BREAK);
      }
      TmpIp  =  VMInstance.ObjectIp - 1;
      snprintf(VMInstance.PrintBuffer,PRINTBUFFERSIZE,"\r\n%4u [%2d] ",(UWORD)(((ULONG)TmpIp) - (ULONG)VMInstance.pImage),VMInstance.ObjectId);
      VmPrint(VMInstance.PrintBuffer);
      snprintf(VMInstance.PrintBuffer,PRINTBUFFERSIZE,"VM       ERROR    [0x%02X]\r\n",*TmpIp);
      VmPrint(VMInstance.PrintBuffer);
      VMInstance.Program[VMInstance.ProgramId].Result          =  FAIL;
    }

    ObjectExit();
 
    Result  =  ObjectExec();

    if (Result == STOP)
    {
      ProgramExit();
      ProgramEnd(VMInstance.ProgramId);
      VMInstance.DispatchStatus  =  NOBREAK;
    }
    else
    {
      if (VMInstance.DispatchStatus != STOPBREAK)
      {
        ProgramExit();
      }
    }
  }

  if (VMInstance.DispatchStatus != STOPBREAK)
  {
    Result  =  ProgramExec();
  }

  if (*pRestart == 1)
  {
    Result  =  FAIL;
  }

#ifdef Linux_X86
  usleep(1);
#endif

  return (Result);
}

其中执行bytecode的代码是这条语句:

PrimDispatchTabel[*(VMInstance.ObjectIp++)]();

显然VMInstance.ObjectIp是当前rbf程序对象的指令指针了,它指向的是指令操作码。

而PrimDispatchTabel是一个函数指针数组,定义如下:

PRIM      PrimDispatchTabel[PRIMDISPATHTABLE_SIZE] =
{
  [opERROR]               =   &Error,
  [opNOP]                 =   &Nop,
  [opPROGRAM_STOP]        =   &ProgramStop,
  [opPROGRAM_START]       =   &ProgramStart,
  [opOBJECT_STOP]         =   &ObjectStop,
  [opOBJECT_START]        =   &ObjectStart,
  [opOBJECT_TRIG]         =   &ObjectTrig,
  [opOBJECT_WAIT]         =   &ObjectWait,
  [opRETURN]              =   &ObjectReturn,
  [opCALL]                =   &ObjectCall,
  [opOBJECT_END]          =   &ObjectEnd,
  [opSLEEP]               =   &Sleep,
  [opPROGRAM_INFO]        =   &ProgramInfo,
  [opLABEL]               =   &DefLabel,
  [opPROBE]               =   &Probe,
  [opDO]                  =   &Do,
  [opADD8]                =   &cMathAdd8,
// 其它指令 ......
}

其中,opXXX是在bytecodes.h中定义的枚举常量
通过与操作码对应的函数指针,VM得以调用执行具体的代码。


五 总结

通过上面的分析与研究,可以看出无论是上层的应用开发,还是中间环节的字节码编译,EV3系统都有较好的扩展性。


- 本文为本站原创文章,转载请保留出处。
- 文章链接:https://www.xpstem.com/article/2000194

2023-07   阅读(10)   评论(0)
 标签: robot EV3

涨知识
传感器

传感器是一种检测装置,能感受到被测量的信息,并按一定规律变换成为电信号或其他所需形式的信息输出,以满足信息的传输、处理、存储、显示、记录和控制等要求。

评论:
相关文章
Scratch 3.0连接EV3

本文介绍如何在Scratch中对EV3机器人进行开发。


Java 机器人编程入门手册(四)

在这一章中,你将学习一组传感器,它们被用来执行有根据的动作。


Java 机器人编程入门手册(三)

在本章中,您将学习启发式搜索策略背后的基本思想以及如何实现爬山算法,这是 leJOS EV3 中最典型的启发式方法之一。


Java 机器人编程入门手册(二)

这一章向你介绍了在莱霍斯 EV3 使用的笛卡尔坐标系的基础知识。它还教你如何在导航课程中应用编程方法来控制轮式车辆,以便在二维平面中用坐标描绘出预定义的路径。


Java 机器人编程入门手册(一)

本章提供了如何使用乐高 MindStorm EV3 公司建立 Java 机器人编程环境的分步指南,包括乐高 MindStorm EV3 的基本概述和leJOS-EV3的介绍。

搜索
小鹏STEM教研服务

专属教研服务系统,助您构建STEM课程体系,打造一站式教学环境。