Hacking EV3系列之六:iOS使用Direct Command 控制EV3

来源:互联网 发布:电影推荐算法数据集 编辑:程序博客网 时间:2024/04/26 08:39

 在上一篇文章中,我们已经实现了iOS通过BTstack给EV3发送Message!
但仅仅发送Message显然不是我们的目标!
我们要控制EV3的动作!

这需要依靠EV3协议中的Direct Command!
EV3 brick内部已经内置了代码,iOS这边只要发送特定的命令也就是Direct Command,那么EV3这边就可以在不启动任何程序的情况下实现对各种传感器设备的控制。
LEGO 官方的App Commander就是依靠Direct Command控制EV3的。只是它的不足是很明显的。就是:
这个Commander App只能输出动力,控制点击,至于逻辑判断,就都做不到!
而我们的目标是通过手机控制EV3,并且把手机端作为机器人的大脑!
这种要求显然远远高于Commander!我们必须能接收传感器的数据,然后经过程序判断,输出动力!

这篇文章将谈如何通过使用Direct Command来控制EV3的电机运动!

首先还是摘录一下Direct Command的协议说明。完全从c_com.h文件中复制。

Beside running user programs the VM is able to execute direct commands from the Communication Module.

  In fact direct commands are small programs that consists of regular byte codes and they are executed

  in parallel with a running user program.\n

  Special care MUST be taken when writing direct commands because the decision until now is NOT to

  restrict the use of "dangerous" codes and constructions (loops in a direct command are allowed).


  If a new direct command from the same source is going to be executed an actual running direct command is terminated.


  Because of a small header objects are limited to one VMTHREAD only - SUBCALLs and BLOCKs is of

  course not possible.\n

  This header contains information about number of global variables (for response), number of local variables

  and command size.


  Direct commands that has data response can place the data in the global variable space. The global

  variable space is equal to the communication response buffer. The composition of the direct command

  defines at which offset the result is placed (global variable 0 is placed at offset 0 in the buffer).


  Offset in the response buffer (global variables) must be aligned (float/32bits first and 8 bits last).

