使用连接到 BeagleBone Black 的 L3G4200D 陀螺仪芯片旋转立方体





5.00/5 (22投票s)
我与硬件的冒险,以及BeagleBone Black和Windows上的C#应用程序之间的通信。
引言
我最近很有趣地使用BeagleBone Black (BBB)将x-y-z旋转运动发送到一个C#应用程序,该应用程序会响应陀螺仪的物理移动来旋转一个3D立方体。我使用RabbitMQ在BBB和Windows C#应用程序之间进行通信。本文介绍了如何完成所有这些工作。这段代码几乎也适用于RaspberryPi。
硬件
您需要的两件硬件是
- 一个BeagleBoneBlack(或rPi、Arduino或类似支持I²C的设备)
- 一个L3G4200D(或类似)陀螺仪模块
I²C是一种多主、多从、单端、串行计算机总线。这里的想法是,您可以通过两条线让许多设备进行通信。每个设备都有一个唯一的地址,让您可以选择您正在与之通信的设备。
请注意,我购买了BBB入门套件,因为它除了BBB本身之外,还附带了很多好东西:电源、面包板和跳线等。
BeagleBoneBlack
BBB有很多功能,其中大部分我不会使用!
我之所以使用BBB而不是rPi或其他单板计算机(SBC),仅仅是因为我为客户的一些工作留下了几台这样的设备。
L3G4200D陀螺仪
我是在Radio Shack买的,说起来也够奇怪的。
这是一个3轴陀螺仪——它将报告三个维度的方向变化。还有一个温度传感器,我不会使用它。我也不使用高通滤波器选项或FIFO队列。关于该设备的一个很好的PDF数据表可以在这里找到。
硬件和软件设置
我选择安装Debian,而不是使用Angstrom发行版Linux——主要是因为我们之前在Angstrom的稳定性方面遇到了一些问题。
安装Debian
硬件
要安装Debian,您基本上需要所有这些才能开始配置Debian。完成后,您真的就不再需要显示器或键盘了,因为您可以从Windows通过SSH连接到Debian。
开始
- 显然,一个BeagleboneBlack和5V电源
- 一个HDMI兼容显示器
- USB键盘
- 以太网线
- 以太网路由器(通常您的DSL/Cable调制解调器会提供额外的以太网端口)
- Micro MMC卡——我一直在使用SanDisk 32GB卡,
- 一台能够读取micro MMC卡的计算机。大多数计算机和笔记本电脑都具备此功能,您可能需要MMC到microSD适配器,或使用MMC USB读卡器,例如这个。
软件
- Debian的镜像。在撰写本文时,我使用的是Jessie。请在此处下载BBB镜像:http://beagleboard.org/latest-images
- 7-zip,用于解压步骤1中下载的.xz镜像。在此处下载:http://www.7-zip.org/download.html
- Win32DiskImager,用于在uMMC卡上创建镜像。在此处下载:http://sourceforge.net/projects/win32diskimager/?source=directory
- PuTTY SSH客户端,用于“远程连接”到您的BBB。在此处下载:http://www.putty.org/
- WinSCP SFTP、SCP和FTP客户端,用于Windows,可以更容易地在Windows和Debian之间复制文件。在此处下载:https://winscp.net/eng/download.php
安装
在您的Windows计算机上,安装
- 7-zip
- Win32DiskImager
- PuTTY
- WinSCP
完成后
- 使用7-zip解压Debian镜像
- 使用Win32DiskImager将镜像写入uMMC卡(确保不要将其写入您计算机的硬盘!)。
- 将Beaglebone断电,将uMMC插入micro SD插槽
- 将以太网线、USB键盘、HDMI显示器连接到BBB
- 将以太网线的另一端插入您的路由器(您的计算机也应连接到该路由器,通过物理连接或无线连接)。
- 插入BBB的电源
当Debian启动时,您应该可以使用用户名“debian”和密码“temppwd”登录。
在提示符下,输入“ifconfig”并按回车键。您应该会看到类似以下内容
eth0 Link encap:Ethernet HWaddr 7c:66:9d:53:cd:97
inet addr:192.168.1.42 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::7e66:9dff:fe53:cd97/64 Scope:Link
注意“inet addr”,例如,上面是192.168.1.42。
启动PuTTY,在主机名处输入“debian@”,后跟BBB的IP地址,类似这样
单击PuTTY窗口底部的“Open”按钮。您需要确认(单击“Yes”)弹出的提示“Continue connection to an unknown server and add its host key to a cache”(继续连接到未知服务器并将其主机密钥添加到缓存)。输入debian密码(temppwd)。您应该会看到类似以下内容
恭喜!您已成功远程连接到BBB!
打开WinSCP并编辑会话设置,类似这样(将IP替换为您BBB的IP)。
单击“Login”(登录)按钮(您可能需要再次确认“Continue connection to an unknown server and add its host key to a cache”问题)。
现在您应该可以使用WinSCP在Windows计算机和BBB之间传输文件了!
请注意,Debian自带SSH服务器。在其他操作系统(如Ubuntu)上,您需要安装SSH服务器。
sudo apt install openssh-server
BBIO软件
Adafruit为rPi和BBB都提供了一个预构建的I/O库,并有极好的文档介绍了如何开始。基本上,您需要从SSH客户端终端窗口运行这些命令。
sudo apt-get update
sudo apt-get install build-essential python-dev python-setuptools python-pip python-smbus -y
sudo pip install Adafruit_BBIO
请访问他们的网站(上面的链接)了解更多信息。
BBIO软件旨在与Python一起使用。Python(以及类似的解释型语言)的美妙之处在于,您可以在Windows和Debian操作系统上运行(更重要的是,测试)所有与硬件无关的代码。
Visual Studio中的Python
我仍然在使用Visual Studio 2012,所以我发现Python Tools for Visual Studio插件非常有帮助,因为我可以使用一个不错的IDE来开发Python代码。JetBrains的PyCharm也是一个不错的选择。
RabbitMQ
我的演示应用程序使用RabbitMQ在BBB和Windows之间进行通信。您可以在此页面下载RabbitMQ服务器。
配置RabbitMQ
为了在两个设备之间发送消息(而不仅仅是localhost上的两个应用程序),您需要为RabbitMQ设置一个用户名和密码,以及该账户的权限。
打开一个控制台窗口,导航到RabbitMQ服务器的“sbin”文件夹。最简单的方法是,安装RabbitMQ后,您应该可以从“开始”->“所有程序”->“RabbitMQ Server”->“RabbitMQ Command Prompt (sbin dir)”中打开它。在控制台窗口中,设置一个用户和权限。
rabbitmqctl add_user [user] [password]
rabbitmqctl set_user_tags [user] administrator
rabbitmqctl set_permissions -p / [user] ".*" ".*" ".*"
将[user]
和[password]
替换为您自己喜欢的。用户名和密码稍后将在BBB客户端上使用。
BBB上的各种组件
最后,您需要在BBB上安装一些额外的组件。SSH连接到BBB,然后
1. 安装pika,RabbitMQ Python客户端
sudo pip install pika
2. 安装bitstring,用于处理二进制数据
sudo pip install bitstring
将陀螺仪连接到BBB
在BBB断电的情况下,连接陀螺仪。虽然这个网站使用了不同的陀螺仪,但我发现它对弄清楚如何将设备连接到I²C总线非常有帮助。只需要四根连接线。
Device Pin BeagleBone Signal GND P9-1 GND VCC P9-3 +3.3V SCL P9-19 I2C2-SCL SDA P9-20 I2C2-SDA
(Visio不是合适的工具!)
BBB上的引脚分布的完整描述是
软件
现在只需要两部分软件——BBB上的Python代码和Windows上的C#旋转立方体(在我这里是矩形)。
Python 代码
Python代码被分成三个模块
- “应用程序”
- 陀螺仪读取器
- RabbitMQ队列发送器
应用程序
这个“主应用程序”实例化了Gyro类和QueueSender,并使用您之前为RabbitMQ服务器设置的用户名和密码进行配置。它还定义了一个回调函数,该函数通过RabbitMQ队列发送一个JSON字符串。
from L3G4200D import *
from QueueSender import *
class App(object):
RABBITMQ_PORT = 5672
SERVER_QUEUE_NAME = "bbbsensors"
def __init__(self):
self.gyro = Gyro(self.callback)
self.queue = QueueSender('[user]', '[password]', '[serverip]', App.RABBITMQ_PORT, App.SERVER_QUEUE_NAME)
def callback(self, x, y, z):
self.queue.send({"x":str(x), "y":str(y), "z":str(z)})
if __name__ == '__main__':
App().gyro.run()
同样,将[user]
和[password]
替换为您在RabbitMQ服务器中配置的用户名和密码。另外,将[serverip]
替换为运行RabbitMQ服务器的机器的IP地址。
队列发送器
QueueSender类使用pika通过RabbitMQ队列发送消息。注意异常处理程序。RabbitMQ会关闭一个队列,如果它一段时间(我估计大约10分钟)没有使用的话。即使我们不断发送消息,在发生异常时重新初始化队列并重试也是一个好主意。
import pika
class QueueSender(object):
def __init__(self, username, password, ipAddress, port, queueName):
self.username = username
self.password = password
self.ipAddress= ipAddress
self.port = port
self.queueName = queueName
self.initialize()
def initialize(self):
self.credentials = pika.PlainCredentials(self.username, self.password)
self.connection = pika.BlockingConnection(pika.ConnectionParameters(self.ipAddress, self.port, '/', self.credentials))
self.channel = self.connection.channel()
def send(self, msg):
try:
self.channel.basic_publish(exchange='', routing_key=self.queueName, body=str(msg))
except:
print("Exception on send. Re-establishing connection...")
self.initialize()
try:
self.channel.basic_publish(exchange='', routing_key=self.queueName, body=str(msg))
except:
print("Unable to send!")
陀螺仪读取器
最后,我们有最长的代码部分,它无休止地读取陀螺仪。在读取之间引入了一个小的超时时间,以避免消息队列过载。同时请注意,此代码不使用中断。该芯片可以配置为在数据可用时生成中断,但将其集成到Python代码中超出了我目前的知识范围!
import smbus
import bitstring
import time
class Gyro(object):
ADDRESS = 0x69
BUS1 = 1
def __init__(self, callback):
self.bus1 = smbus.SMBus(Gyro.BUS1)
self.initializeDevice()
self.callback = callback
print "Calibrating..."
self.calibrate();
print "Rest Offsets: " + str(self.restOffsetX) + ", " + str(self.restOffsetY) + ", " + str(self.restOffsetZ)
def initializeDevice(self):
self.bus1.write_byte_data(Gyro.ADDRESS, 0x20, 0x0F) # normal, x/y/z enabled
def calibrate(self):
""" Average 500 samples to get the rest state offset for each vector. """
sampleSize = 500
dx = 0.0
dy = 0.0
dz = 0.0
for n in xrange(sampleSize):
x, y, z = self.readRaw()
dx += x
dy += y
dz += z
self.restOffsetX = int(dx / sampleSize)
self.restOffsetY = int(dy / sampleSize)
self.restOffsetZ = int(dz / sampleSize)
def readRaw(self):
xl = self.bus1.read_byte_data(Gyro.ADDRESS, 0x28)
xh = self.bus1.read_byte_data(Gyro.ADDRESS, 0x29)
yl = self.bus1.read_byte_data(Gyro.ADDRESS, 0x2A)
yh = self.bus1.read_byte_data(Gyro.ADDRESS, 0x2B)
zl = self.bus1.read_byte_data(Gyro.ADDRESS, 0x2C)
zh = self.bus1.read_byte_data(Gyro.ADDRESS, 0x2D)
# status = self.bus1.read_byte_data(Gyro.ADDRESS, 0x27)
x = bitstring.Bits(uint = (xh << 8) | xl, length=16).unpack('int')[0]
y = bitstring.Bits(uint = (yh << 8) | yl, length=16).unpack('int')[0]
z = bitstring.Bits(uint = (zh << 8) | zl, length=16).unpack('int')[0]
return x, y, z
def readAdjusted(self):
""" Adjust raw vectors by the rest offsets. """
x, y, z = self.readRaw()
x -= self.restOffsetX
y -= self.restOffsetY
z -= self.restOffsetZ
return x, y, z
def run(self):
while True:
x, y, z = self.readAdjusted()
self.callback(x, y, z)
time.sleep(0.01)
请注意,此代码首先运行一个校准例程。这可能不是最好的实现方式,但目的是补偿漂移,因为陀螺仪在静止时不会返回0——实际上,它有相当大的噪声,通常在+/-128之间。校准尝试确定0的平均偏移量,否则会在陀螺仪静止时导致旋转漂移。
还请注意注释掉的状态读取行。在一个更健壮的应用程序中,状态对于确定是否可以获取旋转数据以及是否存在缓冲区溢出非常有用。因为这是一个在解释型语言中实现的非中断实现,所以几乎总会有缓冲区溢出,对于这个演示的目的,我们忽略它们。
C# 代码
在C#方面,我使用了两个额外的程序集
- Newtonsoft.Json(因为我懒,而且我可以轻松地自己解析非常简单的JSON数据!)
- RabbitMQ.Client(RabbitMQ的.NET客户端)
GryoData
这是一个简单的类,用于反序列化JSON数据。
using System;
namespace BeagleboneSensors
{
public class GyroData
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
}
}
辅助函数
我一直用来确定我的本地主机IP地址的类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace BeagleboneSensors
{
public static class Helpers
{
public static List<IPAddress> GetLocalHostIPs()
{
IPHostEntry host;
host = Dns.GetHostEntry(Dns.GetHostName());
List<IPAddress> ret = host.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToList();
return ret;
}
}
}
这确定了我们的IP地址,在我们的例子中,就是RabbitMQ服务器运行的地方。
RabbitMqIO
这个类负责创建到RabbitMQ服务器的连接,并在程序退出时优雅地关闭它。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using RabbitMQ.Client;
namespace BeagleboneSensors
{
public abstract class RabbitMqIO
{
protected List<IPAddress> localHostIPs;
protected IConnection connection;
protected IModel channel;
public RabbitMqIO()
{
localHostIPs = Helpers.GetLocalHostIPs();
}
public virtual void CreateConnection()
{
ConnectionFactory factory = new ConnectionFactory();
factory.UserName = ConfigurationManager.AppSettings["username"];
factory.Password = ConfigurationManager.AppSettings["password"];
factory.VirtualHost = "/";
factory.Protocol = Protocols.DefaultProtocol;
factory.HostName = localHostIPs[0].ToString();
factory.Port = 5672;
connection = factory.CreateConnection();
channel = connection.CreateModel();
}
public virtual void Shutdown()
{
channel.Dispose();
connection.Dispose();
}
}
}
我们从app.config文件获取RabbitMQ服务器的用户名和密码。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <appSettings> <add key ="username" value="[user]"/> <add key ="password" value="[password]"/> </appSettings> </configuration>
将[user]
和[password]
替换为您在RabbitMQ服务器中设置的相同的用户名和密码。
RabbitMqReceiver
接收器继承自RabbitMqIO类,并实现了CreateConnection方法,该方法声明了我们正在监听的队列,并设置了一个事件接收器,该接收器解析JSON数据,填充GyroData对象,并触发一个用于处理数据的处理程序。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Clifton.Core.ExtensionMethods;
namespace BeagleboneSensors
{
public class GyroEventArgs : EventArgs
{
public GyroData GyroData {get;protected set;}
public GyroEventArgs(GyroData data)
{
GyroData = data;
}
}
public class RabbitMqReceiver : RabbitMqIO
{
public string QueueName { get; set; }
public event EventHandler<GyroEventArgs> GyroData;
public RabbitMqReceiver()
{
QueueName = "bbbsensors";
}
public override void CreateConnection()
{
base.CreateConnection();
DeclareQueue(channel, QueueName);
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, eventArgs) =>
{
byte[] body = eventArgs.Body;
string message = Encoding.UTF8.GetString(body);
System.Diagnostics.Debug.WriteLine(message);
GyroData data = JsonConvert.DeserializeObject<GyroData>(message);
GyroData.Fire(this, new GyroEventArgs(data));
};
channel.BasicConsume(QueueName, true, consumer);
}
private static void DeclareQueue(IModel channel, string queueName)
{
channel.QueueDeclare(queueName, false, false, false, null);
}
}
}
正如我之前所说,使用NewtonSoft JSON有点乏味,但同样,如果我要扩展它来做更有趣的事情,拥有一个合适的JSON解析器将会派上用场。
主程序
主应用程序的设置非常简单
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace BeagleboneSensors
{
static class Program
{
public static RabbitMqReceiver receiver;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
receiver = new RabbitMqReceiver();
receiver.CreateConnection();
Form form = new FrmRender();
form.FormClosing += (sender, args) =>
{
receiver.Shutdown();
};
Application.Run(form);
}
}
}
立方体渲染
好吧,它不是一个立方体,而是一个矩形。实际上,它是一个长方体。我完全“借鉴”了vcskicks.com的代码,并对其进行了修改,以对侧面进行着色,并删除了手动x/y/z滑块和其他UI元素。我不会深入介绍旋转的工作原理,您可以阅读这篇文章(以及之前引用的那篇文章)。我所做的是连接了GyroData
事件处理程序。
public FrmRender()
{
InitializeComponent();
Program.receiver.GyroData += OnGyroData;
}
并实现了处理程序。
private float dx = 0;
private float dy = 0;
private float dz = 0;
private void OnGyroData(object sender, GyroEventArgs e)
{
this.BeginInvoke(() =>
{
dx += (float)(e.GyroData.X / 4000.0);
dy += (float)(e.GyroData.Y / 4000.0);
dz += (float)(e.GyroData.Z / 4000.0);
mainCube.RotateX = dx;
mainCube.RotateY = dy;
mainCube.RotateZ = dz;
Render();
});
}
这实现了最初由滑块完成的功能。这是一个很好的代码重用示例,让我以最小的努力得到了我想要的结果。
其他有趣的事情
BeagleBone CPU频率
BBB的默认CPU频率是300MHz。如果您愿意,可以使用以下命令将其提高到1GHz。
sudo cpufreq-set -f 1000MHz
由于Python代码中的10ms采样延迟,我没有注意到这有任何区别。
您可以使用以下命令确定您的BBB运行的频率。
cpufreq-info
debian@beaglebone:~/gyro$ cpufreq-info
cpufrequtils 008: cpufreq-info (C) Dominik Brodowski 2004-2009
Report errors and bugs to cpufreq@vger.kernel.org, please.
analyzing CPU 0:
driver: generic_cpu0
CPUs which run at the same hardware frequency: 0
CPUs which need to have their frequency coordinated by software: 0
maximum transition latency: 300 us.
hardware limits: 300 MHz - 1000 MHz
available frequency steps: 300 MHz, 600 MHz, 800 MHz, 1000 MHz
available cpufreq governors: conservative, ondemand, userspace, powersave, performance
current policy: frequency should be within 300 MHz and 1000 MHz.
The governor "ondemand" may decide which speed to use
within this range.
current CPU frequency is 300 MHz.
cpufreq stats: 300 MHz:99.95%, 600 MHz:0.01%, 800 MHz:0.00%, 1000 MHz:0.03% (338)
检测I²C设备
您可以使用i2cdetect
程序检测I2C设备(但要小心)(在此处阅读相关信息)。
i2cdetect -y -r 1
这会给您一个设备映射。
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- 69 -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
L3G4200D陀螺仪的设备ID是“69”,您可以看到它在地址69处被检测到。
结论
这是一个有趣的项⽬,我计划做其他有趣的事情,比如一个带有温度、压力和湿度传感器的简易天气站。BBB对于这样的例子来说绝对是“超配”的——如果您想在例如遥控汽车或其他RC设备(无人机?)上使用陀螺仪,我肯定建议您考虑使用低功耗设备,如Arduino或rPi,当然您也需要配置Wi-Fi或其他无线连接。
此外,很多艰苦的工作已经完成——Adafruit BBIO库非常棒,Python是原型制作IO的绝佳语言,但如果您想做任何有质量的事情(例如,使用中断),C语言几乎是我会选择的语言,这样您就可以使用编译后的高性能代码。