2. 裸机编程基础:前后台系统架构、中断服务程序(ISR)设计、裸机下的定时器与状态机

在将 SECS/GEM 协议栈移植到 RTOS 之前,必须深刻理解裸机(bare-metal)环境下的编程范式。SECS/GEM 协议栈对实时性有明确要求(如 T3 超时、HSMS 连接保持),裸机下的前后台系统、中断服务程序(ISR)设计、定时器与状态机是实现这些功能的基础。本章将系统梳理这些核心概念,为后续的 RTOS 移植打下坚实的底层基础。

2.1 前后台系统架构(Foreground/Background System)

前后台系统是嵌入式裸机编程中最常见的架构,也称为“超级循环(Super Loop)”系统。其核心思想是将 CPU 时间划分为两个层次:前台(Foreground) 处理异步事件(中断),后台(Background) 处理轮询与主逻辑。

关键认知: 在 SECS/GEM 协议栈的裸机实现中,前台负责接收 HSMS 消息或处理定时器超时,后台负责解析消息、更新状态机、发送响应。这种分工决定了系统的实时性上限。

2.1.1 架构模型

后台是一个无限循环,依次轮询各个任务模块;前台由中断驱动,标记事件或填充缓冲区。下图展示了典型的前后台系统结构:

前后台系统架构(Super Loop + ISR) 后台(Background) 主循环(Super Loop) 轮询 SECS 接收缓冲区 处理状态机(State Machine) 检查定时器超时列表 发送待处理响应消息 前台(Foreground) 中断服务程序(ISR) UART RX ISR:接收 HSMS 字节 定时器 ISR:更新系统滴答 GPIO ISR:设备报警信号 设置事件标志(Flag) 中断触发 事件标志被后台消费

2.1.2 前后台系统的优缺点

特性 优势 劣势
实时性 中断响应快(微秒级),适合处理短时事件 后台任务无优先级,长任务会阻塞其他轮询
资源占用 RAM/ROM 开销极低,无内核栈开销 所有任务共享一个栈,栈溢出风险高
代码复杂度 逻辑直观,适合小型协议栈 随着功能增加,主循环变得臃肿,维护困难
任务调度 无调度器,无上下文切换开销 无法实现抢占,紧急任务必须等待轮询
SECS/GEM 移植警示: 裸机前后台系统在处理 HSMS 多连接或大量设备变量(SV/EC)时,后台循环的单次执行时间可能超过 T3 超时(通常 45 秒),导致连接断开。这是后续引入 RTOS 的核心驱动力之一。

2.2 中断服务程序(ISR)设计

中断服务程序是裸机系统中处理异步事件的核心机制。在 SECS/GEM 协议栈中,ISR 的设计质量直接影响消息接收的完整性和定时器的精度。

2.2.1 ISR 设计黄金法则

  1. 快进快出: ISR 中只做最少的必要操作(如读取寄存器、设置标志),将耗时处理推迟到后台。
  2. 避免阻塞: 禁止在 ISR 中使用自旋锁、延时循环或复杂计算。
  3. 保护共享数据: 前台与后台共享的变量(如环形缓冲区索引)必须使用 volatile 声明,并考虑原子访问。
  4. 嵌套优先级: 合理设置中断优先级,避免低优先级 ISR 阻塞高优先级中断(如定时器中断)。

2.2.2 典型 ISR 实现:UART 接收

以下代码展示了 SECS/GEM 协议栈中 HSMS 消息接收的 ISR 实现。它使用环形缓冲区(Ring Buffer)暂存字节,避免在 ISR 内进行消息解析。

/* 环形缓冲区结构体 */
#define RX_BUF_SIZE 256
typedef struct {
    volatile uint8_t buffer[RX_BUF_SIZE];
    volatile uint16_t head;  /* 写入索引 */
    volatile uint16_t tail;  /* 读取索引 */
} ring_buffer_t;

ring_buffer_t rx_ring;

