μLink 2024-06-07 18:30 采纳率: 37.5%
浏览 15
已结题

嵌入式软件多功能模块架构设计

     嵌入式多线程或模块访问同一资源时应该怎么设计比较合理,比如智能手表项目中用户点击UI开始测量心率,然后心率传感器要上电打开,初始化I2C总线,然后开始工作,UI那边要阻塞等待数据输出,这时如果另外的功能模块比如网络上报的数据也要上报心率数据,那么网络模块也得请求心率数据,心率传感器也得上电打开,初始化,获取数据,然后这个数据通过什么样的方式回传给请求的模块,就是关于这种设计到公共资源数据获取这种架构应该怎么设计才合理?类似的情况很多,比如GPS数据,多个模块都来请求GPS数据,而且GPS上电后不一定会立马定位,难道请求的模块用到GPS时都要阻塞等待?有没有什么设计模式或原则;有个原始的简单粗暴的方式是,比如UI要测量心率,走到此处直接调用心率的驱动,驱动中包括上电,然后通过总线读数据,然后函数返回数据。然后该函数通过互斥锁进行保护,避免多模块访问冲突,好处是调该接口直接能拿到返回结果,但是感觉各个功能模块直接调用心率驱动的方式有点简单粗暴,而且是阻塞方式,关于这一块有没有什么架构或者设计模式可以借鉴的。
  还有一种类似的问题就是,比如一个智能硬件有键盘,LCD UI显示,音频播放,联网功能,按键盘按键时要播音,UI显示,网络下发指令时也要播音和UI现实,这些各个模块之间应该怎样设计架构比较合适,才能让各模块相互间流畅有序稳定的运行,有没有什么架构或者设计模式可以学习的。
  • 写回答

