1. 项目描述
项目介绍:
在此项目中,我使用了 BeagleBone Black (BBB) 进行了一系列试验,包括 GPIO 控制、页面 IDE 体验和基于 Web 界面的点灯(开关和呼吸灯)功能实现。
BeagleBone Black 提供了完善的开发环境,可以通过 USB 模拟网卡和SSL连接并访问。页面IDE功能强大,我现在版本是Cloud9,如果刷了最近固件就是 Vscode 环境,更友好了。
设计思路:
首先,来点亮板上的LED,再通过控制外部GPIO 点呼吸灯,最后写网页控制点灯。
软件端,后台:使用 httplib 开启http服务,提供4个接口(点亮、熄灭、呼吸灯开启、呼吸灯关闭);前台:使用layui框架,可以增加一些好看的UI。
硬件连接和软件架构:
通过IO口P9.12(对应GPIO_60)来控制LED。
2. 主要功能展示
2.1. 点亮板载led
#include <stdio.h>
#include <unistd.h>
int main() {
FILE * trigger = fopen("/sys/class/leds/beaglebone:green:usr3/trigger", "w");
FILE * brightness = fopen("/sys/class/leds/beaglebone:green:usr3/brightness", "w");
int on = 0;
fprintf(trigger, "none\n");
while(1) {
fprintf(brightness, "%d\n", on);
fflush(brightness);
if(!on)
on = 1;
else
on = 0;
usleep(100000);
}
}
通过系统内的LED驱动来控制板载LED,使其闪烁。
2.2. gpio 介绍
使用GPIO的时候首先需要来看一下引脚
BBB 板的每一侧都有两个 23 引脚列,总共有 92 个引脚可供用户使用。右侧标头指定为 P8,左侧标头指定为 P9。
GPIO 编号方案:bbb 的 GPIO 引脚分为 3 组,每组 32 个: GPIO0 、 GPIO1和GPIO2。
可以使用约定GPIOX_Y
来引用单个引脚,其中X
是其 GPIO 寄存器, Y
是其在该寄存器中的编号。然而,在软件中对特定引脚的所有引用都使用其绝对引脚号!一个GPIO的 绝对引脚编号按以下方式计算: Z = 32*X + Y
,其中X
又是 GPIO 寄存器, Y
是该寄存器内的位置。
所以一个IO口有三种编号:PX_Y, GPIOX_Y, GPIOX。
以我用的 P9_12 为例:
P9_12 内部是 GPIO1_28, 也是 GPIO60 (1*32+28) , 60 就是绝对引脚号。
2.3. 控制外部GPIO
#include <iostream>
#include <fstream>
#include <unistd.h>
#define GPIO_PIN "60" // p9-12
// 导出 GPIO 引脚
void exportGPIO(const std::string &pin) {
// 检测目录是否存在,存在就跳过,否则会报错并取消导入
std::ifstream f("/sys/class/gpio/gpio" + pin);
if(f.good()){
std::cout<<"目录已存在,跳过导入";
return;
}
std::cout<<"/sys/class/gpio/export"<<pin<<std::endl;
std::ofstream exportFile("/sys/class/gpio/export");
exportFile << pin;
exportFile.close();
}
// 设置 GPIO 引脚方向
void setGPIODirection(const std::string &pin, const std::string &direction) {
std::ofstream directionFile("/sys/class/gpio/gpio" + pin + "/direction");
directionFile << direction;
directionFile.close();
}
// 设置 GPIO 引脚值
void setGPIOValue(const std::string &pin, int value) {
std::cout<<pin<<" "<<value<<std::endl;
std::ofstream valueFile("/sys/class/gpio/gpio" + pin + "/value");
valueFile << value;
valueFile.close();
}
int main()
{
int a;
// 初始化 GPIO:导出并设置输出模式
exportGPIO(GPIO_PIN);
usleep(100);
setGPIODirection(GPIO_PIN, "out");
usleep(100);
while(1){
setGPIOValue(GPIO_PIN, 0);
usleep(1000000);
setGPIOValue(GPIO_PIN, 1);
usleep(1000000);
}
return 0;
}
控制外部gpio,主要是这三个步骤:
- 导出gpio引脚:将引脚从内核空间导出到用户空间
- 设置方向:配置为out为输出模式
- 设置引脚值:设置为高低电平
也可以在系统内直接使用,以GPIO60为例
echo 60 > /sys/class/gpio/export # 导出
echo out > /sys/class/gpio/gpio60/direction # 设置方向
echo 1 > /sys/class/gpio/gpio49/value # 设置值
2.4. 软件 PWM 实现 呼吸灯
#include <iostream>
#include <fstream>
#include <cmath>
#include <unistd.h>
#define GPIO_PIN "60" // p9-12
// 导出 GPIO 引脚
void exportGPIO(const std::string &pin) {
// 检测目录是否存在,存在就跳过,否则会报错并取消导入
std::ifstream f("/sys/class/gpio/gpio" + pin);
if(f.good()){
std::cout<<"目录已存在,跳过导入";
return;
}
std::cout<<"/sys/class/gpio/export"<<pin<<std::endl;
std::ofstream exportFile("/sys/class/gpio/export");
exportFile << pin;
exportFile.close();
}
// 设置 GPIO 引脚方向
void setGPIODirection(const std::string &pin, const std::string &direction) {
std::ofstream directionFile("/sys/class/gpio/gpio" + pin + "/direction");
directionFile << direction;
directionFile.close();
}
// 设置 GPIO 引脚值
void setGPIOValue(const std::string &pin, int value) {
std::ofstream valueFile("/sys/class/gpio/gpio" + pin + "/value");
valueFile << value;
valueFile.close();
}
// 模拟 PWM 信号输出函数
void softwarePWM(const std::string &pin, int dutyCycle, int period_us) {
int highTime = period_us * dutyCycle / 100; // 高电平持续时间
int lowTime = period_us - highTime; // 低电平持续时间
// 设置高电平
setGPIOValue(pin, 1);
usleep(highTime);
// 设置低电平
setGPIOValue(pin, 0);
usleep(lowTime);
}
int main()
{
// 呼吸灯周期 2秒 (steps*period_us/1e6)
const int steps = 100; // 每个周期的步数
const int period_us = 20000; // 模拟 PWM 的周期,单位为微秒
// 初始化 GPIO:导出并设置输出模式
exportGPIO(GPIO_PIN);
usleep(100);
setGPIODirection(GPIO_PIN, "out");
usleep(100);
while (true) {
// 渐亮
for (int i = 0; i <= steps; ++i) {
double brightness = (1 - cos(2 * M_PI * i / steps)) / 2 * 100; // 占空比百分比
softwarePWM(GPIO_PIN, static_cast<int>(brightness), period_us);
}
// 渐暗
for (int i = steps; i >= 0; --i) {
double brightness = (1 - cos(2 * M_PI * i / steps)) / 2 * 100; // 占空比百分比
softwarePWM(GPIO_PIN, static_cast<int>(brightness), period_us);
}
}
return 0;
}
代码注释写的很清楚,就不赘述了。
2.5. 网页端控制
用httplib 开启http 服务,并开四个接口
httplib::Server svr;
svr.Get("/api/ledOn", [&](const httplib::Request& req, httplib::Response& res) {
fprintf(brightness, "%d\n", 1);
fflush(brightness);
});
svr.Get("/api/ledOff", [&](const httplib::Request& req, httplib::Response& res) {
fprintf(brightness, "%d\n", 0);
fflush(brightness);
});
svr.Get("/api/breathOn", [&](const httplib::Request& req, httplib::Response& res) {
startBreath();
});
svr.Get("/api/breathOff", [&](const httplib::Request& req, httplib::Response& res) {
stopBreath();
});
svr.listen("192.168.7.2", 1234);
led的开关(ledOn, ledOff)直接控制就行,呼吸灯需要开启线程执行
std::atomic<bool> breathRunningFlag(false);
std::thread taskThread;
// 任务
void taskBreath(){
Util::BBB_Breath b;
b.init();
while(breathRunningFlag){
b.once();
}
}
// 启动任务
void startBreath(){
if(breathRunningFlag)
return;
breathRunningFlag = true;
taskThread = std::thread(taskBreath);
}
void stopBreath(){
if(!breathRunningFlag)
return;
breathRunningFlag = false;
if(taskThread.joinable()){
taskThread.join();
}
}
BBB_Breath 类,init初始化,once用来闪一周期
void Util::BBB_Breath::init(){
// 初始化 GPIO:导出并设置输出模式
exportGPIO(this->GPIO_PIN);
usleep(100);
setGPIODirection(this->GPIO_PIN, "out");
usleep(100);
}
void Util::BBB_Breath::once(){
// 渐亮
for (int i = 0; i <= this->STEPS; ++i) {
double brightness = (1 - cos(2 * M_PI * i / this->STEPS)) / 2 * 100; // 占空比百分比
softwarePWM(this->GPIO_PIN, static_cast<int>(brightness), this->PERIOD_US);
}
// 渐暗
for (int i = this->STEPS; i >= 0; --i) {
double brightness = (1 - cos(2 * M_PI * i / this->STEPS)) / 2 * 100; // 占空比百分比
softwarePWM(this->GPIO_PIN, static_cast<int>(brightness), this->PERIOD_US);
}
}
完整代码会放到附件中。
编译:
g++ main.cpp util.cpp lib/json/cJSON.c -lpthread -std=c++11
展示
前端页面
呼吸靓照
3. 心得体会
BBB板还是挺好玩,后续把BBB固件升了,再玩一玩。硬禾的活动也参加过不少了,每个活动都挺好,感谢硬禾,感谢得捷,感谢BeagleBone基金会。
要说这个活动的缺点嘛,还是有的,这个活动时间太长了,毕竟dealine才是第一生产力。嘿嘿嘿
4. 参考:
Setting Up the BeagleBone Black's GPIO Pins