嵌入式应用开发笔记之web端设备控制台

目前正在学习嵌入式应用开发,非科班,非系统学习,半路出家型选手,但是有一定Linux基础,手头正好有一个嵌入式开发设备,硬件资源如下:

主要参数 配置
处理器 单核792MHz Cortex® A7处理器
内存 DDR3 512MB
存储 eMMC 4GB/8GB
RS232 1路调试串口
RS485 4路
CAN-bus 3路
以太网 2路
软件资源如下:
  • Ubuntu20.04系统
  • RT-Linux内核
  • 各种驱动程序

我想要把手头这个嵌入式设备的硬件资源都用起来,所以做了一个web端设备控制台应用来练手,其中需要的技术栈有:

  • lighttpd
  • fcgi
  • sqlite3

接下来就是开发过程了,有几个阶段:

  • 开发环境搭建
  • web服务器及网页设计
  • cgi后端代码编写(di/do、led、adc、485、CAN、sqlite3)

开发环境搭建

采用vscode + docker的方案,其中docker容器提供实际编译环境(arm-linux-gnueabihf-gcc等),vscode用于连接docker容器进行代码编写工作。

docker容器部署在宿主机,安装了必要的软件开发包和编译器,并且添加了异质架构,支持arm64armhf交叉编译

嵌入式设备提供RS232的调试串口,通过这个串口登录到系统中,然后可以进行一些准备工作,比如修改网路设置,因为我需要使用ssh进行宿主机和设备端的连接,并且web服务器也需要解决网络问题。

宿主机可以联网,设备端暂时无法联网,所以需要额外的软件就需要先在宿主机编译源码,然后再scp到设备端,比如lighttpdsqlite3,设备端本身是没有的

🧾sqlite3的安装

  1. 下载源码(SQLite Download Page)到宿主机(docker),选择sqlite-autoconf-3530200.tar.gz这样的包
  2. tar进行解压缩,并进入到解压后的目录中
  3. 配置编译环境和输出目录,采用静态编译方式
bash 复制代码
./configure \
  --host=arm-linux-gnueabihf \
  --prefix=/tmp/em500/embed_pack/sqlite \
  --disable-readline \
  --disable-shared \
  --enable-static
  1. scp可执行文件sqlite3到设备端
  2. 其余的输出文件includelib在cgi代码中会用到

web服务器及网页设计

首先是对lighttpd.conf文件的配置,这个文件决定了嵌入式设备于浏览器交互过程中调用的fcgi代码,如下所示:

text 复制代码
fastcgi.server = (
    # 把 /api/* 的请求交给 FastCGI 处理
    "/cgi-bin/app.fcgi" => (
        "app-handler" => (
            "socket"      => "/tmp/app.fcgi.socket",
            "check-local" => "disable",
            # 核心:告诉 lighttpd 用哪个二进制来启动 FCGI 线程池
            "bin-path"    => "/var/www/cgi-bin/app.fcgi",
            "max-procs"   => 2,           # 常驻进程数,嵌入式设 1~3 即可
            "idle-timeout"=> 30,
        )
    ),
    "/cgi-bin/history.fcgi" => (
        "history-handler" => (
            "socket"      => "/tmp/history.fcgi.socket",
            "check-local" => "disable",
            "bin-path"    => "/var/www/cgi-bin/history.fcgi",
            "max-procs"   => 1,
            "idle-timeout"=> 30,
        )
    ),
)

使用到两个fcgi文件:app.fcgihistory.fcgi,这两个fcgi代码在后面介绍

网页的界面设计,借助ai直接生成,很简约,就只有一个页面,页面上的交互控件对应了嵌入式设备本身自带的硬件资源的可控/可读/可写部分,图片如下所示:

在调试cgi功能的时候,有几种调试方式:

  1. wireshark抓包:查看数据包的详细信息、包括请求头、请求参数等
  2. 浏览器控制台:查看报错信息
  3. cgi代码回发调试信息:浏览器弹窗、控制台打印信息

cgi后端代码编写

📢在静态编译时,所有依赖库都需要显式指定,包括系统库

我写了两个fcgi代码,分别是app.fcgihistory.fcgi