25条回答 默认 最新

  • 专家-赤兔[在线] 全栈领域优质创作者 2024-06-07 18:30
    关注
    获得0.30元问题酬金

    引自免费微信小程序:皆我百晓生

    在设计嵌入式系统的多功能模块架构时,我们可以遵循一些最佳实践和设计模式来确保高效、稳定且可维护的系统。对于您所描述的心率传感器和GPS数据共享问题,可以考虑使用发布/订阅(Publish/Subscribe)模式和消息队列。而对于键盘、LCD、音频播放和联网功能的集成,可以采用事件驱动架构。

    首先,我们来看心率传感器和GPS数据的共享。这里的关键是将数据提供者与消费者分离,并引入一个中介来处理数据分发。发布/订阅模式允许多个订阅者对单个发布者的更新感兴趣,而无需他们之间有任何直接联系。您可以使用一个消息队列作为中介,发布者将数据放入队列,订阅者从队列中取出数据。

    1. 创建一个消息队列类,例如MessageQueue,用于存储和管理数据:
    template <typename T>
    class MessageQueue {
    public:
        void push(const T& data) { queue_.push(data); }
        T pop() { return queue_.front(); }
    private:
        std::queue<T> queue_;
    };
    
    1. 心率传感器模块创建一个发布者,将数据推送到队列中:
    class HeartRateSensor {
    public:
        void startMeasurement();
        void stopMeasurement();
    
        // 当测量到新数据时,调用此方法
        void onHeartRateDataAvailable(int heartRate);
    private:
        MessageQueue<int> heartRateQueue_;
    };
    
    1. 网络上报模块和UI模块作为订阅者,从队列中获取数据:
    class NetworkUploader {
    public:
        void uploadHeartRate(int heartRate);
    };
    
    class UserInterface {
    public:
        void displayHeartRate(int heartRate);
    };
    
    // 在这两个模块中,可以设置定时器或轮询机制来检查新的数据
    
    1. 在主循环中,确保每个模块都能正确地订阅和取消订阅数据:
    int main() {
        HeartRateSensor heartRateSensor;
        NetworkUploader networkUploader;
        UserInterface userInterface;
    
        // 订阅心率数据
        heartRateSensor.onHeartRateDataAvailable += networkUploader.uploadHeartRate;
        heartRateSensor.onHeartRateDataAvailable += userInterface.displayHeartRate;
    
        // 开始测量心率
        heartRateSensor.startMeasurement();
    
        while (true) {
            // 主循环,处理其他任务
        }
    
        // 取消订阅
        heartRateSensor.onHeartRateDataAvailable -= networkUploader.uploadHeartRate;
        heartRateSensor.onHeartRateDataAvailable -= userInterface.displayHeartRate;
    
        return 0;
    }
    

    对于键盘、LCD、音频播放和联网功能的集成,可以使用事件驱动架构。每个模块都有事件处理器,当某个事件发生时,会触发相应的处理逻辑。这样可以保持模块间的松耦合,并且可以通过事件调度实现并发执行。

    1. 定义事件和事件处理器:
    enum class Event {
        KEY_PRESSED,
        LCD_UPDATE,
        AUDIO_PLAY,
        NETWORK_COMMAND_RECEIVED
    };
    
    struct EventData {
        Event type;
        union {
            struct {
                char key;
            } keyPressed;
            struct {
                std::string message;
            } lcdUpdate;
            struct {
                std::string audioFile;
            } audioPlay;
            struct {
                std::string command;
            } networkCommand;
        };
    };
    
    1. 每个模块都有事件处理器:
    class Keyboard {
    public:
        void handleEvent(const EventData& event);
    };
    
    class LCD {
    public:
        void handleEvent(const EventData& event);
    };
    
    class AudioPlayer {
    public:
        void handleEvent(const EventData& event);
    };
    
    class NetworkModule {
    public:
        void handleEvent(const EventData& event);
    };
    
    1. 主循环处理事件:
    int main() {
        Keyboard keyboard;
        LCD lcd;
        AudioPlayer audioPlayer;
        NetworkModule networkModule;
    
        while (true) {
            // 获取事件,可能是来自键盘、网络或其他源
            EventData eventData = getEvent();
    
            // 根据事件类型,分派到相应模块处理
            if (eventData.type == Event::KEY_PRESSED) {
                keyboard.handleEvent(eventData);
            } else if (eventData.type == Event::LCD_UPDATE) {
                lcd.handleEvent(eventData);
            } else if (eventData.type == Event::AUDIO_PLAY) {
                audioPlayer.handleEvent(eventData);
            } else if (eventData.type == Event::NETWORK_COMMAND_RECEIVED) {
                networkModule.handleEvent(eventData);
            }
        }
    
        return 0;
    }
    

    这样的设计允许各个模块独立于其他模块运行,同时保证了它们之间的协同工作。如果有需要同步操作的地方,可以在事件处理器内部使用互斥锁或其他同步原语来保护共享资源。同时,这种方法也支持扩展性,添加新功能模块只需增加新的事件类型和对应的处理器即可。

    评论 编辑记录

报告相同问题?

问题事件

  • 系统已结题 6月15日
  • 创建了问题 6月7日

悬赏问题

  • ¥20 有偿:在ubuntu上安装arduino以及其常用库文件。
  • ¥15 请问用arcgis处理一些数据和图形,通常里面有一个根据点划泰森多边形的命令,直接划的弊端是只能执行一个完整的边界,但是我们有时候会用到需要在有很多边界内利用点来执行划泰森多边形的命令
  • ¥30 在wave2foam中执行setWaveField时遇到了如下的浮点异常问题,请问该如何解决呢?
  • ¥20 看图片)删除这个自动化录屏脚本就一直报错找不到脚本文件,如何解决?(相关搜索:bat文件)
  • ¥750 关于一道数论方面的问题,求解答!(关键词-数学方法)
  • ¥200 csgo2的viewmatrix值是否还有别的获取方式
  • ¥15 Stable Diffusion,用Ebsynth utility在视频选帧图重绘,第一步报错,蒙版和帧图没法生成,怎么处理啊
  • ¥15 请把下列每一行代码完整地读懂并注释出来
  • ¥15 pycharm运行main文件,显示没有conda环境
  • ¥15 寻找公式识别开发,自动识别整页文档、图像公式的软件