这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » BeaglePlay开发板试用-成果贴-基于BeaglePlay的异构双核环境光

共2条 1/1 1 跳转至

BeaglePlay开发板试用-成果贴-基于BeaglePlay的异构双核环境光数据采集与可视化系统

工程师
2026-07-20 22:39:33     打赏


BeaglePlay 开发板试用-成果贴-基于BeaglePlay的异构双核环境光数据采集与可视化系统

1. 项目介绍

本项目实现了一个基于 BeaglePlay 开发板的异构多处理(AMP)数据采集与远程可视化系统。项目利用 BeaglePlay 强大的双核架构:

  • 低功耗/实时侧(Cortex-M4F 内核):运行 Zephyr RTOS,负责通过 I2C 接口实时、高精度地采集 LTR329 环境光传感器数据。

  • 高性能应用侧(Cortex-A53 内核):运行 Linux 系统,负责数据的接收、持久化处理,并通过 Python Web 服务进行前端网页的实时动态流式展示。

本方案完美示了异构双核在物联网网关、工业现场数据采集等场景中“实时控制与高性能应用相结合”的典型架构。

2. 硬件方案说明

本项目核心硬件主要由 BeaglePlay 开发板以及外围 Qwiic 传感器模块组成。

核心器件分析

  • 主控芯片:TI AM6254

    • Cortex-A53 内核:运行 Linux 操作系统,具备强大的网络堆栈和应用层处理能力,适合部署 Web 服务与数据分析。

    • Cortex-M4F 内核:作为协处理器运行 Zephyr RTOS,具备极低的确定性延迟(Deterministic Latency),专职处理硬件外设与传感器时序。

  • 环境光传感器:LTR329

    • 一款低电压 I2C 接口的数字环境光传感器(ALS),支持宽动态范围,能够将光照强度转化为数字信号输出。

  • 硬件接口:Qwiic (I2C)

    • BeaglePlay 板载 Qwiic 接口,极大简化了 I2C 传感器的接线,确保信号传输的电气稳定性。

3. 系统设计思路

系统设计的核心在于异构内核间的通信(IPM / RPMsg)与职责分离

  1. M4F/Zephyr 侧通过设备树(Device Tree Overlay)配置并接管板载 Qwiic 接口上的 LTR329 传感器,定时轮询或中断读取原始亮度数据。

  2. 利用 虚拟串口(TTY RPMsg) 通道,M4F 侧将组装好的字符数据流发送至应用侧。

  3. Linux 侧内核驱动将 RPMsg 虚拟总线映射为普通的物理字符设备(如 /dev/ttyRPMSG1)。

  4. Python 进程通过串口监听该设备,捕获数据后利用 WebSockets 或 Flask 实时推送到前端页面,实现零延迟的可视化。

4. 功能框图

image.png


5. 软件流程图

MCU端

image.png

linux端(本来想上传一张完整的流程图来着,一直提示上传失败,可能卡在了加水印环节,期待后台运维优化代码)

        多张图拼接,大家简单看一下

image.png

image.png

image.png

image.png

6. 实现过程说明

    设备树配置:修改并编译 Zephyr 设备树叠加层(Overlay),将 I2C 节点映射到特定的 Qwiic 引脚,并注册 ltr329 传感器节点。

/* app.overlay */

#include <zephyr/dt-bindings/pinctrl/ti-k3-pinctrl.h>

/* 1. 将 I2C 引脚描述追加到 pinctrl 节点中 */
&pinctrl {
    i2c_qwiic_pins_default: i2c-qwiic-default-pins {
        pinmux = <
            K3_PINMUX(0x0044, PIN_INPUT, 0) /* (A8) MCU_I2C0_SCL */
            K3_PINMUX(0x0048, PIN_INPUT, 0) /* (D10) MCU_I2C0_SDA */
        >;
    };
};

/* 2. 配置 I2C0 控制器并引用上面定义的引脚 */
&i2c0 {
    status = "okay";
    pinctrl-0 = <&i2c_qwiic_pins_default>;
    pinctrl-names = "default";
    clock-frequency = <I2C_BITRATE_STANDARD>; /* 100000 */

    ltr329: ltr329@29 {
        compatible = "liteon,ltr329"; 
        reg = <0x29>;
        status = "okay";
    };
};


