自制工业控制系统:APIC定时器与系统时间实战——寄存器级配置、HPET校准与RTC读写
Contents
起因
调度篇(中断驱动的硬实时层与协作式主循环)承诺"硬实时层的确定性来自中断优先级",这篇兑现它:屏蔽 PIC、启用 APIC、配置定时器、校准频率,最后加上工业系统天天要用的系统时间读写(RTC/CMOS 实操)。全部给到寄存器级。这条时间线上的每个部件都有来历:8259A 是 Intel 1976 年给 8080 配的中断控制器,PC/AT 上两片级联管 15 路中断一用四十年;APIC 体系是九十年代为多处理器机器生的,最早是主板上的外置 82489DX,Pentium 起把 Local APIC 塞进 CPU,多核时代每核一份;HPET 是 Intel 和微软 2000 年代联手定的规范,接 1981 年 IBM PC 那颗 8253/8254 PIT 的班——DOS 时代 18.2Hz 的系统节拍就是 PIT 传下来的祖制;RTC 则从 PC/AT(1984)起蹲在 CMOS 里,靠纽扣电池活到今天。
一、先屏蔽 PIC
8259A 是上世纪遗产,spurious IRQ7/15 会骚扰新框架。屏蔽(先从片后主片,防级联毛刺):
|
|
Local APIC 存在性不用猜:从移交的 RSDP → XSDT → MADT(签名 “APIC”)解析,IO APIC 基址也在 MADT 的条目里。另外一句要提前说:SMI 是平台级黑洞(触发时 CPU 消失几十微秒,软件无感知),工控主板 BIOS 里关掉不必要的 SMI 源(legacy USB 支持之类)是硬件侧功课——它在中断延迟直方图上留下的毛刺,实时性验证篇会再遇到。
二、Local APIC:寄存器表与优先级布局
Local APIC 是一组 MMIO 寄存器,基址从 MSR 0x1B(IA32_APIC_BASE)读,bit 11 是全局使能,基址字段惯例 0xFEE00000。向量号高 4 位就是优先级,布局直接落实两级调度模型:
| 向量区 | 优先级 | 挂载内容 |
|---|---|---|
| 0xF0–0xFF | 最高 | 安全联锁(急停/断丝/限位) |
| 0xE0–0xEF | 次高 | 放电脉冲 |
| 0xD0–0xDF | 高 | 插补节拍 |
| 0x20–0xCF | 普通 | 设备中断(NVMe/USB 等) |
MMIO 偏移表(基址 + 偏移,访问用 read_volatile/write_volatile):
| 偏移 | 名称 | 用途 |
|---|---|---|
| 0x080 | TPR | 任务优先级(屏蔽低优先级中断) |
| 0x0B0 | EOI | 中断结束,写 0 |
| 0x0F0 | SVR | spurious 向量 + bit8 使能 APIC |
| 0x320 | LVT Timer | 定时器 LVT:向量 + 模式 |
| 0x380 | TIMER_ICR | 初始计数(写入即启动) |
| 0x390 | TIMER_CCR | 当前计数(递减) |
| 0x3E0 | TIMER_DCR | 分频 |
初始化序列(对应代码):
|
|
EOI 纪律:每个 ISR 尾部必须 write(APIC, 0xB0/4, 0),忘了就收不到下一个同优先级中断,且无任何报错——症状是"跑着跑着脉冲停了",极难定位,此处立碑。
三、频率校准:APIC 定时器没有标准频率
APIC 定时器频率随 CPU 在几 MHz 到几十 MHz 漂,必须开机校准。用 HPET 做基准:ACPI HPET 表(签名 “HPET”)给出 MMIO 基址,主计数器在偏移 0xF0(64 位自由运行),GCAP_ID(偏移 0x0)的 bits 32–63 是计数周期,单位飞秒(典型 10ns 级):
|
|
校准窗口取 10ms 是个权衡:窗口太短,计数量的量化误差占比大;太长,开机白等。另外 periodic 模式下 ICR 只写一次——计数减到 0 触发中断后硬件自动重装,ISR 里不用重填,这也是选 periodic 不选 one-shot 的理由:节拍固定,省一次写寄存器,也少一个抖动源。放电几十 kHz、插补几 kHz 是这类机床的典型量级,精确数值跟机械参数联动,属应用篇。TSC 单独说:CPUID 确认 invariant TSC(CPUID.80000007H:EDX[8])后只做时间戳(日志、事件审计),不做中断源——它快但不是事件驱动。
四、MSI:外设中断的正确姿势
MSI 的机制一句话:设备对 0xFEE00000 加向量信息发起一次普通内存写,中断即触发——无共享线、无电平维持,延迟确定。自研 FPGA 卡和 NVMe 都设计成 MSI(PCIe 篇细讲 MSI capability 配置)。IO APIC 重定向表保留给真正的外部引脚(目标板有才用)。
五、系统时间:RTC/CMOS 读写实操
工业系统对时间是刚需:加工记录时间戳、定时参数、断电恢复后对表。方案 = 开机从 RTC 读墙钟 + 运行期用校准过的单调时钟(HPET/TSC)推进,需要校时再写回 RTC。
RTC/CMOS 用两个端口:0x70 选寄存器(bit7 置 1 顺便屏蔽 NMI),0x71 读写数据。寄存器布局:
| 索引 | 内容 | 索引 | 内容 |
|---|---|---|---|
| 0x00 | 秒 | 0x07 | 日 |
| 0x02 | 分 | 0x08 | 月 |
| 0x04 | 时 | 0x09 | 年(00–99) |
| 0x0A | 状态 A | 0x0B | 状态 B |
| 0x32 | 世纪(ACPI 有才可靠) |
读之前必须等"更新进行中"标志清零(否则读到撕裂数据):轮询状态 A 的 bit7(UIP),为 1 时 RTC 正在刷寄存器,等它变 0 再读。读出的格式由状态 B 决定,两种编码都要处理:
|
|
写时间(校时)用状态 B 的 bit7(SET 位)冻结更新再写:
|
|
运行期墙钟 = boot_time + 已流逝时间,流逝时间用 HPET 主计数器(或校准后的 TSC)算,跟 RTC 脱钩——RTC 精度月漂几分钟,只做开机基准和人工校时入口。RTC 还送一个隐藏福利:状态 A 的分频允许它产生 2Hz–8192Hz 的周期中断(IRQ8,状态 B bit6 使能),低速场景可以当备用慢时钟。
总结
实战清单:双端口屏蔽 PIC、MSR 0x1B 确认基址、SVR/DCR/LVT/ICR 四步配好周期定时器、HPET 窗口法校准 ticks_per_us、ISR 尾部写 EOI、RTC 等 UIP + BCD/12h 兼容读法 + SET 位冻结写法。跑通标志:插补向量的周期中断稳定触发(串口每秒打印计数不丢拍),read_rtc() 与宿主机时间一致。下一篇:显卡——从 GOP 帧缓冲到自己的显示输出。
参考链接
- OSDev Wiki: APIC / HPET / TSC / MSI / RTC
- 本系列调度篇:中断驱动的硬实时层与协作式主循环;第一篇:自制工业控制系统:从UEFI到内核
Author 软件开发大郭
LastMod 2026-09-11