编译history.fcgi的命令如下:

bash 复制代码
arm-linux-gnueabihf-gcc \
  -static \
  -o history.fcgi \
  history.cpp \
  -I$TARGET_DIR/usr/local/include \
-I$TARGET_DIR/sqlite/include \
$TARGET_DIR/usr/local/lib/libfcgi.a \
$TARGET_DIR/sqlite/lib/libsqlite3.a \
-lm \
-ldl \
-lpthread

其中app.fcgi的功能是多路Led指示灯控制、多路数字输出(DO)控制、多路数字输入状态(DI)控制、以及多路ADC模拟量采集

对于cgi的交互过程,代码讲解如下:

在web端的js请求:

轮询请求:

js 复制代码
const response = await fetch('/cgi-bin/app.fcgi');
const data = await response.json();

发送控制命令:

js 复制代码
const response = await fetch('/cgi-bin/app.fcgi', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ type: type, channel: channel,value: value })
                });
                const result = await response.json();

获取历史数据:

js 复制代码
const url = `/cgi-bin/history.cgi?dataType=${encodeURIComponent(dataType)}&timeRange=${encodeURIComponent(timeRange)}`;
const response = await fetch(url, { method: 'GET' });

在设备服务器端:

首先是获取请求信息:

c 复制代码
/* 获取请求方法 */
char *method = getenv("REQUEST_METHOD"); 
/* 获取请求资源url */
char *uri = getenv("REQUEST_URI");

分别处理不同请求:

c 复制代码
// 处理控制请求(POST)

        if (strcmp(method, "POST") == 0 &&
            strstr(uri, "/cgi-bin/app.fcgi"))
        {
	        // 读取 POST 数据
	        FCGI_fread(post_data + bytes_read,1,
	        content_length - bytes_read,
	        FCGI_stdin);
	        
	        // 解析JSON
	        /* 解析type */
	        json_get_string(post_data, "\"type\"", type, sizeof(type));
	        /* 解析channel */
	        channel = json_get_int(post_data, "\"channel\"");
	        /* 解析value */
	        value = json_get_int(post_data, "\"value\"");
        }
        
// 处理状态请求(GET)
        else if (strcmp(method, "GET") == 0 &&
                 strstr(uri, "/cgi-bin/app.fcgi"))
        {
        ...
        }

📞调用硬件资源前,需要确保硬件环境准备完成,包括初始化、配置以及权限

在调试过程中发现,DO/DI这些资源多涉及到gpio的配置操作,需要做export,然后才能读写

其次是history.fcgi的功能,读取历史记录,数据库是sqlite3,表结构如下:

sql 复制代码
CREATE TABLE history(
id INTEGER PRIMARY KEY AUTOINCREMENT,
time TEXT, 
volt TEXT,
curr TEXT,
soc TEXT,
loader TEXT);

读取的历史记录效果如下:

微信公众号:软趴趴的工程师(一个乐于助人的工程师)

相关推荐
dddwjzx5 小时前
嵌入式Linux C应用编程入门——标准IO库
嵌入式
pai同学5 小时前
ESP-IDF+vscode开发ESP32第十二讲——event
嵌入式
凉、介7 小时前
KVM + QEMU 虚拟化
笔记·学习·嵌入式·arm·qemu·虚拟化·kvm
dddwjzx21 小时前
嵌入式Linux C应用编程入门——文件IO进阶
嵌入式
2023自学中1 天前
imx6ull 开发板, mame 模拟器,运行游戏 测试
linux·游戏·嵌入式·开发板
dddwjzx1 天前
嵌入式Linux C应用编程入门——文件IO
嵌入式
fzm52981 天前
车载ECU单元测试技术与应用研究
c语言·自动化测试·单元测试·嵌入式·白盒测试
用户120487221613 天前
Linux驱动编译与加载
linux·嵌入式
用户805533698033 天前
Input 子系统架构:Core、Handler、Driver 三层是怎么协作的
linux·嵌入式
用户805533698033 天前
RK-Forge外设系列开篇 - 把板子从「能启动」变成「能用」:Ethernet/SPI/MMC 三个纯接线外设
linux·github·嵌入式