Files

383 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- 接口与通讯技术
- 定时器
- 计数器
- 可编程芯片
create time: 2026-06-26 23:05
---
# 8254 定时计数器
## 概述
8254(兼容 8253)是 Intel 公司生产的可编程定时/计数器芯片(Programmable Interval Timer, PIT),**在全部 4 套试卷中均有出现**,其中 **方波产生和计数初值计算** 是最高频题型。芯片内部包含 3 个独立的 16 位计数器,每个计数器有 6 种工作方式。
> [!tip] 考试要点
> - **计数初值计算**:`计数初值 = CLK频率 / 输出频率`
> - **方式 3(方波发生器)** 是最常考的工作方式
> - **控制字编写**:计数器选择 + 读写格式 + 工作方式 + 数制选择
> - **BCD 与二进制** 初值装入的区别
## 正文
### 1. 内部结构
```mermaid
graph TB
A["数据总线缓冲器 D0~D7"] --> B["读/写逻辑"]
B --> C["计数器 0"]
B --> D["计数器 1"]
B --> E["计数器 2"]
C --> F["CLK0, GATE0, OUT0"]
D --> G["CLK1, GATE1, OUT1"]
E --> H["CLK2, GATE2, OUT2"]
```
每个计数器有 3 条信号线:
| 信号 | 方向 | 功能 |
|------|------|------|
| **CLK** | 输入 | 时钟输入,每个下降沿使计数值减 1 |
| **GATE** | 输入 | 门控信号,控制计数的启动/暂停/触发 |
| **OUT** | 输出 | 根据工作方式输出不同波形 |
> [!question] 思考题
> 8254 的计数器是递增还是递减计数?计数值减到 0 后会怎样?
>
> 答:**递减计数**。装入初值后每来一个 CLK 下降沿计数值减 1。减到 0 后的行为取决于工作方式——方式 0 停止,方式 2/3 自动重装初值继续计数。
### 2. 端口地址
8254 占用 **4 个端口地址**:
| A1 A0 | 选中 | 说明 |
|:------:|:----:|------|
| 0 0 | 计数器 0 | 读写计数初值 |
| 0 1 | 计数器 1 | 读写计数初值 |
| 1 0 | 计数器 2 | 读写计数初值 |
| 1 1 | 控制寄存器 | 只写,用于写入控制字 |
> [!note] 每个计数器占用 1 个还是 2 个地址?
> 每个计数器只占用 **1 个端口地址**。16 位初值通过同一地址分两次写入(先低字节后高字节)。整个芯片共需 4 个端口地址。
### 3. 控制字格式
控制字写入控制寄存器(A1A0=11 的端口),格式如下:
```
D7 D6 D5 D4 D3 D2 D1 D0
[计数器] [读写格式] [工作方式] [数制]
00=计数器0 00=锁存 000=方式0 0=二进制
01=计数器1 01=仅低字节 001=方式1 1=BCD
10=计数器2 10=仅高字节 010=方式2
11=先低后高 011=方式3
100=方式4
101=方式5
```
> [!important] 锁存命令(D7D6=00)的特殊用途
> 控制字的 D7D6 位有双重含义:
> - **写入控制寄存器时**(A1A0=11):`00` 表示选择计数器 0 + 设置工作方式
> - **作为锁存命令**:`00` + `D5D4=00` = **锁存当前计数值**到计数锁存器
>
> 锁存原理:8254 内部每个计数器有两个 16 位寄存器——**计数器寄存器**(正在计数的值)和**计数锁存器**(被保护的值)。CPU 读计数值时,当前正在计数的计数器其值会被"冻结"复制到锁存器中,CPU 再从锁存器读出,避免读到中途变化的值。
>
> 注意:其他 2 个非活动计数器不受影响,仍保持原锁存值。
> [!example] 控制字速算
> **要求**:计数器 0,方式 3(方波),先低后高,二进制
>
> D7D6=00(计数器 0), D5D4=11(先低后高), D3D2D1=011(方式 3), D0=0(二进制)
>
> 控制字 = `0011 0110B` = **36H**
>
> **要求**:同上但 BCD 计数 → D0=1 → 控制字 = **37H**
### 4. 六种工作方式
> [!tip] 考试重点
> 方式 2 和方式 3 的区别几乎每卷必考!
#### 4.1 方式总结
| 方式 | 名称 | 输出波形 | 触发 | 自动重装 | 典型应用 |
|:----:|------|----------|------|:--------:|----------|
| 0 | 计数结束中断 | 计数到 0 后 OUT 变高 | 软件 | 否 | 定时中断 |
| 1 | 可编程单稳态 | 触发后输出 N 个 CLK 宽度的负脉冲 | 硬件 | 否 | 单稳态触发器 |
| 2 | **频率发生器** | 每 N 个 CLK 输出 1 个 CLK 宽的负脉冲 | 软件 | 是 | **分频器** |
| 3 | **方波发生器** | 对称方波,占空比约 50% | 软件 | 是 | **方波产生** |
| 4 | 软件触发选通 | 计数到 0 后输出 1 个 CLK 宽的负脉冲 | 软件 | 否 | 软件定时选通 |
| 5 | 硬件触发选通 | 触发后计数到 0 输出 1 个负脉冲 | 硬件 | 否 | 硬件延迟脉冲 |
> [!question] 如何快速区分"软件触发"与"硬件触发"的方式?
> 看触发信号来源:**软件触发**的方式(0、2、3、4)在写入计数初值后就自动开始工作;**硬件触发**的方式(1、5)需要外部 GATE 引脚有有效边沿才能启动。
#### 4.2 GATE 信号在各工作方式下的行为
这是高频考点——考试中经常考查 GATE 对计数的影响:
```mermaid
graph TB
subgraph "GATE = 1 — 正常工作"
direction LR
G1["GATE=1"] --> A["允许计数"]
A --> A1["方式 0/2/3/4<br/>持续减计数"]
A --> A2["方式 1/5<br/>等待硬件触发"]
end
subgraph "GATE = 0 — 暂停 / 复位"
direction LR
G0["GATE=0"] --> B["暂停计数<br/>计数值保持不变"]
B --> B1["方式 0<br/>OUT保持低"]
B --> B2["方式 2/3<br/>OUT变高, 停止计数"]
B --> B3["方式 1/5<br/>无法触发"]
end
subgraph "GATE 上升沿"
direction LR
RE["GATE ↑"] --> C["重新装入初值<br/>开始计数"]
C --> C1["所有方式均适用"]
end
```
#### 4.3 方式 2 vs 方式 3 对比
```mermaid
graph LR
subgraph "方式 2 - 频率发生器"
A2["CLK 输入"] --> B2["计数器 N 分频"]
B2 --> C2["OUT: 周期性窄脉冲"]
end
subgraph "方式 3 - 方波发生器"
A3["CLK 输入"] --> B3["计数器 N 分频"]
B3 --> C3["OUT: 对称方波 50%"]
end
```
| 对比项 | 方式 2 | 方式 3 |
|--------|--------|--------|
| 输出波形 | 负脉冲(宽 1 个 CLK) | 对称方波 |
| 占空比 | 很小 | ≈ 50% |
| 输出频率 | CLK / N | CLK / N |
| 重装机制 | 计数到 1 时 OUT 变低,到 0 时重装 | 计数到 0 时自动重装,交替翻转 OUT |
> [!example] 为什么同样分频 N,方式 3 的半周期是 ⌈N/2⌉?
> 方式 3 产生对称方波,OUT 在每个半周期翻转一次。当 N 为偶数时,每个半周期 = N/2;当 N 为奇数时,两个半周期交替为 ⌈N/2⌉ 和 ⌊N/2⌋,整体仍近似 50% 占空比。
> 例如 N=5:前半周期 3 个 CLK → OUT 翻转,后半周期 2 个 CLK → OUT 再翻转,周期长度仍为 5 个 CLK。
#### 4.4 方式 0 详解(计数结束中断)
方式 0 是理解其他方式的基础,它的行为最直观:
- **写入初值后**:OUT 立即从低变高,计数器开始递减
- **计数到 0 时**:OUT 从高变低,发出中断请求
- **之后**:停止计数,OUT 保持低电平(除非重新写入新初值)
```mermaid
sequenceDiagram
participant CPU
participant 8254
participant OUT
CPU->>8254: 写入计数初值 N
Note over 8254: OUT 变高,开始计数
loop N 个 CLK 下降沿
8254->>8254: 每次下降沿减 1
end
8254->>OUT: OUT 变低 (计数到 0)
Note over OUT: 发出中断请求
```
> [!question] 方式 0 下 GATE 的作用是什么?
> - **GATE=1**:正常计数
> - **GATE=0**:暂停计数,计数值冻结不变
> - **GATE 上升沿**:重新装入初值并恢复计数
> - 注意方式 0 不会自动重装,要再次触发必须**重新写入新的初值**。
### 5. 计数操作机制
#### 5.1 计数器寄存器 vs 计数锁存器
> [!important] 每个计数器有两个 16 位寄存器
> - **计数器寄存器(Counter Register)**:正在执行递减计数,随 CLK 不断变化
> - **计数锁存器(Count Latch Register)**:保存某一时刻被"冻结"的计数值
> - **读写缓冲器**:两端都连接的桥梁,负责 CPU 与内部寄存器的数据交换
>
> **关键规则**:当 CPU 读取某个计数器的值时,该计数器的当前值会被复制到锁存器中;而其余 2 个计数器不受影响,继续保持原有状态。
```mermaid
flowchart LR
CPU["CPU 总线"] <--> BR["读写缓冲器"]
BR <--> CR["计数器寄存器<br/>正在计数"]
BR <--> CLR["计数锁存器<br/>保护的值"]
CR -.CLK下降沿减1.-> CR
CPU --读操作--> CLR
CPU --写控制字+锁存命令--> CLR
```
#### 5.1 计数过程中改写初值
```mermaid
flowchart TD
S["正在计数中..."] --> W["CPU 写入新计数初值"]
W --> R1{"是否在当前\n计数周期内?"}
R1 -- 是 --> F1["新初值在当前周期\n不生效"]
F1 --> F2["等当前周期结束后\n才使用新初值"]
R1 -- 否 / 下一个周期 --> F3["新初值立即生效"]
F3 --> F4["下次计数使用新值"]
```
> [!warning] 易错点
> 在计数过程中向正在工作的计数器写入新的计数初值:
> - **不影响当前正在进行的计数**——新初值要在当前计数周期结束后才会被采用
> - 这意味如果你想在下次计数就用新值,需要在当前计数**即将结束前**写入新初值
### 6. 计数初值计算(核心公式)
$$
计数初值 = \frac{f_{CLK}}{f_{OUT}}
$$
> [!example] 试卷真题计算汇总
>
> | 题目来源 | CLK 频率 | 要求频率 | 计数初值 | 控制字 |
> |:--------:|:--------:|:--------:|:--------:|:------:|
> | 试题 A | 2 MHz | 2000 Hz | 1000 (03E8H) | 34H |
> | 试题 B | 1.193182 MHz | 100 Hz | 11932 (2EA4H) | 36H |
> | 试题 B | 1 MHz | 1 kHz | 1000 (03E8H) | 36H |
> | 试题 C | 1.19318 MHz | 1 kHz | 1193 (04A9H) | 36H |
> | 试题 D | 1 MHz | 1 kHz | 1000 (03E8H) | 36H |
### 6. BCD 计数 vs 二进制计数
| 项目 | 二进制(D0=0) | BCD(D0=1) |
|------|---------------|-------------|
| 计数范围 | 0000H~FFFFH (0~65535) | 0000~9999 |
| 初值装入 | 直接装入十六进制值 | 按 BCD 码装入 |
| 初值 1000 | 03E8H | 1000 (低字节=00, 高字节=10) |
> [!warning] BCD 方式装入初值的坑
> BCD 方式下初值 11932 的装入:
> - 低字节 = **32H**(低 2 位 BCD)
> - 高字节 = **11H**(高 2 位 BCD)
>
> 而不是 2EA4H!控制字也要从 36H 改为 **37H**(D0=1)。
### 7. 初始化编程模板
```asm
; 8254 初始化通用模板
; 假设端口地址为 PORT_0 ~ PORT_3
; 步骤 1:写控制字
MOV DX, PORT_3 ; 控制寄存器地址
MOV AL, 36H ; 控制字:计数器0, 方式3, 先低后高, 二进制
OUT DX, AL
; 步骤 2:写计数初值(先低后高)
MOV DX, PORT_0 ; 计数器 0 地址
MOV AX, 03E8H ; 计数初值 = 1000
OUT DX, AL ; 先写低字节 E8H
MOV AL, AH
OUT DX, AL ; 再写高字节 03H
```
> [!danger] 易错点
> 写初值时必须 **先低后高**(与控制字 D5D4=11 对应),而且低字节和高字节要 **分两次** `OUT` 到同一个端口地址。用 `OUT DX, AX` 一次写 16 位是错误的。
### 8. 综合应用与考试技巧
#### 8.1 PC 系统定时器实例(经典案例)
PC 机的 Intel 8253(兼容 8254)系统时钟使用著名的 **1.193182 MHz** 晶体振荡器频率作为 CLK 输入,产生标准 **100 Hz** 的周期中断:
| 参数 | 值 |
|------|-----|
| CLK 频率 | 1.193182 MHz = 1,193,182 Hz |
| 目标频率 | 100 Hz |
| 计数初值 | 1,193,182 ÷ 100 = **11,931**(取整为 11931 ≈ 11932) |
| 控制字(方式 3,二进制) | 36H |
这个 100 Hz 中断被操作系统用作时间基准——每秒触发 100 次,每次更新系统时钟滴答数(tick count)。这就是为什么老式 Windows 系统的 `GetTickCount()` 精度约为 **10ms**(1/100 s)。
> [!question] 如果改用 BCD 方式实现 100 Hz 中断,初值如何写入?
> BCD 方式下,11931 按四位十进制数字处理:**1,1,9,31** → 高字节=11H,低字节=31H(注意不是 BC 码,而是直接按十进制位拆分)。控制字改为 **37H**。
#### 8.2 串级连接(级联)
当单个 16 位计数器不够用时,可以用两个计数器串级,形成 **32 位** 或更长的分频:
```mermaid
flowchart LR
CLK1["CLK1"] --> C1["计数器 0<br/>分频 N1"]
C1 -- OUT0 驱动 CLK1 --> C2["计数器 1<br/>分频 N2"]
C2 -- OUT1 --> FINAL["最终输出<br/>总分频 = N1 × N2"]
```
> **总分频比 = N₁ × N₂**
>
> 例如:计数器 0 设 N₁=1000,计数器 1 设 N₂=1000,输入 CLK=1 MHz,则最终输出频率 = 1,000,000 ÷ (1000×1000) = **1 Hz**。
#### 8.3 典型考试题解步骤
遇到 8254 计算题时,按以下步骤解题不会遗漏:
```mermaid
flowchart TD
S["题目给出:<br/>f_CLK, f_OUT, 工作方式"] --> A["Step 1: 计算计数初值<br/>N = f_CLK / f_OUT"]
A --> B["Step 2: 将 N 转换为十六进制<br/>例:1000 → 03E8H"]
B --> C["Step 3: 确定控制字<br/>选择计数器 + 读写格式 + 工作方式 + 数制"]
C --> D["Step 4: 写出初始化汇编代码<br/>先写控制字,再分两次写入初值"]
D --> E["Step 5: 检查边界情况<br/>BCD方式? 奇数N? 是否需级联?"]
```
**完整示例**:用 2 MHz 时钟源产生 2000 Hz 方波,选计数器 0,方式 3,二进制。
| 步骤 | 操作 | 结果 |
|------|------|------|
| ① 计数初值 | 2,000,000 ÷ 2,000 | **1000** = **03E8H** |
| ② 控制字 | 计数器0(00)+先低后高(11)+方式3(011)+二进制(0) | **36H** |
| ③ 端口基址假设 | 设 PORT_BASE | 计数器0→PORT_BASE, 控制寄存器→PORT_BASE+3 |
| ④ 初始化代码 | 见下方 | — |
```asm
; 初始化计数器 0,产生 2000Hz 方波
MOV DX, BASE_ADDR + 3 ; 控制寄存器
MOV AL, 36H ; 计数器0, 方式3, 先低后高, 二进制
OUT DX, AL
MOV DX, BASE_ADDR ; 计数器 0
MOV AX, 03E8H ; 计数初值 1000
OUT DX, AL ; 先写低字节 E8H
MOV AL, AH ; 移动高字节
OUT DX, AL ; 再写高字节 03H
```
#### 8.4 快速自查清单
做题时逐项确认:
- [ ] 计数初值计算是否正确?(单位统一:MHz↔Hz)
- [ ] 十六进制转换是否正确?
- [ ] 控制字中 **D5D4** 是否匹配你编写的写入顺序?(若只需写一个字节可用 01/10,但大多数情况用 11)
- [ ] **BCD 方式**下 D0=1,且初值按十进制位拆分而非十六进制
- [ ] 奇数分频时方式 3 的实际占空比不是精确 50%(但仍可接受)
- [ ] 是否需要**串级**?(超出 65535 时需要)
### 9. 性能参数速查
| 参数 | 值 |
|------|-----|
| 最高 CLK 频率(8254-5) | **5 MHz** |
| 最小访问时间 | **500 ns** |
| 计数范围(二进制) | 1 ~ 65536 |
| 计数范围(BCD) | 1 ~ 9999 |
| 计数速率 | 每个 CLK **下降沿**减 1 |
## 关联笔记
- [[8255A可编程并行接口]]
- [[8259A中断控制器]]
- [[8251A串行通信接口]]
- [[接口与通讯技术/试题册/index|试题册索引]]