BeagleBone 平台和使用 C++ 进行软件开发的初学者指南





5.00/5 (12投票s)
使用 C++ 快速了解 BeagleBone 及其内置 A/D 转换器、GPIO 和 I2C 总线的工作方法
操作系统镜像
BeagleBone 上有几种官方镜像可供选择安装。通常,对于物联网类的活动,我建议从“控制台”镜像开始,而不是功能齐全的 LXDE 桌面。如果您要做某种信息亭类型的项目,以后可以随时安装 X.Org 和 GUI 库。最近,出现了“iot”镜像版本,它基本上包含了 LXDE 版本的所有软件包,如 bonescript 和 node.js,但没有 LXDE 或 X.Org。“控制台”镜像则删除了所有 node.js 和 bonescript 工具,大小不到 50MB,但可能缺少您需要的一些编译器和工具。它是一个 Debian 系统,所以您始终可以使用 apt-get
添加您需要的内容。
如果您想通过 mini-USB 连接器(IP 地址:192.168.7.2)通过 SSH 访问 BeagleBone Black,您可能需要安装任何必要的 驱动程序。如果您使用的是 BeagleBone Green 版本,则使用 micro-USB 连接器,几乎任何手机充电线都可以。否则,请使用 DHCP 以太网地址或连接 FTDI 串行调试线。我还没有用到 BeagleBone Black 的 HDMI 输出,所以我已经习惯于通过 SSH 和 SFTP 来使用它们。如果您是 Windows 用户,我强烈推荐 MobaXterm,它通过集成的文件浏览器(实现了 SFTP)可以非常轻松地进行 SSH 和文件复制,否则可以使用 Putty。当然,Linux 用户可以在命令行中完成所有操作,但我发现 PAC Manager 也很有用。
内置硬件选项
为了充分利用 BeagleBone,您可以堆叠“帽子”(capes)来暴露额外的 I/O。但请注意,并非所有第三方帽子都能相互兼容,有时您可能会遇到 GPIO 使用冲突,特别是处理 LCD 屏幕帽子时。如果您坚持使用官方 叠加层存储库中定义的帽子,您将获得更大的成功。
另一方面,暴露 BeagleBone 处理器内置的额外 I/O 功能非常容易。请参考 此处的官方图表,以确定所有 I/O 的引脚位置。I/O 的功能和可访问性可以通过编辑 /boot/uEnv.txt 来控制。此文件控制各种硬件叠加层的加载,允许操作系统访问不同的 I/O 或硬件配置集。例如,要访问内部 12 位 A/D 转换器,请将以下内置叠加层添加到 /boot/uEnv.txt 文件中
cape_enable=bone_capemgr.enable_partno=BB-ADC
下次重新启动后,A/D 引脚将在 Linux 文件系统中可访问,位置是
/sys/bus/iio/devices/iio:device0
您应该会看到一系列文件,如 in_voltage0_raw...in_voltage6_raw
,它们对应于您可用的 7 个 A/D 转换通道。最大 A/D 参考电压为 1.8V,对应的最大 12 位数字值为 4096。因此,进行测量只需将参考电压应用于 VDD_ADC
(P8_32
),接地到 GND_ADC
(P9_34
),并将您正在测量的任何东西的输出连接到 AIN0
...AIN6
中的一个。
电位器演示
在这个分压器演示中,一个 1k 欧姆的固定电阻与一个阻值可在 100 到 10k 欧姆之间变化的电位器串联。
在元件连接处,白色线是采样点,随着电位器旋钮的转动,其电压会发生变化。当电位器达到最大电阻时,采样电压约为 R2/(R1+R2) * VADC = 10,000/(10,000+1,000) * 1.8 ≈ 1.64 V。当电位器设置为低电阻时,采样电压将接近 0.1 V。我实际测量到的固定电阻为 0.98k 欧姆,电位器在最大时为 10.4 k 欧姆,在最小时为 98 欧姆。
您可以在 bash 命令行中非常简单地读回当前的 ADC 值
/sys/bus/iio/devices/iio:device0$ cat in_voltage0_raw
4093
因此,只需将此数字值除以 4096,然后乘以 1.8 即可获得电压测量值。以下是 C++ 中的基本概念,它将每 500 毫秒连续采样 ADC0。在我进行的测试中,精度在我的万用表测量值误差 5-10mV 以内。
#include <iostream>
#include <fstream>
#include <string>
#include <chrono>
#include <thread>
#include <iomanip>
using namespace std;
int main()
{
const string adc0_dev_ = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
const unsigned int MAX_ADC_BB_OUT = 4096;
const double MAX_VADC_BB = 1.8;
while (true)
{
//open the ADC0 pin as a file object
std::ifstream ain0(adc0_dev_);
if (ain0)
{
//read the raw value
string line;
if(getline(ain0, line))
{
auto raw = std::stoul( line );
double measured_voltage =
((double)raw / MAX_ADC_BB_OUT ) * MAX_VADC_BB; //12-bit ADC
cout << setprecision(3) << measured_voltage << " V" << endl;
}
}
this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 0;
}
GPIO
可以通过类似的方式在命令行中公开和操作额外的 GPIO 选项。GPIO 在文件系统位置按 32 个为一组排列
/sys/class/gpio
GPIO 输出 LED 演示
GPIO 可设置为输出(如果您想写入它来将引脚电压从 0 翻转到 3V),或输入(如果您想检测施加到引脚的电压变化)。我们可以通过将 LED 和至少一个 220 欧姆的电阻连接到其中一个 GPIO 引脚,以及一个 DGND 引脚来轻松演示输出。在本例中,我将一个 LED 的短引脚(阴极)连接到 DGND,阳极侧连接到 GPIO_30
(P9 引脚 11)。
要在文件系统中公开 GPIO_30
控制
echo 30 > /sys/class/gpio/export
会出现一个新的文件夹,“gpio30”。里面有一些文件,我们可以读/写它们来控制功能。向 value 文件写入 1 或 0 会触发引脚上的电压,从而使 LED 闪烁。
echo out > /sys/class/gpio/gpio30/direction
echo 1 > /sys/class/gpio/gpio30/value
echo 0 > /sys/class/gpio/gpio30/value
GPIO 输入按钮演示
输入 GPIO 的一个例子是检测按钮按下。将 3V 电压(来自 VDD_3V3,P9 引脚 3 或 4)施加到按钮的一个引脚,并将一个 1k 欧姆的电阻连接到 DGND(引脚 1 或 2)的另一个引脚。从电阻和按钮之间引出一根线到 GPIO_30
。这种“下拉”电阻设置将在按下按钮时将电压发送到输入引脚。
将 GPIO_30
设置为输入
echo in > /sys/class/gpio/gpio30/direction
当您按下按钮时,名为“value”的文件应该值为 1,不按下时值为 0。
如果您想高效地利用系统资源,我不建议连续循环代码读取 GPIO 的值来查找值 1。嵌入式开发人员经常使用“中断”来检测此类事件。Linux 可以使用 poll()
函数等待文件系统事件。poll
函数可以阻塞指定的时���,但在此过程中不会显着占用系统资源。以下是按钮演示的 C++ 示例。
#include <iostream>
#include <stdio.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main()
{
// the button is connected to GPIO_30
int fd = open("/sys/class/gpio/gpio30/value", O_RDONLY);
struct pollfd fdset;
memset((void*) &fdset, 0, sizeof(fdset));
fdset.fd = fd;
fdset.events = POLLPRI;
fdset.revents = 0;
char c[1];
while (true)
{
//1 ms-polling
int ready = poll(&fdset, 1, 1);
if (ready < 0)
{
cout << "poll() failed" << endl;
}
// polling will trigger on the rising edge of the GPIO
// (transition from 0 -> 1)
if ((fdset.revents & POLLPRI))
{
c[0] = 0;
lseek( fdset.fd, 0, 0 );
int size = read(fdset.fd, c, 1);
int value = -1;
if (size != -1)
{
value = atoi(c);
}
if (value == 1)
{
cout << "Button is pressed" << endl;
}
}
}
}
还可以构建设备树,以便在启动时加载特定的 GPIO 引脚配置,这样您就不必通过 bash 命令来公开它们,它们会被预先加载。幸运的是,人们创建了设备树生成工具,特别是为 BeagleBone 设计的。您只需要确保将正确的引脚信息输入到 生成器中。然后,您可以编译设备树并将其添加到 /boot/uEnv.txt 文件中的“cape_enable=bone_capemgr.enable_partno=
”行上的设备树列表中。我们将在下一节中更详细地介绍这一点。
使用 I2C 的额外扩展选项 - 添加实时时钟
BeagleBone 没有内置电池支持的 RTC,并且许多关于添加 RTC 的现有教程已过时,或者在启动过程后期加载时钟,并需要修改 rc.local 来保持同步。您最好的选择是选择一个具有内置 Linux 驱动程序支持的 RTC,或者有一个易于编译的模块,而无需重新构建整个内核。
Linux 使用“设备树”来描述和初始化不可发现的硬件。连接到 I2C 或 SPI 总线的设备通常属于此类。为了正确使用附加的 I2C RTC 时钟与 BeagleBone,您需要在 uEnv.txt 中为其加载设备树。如果您不使用标准的 BeagleBone RTC Cape,您可能需要自己生成一个。
我使用 Epson RX-8900 取得了成功,它体积小巧、价格便宜、精度适中,而且不会产生太多电磁辐射。这款 RTC 有点特殊,因为它的 I2C 接口与标准操作系统镜像中已有的另一个设备驱动程序“rtc-rv8803
”是寄存器兼容的。所以我们可以通过将 rtc-rv8803
添加到 /etc/modules 的设备列表中,告诉 Linux 在启动时加载该驱动程序模块。
现在我们需要创建一个设备树,以便操作系统知道 I2C 和引脚配置应该是怎样的,才能与 RTC 通信。我们可以将 BeagleBone RTC Cape 的 BB-RTC-01-00A0.dts 文件作为起点,并将所有芯片制造商的引用更改为您正在使用的任何芯片,只需确保定义您使用的 I2C 总线的正确引脚即可。如果您使用 i2c1,那么您只需要更改驱动程序名称。更改该行
compatible = "maxim,ds1338";
to
compatible = "epson,rv8803";
如果您使用 i2c2,请记住相应的引脚分别是 P9_19
和 P9_20
。我附上了我在 i2c2 上使用的叠加层的一个示例。最后,将 part-number 行“part-number = "BB-RTC-01";
”更改为新名称,然后保存。然后您可以将其编译为设备树。如果您通过 git 下载 BeagleBone 叠加层,您可以将其添加到 src/arm 目录中,并遵循该存储库中的简单说明来重建和重新安装所有最新的叠加层。
基本上
git clone https://github.com/beagleboard/bb.org-overlays
cd ./bb.org-overlays
./dtc-overlay.sh
./install.sh
只需将新的 DTC 名称添加到 uEnv.txt 中的“cape_enable=bone_capemgr.enable_partno
”行,RTC 驱动程序将在启动时加载。
最后一步是确保 Linux 将 /dev/rtc1 作为其主要的 RTC 设备。如果您查看 /dev,您会发现 /dev/rtc 文件是 /dev/rtc0 的符号链接,这是没有电池备份的内置 BeagleBone RTC。Linux 组件“udev
”负责在启动时进行此操作。要将其切换为使用 rtc1
,请以 root 身份发出以下命令创建一个 udev 规则
echo "SUBSYSTEM=="rtc", KERNEL=="rtc1", SYMLINK+="rtc",
OPTIONS+="link_priority=10", TAG+="systemd"" > /etc/udev/rules.d/55-i2c-rtc.rules
Systemd
内置了与 NTP 服务器同步系统时钟的功能。为确保其已启用,请发出以下命令
timedatectl set-ntp true
一旦它同步了系统时间,您就可以使用以下命令设置 RTC 并读回其时间
hwclock -w -f /dev/rtc1
hwclock -r -f /dev/rtc1
从现在开始,操作系统应该会自动将 /dev/rtc 与系统时间同步。
直接控制 I2C 设备
我只想简要说明一下,即使您没有可用的驱动程序,您仍然可以在 Linux 中非常轻松地在代码中直接处理 I2C 设备。事实上,根据您的应用程序需求,有时自己编写代码会更好。同样,这与在文件系统上读写类似。
第一步,打开一个对应于您要通信的 I2C 总线的文件的地址
g_i2cFile = open("/dev/i2c-2", O_RDWR);
第二步,使用“ioctl
”写入您要通信的设备的地址。总线上的每个设备都应该有一个唯一的地址。
auto res = ioctl(g_i2cFile, I2C_SLAVE, DEVICE_ADDR);
第三步,将数据写入目标寄存器中的设备
const int BUFSIZE = 2;
char buffer[BUFSIZE];
buffer[0] = REG_ADDR_TARGET;
buffer[1] = data_byte;
if(write(g_i2cFile, buffer, BUFSIZE) != BUFSIZE)
{
//error
}
或者,要从目标寄存器读取数据,您只需写入一个包含目标地址的字节,然后从该目标读取一个字节。
char read_byte;
if(write(g_i2cFile, buffer, 1) == 1)
{
if(read(g_i2cFile, &read_byte, 1) !=1)
{
// successfully read a byte into read_byte;
}
}
我包含了一个实现此功能的简单 C++ 类。
在 BeagleBone 上编译软件的选项
通过 QEMU 进行交叉编译
在另一台机器上为 BeagleBone 编译软件的最简单方法之一是通过 QEMU。在另一台运行 Debian 的机器上
apt-get install qemu-user-static
现在您需要获取您正在使用的 BeagleBone 文件系统的副本。使用 rsync
或 tar 打包镜像。
mkdir -p /opt/beagle-root
cd /opt/beagle-root
rsync -Wa --progress --exclude=/proc --exclude=/sys
--exclude=/home/debian --delete bms@192.168.7.2:/* .
将提供仿真的静态 ARM 二进制文件复制到此位置
cp $(which qemu-arm-static) /opt/beagle-root/usr/bin
此时,您可以输入以下命令
cd /opt
sudo chroot beagle-root /usr/bin/qemu-arm-static /bin/bash
然后您将通过 QEMU 在新系统上运行 BeagleBone ARM 二进制文件。您现在可以像往常一样构建任何软件。您创建的任何库或二进制文件都可以复制回 BeagleBone 并正常运行。
交换空间技巧
如果您有耐心并想直接在 BeagleBone 上编译较大的软件包,您可能会遇到内存限制。在这种情况下,请尝试创建一些临时交换空间。这是我用来代替直接调用“make
”的 bash 脚本
#!/bin/sh -e
#
# compile software directly on BB if swap space is needed
#
free
#64 MB swap file
sudo dd if=/dev/zero of=/var/swap.img bs=1024 count=65535
sudo mkswap /var/swap.img
sudo swapon /var/swap.img
free
make
sudo swapoff /var/swap.img
sudo rm /var/swap.img
exit 0
结论
这里提供的绝大多数信息是我在数月的研究文档和博客,以及大量试错中发现的。希望这能为您提供一些初步的帮助。为了更深入地探讨其中一些主题,我引用了一些我从中学习到的资料来源。
图像
- 官方发布:https://beagleboard.org/latest-images
- 最新和控制台镜像:http://elinux.org/Beagleboard:BeagleBoneBlack_Debian
内核、驱动程序和设备树
- 最新的启动脚本和刷写工具:https://github.com/RobertCNelson/boot-scripts.git
- 支持的帽子叠加层:https://github.com/beagleboard/bb.org-overlays.git
- 内核:https://github.com/beagleboard/linux.git
- BeagleBone I/O 和引脚布局:http://beagleboard.org/support/bone101
- 设备叠加层:https://github.com/beagleboard/bb.org-overlays.git
- USB 以太网驱动程序:http://beagleboard.org/getting-started
RTC 示例
- 官方 BeagleBone RTC Cape:http://elinux.org/CircuitCo:RTC_Cape
- Epson RX-8900 文档:https://support.epson.biz/td/api/doc_check.php?dl=brief_RX8900CE&lang=en
- 电池支持的 BeagleBone RTC 指南:http://blog.fraggod.net/2015/11/25/replacing-built-in-rtc-with-i2c-battery-backed-one-on-beaglebone-black-from-boot.html
详细演示
- A/D 转换器:https://www.linux.com/learn/how-get-analog-input-beaglebone-black
- GPIO 输出 LED:http://www.dummies.com/how-to/content/setting-beaglebone-gpios-as-outputs.html
- GPIO 输入按钮:https://learn.adafruit.com/connecting-a-push-button-to-beaglebone-black/overview
有用的 SSH 和 SFTP 工具
- MobaXterm:http://mobaxterm.mobatek.net/
- Putty:http://www.putty.org/
- PAC Manager:https://sourceforge.net/projects/pacmanager/
历史
- 2016 年 8 月 7 日 - 首次发布
- 2017 年 3 月 8 日 - 更新