创建控制Xbox控制器的机械臂-第1部分

2020-06-03 01:49:12

创建Xbox机器人手臂是我看到Kevin Drouglzet的帖子后一直想做的事情,他能够使用Xbox控制器和他自己创建的手臂,并且非常友好地发布了Xbox机器人手臂的设计文件(我绝对不是硬件设计师😅)-谢谢凯文!

我想这样做的另一个原因是希望在强化学习领域有更多的实践知识和实践经验。因为有了这个机器人手臂,我看到了学习物理动作控制的潜力,这将转化为虚拟的世界变化,打开了使用案例的可能性领域。

为了让这篇文章更容易理解,我决定对多篇文章进行深入报道。在第一部分中,我将详细介绍这个机械臂延伸部分是如何构建的,以及当您使用左侧拇指杆移动时,如何使机械臂控制右侧的管杆。而在第二部分中,我将更深入地讲述我们如何能够利用统一环境,并训练它最终由机械臂控制。

注意:看到Kevin在他的帖子中很好地解释了程序集,我决定缩短这一部分,在本节之后将更多的重点放在软件上。

让我们从这个构建所需的部分开始。我总共为这个版本支付了大约75.00美元(没有Xbox控制器的话是20.00美元)。

对于打印,我们可以利用提供的STL文件并将其加载到我们的3D打印软件中。

注:此为白色重印,用于个人造型。我要向我的同事迭戈·特里奇亚雷利和卡里姆·瓦斯大声喊一声,感谢他们帮我打印!

打印完后,我们可以把所有这些组装在一起,这很简单,凯文解释得很好。

现在我们的硬件已经组装好了,我们准备开始在这个美景上安装软件了!

我们想要通过利用左手指杆的输入来实现机械臂的移动,它会将数据发送到我们的计算机,而计算机又会将数据发送回控制电机的Arduino。

在下一篇文章中,我们将把采取行动的行动逻辑放在Arduino上,它将根据从观察状态收到的输入来决定采取哪个行动。

首先,我们需要连接Xbox控制器并开始从它接收数据。这在Windows上本身并不是一件容易的任务,因为很难找到允许我们这样做的最新库。这就是我决定使用浏览器API的原因,您可以在早先的一篇博客文章中找到:通过Node.js获取Xbox控制器输入。

当我们关注这篇文章时,我们可以利用下面的代码来读取拇指棒值,并将它们分别放入UP_DOWN和LEFT_RIGHT变量中。