/* UART 接收中断服务程序 */
void UART_IRQHandler(void) {
    uint8_t byte;
    uint16_t next_head;

    /* 读取接收数据寄存器 */
    byte = UART->DR;

    /* 计算下一个 head 位置 */
    next_head = (rx_ring.head + 1) & (RX_BUF_SIZE - 1);

    /* 检查缓冲区是否满 */
    if (next_head != rx_ring.tail) {
        rx_ring.buffer[rx_ring.head] = byte;
        rx_ring.head = next_head;
    } else {
        /* 缓冲区溢出:丢弃数据,设置溢出标志 */
        g_uart_overflow_flag = 1;
    }

    /* 清除中断标志 */
    UART->ICR |= (1 << 4);
}
设计要点: 环形缓冲区大小必须为 2 的幂(256),以便使用位与运算实现快速取模。head 和 tail 使用 volatile 防止编译器优化,确保后台读取时看到最新的值。

2.2.3 ISR 与后台的同步机制

下表总结了裸机系统中常用的 ISR 与后台同步方式:

同步方式 实现原理 适用场景 注意事项
全局标志 ISR 设置 volatile 标志,后台轮询清除 简单事件通知(如数据到达) 无法传递数据,仅适合“有/无”事件
环形缓冲区 ISR 写入数据,后台读取数据 串口接收、DMA 传输 需处理缓冲区溢出,索引需原子操作
信号量(软件模拟) ISR 递增计数器,后台递减并等待 多事件计数 裸机下需关中断保护计数器
消息队列(软件模拟) ISR 入队,后台出队处理 传递结构化消息(如 SECS 消息帧) 队列长度需静态分配,ISR 内不可动态分配内存

2.3 裸机下的定时器与状态机

SECS/GEM 协议栈的核心逻辑由有限状态机(FSM)驱动,而状态机的超时与周期性心跳(Link Test)依赖硬件定时器。裸机环境下,定时器与状态机必须紧密配合。

2.3.1 硬件定时器配置

以常见的 ARM Cortex-M 系列 MCU 为例,配置一个 1ms 周期的系统滴答定时器(SysTick):

/* SysTick 初始化:配置为 1ms 中断 */
void SysTick_Init(uint32_t system_clock_hz) {
    /* 重装载值 = 系统时钟频率 / 1000 - 1 */
    uint32_t reload_value = (system_clock_hz / 1000) - 1;

    SysTick->LOAD = reload_value;
    SysTick->VAL  = 0;
    SysTick->CTRL = (1 << 0) |  /* 使能定时器 */
                    (1 << 1) |  /* 使能中断 */
                    (1 << 2);   /* 使用处理器时钟 */
}

/* SysTick 中断服务程序 */
volatile uint32_t system_tick = 0;

void SysTick_Handler(void) {
    system_tick++;
}

/* 获取当前系统滴答 */
uint32_t GetTick(void) {
    return system_tick;
}

2.3.2 软件定时器实现

基于系统滴答,可以实现多个软件定时器,用于 SECS/GEM 的 T3(Reply Timeout)、T5(Connect Separation Timeout)、T6(Control Transaction Timeout)等超时管理。

/* 软件定时器结构体 */
typedef struct {
    uint32_t timeout_ms;      /* 超时时间(毫秒) */
    uint32_t start_tick;      /* 启动时的系统滴答 */
    uint8_t  is_running;      /* 定时器是否在运行 */
    uint8_t  is_expired;      /* 是否已超时 */
} soft_timer_t;

/* 启动定时器 */
void Timer_Start(soft_timer_t *timer, uint32_t timeout_ms) {
    timer->timeout_ms = timeout_ms;
    timer->start_tick = GetTick();
    timer->is_running = 1;
    timer->is_expired = 0;
}

/* 检查定时器是否超时(在后台主循环中调用) */
void Timer_Check(soft_timer_t *timer) {
    if (timer->is_running) {
        uint32_t elapsed = GetTick() - timer->start_tick;
        if (elapsed >= timer->timeout_ms) {
            timer->is_running = 0;
            timer->is_expired = 1;
        }
    }
}

