项目分享| 高中生用arduino做的魔方机器人

第一次看到这个项目完全被吸引住了—魔方机器人。要知道妮姐接触过不少用stm32做的类似案例,论控制stm32绝对是杠杠滴,而这个项目是基于性能相对弱很多的Arduino完成。同时,这个魔方机器人巧用了算法。总结两点打动妮姐:1)算法计算如何操作,2)用arduino来控制电机运动。在此,分享给各位。

ps:最后得知该项目居然出自一位高中生!#牛皮高中生(知乎ID:g20150120)# 妮姐不得不感慨:前浪稍不注意就被拍打在沙滩上啊!

——达尔闻

 

话不多说,来看下项目演示的结果:

魔方机器人的Arduino源码可以在“达尔闻说”微信回复:魔方机器人,获取。

 

✦ 我的魔方机器人设计由来。这个作品始于2016年上半年,也是我高一下学期开学没多久,我奶奶转了一个德国的魔方机器人的视频给我,视频里的机器在1秒钟之内就复原了三阶魔方。我觉得特别有意思,并且我比较喜欢玩魔方,当时又选修了有关Arduino的校本课程,就抱着试试看的心态准备自己做一个魔方机器人。中间遇到了特别多的困难,我也不是一直时间充裕,因此这个魔方机器人断断续续到2017年11月才最终完成,复原了一个打乱的魔方。

做完整个项目不容易,我起初只有一个Arduino UNO,一个GAN 356, 不过我还有无限的热情和不轻易放弃的执着

 

 魔方机器人涉及的元器件:

6* 42号步进电机两相四线步进电机 (额定电压3.3V额定电流1.5A)

6* L298N驱动板

6* 自行设计、3D打印的连接件,使步进电机可以直接转动魔方的中心块,同时容易脱开。

1* 直流稳压电源 (0-15V可调最大2A)

1* Arduino MEGA2560 (总共需要 4*6=24 个digital output来控制驱动和步进电机,UNO无法胜任)

1* GAN356魔方

面包板两块、杜邦线若干。。。


我使用的魔方是GAN356,一款容错性能极佳的魔方。但是还有一个至关重要的优点是它的中心块可以打开,并且是中空的,这就给连接步进电机提供了方便。


我测量了尺寸,设计出了连接件如上图(联想到了可以换多种头的那种螺丝刀的接口设计),3D打印出了成品。7对小东西花了九十几块还不包邮,我买单片机都只花了40不到啊,肉疼。当我终于看到我的魔方机器人复原了一个魔方,而不是把它拧的更乱的时候我内心的成就感是爆棚的。这么久的尝试终于(居然??)有了结果。


 再来说一下魔方机器人的工作流程。首先需要把打乱的魔方以展开图的形式把各个面各个色块的颜色输入计算机,执行一个C++(其实只是C with STL)程序输出魔方解法步骤。然后把解法复制到Arduino的代码里,烧录进单片机。单片机随即控制步进电机执行解法,复原魔方。复原过程平均来说在五秒钟之内可以完成。

解魔方的算法叫Thistlethwaite's Algorithm,以群论为理论基础求解魔方。对于这个算法来说,魔方不再是由颜色来描述,而是每个棱块角块的相对位置和朝向表达。因此,有别于人类使用的层先法和CFOP之类的方法,这个算法极其抽象,但是优点是稳定高效,解法较短。(对算法感兴趣的话,可以在“达尔闻说”微信公众号回复:魔方机器人

这个算法有多种编程语言的多个实现方式,我试图寻找并调试出高效易懂的实现代码。给这个算法输入的魔方状态不再是用各个小色块表示,而是确定了整个魔方的朝向以后直接用棱块和角块的相对位置关系和朝向表示(形如 UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRFDFL DLB DBR)。这也给我提出了一个新的要求:编写一个程序把用颜色展开图形式表示的魔方转换成如上,用一个棱块和角块的字符串表示的魔方。我自己体验了几次把打乱的魔方转换成该字符串的过程,找到了对应规律,归纳出了对应方法,存入几个常量数组,通过对这些数组的巧妙访问高效地使独立的六个面形成一个完整的二维数组表示的展开图,并且转化为最终的字符串;后续的修改还使得各个面输入时只要方向正确,不必按照特定的顺序,带来了一定的方便;不足之处是这个代码不能检查出是否有颜色输错、该魔方是否可以通过正常的转动形成。

/*******算法代码*****************/

char cube[9][12];      

/*

cube[x][y] 储存9行12列的颜色展开图
   XXX    

   XWX    

   XXX
XXX XXX XXX XXX

XOX XGX XRX XBX

XXX XXX XXX XXX
   XXX    

   XYX    

   XXX
*/
const int
//展开图中六个面填色的起始位置    0-5为 GWORYB 

start_x[6]={3,0,3,3,6,3}, 

start_y[6]={3,3,0,6,3,9},
//代表颜色信息和位置信息的关系 人工总结 

edge_x[24]={2,3,1,3,0,3 ,1,3,6,5,7,5,8,5 ,7,5,4,4,4,4,4,4,4 ,4},

edge_y[24]={4,4,5,7,4,10,3,1,4,4,5,7,4,10,3,1,5,6,3,2,9,8,11,0},

apex_x[24]={2,3,3,0,3,3,0,3 ,3,2,3,3,6,5,5,6,5,5,8,5,5 ,8,5,5},

apex_y[24]={5,5,6,5,8,9,3,11,0,3,2,3,5,6,5,3,3,2,3,0,11,5,9,8};
int func(char ch)