Zephy 固件开发:可以先编写iic数据读取的代码用以验证iic及传感器工作正常

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

bool sensorOnlineFlag = true;

void main(void)
{
    /* 获取传感器设备实例 */
    const struct device *const dev = DEVICE_DT_GET(DT_NODELABEL(ltr329));

    if (!device_is_ready(dev)) {
        LOG_ERR("LTR329 device not ready");
       
        sensorOnlineFlag = false;
    }

    struct sensor_value light_val;

    while (1) {

        LOG_INF("-- this is main loop ---");

        if (sensorOnlineFlag == false)
        {
            LOG_INF("-- iic sensor is offline ---");
           
            k_msleep(1000);

            continue;
        }

        /* 1. 触发传感器数据更新 */
        if (sensor_sample_fetch(dev) < 0) {
            LOG_ERR("Sensor sample fetch failed");
            k_msleep(1000);
            continue;
        }

        /* 2. 获取光照强度通道数据 (单位: Lux) */
        if (sensor_channel_get(dev, SENSOR_CHAN_LIGHT, &light_val) < 0) {
            LOG_ERR("Cannot get sensor data");
        } else {
            /* sensor_value 包含 val1(整数) 和 val2(微小部分) */
            LOG_INF("Light intensity: %d.%06d Lux", light_val.val1, light_val.val2);
        }

        k_msleep(1000);
    }
}

然后再和例程openamp-rsc-table进行组合实现亮度数据通过ttyRPMSG发送

数据流激活机制:在 Linux 端需要先对 /dev/ttyRPMSG1 写入触发数据(如 echo "start"),从而唤醒并激活 M4F 端的发送循环。

                    echo "hello" > /dev/ttyRPMSG1

应用层构建:使用 Python 编写一个轻量级的服务端,通过 pyserial 读取串口数据,配合 Web 框架将数据推送至浏览器客户端。

                    linxu端代码大部分是把想要实现的功能告诉AI之后,AI自动生成的

7. 关键代码解析

M4F / Zephyr 侧核心:数据读取与 RPMsg 发送

/*
 * Copyright (c) 2020, STMICROELECTRONICS
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <zephyr/drivers/ipm.h>
#include <zephyr/drivers/sensor.h>

#include <openamp/open_amp.h>
#include <metal/sys.h>
#include <metal/io.h>
#include <resource_table.h>
#include <addr_translation.h>

#ifdef CONFIG_SHELL_BACKEND_RPMSG
#include <zephyr/shell/shell_rpmsg.h>
#endif

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(openamp_rsc_table);

#define SHM_DEVICE_NAME "shm"

#if !DT_HAS_CHOSEN(zephyr_ipc_shm)
#error "Sample requires definition of shared memory for rpmsg"
#endif

#if CONFIG_IPM_MAX_DATA_SIZE > 0

#define IPM_SEND(dev, w, id, d, s) ipm_send(dev, w, id, d, s)
#else
#define IPM_SEND(dev, w, id, d, s) ipm_send(dev, w, id, NULL, 0)
#endif

/* Constants derived from device tree */
#define SHM_NODE                DT_CHOSEN(zephyr_ipc_shm)
#define SHM_START_ADDR  DT_REG_ADDR(SHM_NODE)
#define SHM_SIZE                DT_REG_SIZE(SHM_NODE)

// 亮度上报周期 单位ms
#define LIGHT_PERIOD_MS 300
// 报文最大长度
#define LIGHT_BUFF_LEN 64

#define APP_TASK_STACK_SIZE (1024)

/* Add 1024 extra bytes for the TTY task stack for the "tx_buff" buffer. */
#define APP_TTY_TASK_STACK_SIZE (1536)

K_THREAD_STACK_DEFINE(thread_mng_stack, APP_TASK_STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_rp__client_stack, APP_TASK_STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_tty_stack, APP_TTY_TASK_STACK_SIZE);

// 光照上报线程栈与线程句柄
K_THREAD_STACK_DEFINE(thread_light_stack, 512);
static struct k_thread thread_light_data;