Direct Command Bytes:

  ,------,------,------,------,------,------,------,------,

  |Byte 0|Byte 1|Byte 2|Byte 3|Byte 4|Byte 5|      |Byte n|

  '------'------'------'------'------'------'------'------'


  Byte 0 – 1: Command size, Little Endian\n


  Byte 2 – 3: Message counter, Little Endian\n


  Byte 4:     Command type. see following defines   */


  #define     DIRECT_COMMAND_REPLY          0x00    //  Direct command, reply required

  #define     DIRECT_COMMAND_NO_REPLY       0x80    //  Direct command, reply not required


                                                    /*


  Byte 5 - 6: Number of global and local variables (compressed).


               Byte 6    Byte 5

              76543210  76543210

              --------  --------

              llllllgg  gggggggg


                    gg  gggggggg  Global variables [0..MAX_COMMAND_GLOBALS]


              llllll              Local variables  [0..MAX_COMMAND_LOCALS]


  Byte 7 - n: Byte codes




  Direct Command Response Bytes:

  ,------,------,------,------,------,------,------,------,

  |Byte 0|Byte 1|Byte 2|Byte 3|      |      |      |Byte n|

  '------'------'------'------'------'------'------'------'


  Byte 0 – 1: Reply size, Little Endian\n


  Byte 2 – 3: Message counter, Little Endian\n


  Byte 4:     Reply type. see following defines     */


  #define     DIRECT_REPLY                  0x02    //  Direct command reply

  #define     DIRECT_REPLY_ERROR            0x04    //  Direct command reply error


                                                    /*


  Byte 5 - n: Response buffer (global variable values)


以上这些只是Direct Command的协议!
关键点:Direct Command就是前缀加上具体命令!所以搞清楚各种控制命令的格式是非常重要的!
我们要控制EV3的电机,还得需要知道控制电机的命令及组成方式!
更进一步地我们需要知道所有可以用的命令的格式!
从python-ev3.org 这个网站我们可以找到这些命令的格式!
当然从EV3的源代码也是可以找到的!在c_output.h文件中可以看到!

opOUTPUT_POWER(LAYER,NOS,SPEED)  // 设置电机的Power功率

Set power of the outputs

Dispatch status unchanged

Parameters:

     (DATA8)LAYER - Chain layer number[0..3]

     (DATA8)NOS - Output bit field[0x00..0x0F] output 1 to 4 (0x01, 0x02, 0x04, 0x08)

     (DATA8)POWER - Power[-100..100]

opOUTPUT_START(LAYER,NOS)  // 启动电机

     Starts the outputs

     Dispatch status unchanged

     Parameters:

     (DATA8)LAYER - Chain Layer number[0..3]

     (DATA8)NOS  -  Output bit field[0x00..0x0F]    端口

opOUTPUT_STOP(LAYER,NOS)  // 停止电机

     Stop the outputs

     Dispatch status unchanged

     Parameters:

     (DATA8)LAYER - Chain layer number[0..3]

     (DATA8)NOS - Output bit field[0x00,0x0F]

     (DATA8)BRAKE - Brake[0,1]  
经过研究,上面的DATA8格式其实就是unsigned char格式!
可以说有了这三个命令我们就能控制电机了。但还有个问题就是这个命令具体的字节组成是怎样的呢?

Start motor connected to port A with speed 20:


        Byte codes:             opOUTPUT_POWER,LC0(0),LC0(0x01),LC0(20), opOUTPUT_START,LC0(0),LC0(0x01)

                                         \    /

                                          \  /

        Hex values send:    0C00xxxx800000A4000114A60001


每个命令都有专门的命令码,如下:

  opOUTPUT_STOP               = 0xA3//     00011

  opOUTPUT_POWER              = 0xA4//     00100

  opOUTPUT_START              = 0xA6//     00110

 详见源代码中的bytecodes.h这个文件

所以上面要驱动电机,opOUTPUT_POWER的值就是0xA4。op是命令前缀。

接下来就是如何添加参数?
每个参数之前要先添加参数的长度!但上面的例子并没有这一条!所以官方的例子是个bug!

typedef enum {

    ByteSize = 0x81,        // 1 byte

    ShortSize = 0x82,       // 2 bytes

    IntSize = 0x83,      // 4 bytes

    StringSize = 0x84      // null-terminated string

}ArgumentSize; 

不同长度的参数前面要添加的size参数不一样!也就是说比如我这边要添加power参数,那么之前就要先添加一个size参数,由于power参数是1 byte,所以添加0x81!

这里直接给出我编写的方法:

+ (void)turnMotorAtPowerAsyncInternal:(OutputPort)port power:(int)power

{

    FSEV3DirectCommand *command = [[FSEV3DirectCommand allocinitWithCommandType:DirectReplyglobalSize:0 localSize:0];

    [command addOperationCode:OutputPower];

    [command addParameterWithInt8:0];

    [command addParameterWithInt8:port];

    if (abs(power) > 100) {

        [command addParameterWithInt8:100];

    } else {

        [command addParameterWithInt8:(u_int8_t)power];

    }

    

    [command addOperationCode:OutputStart]; // 添加命令码

    [command addParameterWithInt8:0];    // 添加参数

    [command addParameterWithInt8:port];

    

    [command sendCommand];

    

}

- (id)initWithCommandType:(CommandType)commandType

               globalSize:(uint16_t)globalSize

                localSize:(int)localSize

{

    self = [super init];

    if (self) {

        

        // Set BTstack delegate to receive packet

        [BTstackManager sharedInstance].delegate = self;

        

        

        // Command size,this gets filled later

        buffer[0] = 0xff;

        buffer[1] = 0xff;

        

        // Message count set default 0x00

        buffer[2] = 0x00;

        buffer[3] = 0x00;

        

        // Command type

        buffer[4] = commandType;

        

        

        buffer[5] = globalSize & 0xff;

        buffer[6] = (u_int8_t)((localSize << 2) | ((globalSize >> 8) & 0x03));

        cursor = 7;

        

    }

    return self;

}


通过以上的方法,就可以驱动电机了。这里再提醒一个:电机的端口PortA,PortB,PortC,PortD对应的数值是0x01,0x02,0x04,0x08!

好了,先说到这。接下来要研究的就是如何获取传感器的数据了!
我自己现在的程序还在进一步完善当中。 一旦完成,将发布到GitHub上。敬请关注!

未完待续!
欢迎交流!QQ:363523441 

0 0
原创粉丝点击