找回密码
 马上注册

QQ登录

只需一步,快速开始

查看: 25076|回复: 17

EV3G与原生C++程序通信实验贴

  [复制链接]
发表于 2014-3-21 23:44:13 | 显示全部楼层 |阅读模式
本帖最后由 ntwuhui 于 2014-3-25 23:19 编辑

       此贴中文乐高首发,转帖者请注明出处,特此声明!
       最近研读EV3魔方机器人程序,发现其使用官方软件(图形化编程工具LEGO MINDSTORMS EV3 Home Edition,下面简称EV3G,G为Graphic简称)配合原生C++(mc3solver-v1p1.rtf)很好地完成了复原魔方这一复杂的工程,通信大概如此:EV3G主程序扫描的结果通过文件的形式(mc3dat.rtf)交给mc3solver程序,mc3solver.rtf将复原方法亦通过该文件将其传给主程序。
      该程序给我们提供了一个非常好的设计复杂工程的思路:主程序通过传感器进行原始数据采集以及结果的输出处理(比如合适的运动等),利用原生C++程序可以很好的完成更复杂的算法,比如复杂的搜索算法(解魔方、走迷宫等)、模式识别(声音、图形文字、人脸识别等),二者之间通过文件作为桥梁进行双向通信。
       原生C++程序天生有个缺点,无法通过EV3按钮直接选择,要想运行它,目前我只了解以下两种,一是bricxcc下面直接下载并运行,方法见这里,二是用telnet客户端软件登录进去运行,telnet又有两个办法,1是自制连接线的方法,见这里,2是通过wifi连接进去,telnet时选择IP地址即可;不过一个具体的项目以上方法都不太妥当,魔方机器人给我们提供了另一种运行原生C++程序的方法。
       首先要把刷成自制固件1.05M版,为什么呢?我的想法是这样的:当我们运行主程序时,1.05M版会自动调用autoruan.rtf程序,而autorun.rtf本质是一个脚本程序,内容如下:
  1. #!/bin/sh
  2. if [ -x mc3solver-v1p1.rtf ]
  3. then
  4.     ./mc3solver-v1p1.rtf &
  5. fi
复制代码
       不难读懂意思:如果文件mc3solver-v1p1.rtf是可执行的文件,那么就执行它。
        这样该程序就可以作为后台程序一直运行,等待与主程序通信。
        通过多次反复开机重启实验,终于弄明白上述脚本程序是在ev3开机后自动运行,并非是调用主程序时运行,这也是为什么魔方机器人把mc3solver.rtf导入进去以后需要重启的原因。

如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
 楼主| 发表于 2014-3-21 23:44:59 | 显示全部楼层