// 亮度发送缓存
static char light_tx_buf[LIGHT_BUFF_LEN];
// LTR329传感器设备指针
static const struct device *ltr329_dev = NULL;
static bool sensorOnlineFlag = true;

static struct k_thread thread_mng_data;
static struct k_thread thread_rp__client_data;
static struct k_thread thread_tty_data;

static const struct device *const ipm_handle =
        DEVICE_DT_GET(DT_CHOSEN(zephyr_ipc));

static metal_phys_addr_t shm_physmap = SHM_START_ADDR;
static metal_phys_addr_t rsc_tab_physmap;

static struct metal_io_region shm_io_data; /* shared memory */
static struct metal_io_region rsc_io_data; /* rsc_table memory */

struct rpmsg_rcv_msg {
        void *data;
        size_t len;
};

static struct metal_io_region *shm_io = &shm_io_data;

static struct metal_io_region *rsc_io = &rsc_io_data;
static struct rpmsg_virtio_device rvdev;

static void *rsc_table;
static struct rpmsg_device *rpdev;

static char rx_sc_msg[20];  /* should receive "Hello world!" */
static struct rpmsg_endpoint sc_ept;
static struct rpmsg_rcv_msg sc_msg = {.data = rx_sc_msg};

static struct rpmsg_endpoint tty_ept;
static struct rpmsg_rcv_msg tty_msg;

static K_SEM_DEFINE(data_sem, 0, 1);
static K_SEM_DEFINE(data_sc_sem, 0, 1);
static K_SEM_DEFINE(data_tty_sem, 0, 1);

static void platform_ipm_callback(const struct device *dev, void *context,
                                  uint32_t id, volatile void *data)
{
        LOG_DBG("%s: msg received from mb %d", __func__, id);
        k_sem_give(&data_sem);
}

static int rpmsg_recv_cs_callback(struct rpmsg_endpoint *ept, void *data,
                                  size_t len, uint32_t src, void *priv)
{
        memcpy(sc_msg.data, data, len);
        sc_msg.len = len;
        k_sem_give(&data_sc_sem);

        return RPMSG_SUCCESS;
}

static int rpmsg_recv_tty_callback(struct rpmsg_endpoint *ept, void *data,
                                   size_t len, uint32_t src, void *priv)
{
        struct rpmsg_rcv_msg *msg = priv;

        rpmsg_hold_rx_buffer(ept, data);
        msg->data = data;
        msg->len = len;
        k_sem_give(&data_tty_sem);

        return RPMSG_SUCCESS;
}

static void receive_message(unsigned char **msg, unsigned int *len)
{
        int status = k_sem_take(&data_sem, K_FOREVER);

        if (status == 0) {
                rproc_virtio_notified(rvdev.vdev, VRING1_ID);
        }
}

static void new_service_cb(struct rpmsg_device *rdev, const char *name,
                           uint32_t src)
{
        LOG_ERR("%s: unexpected ns service receive for name %s",
                __func__, name);
}

int mailbox_notify(void *priv, uint32_t id)
{
        ARG_UNUSED(priv);

        LOG_DBG("%s: msg received", __func__);
        IPM_SEND(ipm_handle, 0, id, &id, 4);

        return 0;
}

int platform_init(void)
{
        int rsc_size;
        struct metal_init_params metal_params = METAL_INIT_DEFAULTS;
        int status;

        status = metal_init(&metal_params);
        if (status) {
                LOG_ERR("metal_init: failed: %d", status);
                return -1;
        }

        /* declare shared memory region */
        metal_io_init(shm_io, (void *)SHM_START_ADDR, &shm_physmap,
                      SHM_SIZE, -1, 0, addr_translation_get_ops(shm_physmap));

        /* declare resource table region */
        rsc_table_get(&rsc_table, &rsc_size);
        rsc_tab_physmap = (uintptr_t)rsc_table;

        metal_io_init(rsc_io, rsc_table,
                      &rsc_tab_physmap, rsc_size, -1, 0, NULL);

        /* setup IPM */
        if (!device_is_ready(ipm_handle)) {
                LOG_ERR("IPM device is not ready");
                return -1;
        }

        ipm_register_callback(ipm_handle, platform_ipm_callback, NULL);

        status = ipm_set_enabled(ipm_handle, 1);
        if (status) {
                LOG_ERR("ipm_set_enabled failed");
                return -1;
        }

        return 0;
}

