写篇文章记录一下学校的学科入门实验——基于 BeagleBone Black 的嵌入式 Linux 系统开发。第一次玩这种东西觉得还有点意思……
以下内容为图省事大量地摘编自我的实验报告。
使用脚本程序控制 BeagleBone Black 自带的 LED 灯
实现目标及所需硬件资源
BeagleBone Black 自带 4 个 LED 灯(分别是 USR0, USR1, USR2, USR3),本次实验的实现目标是通过 Bash 脚本语言控制这四个 LED 灯,按照人为定义的规则实现亮灭。
软件流程
BeagleBone Black 的 LED 灯设备驱动文件位于 /sys/class/leds 下,在该目录下执行 ls 命令可以看到四个灯的控制文件分别所对应的目录为 beaglebone:green:usr0-3,进入其中一个目录,可以看到一些文件,这些文件各自有各自的用途:
trigger可以用来指定 LED 的触发状态,可以通过 cat 命令查看当前 LED 由哪个设备或事件触发;前文已知第三个 LED 灯指示的是 CPU 的使用状态,如在 beaglebone:green:usr2 目录下执行 cat trigger 命令,返回的结果说明 LED3 的出发状态由 cpu0 所控制。对 trigger 写入内容则可以更改 LED的触发规则:例如执行 echo none > trigger 对 trigger 写入 “none”,则会断开对应 LED 与其它设备的连接;执行 echo timer > trigger对 trigger 写入 “timer”,则将当前 LED 设置为延时触发模式。
brightness 文件用于控制当前 LED 灯的亮度。若执行 echo 0 > brightness 将0写入亮度文件中,那么对应 LED 灯就会熄灭;相反执行 echo 1 > brightness 将 1 写入亮度文件中,那么对应 LED 灯就会亮起。
如果执行 echo timer > trigger向 trigger 中写入 “timer”, 那么当前 LED 的触发模式会变为延时触发,此时当前目录下会多出 delay_off 和 delay_on 两个控制文件,分别控制该 LED 熄灭和亮起的延迟时间,单位为 ms.
如果执行 echo gpio > trigger 向 trigger 中写入 “gpio”,那么当前 LED 的触发方式会由 GPIO 的电平状态决定,此时当前目录下会多出 desired_brightness, gpio 两个文件,可能代表期望亮度和监听的 GPIO 编号。
因为本次实验我们只控制自带的 LED 灯而不涉及 GPIO 的操作,因此程序的流程如下:
首先对四个 LED 灯对应的 trigger 写入 none, 切断四个 LED 灯与其它设备的关联;
写入一个 [0, 255] 的整数到 brightness 文件中,控制 LED 灯的亮度;使用 Bash 函数控制 LED 灯的亮灭规则;
再对四个 LED 灯对应的 trigger 写入 timer,更改 LED 灯为延时触发模式;
分别设置四个 LED 灯的 brightness 和 delay_on, delay_off 控制 LED 灯的亮灭规则。
关键代码分析及现象结果
以下 Bash 程序实现了让 BeagleBone Black 的四个指示灯一个接一个亮起、熄灭的功能:
#!/bin/bash
# reset leds' trigger
echo "Setting LEDs trigger to none..."
for i in {0..3}
do
echo none > /sys/class/leds/beaglebone\:green\:usr$i/trigger
done
# turn on leds, controlling by sleep
echo "Turning on/off LEDs one after another..."
while :
do
# turn on leds
for i in {0..3}
do
echo 1 > /sys/class/leds/beaglebone\:green\:usr$i/brightness
sleep 0.5s # pause for 500 milliseconds
done
sleep 0.5s
# turn off leds
for i in {3..0}
do
echo 0 > /sys/class/leds/beaglebone\:green\:usr$i/brightness
sleep 0.5s
done
sleep 0.5s
done
代码分析:首先将 LED 的触发器设置为 “none” 断开 LED 与其它硬件设备间的关联,然后不断执行两段循环:第一段循环会将编号为 0-3 的 LED 分别亮起;每亮起一颗 LED 后,sleep 命令会使得程序暂停 0.5s,然后再亮起下一颗 LED. 第二段循环则每隔 0.5s 按照 3, 2, 1, 0 的顺序熄灭一颗 LED. 一轮操作完成后,四颗 LED 回到全部熄灭的初始状态,然后重复上述步骤。
除了使用系统自带的 sleep 命令以外,还可以设置 delay_on 和 delay_off 来控制延时亮灭。以下 Bash 程序实现了让四颗 LED 灯按不同的间隔时间分别闪烁的功能:
#!/bin/bash
#reset leds' trigger
for i in {0..3}
do
echo none > /sys/class/leds/beaglebone\:green\:usr$i/trigger
done
# set triggers to 'timer'
for i in {0..3}
do
echo timer > /sys/class/leds/beaglebone\:green\:usr$i/trigger
done
# set delay time for triggers
for i in {0..3}
do
# calculate current led's delay time
base=200
multiply=`expr $multiply + 1`
delaytime=`expr $multiply \* $base`
echo $delaytime > /sys/class/leds/beaglebone\:green\:usr$i/delay_on
echo $delaytime > /sys/class/leds/beaglebone\:green\:usr$i/delay_off
done
代码分析:首先将 LED 的触发器设置为 “none” 断开 LED 与其它硬件设备间的关联,然后再将其设置为 “time”设置 LED 的触发规则为延时模式,此时在对应 LED 的目录下会多出 delay_on, delay_off 两个文件。接下来执行一个循环,循环体内首先根据当前操作的 LED 的编号 (0, 1, 2, 3) 计算对应的间隔时间(上述代码中,对于编号为 i 的 LED,亮起和熄灭的间隔时间定义为 \(\left(i+1\right)\times200 ms\),再将这个间隔时间写入到 delay_on 和 delay_off 两个文件中。执行该程序的现象:四颗 LED 分别以 200, 400, 600, 800ms 的亮灭间隔闪烁。
使用C语言程序控制外接 LED 设备
实现目标和所需硬件资源
实现目标:外接一个三色 LED 设备,使用 C 语言程序控制三颗 LED 灯闪烁;并实现点击外接 LED 上的按钮,控制 LED 灯的闪烁。
所需硬件资源:一块外接 LED, 杜邦线若干。
BeagleBone Black 的 GPIO 和引脚接口
GPIO (General Purpose Input Output, 通用输入/输出接口),又称总线拓展器,是一个能用于输入/输出二进制控制信号的接口,可用于 BeagleBone Black 与外接设备之间的数据交互。每个 GPIO 接口可以被人为设定为 Input 和 Output 两种模式,分别用于接收外接设备输入的信号,或向外接设备输出信号。GPIO 的值可以为 0 或 1,分别代表低电平和高电平。
与自带 LED 相同,对 GPIO 的读写和操作,同样是通过操作其对应的设备文件来实现。GPIO 的设备文件位于 /sys/class/gpio 下。执行命令 ls /sys/class/gpio,可以看到该目录具有的文件信息.BeagleBone Black 具有四组 GPIO (GPIO0-3), 每组下分别有若干个 GPIO 接口。对于每个接口,都具有一个 Name 属性(GPIOx_y, x 为该接口的所属分组,y 为一个整数)、Head_pin 属性(Px_y, Px 表示该接口位于 P8/P9 排引脚上,y表示该接口在引脚上的编号)、GPIO No.(GPIO 接口编号)。其中,Head_pin 用于确定该接口在 BBB 上的位置,确保外接设备连接到了正确的接口;GPIO No. 用于在 Linux 下通过此编号操作对应的 GPIO 接口,计算 GPIOx_y 编号的规则为 \((x\ \times32\ +\ y)\)。
以上属性都可以在 BeagleBone Black 的引脚速查表上找到。需要注意,并不是所有的 GPIO 口都可用,有些 GPIO 口可能已经被占用。
BeagleBone Black 操作 GPIO 口的方法:当我们需要操作 GPIO 口时,首先需要根据编号导出 (export) 所需 GPIO 口的设备文件。导出 GPIO 接口设备文件的方法是执行如下命令:echo gpio_no > /sys/class/gpio/export,其中 gpio_no 是所需 GPIO 的编号 (GPIO No.)。执行以上命令后,在 /sys/class/gpio 目录下会多出一个名为 gpio + 编号的目录,该目录内即为控制所需 GPIO 口的设备文件。 例如,若需要使用位于 P8 引脚排上的 11 号接口对应的 GPIO1_13,其对应的 GPIO No. 为45,则执行:echo 45 > /sys/class/gpio/export . 此时, /sys/class/gpio 目录下会产生一个名为 gpio45 的目录。
执行 cd /sys/class/gpio/gpio45 && ls,查看该目录下的文件;本次实验中我们只涉及其中两个设备文件:direction 和 value。direction 的值可以为 in/out,用于设置当前 GPIO 口的数据流动方向; value 的值可以为 0/1, 用于设置当前 GPIO 口是高电平还是低电平。
如果不再使用一个 GPIO 接口,可以使用 echo gpio_no > /sys/class/gpio/unexport 来释放这个 GPIO 接口。
硬件连线
首先分析外接设备三颗 RGB LED灯和按键对应的电路图:
根据LED的电路图可知,RGB LED 灯一端与 VDD(电源电压)相连,为高电平;另一端是三种颜色对应的引脚 P26, P27, P28. 只需将这三个引脚连接到 BeagleBone Black 的三个 GPIO 口上,设置对应的 GPIO 为低/高电平即可点亮/熄灭某个 LED.
本次实验以选择按键 K2 为例,将 P22 引脚连接到 BeagleBone Black 的一个 GPIO 口上,则当按下 K2 后,对应的 GPIO 口会变为高电平,此时 GPIO 的 value 值为 1;松开后会变为低电平,GPIO 的 value 值为 0. 本次实验选择 BeagleBone 的 GPIO 编号为 26(位于P8_14), 48(位于P9_15), 49(位于P9_17), 60(位于P9_12),分别连接三个 LED 引脚 P26, P27, P28 和 K2 按键引脚 P22;此外还需要连接 LED 的VDD到 BeagleBone Black 的 DC3.3V (位于P9_03/04) 、连接LED的GND到BeagleBone 的 GND(位于P9_01)。
实现流程
- 导出(export) 本实验需要使用的四个 GPIO;
- 编写 C 语言程序,从控制台读入参数,设置 LED 工作模式(亮起、熄灭、闪烁);
- 通过 C 语言的文件操作函数读写对应的设备文件,实现对 LED 和按键的控制。
关键代码分析及现象结果 (C++ 实现)
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define GPIO_PATH "/sys/class/gpio/"
int gpio_ports[] = {60, 48, 49, 26};
int gpio_count = 4;
/**
* 导出对应编号的 GPIO 设备文件
* @param number 要导出的 GPIO 编号
*/
void export_gpio(int number) {
// do not use `w+` or you will meet segmatation fault for permission denied
FILE *f = fopen(GPIO_PATH"export", "w");
printf("%s", GPIO_PATH"export");
fprintf(f, "%d", number);
printf("gpio port %d exported.\n", number);
fclose(f);
}
/**
* 熄灭 RGB, 原理是设置 GPIO 为高电平
* @param index RGB所对应的GPIO编号
*/
void stop_light(int index) {
char path[200];
sprintf(path, "%sgpio%d/value", GPIO_PATH, index);
FILE *f = fopen(path, "w");
fprintf(f, "1");
fclose (f);
printf("stopping light at gpio %d...\n", index);
}
/**
* 亮起 RGB, 原理是设置 GPIO 为低电平
* @param index RGB所对应的GPIO编号
* @param need_out 是否需要先设置 direction 为 out
*/
void start_light(int index, bool need_out) {
char path[200];
if (need_out) {
sprintf(path, "%sgpio%d/direction", GPIO_PATH, index);
// set direction to out
FILE *f = fopen(path, "w");
fprintf(f, "out");
fclose(f);
printf("setting direction to 'out' for gpio %d...\n", index);
}
memset(path, 0, sizeof path);
FILE *f;
sprintf(path, "%sgpio%d/value", GPIO_PATH, index);
f = fopen(path, "w");
fprintf(f, "0");
fclose(f);
printf("led connected at gpio %d should be lighted now.\n", index);
}
/**
* 闪烁 RGB LED
* @param limit 闪烁次数限制,-1 表示无限
*/
void flash_lights(int limit) {
printf("show time!\n");
long long cnt = 0LL;
while (1) {
// before flash, stop all lights
for (int i = 0; i < gpio_count - 1; i++)
stop_light(gpio_ports[i]);
usleep(500000);
for (int i = 0; i < gpio_count - 1; i++) {
start_light(gpio_ports[i], cnt == 0);
usleep(200000);
}
for (int i = 0; i < gpio_count - 1; i++) {
stop_light(gpio_ports[i]);
usleep(200000);
}
cnt++;
if (cnt > limit && limit != -1)
break;
}
}
int main(int argc, char* argv[]) {
// 判断参数个数
if (argc != 2) {
printf("Usage: led [on/off/flash/keymode]\n");
exit(0);
}
// 导出 GPIO 设备文件
for (int i = 0; i < gpio_count; i++)
export_gpio(gpio_ports[i]);
// 根据 argv[1] 参数内容不同,执行不同的操作
if (strcmp("on", argv[1]) == 0) {
for (int i = 0; i < gpio_count - 1; i++)
start_light(gpio_ports[i], true); // 亮灯
} else if (strcmp("off", argv[1]) == 0) {
for (int i = 0; i < gpio_count - 1; i++)
stop_light(gpio_ports[i]); // 灭灯
} else if (strcmp("flash", argv[1]) == 0) {
flash_lights(-1); // 闪烁
} else if (strcmp("keymode", argv[1]) == 0) {
// 按键模式,点按按键闪烁,松开按键停止
for (int i = 0; i < gpio_count - 1; i++)
stop_light(gpio_ports[i]);
// 设置 direction 为 in
char path[200];
sprintf(path, "%sgpio%d/direction", GPIO_PATH, gpio_ports[3]);
FILE *f = fopen(path, "w");
fprintf(f, "in");
fclose(f);
// set path to stream
memset(path, 0, sizeof path);
sprintf(path, "%sgpio%d/value", GPIO_PATH, gpio_ports[3]);
char a[10];
bool isFlashing = false, changed = false;
while (1) {
f = fopen(path, "r+");
fscanf(f, "%s", a);
// 如果接收到按键信号,且当前仍在闪烁,则停止闪烁
if (a[0] == '1' && isFlashing) {
isFlashing = false;
printf("stopped flash:) farewell \n");
for (int i = 0; i < gpio_count - 1; i++)
stop_light(gpio_ports[i]);
sleep(2);
}
// 如果接收到按键型号,且当前没有闪烁,则开始闪烁
else if (a[0] == '1' && !isFlashing) {
isFlashing = true;
flash_lights(1);
}
// 如果没有接收到按键信号,则继续闪烁
else if (isFlashing) {
flash_lights(1);
}
usleep(100000);
}
}
return 0;
}
在终端执行编译命令:g++ -o led led.cpp,编译上述程序,然后执行:
./led on # 亮起LED
./led off # 熄灭LED
./led flash # 闪烁LED
./led keymode # 按键模式,按下闪烁,再按熄灭
物联网概念及其在 BeagleBone Black 下的实现
BeagleBone Black 实现物联网的几种架构
(1)BeagleBone Black 作为 Web 服务器:用户可以通过浏览器发送 HTTP 请求到 BeagleBone Black 的 Web 服务器上,获取 BBB 的设备信息或与其相连的传感器的信息;
(2) BeagleBone Black 作为 Web 客户端:可以编写程序使 BeagleBone Black 与其它 Web 服务器进行通信;
(3)TCP 通信:一台作为 TCP 服务端,一台作为 TCP 客户端,两者间可以通过 Socket 发送请求、接收请求;
(4)通过PaaS(Platform as a Service, 平台即服务):BeagleBone Black 上的传感器信息可以通过 HTTP/RESTful API 与 PaaS 平台通信,用户从浏览器端访问该 PaaS 服务器即可获取来自 BBB 的信息。
动态 CGI 页面实现
CGI (通用网关接口,Common Gateway Interface) 是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。几乎所有服务器都支持CGI,可用任何语言编写CGI,包括流行的C、C ++ 等。
在 BeagleBone Black 中,利用 CGI 可以实现 Web 页面与物理环境的交互:如读取传感器,驱动 LED 等。CGI 脚本的位置默认位于 /usr/lib/cgi-bin 中,可以通过 Apache 2 服务器访问,地址是 http://192.168.7.2:8000/cgi-bin/ .
如,在该目录下创建一个 CGI 脚本页面,使我们访问该页面时,显示 BeagleBone Black 的运行时间、用户、负载等信息。在 /usr/lib/cgi-bin 创建文件 info.cgi, 插入以下代码:
#!/bin/bash
echo "Content-Type: text/html"
echo ""
echo "<!doctype html>"
echo "<html>"
echo "<head>"
echo "<meta charset=\"utf-8\">"
echo "<title>BeagleBone Black System Info</title>"
echo "</head>"
echo "<body>"
echo "<h1>BeagleBone Black system info</h1>"
echo "Host"
hostname
echo " has been up "
uptime
echo "</body>"
echo "</html>"
执行命令:chmod +x /usr/lib/cgi-bin/info.cgi, 然后浏览器访问页面 http://192.168.7.2:8080/cgi-bin/info.cgi 即可。
CGI 脚本还可以调用 C/C++ 程序,只需在脚本中调用目标程序的路径即可,目标程序的标准输出则会被转发到浏览器中。
BeagleBone Black 与 Windows 宿主机共享网络
让 BeagleBone Black 连接网络。可以直接将网络电缆连接到 BeagleBone Black 的 RJ45 接口上,或者使用 USB 与宿主机共享网络。本实验的宿主机为 Windows 10, 与 BeagleBone Black 共享网络需要进行如下操作:
Windows 下:安装 BBB 的驱动,设置可访问互联网的网络适配器与 BeagleBone Black 共享网络,如宿主机与互联网建立连接的网卡为 “WLAN”, BeagleBone Black 在系统中的适配器为“以太网3 (Linux USB Ethernet/RNDIS Gadget)”, 则需要:右击 WLAN => 属性 => 共享 => 允许其它用户通过此计算机的 Internet 连接来连接 => 勾选“以太网3”。再设置“以太网3”的 IP 地址为 192.168.7.1, 子网掩码为 255.255.255.0.
Linux 下:执行命令 route add default gw 192.168.7.1 添加路由表;然后编辑/etc/resolv.conf,在文件头部加入:
nameserver 127.0.0.1
nameserver 8.8.8.8
nameserver 114.114.114.114
配置 BBB 的 DNS 解析服务器。
BeagleBone 实现实时温度检测系统
目的及原理
使用 BeagleBone Black 连接德州仪器的 TMP75 传感器,通过 i2c 协议通信,数据交由运行在 BeagleBone Black 上的后端 Web Server 处理,在浏览器端显示温度数据、绘制温度曲线并进行交互,效果如图:
原理:TMP75 采用 i2c 协议来发送温度数据。在 BeagleBone Black 的 Debian 系统下,我们可以运行 i2cdetecct -y -r 1
来获得 TMP75 的总线地址;当我们运行 i2cget -y -f 1 0xyy
(0xyy 是从 i2cdetect
获取的总线地址) 时,会返回一个代表温度的十六进制数。我们可以整合这些操作到一个前后端方案中。
连线
TMP75 V33 to BBB DC3.3V (P9_03)
TMP75 GND to BBB GND (P9_01)
TMP75 SCL to BBB SCL(P9_19, do not connect to 17 which is not occupied:) )
TMP75 SDA to BBB SDA(P9_20, do not connect to 18 which is not occupied:) )
TMP75 I/O to BBB GPIO(whichever has not been occupied is OK)
实现代码
实现的方案代码已托管于 GitHub: https://github.com/kirainmoe/beaglebone-temperature-monitor