3、SECS/GEM裸机实现分析:消息队列的轮询处理、单线程下的HSMS连接管理、裸机下的超时处理机制

在裸机(bare-metal)环境中,SECS/GEM协议栈的实现面临资源极度受限的挑战:无操作系统、无多线程、无内核定时器服务。本章深入分析在单线程、无RTOS条件下,如何通过消息队列轮询、状态机驱动的HSMS连接管理以及软件定时器链来构建一个可工作的SECS/GEM通信节点。

核心认知:裸机SECS/GEM的本质是一个超级循环(Super Loop)驱动的有限状态机。所有协议层(HSMS、消息队列、超时管理)都在同一个线程内分时复用CPU,通过轮询和状态切换来模拟并发。

3.1 消息队列的轮询处理机制

裸机下没有阻塞式IPC(进程间通信),消息队列必须实现为无锁环形缓冲区,由主循环主动轮询。队列用于解耦HSMS底层接收与上层SECS消息解析。

3.1.1 环形消息队列数据结构

字段 类型 说明
buffer MsgNode_t[MQ_SIZE] 消息节点数组,每个节点包含消息类型、长度、数据指针
head uint16_t 生产者(中断/接收任务)写入位置
tail uint16_t 消费者(主循环)读取位置
count uint16_t 当前队列中待处理消息数
// 裸机消息队列核心结构
#define MQ_SIZE 32

typedef struct {
    uint8_t  msg_type;   // 0:HSMS数据, 1:连接事件, 2:超时事件
    uint8_t  *data;      // 指向消息负载
    uint16_t length;
} MsgNode_t;

