找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5838

积分

0

好友

752

主题
发表于 7 天前 | 查看: 1| 回复: 0

一、什么是单例模式?

1. 定义与核心思想

单例模式(Singleton Pattern)是一种创建型设计模式,核心目标是确保一个类在系统中仅有一个实例,并提供全局访问点。在嵌入式开发中,这一模式常用于管理硬件资源或全局状态,避免因多次初始化或并发操作导致的资源冲突。

2. 嵌入式中的典型特征

  • 唯一性:如 UART 串口控制器,多任务同时操作会引发数据混乱。
  • 资源节约:避免重复初始化占用额外内存(尤其在 RAM 稀缺的 MCU 中)。
  • 线程安全:需通过锁或关中断保护临界区(例如 RTOS 中的任务竞争)。

二、单例模式的作用与应用场景

1. 四大核心作用

  • 统一管理硬件外设:如 SPI Flash、ADC 模块的全局访问。
  • 减少内存开销:避免重复创建对象(适合资源有限的 MCU)。
  • 防止状态不一致:确保配置参数(如校准值)全局唯一。
  • 简化跨模块调用:日志系统、任务调度器等无需传递实例。

2. 六大嵌入式应用场景

场景 示例 风险规避
硬件控制器 I2C 总线驱动、PWM 发生器 避免多任务操作时序冲突
系统配置管理 设备序列号、网络参数 防止配置被意外覆盖
日志记录器 通过 UART 或 Flash 写入日志 确保日志文件原子写入
中断服务管理器 统一管理外部中断优先级 防止中断嵌套导致死锁
电源管理模块 休眠唤醒策略、低功耗模式 统一控制电源状态
数据缓存池 传感器数据环形缓冲区 避免多任务读写越界

三、手把手用 C语言 实现单例模式

1. 裸机环境实现(关中断保护)

#include <stdint.h>
#include "stm32g0xx_hal.h" // 以STM32G0系列为例

// 单例对象:模拟SPI控制器
typedef struct {
    uint32_t spi_speed;     // SPI时钟频率
    uint8_t  tx_buffer[64]; // 发送缓冲区
} SPIController;

static SPIController instance;     // 静态实例(编译期分配内存)
static volatile uint8_t initialized = 0;  // volatile防止编译器优化

// 获取SPI控制器实例(关中断保护原子操作)
SPIController* get_spi_controller(void){
    if (!initialized) {
        __disable_irq();            // 关中断
        if (!initialized) {         // 双重检查锁定
            instance.spi_speed = 1000000;  // 默认1MHz
            HAL_SPI_Init(&hspi1);   // 初始化硬件SPI(需提前配置hspi1)
            initialized = 1;
        }
        __enable_irq();
    }
    return &instance;
}

// 示例:多任务调用SPI发送数据
void task_send_data(void){
    SPIController* spi = get_spi_controller();
    HAL_SPI_Transmit(&hspi1, spi->tx_buffer, 64, 100); // 发送数据
}

代码解析

  • 静态内存分配SPIController instance 直接分配在全局区,避免动态内存风险。
  • 关中断保护:裸机环境下通过 __disable_irq() 确保初始化原子性。

那么问题来了:为什么初始化一个单例对象需要关中断?因为在裸机环境中,中断服务函数随时可能打断主流程。如果主流程刚走到 initialized 判断之后、赋值之前,突然被中断打断,而中断处理里又调用了 get_spi_controller(),就会造成重复初始化。双重检查配合关中断,正是为了堵上这道缝隙。这也是为什么 initialized 必须声明为 volatile——编译器可不知道这个变量会在中断里被改写。

如果你正在写单片机程序,又不想在裸机上手工管理这些细节,可以到 云栈社区 上找其他开发者聊聊,看看大家在 RTOS 环境下都是怎么封装单例的。




上一篇:DeepSeek Harness 命令速查手册:dsh 启动、插件、配置与参数顺序
下一篇:Linux fcntl 文件锁:解决多进程日志交错与单例启动问题
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-26 01:52 , Processed in 1.244404 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表