树莓派笔记6:自制小车(手柄控制)
2018-02-11 19:41
309 查看
继续树莓派小车的内容,这次记录手柄控制小车运动的实现。
①用红外遥控器,小车上放一个接收器,读取遥控器信息。实现应该比较简单,红外收发元件也很便宜,不过遥控器得对着小车,恐怕不太方便;
②蓝牙手柄,因为树莓派带蓝牙,可以通过蓝牙接收手柄数据,不过一个蓝牙手柄可不便宜;
③有线手柄,相比无线设备肯定low一点,不过我手头就只有一个有线手柄,50多块钱的小鸡G3,就是个xbox 360手柄;
④手机,如果写个带方向键的手机客户端来控制小车应该是最酷的了,不过我不会Android或IOS开发。
最终选择用有线手柄控制小车,而控制方式不是把手柄连到树莓派上,而是连到我的笔记本上,用C#写个客户端程序,一方面读取手柄数据,一方面与树莓派通信。至于通信,采用TCP通信,树莓派上运行服务器程序,接收控制信息,电脑上运行客户端程序,发送手柄数据。
首先需要下载SharpDX.XInput库,可以在Visual Studio的Nuget包管理器里直接下,不过如果直接下SharpDX.XInput会提示需要依赖SharpDX库,因此先下SharpDX库。下好SharpDX后再下载SharpDX.XInput竟然还是报错,说缺少SharpDX依赖,这时可以先卸载Nuget管理器再重安装(“工具”->“扩展和更新”),这样就可以顺利下载了。
添加XInput的引用
这个类代码很短,因为我只需要读取方向键、start和back键,如果要读取摇杆和震动数据的话就需要麻烦一些了。Controller对象就代表了手柄设备,初始化的时候指定了设备号为“UserIndex.One”,总共好像可以有四个设备;Gamepad对象存储了手柄当前的状态信息,其中有一个结构体为GamepadButtonFlags属性,它以二进制位的形式存储了按键信息,比如0x0001就表示“上”方向键处于按下状态,0x0002就表示“下”方向键处于按下状态,在上述代码中,我读取Buttons属性,判断每一位的状态来检测哪个键被按下,如果是没有键被按下或者除了方向键、start和back键外的键被按下,就返回“undefine”。
下面是程序主体代码:
在发送数据到小车的代码中,是把字符串编码为UTF-8格式的,一开始我用的UNICODE编码,结果树莓派上的Python程序接收乱码,因为Python中的网络数据解析默认是按照UTF-8格式的。下图是程序界面,用了一个文本框显示程序信息。
Python代码如下:
在接收处理的代码中, 我用match方法匹配相应的关键词而不是直接用等于号,是因为TCP传输中可能会出现拆包、黏包等现象,用match方法稳妥一些;对于“undefine”指令,直接停止小车,而受到“back”指令时终止程序。
1 方案设计
对于手动控制小车的工具,大概有这么几种:①用红外遥控器,小车上放一个接收器,读取遥控器信息。实现应该比较简单,红外收发元件也很便宜,不过遥控器得对着小车,恐怕不太方便;
②蓝牙手柄,因为树莓派带蓝牙,可以通过蓝牙接收手柄数据,不过一个蓝牙手柄可不便宜;
③有线手柄,相比无线设备肯定low一点,不过我手头就只有一个有线手柄,50多块钱的小鸡G3,就是个xbox 360手柄;
④手机,如果写个带方向键的手机客户端来控制小车应该是最酷的了,不过我不会Android或IOS开发。
最终选择用有线手柄控制小车,而控制方式不是把手柄连到树莓派上,而是连到我的笔记本上,用C#写个客户端程序,一方面读取手柄数据,一方面与树莓派通信。至于通信,采用TCP通信,树莓派上运行服务器程序,接收控制信息,电脑上运行客户端程序,发送手柄数据。
2 手柄数据读取
现在主要的问题是怎么读取手柄数据呢?通过网络搜索,发现可以使用SharpDX库在.NET平台下读取手柄输入数据,SharpDX是一个全新的、开源的、封装了 DirectX API的项目(Wiki文档链接:http://sharpdx.org/ ,目前还不全),其支持的API包括2D和3D渲染、音频、设备输入等方面,貌似游戏开发中使用的挺多的。不过我不需要用到那么多功能,只需要使用XInput库就行,XInput支持XBox360手柄的数据读取。关于SharpDX及XInput的教程或文档有点难找,Wiki上的官方文档只是接口说明,而且现在还不全;微软官方文档上可以找到XInput的相关文档,不过它是针对C++的;我在Stack Overflow找到一个回答,介绍了SharpDX.XInput的简单使用方法(https://stackoverflow.com/questions/39109609/how-to-use-xbox-one-controller-in-c-sharp-application/39109610#39109610)。首先需要下载SharpDX.XInput库,可以在Visual Studio的Nuget包管理器里直接下,不过如果直接下SharpDX.XInput会提示需要依赖SharpDX库,因此先下SharpDX库。下好SharpDX后再下载SharpDX.XInput竟然还是报错,说缺少SharpDX依赖,这时可以先卸载Nuget管理器再重安装(“工具”->“扩展和更新”),这样就可以顺利下载了。
添加XInput的引用
using SharpDX.XInput;,然后可以写一个手柄控制类:
class XInputController { Controller controller; Gamepad gamepad; public bool connected = false; public XInputController() { controller = new Controller(UserIndex.One); connected = controller.IsConnected; } /// <summary> /// 读取方向键信息 /// </summary> /// <returns></returns> public string GetDirection() { if (!controller.IsConnected) return null; gamepad = controller.GetState().Gamepad; GamepadButtonFlags flag = gamepad.Buttons; int resultStart = ((int)flag) & 0x10; int resultBack = ((int)flag) & 0x20; int resultUp=((int)flag) & 0x01; int resultDown=((int)flag) & 0x02; int resultLeft=((int)flag) & 0x04; int resultRight=((int)flag) & 0x08; if (resultStart != 0) return "start"; else if (resultBack != 0) return "back"; else if (resultUp != 0) return "up"; else if (resultDown != 0) return "down"; else if (resultLeft != 0) return "left"; else if (resultRight != 0) return "right"; else return "undefine"; } }
这个类代码很短,因为我只需要读取方向键、start和back键,如果要读取摇杆和震动数据的话就需要麻烦一些了。Controller对象就代表了手柄设备,初始化的时候指定了设备号为“UserIndex.One”,总共好像可以有四个设备;Gamepad对象存储了手柄当前的状态信息,其中有一个结构体为GamepadButtonFlags属性,它以二进制位的形式存储了按键信息,比如0x0001就表示“上”方向键处于按下状态,0x0002就表示“下”方向键处于按下状态,在上述代码中,我读取Buttons属性,判断每一位的状态来检测哪个键被按下,如果是没有键被按下或者除了方向键、start和back键外的键被按下,就返回“undefine”。
3 PC端程序
新建一个Winform项目,编写PC客户端程序。因为要做TCP客户端,需要使用socket相关类;另外也需要考虑读取手柄信息的方式,是事件机制还是主动轮询,怎么处理手柄的事件我还不知道怎么写,我采用定时器的方式主动查询手柄状态。下图是PC端程序的流程图。下面是程序主体代码:
public partial class HandleControlForm : Form { private TcpClient client = null; private NetworkStream streamToServer = null; private XInputController controller = null; public HandleControlForm() { InitializeComponent(); this.button1.Enabled = false; this.timer1.Interval = 100; this.timer1.Stop(); } private void HandleControlForm_Load(object sender, EventArgs e) { //手柄初始化 controller = new XInputController(); if (controller.connected) { this.textBox1.Text = "the handle has connected...\r\n"; this.button1.Enabled = true; } } /// <summary> /// 启动 /// </summary> private void button1_Click(object sender, EventArgs e) { //连接小车 client = new TcpClient(); try { client.Connect(IPAddress.Parse("192.168.1.17"), 5150); } catch { this.textBox1.Text += "connect to the car falled...\r\n"; this.button1.Enabled = false; return; } this.textBox1.Text += "connect to the car successfully...\r\n"; streamToServer = client.GetStream(); this.timer1.Start();//开始接收手柄输入 this.button1.Enabled = false; } /// <summary> /// 定时器处理 /// </summary> private void timer1_Tick(object sender, EventArgs e) { string input = null; input = controller.GetDirection(); if (input == null) { this.textBox1.Text += "the handle disconnect...\r\n"; return; } this.textBox1.Text += input + "\r\n"; //向小车发送控制指令 byte[] buffer = Encoding.UTF8.GetBytes(input); streamToServer.Write(buffer, 0, buffer.Length); if (input == "back") { //关闭 this.timer1.Stop(); streamToServer.Close(); client.Close(); streamToServer = null; client = null; this.button1.Enabled = true; this.textBox1.Text += "shut down the connection...\r\n"; } } private void textBox1_TextChanged(object sender, EventArgs e) { this.textBox1.SelectionStart = this.textBox1.Text.Length; this.textBox1.ScrollToCaret(); } }
在发送数据到小车的代码中,是把字符串编码为UTF-8格式的,一开始我用的UNICODE编码,结果树莓派上的Python程序接收乱码,因为Python中的网络数据解析默认是按照UTF-8格式的。下图是程序界面,用了一个文本框显示程序信息。
4 树莓派程序
树莓派上的Python程序主要由电机控制模块和TCP服务器代码组成,程序流程如下图所示:Python代码如下:
#! /usr/bin/python3 import socket import re import Motor_Module print("init motor module...") try: motor=Motor_Module.Motor_Module() motor.setup() except: print("init motor module fail...") exit() server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) host='192.168.1.17' port=5150 try: server.bind((host,port)) except: print("socket bind fail...") exit() server.listen(5) print("listening for connect...") client,addr=server.accept() print("Accept the connect , now receving commands...") duration=1 #不断接收指令 while True: data=client.recv(1024) info=bytes.decode(data) #解码字符串 if re.match('back',info): break elif re.match('up',info): print("move ahead") motor.ahead() elif re.match('down',info): print("move rear") motor.rear() elif re.match('left',info): print("move left") motor.left() elif re.match('right',info): print("move right") motor.right() elif re.match('undefine',info): print("no control command") motor.stop() print("end the connection...") motor.stop() client.close()
在接收处理的代码中, 我用match方法匹配相应的关键词而不是直接用等于号,是因为TCP传输中可能会出现拆包、黏包等现象,用match方法稳妥一些;对于“undefine”指令,直接停止小车,而受到“back”指令时终止程序。
5 测试
首先启动树莓派上的程序监听连接,然后启动PC端程序,手柄需要提前连接上,否则PC程序上的按钮不可用;由于程序用定时器轮询手柄,并且设置了“undefine”指令,小车可实现随时停止的效果,长按方向键可以持续运行,松开按钮小车立即停止;此外,可以在树莓派上插上摄像头,后台运行mjpg-streamer,在PC端网页查看实时视频,这样就可以实现简易的远程控制了。相关文章推荐
- 树莓派笔记5:自制小车(简单避障)
- 人工智能-树莓派小车(5)——用微信控制智能小车
- 基于Z301P摄像头 H.264OK6410的远程视频web监控 项目笔记5(小车驱动)GPIO控制
- 树莓派学习笔记——webiopi网页控制LED
- Ubuntu16.04虚拟机+ROS+树莓派控制小车
- Android手机控制树莓派制作的四驱小车
- 在安卓下控制基于树莓派的小车 皆用python实现
- Android手机控制树莓派制作的四驱小车
- 树莓派学习笔记——yeelink 远程控制LED
- Android手机控制树莓派制作的四驱小车
- 虚拟手柄控制的小车 air3.4 Android IPones4s 下运行正常
- 树莓派学习笔记-按键控制LED灯-WiringPi
- 人工智能-树莓派小车(3)——GPIO控制小车
- boost format 格式控制输出(学习笔记)
- 笔记-NopCommerce系统架构分析-单实例控制、Type探测器
- golang 的自制Pool中连接使用笔记
- cocos2dx-lua 笔记 >方向控制 v1
- Java学习笔记(四):流程控制
- Win 32 多线程程序设计学习笔记之四:同步控制(Synchronization)
- 【Java编程思想--学习笔记(一)】访问控制-包