实验准备:
硬件:
1、EV3套装(家庭版或者教育版,刷新固件至1.05M)
2、WIFI适配器(NetGear WNA1100 USB WIFI dongle)
3、USB连接线(内置于EV3套装)
软件:
1、EV3官方软件(家庭版或者教育版均可)
2、Bricxcc(环境搭建步骤见这里:http://bbs.cmnxt.com/thread-13374-1-1.html

如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2014-3-21 23:45:50 | 显示全部楼层
本帖最后由 ntwuhui 于 2014-3-22 17:10 编辑

知识准备:
       由于通过使用文件进行通信,必然需要用到文件的读写操作,翻阅相关资料得知基于ARM的EV3固件采用POSIX(Portable Operating System Interface,可移植操作系统接口),通过度娘找到如下资料。POSIX 文件操作,其中关于文件读写操作如下:
1、打开文件Open
表头文件:
  1. #include<sys/types.h>
  2. #include<sys/stat.h>
  3. #include<fcntl.h>
复制代码
函数定义:
  1.       int open( const char * pathname, int flags);
  2.       int open( const char * pathname,int flags, mode_t mode);
复制代码
参数说明:
      Pathname:指向欲打开的文件路径字符串
      flags:打开文件的方式(节选)
      O_RDONLY: 以只读方式打开文件
      O_WRONLY: 以只写方式打开文件
      O_RDWR:   以可读写方式打开文件。
      上述三种标志是互斥的,也就是不可同时使用,但可与下列标志利用OR(|)运算符组合。
      O_CREAT: 若文件不存在则自动建立该文件。
      O_TRUNC: 若文件存在并且以可写的方式打开时,此标志会令文件长度清为0,而原来存于该文件的资料也会消失。
      O_APPEND:当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。

      mode:在建立新文件时才会生效,此外真正建文件时的权限会受到umask值所影响,因此该文件权限应该为(mode-umaks)。
      S_IRWXU00700 权限:代表该文件所有者具有可读、可写及可执行的权限。
      S_IRUSR 或S_IREAD,00400权限:代表该文件所有者具有可读取的权限。
      S_IWUSR 或S_IWRITE,00200 权限:代表该文件所有者具有可写入的权限。
      S_IXUSR 或S_IEXEC,00100 权限:代表该文件所有者具有可执行的权限。

      返回值:  若所有欲核查的权限都通过了检查则返回0 值,表示成功,只要有一个权限被禁止则返回-1。
2、建立文件Create
表头文件:
  1. #include<sys/types.h>
  2. #include<sys/stat.h>
  3. #include<fcntl.h>
复制代码
函数定义:
  1. int creat(const char * pathname, mode_tmode);
复制代码
函数说明:参数pathname指向欲建立的文件路径字符串。关于参数mode请参考open()函数。Creat()相当于使用下列的调用方式调用open()
  1. open(const char * pathname ,(O_CREAT|O_WRONLY|O_TRUNC));
复制代码

3、关闭文件close
表头文件:
  1. #include<unistd.h>
复制代码
函数定义:      
  1. int close(int fd);
复制代码
函数说明:当使用完文件后若已不再需要则可使用close()关闭该文件,close()会让数据写回磁盘,并释放该文件所占用的资源。参数fd为先前由open()或creat()所返回的文件描述词。
返回值:若文件顺利关闭则返回0,发生错误时返回-1。   
4、读文件read
表头文件:
  1. #include<unistd.h>
复制代码
函数定义:
  1. ssize_t read(int fd,void * buf ,size_t count);
复制代码
函数说明:read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。
返回值:如果顺利,read()会返回实际读到的字节数,最好能将返回值与参数count 作比较,若返回的字节数比要求读取的字节数少,则有可能读到了文件尾、从管道(pipe)或终端机读取,或者是read()被信号中断了读取动作。当有错误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期。
5、写文件write
表头文件:
  1. #include<unistd.h>
复制代码
函数定义:
  1. ssize_t write (int fd,const void * buf,size_t count);
复制代码
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移动。
返回值: 如果顺利,write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2014-3-21 23:46:20 | 显示全部楼层
本帖最后由 ntwuhui 于 2014-4-1 15:01 编辑

实验一: EV3G到C++

1、传递文本
Step1:EV3G端的文件读写
项目文件名:test1.ev3p
包括两个程序:WriteFile、ReadFile
WriteFile内容如下: write.png
ReadFile内容如下:
read.png
程序下载到EV3内,执行前显示效果:
disp1.png
选中WriteFile并运行,效果如下:
disp2.png
执行完毕,会增加文件Hello(这里区分大小写)
disp3.png
执行ReadFile检验一下:
disp4.png
读取成功!
打开Bricxcc, 依次单击菜单Tool、Explorer
bricxcc1.jpg
双击打开test1文件夹:
b2.png
这里可以很清晰的看到由EV3G生成的文件会自动加上扩展名rtf,Hello的完整名称为Hello.rtf;右击该文件,选择View,会显示内容如下:
  1. 00000  48 65 6C 6C  6F 0D 45 56  33 0D                        Hello.EV3.
复制代码
这里不难发现,EV3G程序在发送多个文本时会在文本末尾加上换行符0D,对应字符'\r',在编写C++程序时需要特别注意这一点。

Step2:编写C++程序并编译下载到EV3
在bricxcc中编写程序如下:
  1. #include<unistd.h>
  2. #include<sys/types.h>
  3. #include<sys/stat.h>
  4. #include<fcntl.h>
  5. int main()
  6. {
  7.       int i=0,fd,size;
  8.       char buffer[10];

  9.       fd=open("Hello.rtf",O_RDONLY);
  10.       size=read(fd,buffer,sizeof(buffer));
  11.       close(fd);
  12.       
  13.       if (size){
  14.       printf("Open file success!\nRead %d bytes data\n\n",size);
  15.       for (i=0;i<size;i++)
  16.            if (buffer[i]=='\r') buffer[i] = '\n';
  17.       printf("%s",buffer);
  18.       }
  19.       else
  20.            printf("open file error!\n");
  21.          
  22.       return 0;
  23. }
复制代码
程序不多解释,其中为了正常显示,将换行符'\r'替换为回车符'\n'。保存后单击菜单Compile下面的Compile及Download;bricxcc会自动将程序下载到EV3,用上面类似的方法可以了解到readfile程序位于prjs文件夹下.
Step3 : 登陆EV3,运行C++程序
打开Putty,填入EV3正确的IP地址后,选择连接方式为Telnet
p1.png
单击Open打开,输入账号root(密码为空)即可登陆EV3
P3.png
这里用到几个常见命令:
cd  改变当前位置     ls    查看文件(夹)列表    cp  复制文件   执行程序为   ./readfile
操作说明:
1、转到文件夹prjs(bricxcc默认编译下载位置):cd lms2012/prjs
2、显示文件夹prjs内容:ls
3、复制文件readfile到文件夹test1内:cp readfile test1/readfile
4、执行readfile: ./readfile  
至此实验成功!
若在代码中加入头文件:
  1. #include "D:\BricxCC\API\ev3_lcd.h"
  2. #include "D:\BricxCC\API\ev3_command.h"
复制代码
以及在代码    printf("%s",buffer);   之后增加
  1.          LcdInit();
  2.          LcdText(1,0,0,buffer);
  3.          Wait(SEC_1);
  4.          LcdExit();
复制代码
运行上述命令(./readfile),EV3屏幕上会同时显示如下
b.png

2、传递数字
Step1:EV3G端的文件读写
项目文件名:test2.ev3p
包括两个程序:WriteFile、ReadFile
WriteFile内容如下: 1.png
ReadFile内容如下:
2.png
程序下载到EV3,选中WriteFile并运行,效果如下:
3.png
这里“Write Num ”这一行后面的数字会从1依次递增到10。
执行完毕,此程序会将1-10写入文件文件Number.rtf,   执行ReadFile检验一下:
4.png
读取成功!文件Number.rtf内容如下:
  1. 00000  31 0D 32 0D  33 0D 34 0D  35 0D 36 0D  37 0D 38 0D         1.2.3.4.5.6.7.8.
  2. 00010  39 0D 31 30  0D                                            9.10.
复制代码
可见数字与文本存储格式相同,数与数用换行符'\r'隔开

Step2:编写C++程序并编译下载到EV3
在bricxcc中编写程序如下:
  1. #include<unistd.h>
  2. #include<sys/types.h>
  3. #include<sys/stat.h>
  4. #include<fcntl.h>

  5. #include "D:\BricxCC\API\ev3_lcd.h"
  6. #include "D:\BricxCC\API\ev3_command.h"

  7. int main()
  8. {
  9.       int i,j,t,fd,size;
  10.       char buffer[21];
  11.       int num[10];

  12.       fd=open("Number.rtf",O_RDONLY);
  13.       size=read(fd,buffer,sizeof(buffer));
  14.       close(fd);
  15.       
  16.       if (size){
  17.            printf("Open file success!\nRead %d bytes data\n\n",size);
  18.            j=0; t=0;
  19.            for (i=0;i<size;i++){
  20.                 if (buffer[i]=='\r'){
  21.                      num[j]=t;
  22.                      printf("%d ",num[j]);
  23.                      j++;
  24.                      t=0;
  25.                 } else
  26.                      t=t*10+(buffer[i]-'0');
  27.            }
  28.            printf("\n");

  29.            LcdInit();
  30.            LcdText(1,0,0,buffer);
  31.            Wait(SEC_1);
  32.            LcdExit();
  33.      }  else
  34.           printf("open file error!\n");
  35.      printf("\n");
  36.      return 0;
  37. }
复制代码
程序说明:这里我仍然采用字符格式读取文件,读入成功后利用换行符逐一将字符转换为数字,(PS:应该有更简单的办法,暂时不清楚怎么弄)
编译下载到EV3。
Step3 : 登陆EV3,运行C++程序
用Putty登陆到EV3执行如下:
5.png
EV3屏幕上会同时显示如下
6.png
小结:由EV3G生成的文件会自动在文件名后增加扩展名rtf,不管是写入文本还是数字,一次写入后均会使用换行符'\r'(16进制:0D)结束;C++程序可以利用这一字符进行数据的分析与读取。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2014-3-21 23:46:43 | 显示全部楼层
本帖最后由 ntwuhui 于 2014-4-13 12:29 编辑

实验二:C++到EV3G的通信
本过程可简单的理解为是实验一的反向过程(C++:写入文件,EV3G:读取文件)
1、传递文本
Step1:编写C++程序生成目标文件
程序如下:
  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>

  5. int main()
  6. {
  7.     int fd,size;
  8.     char buffer[]="Hello\rEV3\r";
  9.    
  10.     fd=open("Hello.rtf",O_WRONLY|O_TRUNC|O_CREAT);
  11.     size=write(fd,buffer,10);
  12.     close(fd);
  13.     if (size)
  14.         printf("Write file success!\nWrite %d bytes data\n\n",size);
  15.     else
  16.         printf("Write file error!\n");
  17.    
  18.     return 0;
  19. }
复制代码
执行后生成符合EV3G能够读取的格式文件Hello.rtf
Step2:EV3G端的文件读取
这个过程就简单了,直接使用实验一test1.ev3p中的ReadFile程序即可,程序运行效果和前述完全相同!

2、传递数字
Step1:编写C++程序生成目标文件
程序如下:
  1. #include<unistd.h>
  2. #include<sys/types.h>
  3. #include<sys/stat.h>
  4. #include<fcntl.h>

  5. int main()
  6. {
  7.       int i,fd,size,num[10];
  8.       char buffer[21]={0},cnum[3];

  9.       for (i=0;i<10;i++) num[i]=i+1;
  10.       for (i=0;i<10;i++)
  11.       {
  12.             sprintf(cnum,"%d",num[i]);    //  num to string
  13.             strcat(buffer,cnum);
  14.             strcat(buffer,"\r");
  15.       }
  16.       
  17.       fd=open("Number.rtf",O_WRONLY|O_TRUNC|O_CREAT);
  18.       size=write(fd,buffer,sizeof(buffer));
  19.       close(fd);
  20.       
  21.       if (size)
  22.             printf("write file success!\nwrite %d bytes data\n",size);
  23.       else
  24.             printf("write file error!\n");
  25.       printf("\n");
复制代码
执行后生成符合EV3G能够读取的格式文件Number.rtf
Step2:EV3G端的文件读取
同上,直接使用实验二test2.ev3p中的ReadFile程序即可,程序运行效果和前述完全相同!

如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2014-3-21 23:47:08 | 显示全部楼层
本帖最后由 ntwuhui 于 2014-4-15 16:02 编辑

实验三: 双向通信

为简单起见,这里以计算1+2+3+...+10为例;EV3传递10个数字给C++程序;C++程序计算完毕将结果回传显示。
项目各文件介绍如下:
(1)、命令文件cmd.rtf
作用:数据传送方向
说明:0表示从EV3到C++传递信息(EV3:写,C++:读),1表示从C++到EV3传递信息(C++:写,EV3:读)
(2)、数据文件Number.rtf
作用:需要传递的具体数据(同上,也是双向传递的)
(3)、脚本程序autorun.rtf
作用:用于自启动C++端程序
代码如下:(这里假定为test.rtf)
  1. #!/bin/sh
  2. if [ -x test.rtf ]
  3. then
  4.     ./test.rtf &
  5. fi
复制代码
(4)、EV3G项目文件test3.ev3p
说明:程序test为项目主程序
代码如下:(可修改项目test2.ev3p中WirteFile得到,后面增加以下内容)
1.png

其中第二、三行为新增部分,逻辑就不多解释了。
(5)、C++程序文件说明:此程序通过脚本autorun.rtf自动加载,驻留后台运行!
代码如下:(程序中有注释)
  1. #include<unistd.h>
  2. #include<sys/types.h>
  3. #include<sys/stat.h>
  4. #include<fcntl.h>

  5. // Wait for EV3
  6. void WaitforEV3()
  7. {
  8.       char buffer[2],ch='1';
  9.       int fd=-1;
  10.       
  11.       while (ch=='1') {
  12.           while (fd==-1) {
  13.                fd=open("cmd.rtf",O_RDONLY);
  14.                read(fd,buffer,2);
  15.                close(fd);
  16.           }
  17.           ch=buffer[0];
  18.           fd=(ch=='0')?1:-1;
  19.       }
  20. }

  21. // Read data from EV3
  22. void ReadfromEV3(int num[])
  23. {
  24.       int fd,size;
  25.       int i,j,t;
  26.       char buffer[21];

  27.       //read data from file
  28.       fd=open("Number.rtf",O_RDONLY);
  29.       size=read(fd,buffer,sizeof(buffer));
  30.       close(fd);

  31.       // Prepare data
  32.       j=0; t=0;
  33.       for (i=0;i<size;i++)
  34.           if (buffer[i]=='\r'){
  35.              num[j]=t;
  36.              j++;
  37.              t=0;
  38.           }
  39.           else t=t*10+(buffer[i]-'0');
  40. }

  41. // Send Result to EV3
  42. void WritetoEV3(int t)
  43. {
  44.       int fd;
  45.       char cnum[3],buffer[3];

  46.       //write data to file
  47.       fd=open("Number.rtf",O_WRONLY|O_TRUNC|O_CREAT);
  48.       sprintf(cnum,"%d",t);
  49.       strcat(buffer,cnum);
  50.       strcat(buffer,"\r");
  51.       write(fd,buffer,3);
  52.       close(fd);

  53.       //Notice EV3
  54.       fd=open("cmd.rtf",O_WRONLY|O_TRUNC|O_CREAT);
  55.       write(fd,"1\r",2);
  56.       close(fd);
  57. }

  58. int main()
  59. {
  60.       int i,j,result;
  61.       int num[10];
  62.       
  63.       while(1){
  64.          printf("Waiting for EV3...\n");
  65.          WaitforEV3();
  66.       
  67.          printf("Read data from EV3\n");
  68.          ReadfromEV3(num);

  69.          // Core Calculate
  70.          result=0;
  71.          printf("Calculting...\n");
  72.          for (i=0;i<10;i++) result+=num[i];
  73.       
  74.          printf("Write data to EV3\n");
  75.          WritetoEV3(result);
  76.       }
  77.       return 0;
  78. }
复制代码


几点说明:
1、调试时本人采用了telnet方式交互运行,建议最终运行时将上述代码中所有printf注释掉,并将该文件复制到test3文件夹下(注意改名为test.rtf);
2、项目初始只有三个文件:test.rbf、test.rtf、autorua.rtf,其余文件均为自动生成;
3、C++端程序test.rtf、autorun.rtf也可以用EV3G软件下载到EV3。

运行EV3G程序test效果如下:
2.png
至此实验圆满结束!



如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复 1 0

使用道具 举报

发表于 2014-3-22 08:51:06 | 显示全部楼层
不错,支持一下
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2014-3-22 09:04:49 | 显示全部楼层
水师研究EV3已经有点功力了!给力啊!

不过现在的原生C++相对EV3的应用,我总觉得太费事了,可能后续国外的一些大神会有更方便的办法......

目前状态下,觉得JAVA相对EV3,可能更好用一点.
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2014-3-22 13:13:30 | 显示全部楼层
更好的办法恐怕得从底层革起,彻底把固件给换了!
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2014-3-23 11:02:45 | 显示全部楼层
lz的介绍让人有豁然开朗的感觉,感谢各位的奉献以及指导。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2014-3-30 21:01:32 | 显示全部楼层
EV3是基于LINUX系统的,不知道能不能在系统文件里找到和硬件相关的驱动程序。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2014-3-30 21:03:37 | 显示全部楼层
另外在linux系统下是能直接运行.C++文件的。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2014-4-7 20:05:10 | 显示全部楼层
18651982955 发表于 2014-3-30 21:01
EV3是基于LINUX系统的,不知道能不能在系统文件里找到和硬件相关的驱动程序。

找驱动应该从源码考虑:https://github.com/mindboards/ev3sources/tree/master/lms2012
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2015-2-27 11:15:34 | 显示全部楼层
感谢
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2017-3-7 12:45:25 | 显示全部楼层
有看没有懂,大神厉害,一定要自己完成一次,抓紧学习
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

QQ|手机版|中文乐高 ( 桂ICP备13001575号-7 )

GMT+8, 2024-3-29 01:44 , Processed in 0.662986 second(s), 26 queries .

Powered by Discuz! X3.5

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表