typedef struct {
    MsgNode_t buffer[MQ_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
    volatile uint16_t count;
} MQueue_t;

// 轮询处理函数——在主循环中调用
void MQ_Poll(MQueue_t *mq) {
    while (mq->count > 0) {
        MsgNode_t msg = mq->buffer[mq->tail];
        // 根据msg_type分发处理
        switch (msg.msg_type) {
            case 0: // HSMS数据消息
                SECS_ProcessMessage(msg.data, msg.length);
                break;
            case 1: // 连接状态变化
                HSMS_StateMachine_Event(msg.data[0]);
                break;
            case 2: // 超时事件
                Timer_HandleTimeout(msg.data[0]);
                break;
        }
        // 更新tail(环形回绕)
        mq->tail = (mq->tail + 1) % MQ_SIZE;
        // 原子减count(裸机关中断保护)
        __disable_irq();
        mq->count--;
        __enable_irq();
    }
}
设计要点:裸机队列的head和tail必须声明为volatile,防止编译器优化导致中断与主循环之间的数据不一致。count的增减操作需在关中断下执行,保证原子性。

3.1.2 轮询处理流程图

裸机消息队列轮询处理流程 主循环入口 count > 0 ? 执行其他任务 从队列取出消息 switch(msg_type) SECS解析 HSMS状态机 超时处理 返回主循环入口

3.2 单线程下的HSMS连接管理

HSMS(High-Speed SECS Message Services)连接管理在裸机下必须实现为有限状态机(FSM)。主循环每次迭代调用状态机一次,根据当前状态和事件决定下一步动作。

3.2.1 HSMS连接状态定义

状态 枚举值 描述
HSMS_NOT_CONNECTED 0 TCP连接未建立,等待连接或监听
HSMS_CONNECTED 1 TCP已连接,等待Select/分离测试
HSMS_SELECTED 2 HSMS会话已建立,可收发SECS消息
HSMS_SELECT_PENDING 3 已发送Select.req,等待Select.rsp
HSMS_DESELECTED 4 会话已分离,TCP连接仍存在

3.2.2 状态机实现核心代码

// HSMS状态机事件枚举
typedef enum {
    EVT_TCP_CONNECTED,      // TCP连接建立
    EVT_TCP_DISCONNECTED,   // TCP连接断开
    EVT_SELECT_RSP_RCVD,    // 收到Select.rsp
    EVT_SELECT_REQ_RCVD,    // 收到Select.req(被动方)
    EVT_DESELECT_REQ_RCVD,  // 收到Deselect.req
    EVT_TIMEOUT_T3,         // T3连接超时
    EVT_TIMEOUT_T5,         // T5分离超时
    EVT_DATA_RCVD           // 收到数据消息
} HSMS_Event_t;

// 状态机处理函数——每次主循环调用一次
void HSMS_StateMachine_Run(HSMS_Event_t event) {
    switch (g_hsms_state) {
        case HSMS_NOT_CONNECTED:
            if (event == EVT_TCP_CONNECTED) {
                // 发送Select.req,进入Select Pending
                HSMS_SendSelectReq();
                g_hsms_state = HSMS_SELECT_PENDING;
                Timer_Start(T3_TIMER, T3_TIMEOUT_MS);
            }
            break;

        case HSMS_SELECT_PENDING:
            if (event == EVT_SELECT_RSP_RCVD) {
                // 检查Select.rsp状态码
                if (g_select_rsp_status == 0) {
                    g_hsms_state = HSMS_SELECTED;
                    Timer_Stop(T3_TIMER);
                } else {
                    // 连接拒绝,关闭TCP
                    TCP_Close();
                    g_hsms_state = HSMS_NOT_CONNECTED;
                }
            } else if (event == EVT_TIMEOUT_T3) {
                // T3超时,重试或断开
                if (++g_t3_retry_count <= MAX_T3_RETRY) {
                    HSMS_SendSelectReq();
                    Timer_Start(T3_TIMER, T3_TIMEOUT_MS);
                } else {
                    TCP_Close();
                    g_hsms_state = HSMS_NOT_CONNECTED;
                }
            }
            break;

        case HSMS_SELECTED:
            if (event == EVT_TCP_DISCONNECTED) {
                g_hsms_state = HSMS_NOT_CONNECTED;
            } else if (event == EVT_DESELECT_REQ_RCVD) {
                HSMS_SendDeselectRsp();
                g_hsms_state = HSMS_DESELECTED;
                Timer_Start(T5_TIMER, T5_TIMEOUT_MS);
            }
            // 正常数据收发在此状态下进行
            break;

        // 其他状态处理...
    }
}
裸机陷阱:单线程下,状态机处理期间不能阻塞等待网络响应。所有网络收发必须设置为非阻塞模式,或使用DMA+中断完成。状态机只做状态迁移和命令发起,实际的TCP数据收发由中断服务程序(ISR)完成并放入消息队列。

3.2.3 HSMS状态机迁移图

裸机HSMS连接状态机迁移 NOT_CONNECTED SELECT_PENDING SELECTED DESELECTED TCP连接成功 Select.rsp(成功) T3超时/重试耗尽 收到Deselect.req T5超时/TCP断开 TCP断开

3.3 裸机下的超时处理机制

SECS/GEM协议定义了多个超时定时器(T1~T8),裸机下没有RTOS的软件定时器服务,必须自行实现基于硬件定时器中断的软件定时器链。

3.3.1 软件定时器链设计

使用一个单向链表管理所有活跃定时器。每个定时器节点包含剩余时间、超时回调函数指针和标志位。硬件定时器(如SysTick)以1ms为周期产生中断,中断服务程序遍历链表,递减所有节点的计数值。

// 软件定时器节点
typedef struct TimerNode {
    uint32_t            remain_ms;      // 剩余毫秒数
    uint32_t            reload_ms;      // 重装载值(用于周期定时器)
    void                (*callback)(void *arg); // 超时回调
    void                *arg;           // 回调参数
    uint8_t             is_periodic;    // 是否周期定时器
    uint8_t             is_active;      // 是否激活
    struct TimerNode    *next;          // 链表指针
} TimerNode_t;

// 硬件定时器中断服务程序(1ms周期)
void SysTick_Handler(void) {
    TimerNode_t *node = timer_list_head;
    while (node) {
        if (node->is_active) {
            if (--node->remain_ms == 0) {
                // 超时:将超时事件放入消息队列
                MQ_PutEvent(EVENT_TIMEOUT, (uint8_t*)&node, sizeof(node));
                if (node->is_periodic) {
                    node->remain_ms = node->reload_ms; // 重装载
                } else {
                    node->is_active = 0; // 单次定时器自动停用
                }
            }
        }
        node = node->next;
    }
}

3.3.2 SECS/GEM标准超时参数表

定时器 默认值 用途 裸机实现方式
T1 1.0 s 等待Reply超时 软件定时器链,精度1ms
T2 10.0 s 等待通信超时 软件定时器链
T3 45.0 s HSMS Select超时 软件定时器链,支持重试
T4 45.0 s HSMS分离超时 软件定时器链
T5 10.0 s 连接响应超时 软件定时器链
T6 5.0 s 等待控制事务超时 软件定时器链
T7 10.0 s NOT_SELECTED状态超时 软件定时器链
T8 5.0 s 网络间隔超时 软件定时器链

3.3.3 超时处理与主循环的协作

超时事件不直接在中断中处理,而是通过消息队列传递给主循环。这种设计保证了中断服务程序保持短小,且超时处理与状态机在同一上下文执行,避免了竞态条件。

// 主循环中的超时事件处理
void Timer_HandleTimeout(uint8_t timer_id) {
    switch (timer_id) {
        case T3_TIMER:
            // 通知HSMS状态机T3超时
            HSMS_StateMachine_Run(EVT_TIMEOUT_T3);
            break;
        case T5_TIMER:
            HSMS_StateMachine_Run(EVT_TIMEOUT_T5);
            break;
        case T1_TIMER:
            // SECS消息Reply超时,重发或报错
            SECS_RetransmitMessage();
            break;
        default:
            break;
    }
}
裸机超时设计黄金法则:中断只做“计数递减+事件入队”,主循环做“超时逻辑处理”。这确保了超时精度(1ms)不受主循环执行时间影响,同时避免了中断上下文调用复杂状态机带来的风险。

3.3.4 超时处理时序图

裸机超时处理时序(T3超时示例) 硬件定时器 消息队列 主循环(状态机) T3启动 注册定时器节点 递减计数 remain==0, 入队超时事件 事件 MQ_Poll取出 处理T3超时 状态迁移/重试

3.4 裸机实现的整体架构

将上述三个机制整合,裸机SECS/GEM协议栈的主循环结构如下:

// 裸机SECS/GEM主循环
int main(void) {
    // 初始化硬件:UART/SPI、以太网MAC、SysTick定时器
    HW_Init();
    // 初始化协议栈模块
    MQ_Init();           // 消息队列初始化
    HSMS_Init();         // HSMS状态机初始化
    Timer_Init();        // 软件定时器链初始化
    SECS_Init();         // SECS消息层初始化

    // 启动硬件定时器(1ms中断)
    SysTick_Enable(1000);

    while (1) {
        // 1. 轮询消息队列,处理所有待处理事件
        MQ_Poll(&g_msg_queue);

        // 2. 轮询网络接口(非阻塞)
        TCP_Poll();

        // 3. 执行HSMS状态机(空闲状态下的心跳维护)
        HSMS_StateMachine_Run(EVT_IDLE);

        // 4. 执行SECS事务管理(重发、超时检查等)
        SECS_TransactionManager();

        // 5. 执行其他低优先级任务
        Idle_Tasks();
    }
}
裸机实现总结:
  • 消息队列轮询:无锁环形缓冲区 + 主循环主动拉取,实现中断与主循环的解耦。
  • HSMS连接管理:有限状态机 + 非阻塞网络收发,单线程内完成连接建立、会话选择、分离等全部状态迁移。
  • 超时处理:硬件定时器驱动软件定时器链,中断入队 + 主循环处理,兼顾精度与安全性。
这套架构为后续移植到RTOS奠定了坚实基础——消息队列对应RTOS的消息队列,状态机可封装为独立任务,软件定时器可替换为RTOS的定时器服务。