static void cleanup_system(void)
{
        ipm_set_enabled(ipm_handle, 0);
        rpmsg_deinit_vdev(&rvdev);
        metal_finish();
}

struct  rpmsg_device *
platform_create_rpmsg_vdev(unsigned int vdev_index,
                           unsigned int role,
                           void (*rst_cb)(struct virtio_device *vdev),
                           rpmsg_ns_bind_cb ns_cb)
{
        struct fw_rsc_vdev_vring *vring_rsc;
        struct virtio_device *vdev;
        int ret;

        vdev = rproc_virtio_create_vdev(VIRTIO_DEV_DEVICE, VDEV_ID,
                                        rsc_table_to_vdev(rsc_table),
                                        rsc_io, NULL, mailbox_notify, NULL);

        if (!vdev) {
                LOG_ERR("failed to create vdev");
                return NULL;
        }

        /* wait master rpmsg init completion */
        rproc_virtio_wait_remote_ready(vdev);

        vring_rsc = rsc_table_get_vring0(rsc_table);
        ret = rproc_virtio_init_vring(vdev, 0, vring_rsc->notifyid,
                                      (void *)vring_rsc->da, rsc_io,
                                      vring_rsc->num, vring_rsc->align);
        if (ret) {
                LOG_ERR("failed to init vring 0");
                goto failed;
        }

        vring_rsc = rsc_table_get_vring1(rsc_table);
        ret = rproc_virtio_init_vring(vdev, 1, vring_rsc->notifyid,
                                      (void *)vring_rsc->da, rsc_io,
                                      vring_rsc->num, vring_rsc->align);
        if (ret) {
                LOG_ERR("failed to init vring 1");
                goto failed;
        }

        ret = rpmsg_init_vdev(&rvdev, vdev, ns_cb, shm_io, NULL);
        if (ret) {
                LOG_ERR("failed rpmsg_init_vdev");
                goto failed;
        }

        return rpmsg_virtio_get_rpmsg_device(&rvdev);

failed:
        rproc_virtio_remove_vdev(vdev);

        return NULL;
}

void app_rpmsg_client_sample(void *arg1, void *arg2, void *arg3)
{
        ARG_UNUSED(arg1);
        ARG_UNUSED(arg2);
        ARG_UNUSED(arg3);

        unsigned int msg_cnt = 0;
        int ret = 0;

        k_sem_take(&data_sc_sem,  K_FOREVER);

        LOG_INF("OpenAMP[remote] Linux sample client responder started");

        ret = rpmsg_create_ept(&sc_ept, rpdev, "rpmsg-client-sample",
                               RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
                               rpmsg_recv_cs_callback, NULL);
        if (ret) {
                LOG_ERR("[Linux sample client] Could not create endpoint: %d", ret);
                goto task_end;
        }

        while (msg_cnt < 100) {
                k_sem_take(&data_sc_sem,  K_FOREVER);
                msg_cnt++;
                LOG_INF("[Linux sample client] incoming msg %d: %.*s", msg_cnt, sc_msg.len,
                        (char *)sc_msg.data);
                rpmsg_send(&sc_ept, sc_msg.data, sc_msg.len);
        }
        rpmsg_destroy_ept(&sc_ept);

task_end:
        LOG_INF("OpenAMP Linux sample client responder ended");
}

