2. 裸机编程基础:前后台系统架构、中断服务程序(ISR)设计、裸机下的定时器与状态机
在将 SECS/GEM 协议栈移植到 RTOS 之前,必须深刻理解裸机(bare-metal)环境下的编程范式。SECS/GEM 协议栈对实时性有明确要求(如 T3 超时、HSMS 连接保持),裸机下的前后台系统、中断服务程序(ISR)设计、定时器与状态机是实现这些功能的基础。本章将系统梳理这些核心概念,为后续的 RTOS 移植打下坚实的底层基础。
2.1 前后台系统架构(Foreground/Background System)
前后台系统是嵌入式裸机编程中最常见的架构,也称为“超级循环(Super Loop)”系统。其核心思想是将 CPU 时间划分为两个层次:前台(Foreground) 处理异步事件(中断),后台(Background) 处理轮询与主逻辑。
2.1.1 架构模型
后台是一个无限循环,依次轮询各个任务模块;前台由中断驱动,标记事件或填充缓冲区。下图展示了典型的前后台系统结构:
2.1.2 前后台系统的优缺点
| 特性 | 优势 | 劣势 |
|---|---|---|
| 实时性 | 中断响应快(微秒级),适合处理短时事件 | 后台任务无优先级,长任务会阻塞其他轮询 |
| 资源占用 | RAM/ROM 开销极低,无内核栈开销 | 所有任务共享一个栈,栈溢出风险高 |
| 代码复杂度 | 逻辑直观,适合小型协议栈 | 随着功能增加,主循环变得臃肿,维护困难 |
| 任务调度 | 无调度器,无上下文切换开销 | 无法实现抢占,紧急任务必须等待轮询 |
2.2 中断服务程序(ISR)设计
中断服务程序是裸机系统中处理异步事件的核心机制。在 SECS/GEM 协议栈中,ISR 的设计质量直接影响消息接收的完整性和定时器的精度。
2.2.1 ISR 设计黄金法则
- 快进快出: ISR 中只做最少的必要操作(如读取寄存器、设置标志),将耗时处理推迟到后台。
- 避免阻塞: 禁止在 ISR 中使用自旋锁、延时循环或复杂计算。
- 保护共享数据: 前台与后台共享的变量(如环形缓冲区索引)必须使用 volatile 声明,并考虑原子访问。
- 嵌套优先级: 合理设置中断优先级,避免低优先级 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.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;
}
2.3.3 SECS/GEM 状态机设计
SECS/GEM 协议栈的核心状态机包括:HSMS 连接状态机 和 GEM 设备状态机。以下以 HSMS 连接状态机为例,展示裸机下的实现。
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];
}
2.4 本章小结
裸机编程是理解嵌入式系统底层运行机制的基础。前后台系统架构提供了简单直接的任务组织方式,ISR 设计决定了系统的实时响应能力,而定时器与状态机的配合则是 SECS/GEM 协议栈逻辑正确性的保障。本章所学的环形缓冲区、软件定时器、状态转移表等技巧,将在后续 RTOS 移植中直接复用或升级为 RTOS 原生机制。