{    

 //对应start_x/start_y中的颜色顺序    

  if(ch=='W')        

    return 1;    

  if(ch=='G')        

    return 0;    

  if(ch=='O')        

    return 2;    

  if(ch=='R')        

    return 3;    

  if(ch=='B')        

    return 5;    

  if(ch=='Y')        

    return 4;    

  return 0;}
char convert(char ch)

{  //将展开图中的颜色信息转换为FU LR etc的位置信息  

  //其中 绿色为F 白色为U  形如 cube[9][12]  

  if(ch=='W')        

    return 'U';    

  if(ch=='G')        

    return 'F';    

  if(ch=='O')        

    return 'L';    

  if(ch=='R')        

    return 'R';    

  if(ch=='B')        

    return 'B';    

  if(ch=='Y')         

    return 'D';  

  return 0;}
int main()

{  

  //do sth.
 //储存一个面的3*3矩阵  

 string tmp[3];
 for(int ii=0;ii<6;ii++)//执行六次 读取六个面的3*3矩阵  

 {    

   for(int jj=0;jj<3;jj++)//读取一个矩阵的三行      

   cin>>tmp[jj];
   //tmp[1][1]记录中心块颜色 从而确定这一面的颜色 和 在展开图中的相对位置        int q=func(tmp[1][1]);
   //将tmp中的信息转存到cube[9][12]展开图中    

   for(int i=0;i<3;i++)        

     for(int j=0;j<3;j++)            

       cube[start_x[q]+i][start_y[q]+j]=tmp[j];  

  }
 //argv[1-20]存储魔方的状态 值为 UF DBR etc  

  string argv[21];
 //后面通过+=写入数据 务必先初始化置为空  

  for(int i=0;i<21;i++)    

  argv="";  //代表现在向argv[index]写入数据  

  int index=1;
  for(int i=0;i<24;i++)  

  {    

   //把颜色信息转换成位置信息    

    argv[index]+=convert(cube[edge_x][edge_y]);        
   //前12组表示棱的位置 每组两个 UF UR etc    

    if(i%2==1)        

      index++;  

    }  

    for(int i=0;i<24;i++)  

   {    

     //把颜色信息转换成位置信息    

     argv[index]+=convert(cube[apex_x][apex_y]);
     //后8组表示角的位置 每组三个 ULF DBR etc    

     if(i%3==2 && i!=23)        

        index++; 

    }
   //do sth.
   return 0;

   }

/**********end***********/

上面是魔方表示方法转换的代码。求解魔方的代码涉及群论知识,超出了我的知识水平,感兴趣的可以查阅我分享的 SOLVECUBE.cpp 了解具体的实现方法。魔方状态的核心数据结构是一个 vector<int> ,求解核心算法是BFS。在“达尔闻说”微信公众号回复:魔方机器人,获得我分享的代码。


计算机求解部分做的差不多了之后我就开始写Arduino的代码。这里就不在贴代码了,代码获得方式见上。这个代码需要阅读解法,并控制对应的步进电机旋转对应的方向和度数。首先,所有的参数(电机速度、步数、步进角、延迟时间、顺逆时针旋转等等)都预先用常变量定义在代码最开始;定义一个字符串保存之前的程序算得的解法,两个一读遍历字符串,得到哪一面旋转多少次。接着,通过switch语句嵌套和Stepper.h的函数具体控制哪个电机旋转多少步。起初的代码并不简洁美观,直到我之后重构代码引入了一个常量数组代替了第二层switch语句。

✦ 接下来最关键的就是接线了。具体接线其实并无对错之分,只需使得步进电机的时序图和Stepper.h中的控制函数通过接线对应好即可。

我希望设计出简单、可靠、易脱开的连接机制,方便即时的连接、脱开。首先我用尺规画出了测得魔方中所需的宽度、深度等二维设计图;随后使用CAD将此画成机械零件设计图,其中包括主视图的剖面图;然后交由淘宝网店3D建模;再发给3D打印店打印出最终的成品。这个连接件可以使得电机直接带动魔方每一面旋转,同时也很容易将二者脱开。参考了可以换头的磁性螺丝刀的设计。

最终,我把机器组装起来,代码编译好备用,可以按照预期的方式来工作啦,而且效果还不错。

现阶段还有两个遗憾。其一,我没有想到一个好办法做出一个稳定而又灵活能脱开的支架固定步进电机,每次都要靠手拼命按着步进电机才不会跑掉(如视频上看到的)。第二,我需要人眼识别颜色再输入计算机,我想使用OpenCV或者Mathematica之类的做颜色识别,苦于水平有限,没有什么好的想法。

更多有关代码和魔方机器人本身的内容,可以在达尔闻说”微信回复:魔方机器人

达尔闻项目分享系列——聚焦物联网、嵌入式、AI、FPGA等热门应用技术,开源分享原理图、代码等项目方案,做你手边的知识库。

项目分享系列集锦:

STM32物联网智能家居项目

树莓派+计算棒2完成实时人脸识别项目

嵌入式开发板的云计算平台搭建

STM32实现最简单空中鼠标

我们是妮mo,达尔闻创始人,只讲技术不撩汉的小姐姐。达尔闻在线教育平台旨在服务电子行业专业人士,提供技能培训视频,覆盖各细分领域热门话题,比如嵌入式,FPGA,人工智能等。并针对不同人群量身定制分层级学习内容,例如常用知识点,拆解评测,电赛/智能车/考研等,欢迎关注。

官网:www.darwinlearns.com

B站:达尔闻

QQ群:786258064


本文为我原创

本文禁止转载或摘编

-- --
  • 投诉或建议
评论