void app_rpmsg_tty(void *arg1, void *arg2, void *arg3)
{
        ARG_UNUSED(arg1);
        ARG_UNUSED(arg2);
        ARG_UNUSED(arg3);

        unsigned char tx_buff[512];
        int ret = 0;

        k_sem_take(&data_tty_sem,  K_FOREVER);

        LOG_INF("OpenAMP[remote] Linux TTY responder started");

        tty_ept.priv = &tty_msg;
        ret = rpmsg_create_ept(&tty_ept, rpdev, "rpmsg-tty",
                               RPMSG_ADDR_ANY, RPMSG_ADDR_ANY,
                               rpmsg_recv_tty_callback, NULL);
        if (ret) {
                LOG_ERR("[Linux TTY] Could not create endpoint: %d", ret);
                goto task_end;
        }

        while (tty_ept.addr !=  RPMSG_ADDR_ANY) {
                k_sem_take(&data_tty_sem,  K_FOREVER);
                if (tty_msg.len) {
                        LOG_INF("[Linux TTY] incoming msg: %.*s",
                                (int)tty_msg.len, (char *)tty_msg.data);
                        snprintf(tx_buff, sizeof(tx_buff), "TTY hex debug 0x%04x: ", tty_ept.addr);
                        memcpy(&tx_buff[12], tty_msg.data, tty_msg.len);
                        rpmsg_send(&tty_ept, tx_buff, tty_msg.len + 12);
                        rpmsg_release_rx_buffer(&tty_ept, tty_msg.data);
                }
                tty_msg.len = 0;
                tty_msg.data = NULL;
        }
        rpmsg_destroy_ept(&tty_ept);

task_end:
        LOG_INF("OpenAMP Linux TTY responder ended");
}

void rpmsg_mng_task(void *arg1, void *arg2, void *arg3)
{
        ARG_UNUSED(arg1);
        ARG_UNUSED(arg2);
        ARG_UNUSED(arg3);

        unsigned char *msg;
        unsigned int len;
        int ret = 0;

        LOG_INF("OpenAMP[remote] Linux responder demo started");

        /* Initialize platform */
        ret = platform_init();
        if (ret) {
                LOG_ERR("Failed to initialize platform");
                ret = -1;
                goto task_end;
        }

        rpdev = platform_create_rpmsg_vdev(0, VIRTIO_DEV_DEVICE, NULL,
                                           new_service_cb);
        if (!rpdev) {
                LOG_ERR("Failed to create rpmsg virtio device");
                ret = -1;
                goto task_end;
        }

#ifdef CONFIG_SHELL_BACKEND_RPMSG
        (void)shell_backend_rpmsg_init_transport(rpdev);
#endif

        /* start the rpmsg clients */
        k_sem_give(&data_sc_sem);
        k_sem_give(&data_tty_sem);

        while (1) {
                receive_message(&msg, &len);
        }

task_end:
        cleanup_system();

        LOG_INF("OpenAMP demo ended");
}

// ========== 替换原模拟温湿度线程,改为真实光照读取发送 ==========
static void app_light_send_task(void *arg1, void *arg2, void *arg3)
{
    ARG_UNUSED(arg1);
    ARG_UNUSED(arg2);
    ARG_UNUSED(arg3);

        /* 获取传感器设备实例 */
    const struct device *const dev = DEVICE_DT_GET(DT_NODELABEL(ltr329));

    if (!device_is_ready(dev)) {
        LOG_ERR("LTR329 device not ready");

                sensorOnlineFlag = false;
    }
    else
    {
        sensorOnlineFlag = true;
    }

    // 等待rpmsg-tty端点就绪
    while (tty_ept.addr == RPMSG_ADDR_ANY)
    {
        k_msleep(100);
    }

    LOG_INF("LTR329 Light send task start, period %dms", LIGHT_PERIOD_MS);

    struct sensor_value light_val;

    while (1)
    {
        if (!sensorOnlineFlag)
        {
            snprintf(light_tx_buf, sizeof(light_tx_buf), "LTR329 SENSOR OFFLINE\n");
            rpmsg_send(&tty_ept, light_tx_buf, strlen(light_tx_buf));
            k_msleep(LIGHT_PERIOD_MS);
            continue;
        }

        // 抓取传感器样本
        if (sensor_sample_fetch(dev) < 0)
        {
            LOG_ERR("Sensor sample fetch failed");
            snprintf(light_tx_buf, sizeof(light_tx_buf), "LTR329 READ FAIL\n");
        }
        else if (sensor_channel_get(dev, SENSOR_CHAN_LIGHT, &light_val) < 0)
        {
            LOG_ERR("Get light data fail");
            snprintf(light_tx_buf, sizeof(light_tx_buf), "LTR329 GET DATA ERR\n");
        }
        else
        {
            // 拼接照度字符串:整数部分.微小数 Lux
            snprintf(light_tx_buf, sizeof(light_tx_buf),
                     "LIGHT: %d.%06d Lux\n", light_val.val1, light_val.val2);
            LOG_INF("%s", light_tx_buf);
        }

        // RPMSG发送到Linux侧 /dev/ttyRPMSG1
        rpmsg_send(&tty_ept, light_tx_buf, strlen(light_tx_buf));

        k_msleep(LIGHT_PERIOD_MS);
    }
}

