65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2016年8月7日

CPOL

11分钟阅读

viewsIcon

34990

downloadIcon

195

使用 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_ADCP8_32),接地到 GND_ADCP9_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_19P9_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

结论

这里提供的绝大多数信息是我在数月的研究文档和博客,以及大量试错中发现的。希望这能为您提供一些初步的帮助。为了更深入地探讨其中一些主题,我引用了一些我从中学习到的资料来源。

图像

内核、驱动程序和设备树

RTC 示例

详细演示

有用的 SSH 和 SFTP 工具

历史

  • 2016 年 8 月 7 日 - 首次发布
  • 2017 年 3 月 8 日 - 更新
© . All rights reserved.