const GameController=Required(';./GameController';);(Async()=>;{const gameController=new(100);//100ms轮询间隔等待GameController。init();gameController。在(';按钮';,(Btn)=>;控制台上。log(`Button:${btn}按下`))gameController。在(';拇指棒';,Async(Val)=>;{let valParsed=JSON.。parse(Val);let up_down=valParsed[1];let Left_right=valParsed[0];Console。log(`up_down:${valParsed[1]}|LEFT_RIGHT:${valParsed[0]}`);})})();

在开始交换数据和编程Arduino之前,我们必须做的另一件事是连接Node.js,以便它能够交换数据。幸运的是,我们有一个名为Serialport的优秀库,我们可以通过NPM install seralport安装它。

一旦安装完成,我们就可以通过使用我们想要的配置实例化对象来打开连接Arduino的COM端口。然后,为了发送和接收数据,我们等待一个打开事件,之后我们可以利用port.write(Data)命令,并且我们可以通过侦听Data事件来接收数据,我们可以将该事件记录到控制台。

请注意,我们接收的是字节,因此必须将其转换为通过ab2str函数实现的字符串。

Const SerialPort=Required(';SerialPort';);Const GameController=Required(';./GameController';);Const Port=NEW(';COM6';,{baudRate:38400,Parity:';None';,stopBits:1,FlowControl:False});函数ab2str(Buf){返回字符串。FromCharCode。Apply(null,new(Buf));}端口。在(';打开';,异步()=>;{控制台。log(";端口打开,等待重置";);//Arduino在获得串行连接时自动重置//因此,我们在发送setTimeout(async()=>;{console.。日志(";重置完成";);},3000);});端口。在(';数据';,(数据)=>;控制台上。log(ab2str(Data));

在我们开始对Arduino进行编程之前,我们必须决定如何交换数据,因为这是一个串行连接。

来自Xbox控制器的值以浮点形式到达,其中我们需要表示向上、向下和向左、向右方向的值中的2个。

在Javascript中,我们必须通过串口库发送Uint8Array。因此,要做到这一点,我们需要获取变量并将其从src类型转换为目标类型。在Javascript(64位)中,一个浮点表示8个字节,但是Arduino需要4个字节。通过将变量包装在Float32Array中,我们可以将此类型转换为4字节。

现在要将其发送到Arduino,我们需要将此Float32Array转换为Uint8Array。幸运的是,我们可以通过以下函数轻松实现这一点:

//将src转换为目标//示例:ConvertTypedArray(new Float32Array([1.00]),Uint8Array);函数ConvertTypedArray(src,type){let buffer=new(src.byteLength);let baseView=new(Buffer)。set(Src);return new(Buffer);}。

我们可以通过port.write()方法通过串行连接立即发送。但是,在执行此操作之前,我们确实希望确保可以发送新值(以避免过载),这可以通过port.drain()函数来完成。因此,我们编写另一个函数,该函数允许我们写入AndDrain以成功发送一条消息:

对于我们的阿杜伊诺来说,这有点复杂。我们需要接收数据并以某种方式保存它,但是这些数据是以字节而不是全浮点数的形式传入的。

幸运的是,我们可以使用一个技巧,它允许我们读取传入的字节并将它们复制到结构中。允许我们按如下方式定义输入:

tyecif struct{Float Up_Down;//Up|Down值[-1,1]Float Left_Right;//Left|Right Value[-1,1]}InputThumbtick;

无效设置(){序列号。Begin(38400);}void loop(){char buffer[sizeof(InputThumbStick)];IF(Serial.。Available()>;=sizeof(InputThumbtick)){串行。readBytes(buffer,sizeof(InputThumbtick));memcpy(&;inThumbticks,&;buffer,sizeof(InputThumbtick));}}。

最后但并非最不重要的是,我们现在必须根据传入的值移动伺服(水平和垂直)。在实际编写之前,我们首先必须考虑如何选择正确的值,因为文档(Servo.h&;&;Servo.h writeMicrosec)说明应该选择介于[1000,2000]之间的值。

在我们的例子中,这个范围是[1200,1800],1500是中间,因为我们不需要180度。

将其转换可以与规格化伺服值相比较。但是,默认情况下,我们不会在[0,1]之间进行规格化,而是会规格化到[-1,1]。具有以下参数:

X n或rm=X−X m i n X m a x−X m i n XNorm=\FRAC{X-Xmin}{xmax-Xmin}X n或rm=X m a x−X m i n X−X m i n​

X s c a l e d=2∗X n o r m−1 X缩放=2*XNorm-1 X s c a l e d=2∗X n o r m−1。

X s c a l e d=(2∗X−X m i n X m a x−X m I n X Scaled=(2*\FRAC{X-Xmin}{xmax-Xmin}X s c a l e d=(2∗X m a x−X m i n X−X m I n​)-1。

X=((X s c a l e d+1 2)∗(X m a x−X m i n))+X m i n X=((\frac{X缩放+1}{2})*(x最大-X最小))+X min X=((2 X s c a l e d+1​)∗(X m a x−X m i n)+X m i n。

int inputToServoValue(Float In,int Xmin,int xMax){in=in*-1;//反号return(Int)(in+1)/2)*(xmax-xmin))+Xmin);}。

在下面,您将能够找到最终代码,该代码用于实现基于左侧拇指棒输入的移动机械臂。我努力使这段代码尽可能干净,这样它就可以在其他项目中重用。

在下一篇文章中,我希望将此项目用于强化学习用例,但稍后将详细介绍该😉。

#include<;Servo.h>;Servo Vertical;//Servo做垂直移动Servo水平移动;//Servo做水平移动类型定义f struct{Float Up_Down;//Up|Downvalue[-1,1]Float Left_Right;//Left|Right Value[-1,1]}InputThumbtick;InputThumbticks in Thumbticks;//Startup void setup(){串行.。开始(38400);序列号。println(";此程序需要2件:";);序列号。println(";[U|D,L|R],范围[1,0]";);序列号。println(";这些是每个4字节(浮点)";);串行。println(";示例:[-1.0,0.0]";);串行。println();水平。连接(9);//垂直连接到9号针脚。Attach(10);//附加到引脚10}//Main Loop void loop(){char buffer[sizeof(InputThumbtick)];IF(Serial.。Available()>;=sizeof(InputThumbtick)){串行。readBytes(Buffer,sizeof(InputThumbtick));memcpy(&;inThumbticks,&;Buffer,sizeof(InputThumbtick));垂直。write微秒(inputToServoValue(inThumbtics.up_down,1200,1800));水平。writeMicrosec(inputToServoValue(inThumbtics.Left_Right,1200,1800));}}/*将输入值转换为伺服微秒值*-伺服微秒值为[1200,1800]*-输入值为[-1,1]**我们因此希望对微秒值进行归一化并提取X*-归一化[0,1]:Xnorm=(X-Xmin)/(xmax-Xmin)*-归一化[-1,1]:Xscale=2*Xnorm-1*-归一化[-1,1]:Xscale=(2*((X-Xmin)/(xmax-Xmin)-1*-提取X:x=(Xscale+1)/2)*(xmax-Xmin))+Xmin*-示例1:x=(-1+1)/2)*(1800-1200))+1200=1200*-示例2:x=(1+1)/2)*(1800-1200))+1200=1800。int xmin,int xmax){in=in*-1;//反号返回(Int)(in+1)/2)*(xmax-xmin))+xmin);}。

Const SerialPort=Required(';SerialPort';);Const GameController=Required(';./GameController';);Const Port=NEW(';COM6&39;,{baudRate:38400,Parity:';None';,stopBits:1,FlowControl:False})函数ab2str(Buf){返回字符串。FromCharCode。Apply(null,new(Buf));}异步函数writeAndDrain(port,data){return new((Resolve,Reject)=&>T;{port.。写(数据);端口。排出(Resolve);})};(Async()=>;{常量GameController=new(100);等待GameController。init();gameController。在(';按钮';,(Btn)=>;控制台上。log(`Button:${btn}按下`))gameController。在(';拇指棒';,Async(Val)=>;{let valParsed=JSON.。parse(Val);let up_down=valParsed[1];let Left_right=valParsed[0];Console。log(`up_down:${valParsed[1]}|Left_right:${valParsed[0]}`);//console.log(ValParsed);let data=ConvertTypedArray(new([UP_DOWN,LEFT_RIGHT]),Uint8Array);等待writeAndDrain(port,data);//[L|R,U|D]Range[-1,1]})端口。在(';打开';,异步()=>;{控制台。log(";端口打开,等待重置";);//Arduino在获得串行连接时自动重置//因此,我们在发送setTimeout(async()=>;{console.。log(";Reset Done";);},3000);});//port.on(';open';,()=>;port.write(';123';))端口。在(';数据';,(数据)=>;控制台上。log(ab2str(Data);函数ConvertTypedArray(src,type){let buffer=new(src.byteLength);let baseView=new(Buffer)。set(Src);return new(Buffer);}})();