/* 停止定时器 */
void Timer_Stop(soft_timer_t *timer) {
    timer->is_running = 0;
    timer->is_expired = 0;
}
注意: 裸机下软件定时器的精度受限于主循环的轮询频率。如果主循环执行时间超过 10ms,T3 超时(45s)的误差可能累积到不可接受的程度。这是后续引入 RTOS 定时器任务的另一个原因。

2.3.3 SECS/GEM 状态机设计

SECS/GEM 协议栈的核心状态机包括:HSMS 连接状态机 和 GEM 设备状态机。以下以 HSMS 连接状态机为例,展示裸机下的实现。

HSMS 连接状态机(裸机实现) NOT_CONNECTED 初始状态 CONNECTED 已建立 TCP 连接 SELECTED HSMS 会话建立 NOT_SELECTED T3 超时或分离 TCP 连接建立 Select.req/rsp T3 超时 重新 Select TCP 断开

2.3.4 状态机实现模板

裸机下的状态机通常使用 switch-case 或 函数指针表 实现。以下展示使用函数指针表的高效实现方式:

/* 状态枚举 */
typedef enum {
    STATE_NOT_CONNECTED,
    STATE_CONNECTED,
    STATE_SELECTED,
    STATE_NOT_SELECTED,
    STATE_MAX
} hsms_state_t;

/* 事件枚举 */
typedef enum {
    EVENT_TCP_CONNECTED,
    EVENT_SELECT_REQ,
    EVENT_SELECT_RSP,
    EVENT_T3_TIMEOUT,
    EVENT_TCP_DISCONNECTED,
    EVENT_MAX
} hsms_event_t;

/* 状态处理函数指针类型 */
typedef void (*state_handler_t)(hsms_event_t event);

/* 状态处理函数声明 */
static void not_connected_handler(hsms_event_t event);
static void connected_handler(hsms_event_t event);
static void selected_handler(hsms_event_t event);
static void not_selected_handler(hsms_event_t event);

/* 状态表:二维数组 [当前状态][事件] -> 下一个状态 */
static const hsms_state_t state_transition[STATE_MAX][EVENT_MAX] = {
    /* NOT_CONNECTED */
    { STATE_CONNECTED,    STATE_NOT_CONNECTED, STATE_NOT_CONNECTED, STATE_NOT_CONNECTED, STATE_NOT_CONNECTED },
    /* CONNECTED */
    { STATE_CONNECTED,    STATE_SELECTED,      STATE_SELECTED,      STATE_NOT_CONNECTED, STATE_NOT_CONNECTED },
    /* SELECTED */
    { STATE_SELECTED,     STATE_SELECTED,      STATE_SELECTED,      STATE_NOT_SELECTED,  STATE_NOT_CONNECTED },
    /* NOT_SELECTED */
    { STATE_NOT_SELECTED, STATE_SELECTED,      STATE_SELECTED,      STATE_NOT_SELECTED,  STATE_NOT_CONNECTED },
};

/* 状态处理函数表 */
static const state_handler_t state_handlers[STATE_MAX] = {
    not_connected_handler,
    connected_handler,
    selected_handler,
    not_selected_handler
};

/* 当前状态 */
static hsms_state_t current_state = STATE_NOT_CONNECTED;

/* 事件分发函数 */
void DispatchEvent(hsms_event_t event) {
    /* 执行当前状态的处理函数 */
    state_handlers[current_state](event);

    /* 根据状态转移表更新状态 */
    current_state = state_transition[current_state][event];
}
性能优势: 使用二维数组实现状态转移表,时间复杂度为 O(1),且完全避免 if-else 嵌套。在裸机主循环中,每次轮询只需调用 DispatchEvent() 即可驱动状态机,非常适合 SECS/GEM 这种状态明确的协议栈。

2.4 本章小结

裸机编程是理解嵌入式系统底层运行机制的基础。前后台系统架构提供了简单直接的任务组织方式,ISR 设计决定了系统的实时响应能力,而定时器与状态机的配合则是 SECS/GEM 协议栈逻辑正确性的保障。本章所学的环形缓冲区、软件定时器、状态转移表等技巧,将在后续 RTOS 移植中直接复用或升级为 RTOS 原生机制。