int main(void)
{
        LOG_INF("Starting application threads!");
        k_thread_create(&thread_mng_data, thread_mng_stack, APP_TASK_STACK_SIZE,
                        rpmsg_mng_task,
                        NULL, NULL, NULL, K_PRIO_COOP(8), 0, K_NO_WAIT);
        k_thread_create(&thread_rp__client_data, thread_rp__client_stack, APP_TASK_STACK_SIZE,
                        app_rpmsg_client_sample,
                        NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
        k_thread_create(&thread_tty_data, thread_tty_stack, APP_TTY_TASK_STACK_SIZE,
                        app_rpmsg_tty,
                        NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);

        // 原温湿度线程注释/删除,替换为光照上报线程
        k_thread_create(&thread_light_data, thread_light_stack, 512,
                        app_light_send_task,
                        NULL, NULL, NULL, K_PRIO_COOP(6), 0, K_NO_WAIT);

        return 0;
}

Linux 侧核心:Python 监听

import time
import re
import threading
import os

# 核心:必须在导入 flask 之前对底层网络进行“猴子补丁”以支持高并发异步推送
import eventlet
eventlet.monkey_patch()

from flask import Flask, render_template_string
from flask_socketio import SocketIO

app = Flask(__name__)
# 允许任何设备跨域访问网页
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')

# ==================== RPMSG 配置 ====================
SERIAL_PORT = '/dev/ttyRPMSG1' 
# ===================================================

# 极简炫酷的深色大屏前端 HTML 页面
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BeaglePlay 环境光实时监控</title>
    <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #121212; color: #e0e0e0; display: flex; flex-direction: column;
            align-items: center; justify-content: center; height: 100vh; margin: 0;
        }
        .card {
            background: #1e1e1e; padding: 40px; border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0,0,0,0.6); text-align: center;
            border: 1px solid #333; min-width: 320px;
        }
        h1 { margin: 0 0 10px 0; color: #4fc3f7; font-size: 24px; letter-spacing: 1px; }
        .lux-container { margin: 30px 0; }
        .lux-value { font-size: 56px; font-weight: bold; color: #00e676; text-shadow: 0 0 10px rgba(0, 230, 118, 0.3); }
        .lux-unit { font-size: 20px; color: #888; margin-left: 5px; }
        .status { font-size: 13px; color: #aaa; display: inline-flex; align-items: center; justify-content: center; background: #2a2a2a; padding: 6px 14px; border-radius: 20px; }
        .dot { height: 8px; width: 8px; background-color: #ff1744; border-radius: 50%; display: inline-block; margin-right: 8px; transition: background-color 0.3s ease; }
    </style>
</head>
<body>
    <div class="card">
        <h1>BeaglePlay 光强监控</h1>
        <div class="lux-container">
            <span class="lux-value" id="lux">--</span>
            <span class="lux-unit">Lux</span>
        </div>
        <div class="status">
            <span class="dot" id="dot"></span>
            <span id="status-text">连接服务器中...</span>
        </div>
    </div>
    <script>
        const socket = io();
        socket.on('connect', () => {
            document.getElementById('status-text').innerText = '系统正常接收数据';
            document.getElementById('dot').style.backgroundColor = '#00e676';
        });
        socket.on('disconnect', () => {
            document.getElementById('status-text').innerText = '与服务器断开';
            document.getElementById('dot').style.backgroundColor = '#ff1744';
        });
        socket.on('light_update', (data) => {
            document.getElementById('lux').innerText = data.value;
        });
    </script>
</body>
</html>
"""

@app.route('/')
def index():
    return render_template_string(HTML_TEMPLATE)

def read_rpmsg_loop():
    """针对 ttyRPMSG1 深度优化的流式低级读写后台线程"""
    print(f"【后台线程】开始监听 RPMessage 节点: {SERIAL_PORT}")
   
    while True:
        try:
            # 1. 以底层读写模式打开,彻底杜绝高级 io 产生的 'Seekable' 报错
            fd = os.open(SERIAL_PORT, os.O_RDWR)
            print(f"【后台线程】成功打开 {SERIAL_PORT}!正在发送激活信号...")
           
            # 2. 写入激活数据
            os.write(fd, b"hello\n")
            print("【后台线程】信号已发送,进入数据流解析状态...")
           
            # 用于累加字节的缓冲区,防止数据断章
            data_buffer = ""
           
            while True:
                # 3. 每次读取一个小数据块 (128字节),防止数据在内核管道里积压
                chunk = os.read(fd, 128)
                if not chunk:
                    # 如果读到空数据,说明通道可能断开
                    time.sleep(0.01)
                    continue
                   
                # 解码并拼接到缓冲区
                data_buffer += chunk.decode('utf-8', errors='ignore')
               
                # 4. 如果缓冲区里包含换行符,说明至少有一条完整的日志到了
                if "\n" in data_buffer:
                    lines = data_buffer.split("\n")
                    # 最后一个可能是不完整的行,留给下一次循环拼接
                    data_buffer = lines[-1]
                   
                    # 遍历前面所有完整的行
                    for line in lines[:-1]:
                        line = line.strip()
                        if not line:
                            continue
                       
                        # 5. 应用在 USB 上测试完美的正则
                        match = re.search(r'LIGHT:\s*([\d.]+)\s*Lux', line)
                        if match:
                            lux_val = match.group(1)
                            lux_short = f"{float(lux_val):.2f}"
                            print(f"[RPMSG1数据] 解析到光强: {lux_short} Lux")
                           
                            # 通过 WebSocket 推送到前端
                            socketio.emit('light_update', {'value': lux_short})
                           
                # 微小的休眠,配合 eventlet 释放 CPU 调度权
                time.sleep(0.005)
               
        except Exception as e:
            print(f"【⚠️ RPMSG 错误】: {e}。5秒后尝试重连...")
            time.sleep(5)

if __name__ == '__main__':
    # 启动后台优化后的 RPMessage 扫描线程
    threading.Thread(target=read_rpmsg_loop, daemon=True).start()
   
    # 启动 Web 服务器
    print("【系统】正在启动网页服务器,请访问 http://<BeaglePlay_IP>:5000")
    socketio.run(app, host='0.0.0.0', port=5000, log_output=False)

8. 功能展示

zephyr编译效果

image.png

运行日志截图

在 Linux 终端通过 cat /dev/ttyRPMSG1 或 Python 后台打印的实时光照输出

image.png

image.png

硬件实物图

image.png

9. 技术难点与解决方案 / 项目总结

遇到的技术难点

  1. RPMsg 阻塞与通道激活:在开发初期,发现 M4F 固件启动后,Linux 端直接 cat 无法接收到数据。经过反复调试 kernel 表现发现,AM6254 的虚拟串口存在单向激活机制:Linux 侧必须先向 /dev/ttyRPMSG1 写入任意字节,才能打开通信管道,使 M4F 能够顺利写入。

  2. 多核设备树冲突:I2C 接口如果在 Linux 侧被默认挂载,M4F 侧的 Zephyr 就会因为硬件资源被占用而无法初始化传感器。解决方案是通过设备树 Overlay,在 Linux 侧将该 Qwiic I2C 节点配置为 status = "reserved" 或直接禁用,全权交给实时核(M4F)管理。

项目总结

本项目成功落地了 BeaglePlay 的 AMP(异构不对称多处理)方案。通过将耗费硬件时序、高实时要求的传感器轮询工作交由运行 Zephyr RTOS 的 M4F 内核处理,将耗费网络吞吐与内存的可视化栈交由 Linux 处理,最大化地利用了硬件资源。该架构为后续更复杂的工业级低延迟物联网网关开发打下了坚实的技术基础。





关键词: zephyr    

院士
2026-07-21 22:45:25     打赏
2楼

楼主 这也太详细,太棒了!

这个黑色的扩展板是楼主自已画的吗?


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]