第9章 计时器与时序控制系统设计详解
时序控制是计算机系统中的基础功能之一,通过精确的时间计量和控制,可以实现各种定时任务、时钟同步和精准延时等功能。本章将详细讲解8253/8254定时计数器芯片的工作原理、编程方法和实际应用案例,帮助读者全面理解时序控制系统的设计与实现。
9.1 8253/8254时序控制器基础
9.1.1 8253/8254时序控制器的结构与引脚功能
8253/8254是Intel设计的可编程定时器/计数器芯片,广泛应用于各种需要精确时序控制的场合。8254是8253的增强版本,两者在功能上基本兼容,但8254具有更好的可靠性和更高的工作频率。
内部结构:
8253/8254内部包含三个独立的16位计数器(通常称为Counter 0、Counter 1和Counter 2),每个计数器都可以独立工作,执行不同的计时任务。
芯片内部主要组件包括:
三个16位计数器数据总线缓冲器读/写控制逻辑控制寄存器
引脚功能:
8253/8254采用24引脚DIP封装,主要引脚功能如下:
D₀-D₇:8位双向数据总线,用于CPU与芯片之间的数据传输CLK 0、CLK 1、CLK 2:三个计数器的时钟输入引脚OUT 0、OUT 1、OUT 2:三个计数器的输出引脚GATE 0、GATE 1、GATE 2:三个计数器的门控制引脚A₀、A₁:地址输入线,用于选择内部寄存器CS̅:片选信号,低电平有效RD̅:读信号,低电平有效WR̅:写信号,低电平有效VCC、GND:电源和地线
下面是一段C语言模拟8253/8254内部结构的代码示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 模拟8254计时器内部结构
typedef struct {
// 三个16位计数器
uint16_t counters[3];
// 三个计数器的当前模式
uint8_t modes[3];
// 三个计数器的初始计数值
uint16_t initial_counts[3];
// 三个计数器的门控状态
bool gates[3];
// 三个计数器的输出状态
bool outputs[3];
// 控制字寄存器
uint8_t control_register;
// 锁存器状态
bool latched[3];
// 锁存值
uint16_t latch_values[3];
// 读写状态(0=未设置, 1=低字节, 2=高字节)
uint8_t rw_state[3];
} Timer8254;
// 初始化8254计时器
void init_timer(Timer8254 *timer) {
for (int i = 0; i < 3; i++) {
timer->counters[i] = 0xFFFF; // 默认最大值
timer->modes[i] = 0;
timer->initial_counts[i] = 0;
timer->gates[i] = true;
timer->outputs[i] = false;
timer->latched[i] = false;
timer->latch_values[i] = 0;
timer->rw_state[i] = 0;
}
timer->control_register = 0;
printf("8254 定时器已初始化
");
}
// 打印计时器状态
void print_timer_status(Timer8254 *timer) {
printf("
8254 定时器状态:
");
printf("--------------------
");
for (int i = 0; i < 3; i++) {
printf("计数器 %d:
", i);
printf(" 当前值: 0x%04X (%u)
", timer->counters[i], timer->counters[i]);
printf(" 工作模式: %d
", timer->modes[i]);
printf(" 初始计数值: 0x%04X (%u)
", timer->initial_counts[i], timer->initial_counts[i]);
printf(" 门控状态: %s
", timer->gates[i] ? "启用" : "禁用");
printf(" 输出状态: %s
", timer->outputs[i] ? "高电平" : "低电平");
printf(" 锁存状态: %s
", timer->latched[i] ? "已锁存" : "未锁存");
printf(" 读写状态: %s
",
timer->rw_state[i] == 0 ? "未设置" :
timer->rw_state[i] == 1 ? "低字节" : "高字节");
printf("
");
}
}
int main() {
Timer8254 timer;
// 初始化计时器
init_timer(&timer);
// 打印初始状态
print_timer_status(&timer);
return 0;
}
此代码模拟了8253/8254定时器的内部结构,包含三个16位计数器及相关控制状态。真实硬件中,这些状态会通过芯片内部的晶体管电路实现。
9.1.2 8253/8254定时计数器的运行模式
8253/8254可以工作在6种不同的模式下,每种模式都有特定的计时行为和输出特性。理解这些模式对正确编程和使用定时器至关重要。
模式0:计数终止模式
计数器被设置初始值后,开始向下计数当计数到0时,输出信号变为高电平并保持门控信号(GATE)为低电平时暂停计数典型应用:事件计数、产生延时
模式1:可编程单稳态模式
输出初始为高电平当触发信号(GATE从低到高跳变)到来时,输出变为低电平计数器开始从初始值向下计数,计数到0时输出恢复高电平典型应用:产生可编程宽度的脉冲
模式2:频率发生器模式
产生连续的方波信号计数器从初始值N开始计数,到1时输出短暂低电平脉冲然后自动重装载初始值N继续计数产生周期为N个时钟的方波,占空比接近50%典型应用:产生稳定的频率信号
模式3:方波发生器模式
类似模式2,但产生的是精确50%占空比的方波对于偶数计数值N,输出为高电平N/2个时钟,低电平N/2个时钟对于奇数计数值N,高电平为(N+1)/2个时钟,低电平为(N-1)/2个时钟典型应用:时钟信号生成
模式4:软件触发选通模式
计数器加载后输出保持高电平计数到0时,输出产生一个时钟周期的低电平脉冲不会自动重装载,需要软件重新写入计数值典型应用:精确定时触发
模式5:硬件触发选通模式
类似模式4,但需要硬件触发(GATE从低到高跳变)才开始计数计数到0时,输出产生一个时钟周期的低电平脉冲典型应用:硬件同步的精确定时
下面是一个模拟这些工作模式的C语言函数:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
// 定义工作模式
#define MODE0_INTERRUPT_ON_TERMINAL_COUNT 0
#define MODE1_HARDWARE_RETRIGGERABLE 1
#define MODE2_RATE_GENERATOR 2
#define MODE3_SQUARE_WAVE_GENERATOR 3
#define MODE4_SOFTWARE_TRIGGERED_STROBE 4
#define MODE5_HARDWARE_TRIGGERED_STROBE 5
// 模拟8254计时器计数器
typedef struct {
uint16_t counter; // 当前计数值
uint16_t initial_value; // 初始计数值
uint8_t mode; // 工作模式
bool gate; // 门控信号状态
bool output; // 输出信号状态
bool counting; // 是否正在计数
} Counter8254;
// 初始化计数器
void init_counter(Counter8254 *counter, uint16_t initial_value, uint8_t mode) {
counter->counter = initial_value;
counter->initial_value = initial_value;
counter->mode = mode;
counter->gate = true;
counter->counting = false;
// 根据模式设置初始输出状态
switch (mode) {
case MODE0_INTERRUPT_ON_TERMINAL_COUNT:
counter->output = false; // 初始为低电平
break;
case MODE1_HARDWARE_RETRIGGERABLE:
case MODE2_RATE_GENERATOR:
case MODE3_SQUARE_WAVE_GENERATOR:
case MODE4_SOFTWARE_TRIGGERED_STROBE:
case MODE5_HARDWARE_TRIGGERED_STROBE:
counter->output = true; // 初始为高电平
break;
default:
counter->output = false;
break;
}
// 在模式0、2、3、4中,写入初始值后就开始计数
if (mode == MODE0_INTERRUPT_ON_TERMINAL_COUNT ||
mode == MODE2_RATE_GENERATOR ||
mode == MODE3_SQUARE_WAVE_GENERATOR ||
mode == MODE4_SOFTWARE_TRIGGERED_STROBE) {
counter->counting = counter->gate;
}
printf("计数器初始化:模式%d, 初值=%u, 输出=%s
",
mode, initial_value, counter->output ? "高" : "低");
}
// 设置门控信号
void set_gate(Counter8254 *counter, bool gate) {
bool prev_gate = counter->gate;
counter->gate = gate;
// 处理门控变化引起的行为
switch (counter->mode) {
case MODE0_INTERRUPT_ON_TERMINAL_COUNT:
case MODE2_RATE_GENERATOR:
case MODE3_SQUARE_WAVE_GENERATOR:
// 门控影响计数状态
counter->counting = gate;
break;
case MODE1_HARDWARE_RETRIGGERABLE:
case MODE5_HARDWARE_TRIGGERED_STROBE:
// 门控上升沿触发
if (!prev_gate && gate) {
counter->counter = counter->initial_value;
counter->output = false; // 输出变为低电平(模式1)
counter->counting = true;
}
break;
case MODE4_SOFTWARE_TRIGGERED_STROBE:
// 门控不影响模式4的触发
break;
}
printf("门控信号设置为%s
", gate ? "高" : "低");
}
// 模拟时钟滴答
void clock_tick(Counter8254 *counter) {
if (!counter->counting) {
return; // 未计数状态
}
// 根据不同模式处理计数
switch (counter->mode) {
case MODE0_INTERRUPT_ON_TERMINAL_COUNT:
if (counter->counter > 0) {
counter->counter--;
if (counter->counter == 0) {
counter->output = true; // 输出变为高电平
printf("模式0: 计数终止,输出变为高
");
}
}
break;
case MODE1_HARDWARE_RETRIGGERABLE:
if (counter->counter > 0) {
counter->counter--;
if (counter->counter == 0) {
counter->output = true; // 输出变为高电平
counter->counting = false;
printf("模式1: 单稳态脉冲结束,输出变为高
");
}
}
break;
case MODE2_RATE_GENERATOR:
if (counter->counter > 0) {
counter->counter--;
if (counter->counter == 0) {
counter->output = false; // 输出产生低电平脉冲
printf("模式2: 计数到0,输出低脉冲
");
counter->counter = counter->initial_value; // 自动重装载
} else {
counter->output = true; // 正常时为高电平
}
}
break;
case MODE3_SQUARE_WAVE_GENERATOR:
if (counter->counter > 0) {
counter->counter--;
// 计算占空比切换点
uint16_t half = counter->initial_value / 2;
if (counter->initial_value % 2) { // 奇数
if (counter->counter == half) {
counter->output = false; // 切换到低电平
printf("模式3: 输出变为低
");
} else if (counter->counter == 0) {
counter->output = true; // 切换到高电平
counter->counter = counter->initial_value; // 重装载
printf("模式3: 输出变为高,重装载
");
}
} else { // 偶数
if (counter->counter == half) {
counter->output = !counter->output; // 切换电平
printf("模式3: 输出翻转为%s
", counter->output ? "高" : "低");
}
if (counter->counter == 0) {
counter->counter = counter->initial_value; // 重装载
printf("模式3: 重装载
");
}
}
}
break;
case MODE4_SOFTWARE_TRIGGERED_STROBE:
if (counter->counter > 0) {
counter->counter--;
if (counter->counter == 0) {
counter->output = false; // 输出产生低电平脉冲
printf("模式4: 计数到0,输出低脉冲
");
// 下一个时钟恢复高电平
} else {
counter->output = true;
}
} else {
counter->output = true; // 保持高电平
}
break;
case MODE5_HARDWARE_TRIGGERED_STROBE:
if (counter->counter > 0) {
counter->counter--;
if (counter->counter == 0) {
counter->output = false; // 输出产生低电平脉冲
counter->counting = false;
printf("模式5: 计数到0,输出低脉冲
");
} else {
counter->output = true;
}
} else {
counter->output = true; // 保持高电平
}
break;
}
}
// 打印计数器状态
void print_counter_status(Counter8254 *counter) {
printf("计数值=%u, 输出=%s, 计数状态=%s
",
counter->counter,
counter->output ? "高" : "低",
counter->counting ? "计数中" : "停止");
}
// 模拟特定模式运行
void simulate_mode(uint8_t mode, uint16_t initial_value, int ticks) {
Counter8254 counter;
printf("
=======================================
");
printf("模拟模式 %d (初值=%u, 时钟周期=%d)
", mode, initial_value, ticks);
printf("=======================================
");
init_counter(&counter, initial_value, mode);
printf("
初始状态: ");
print_counter_status(&counter);
// 特定模式的特殊操作
if (mode == MODE1_HARDWARE_RETRIGGERABLE ||
mode == MODE5_HARDWARE_TRIGGERED_STROBE) {
// 需要硬件触发,模拟一次GATE上升沿
printf("
模拟GATE上升沿触发...
");
set_gate(&counter, false);
set_gate(&counter, true);
}
// 模拟时钟周期
for (int i = 0; i < ticks; i++) {
printf("
第%d个时钟周期: ", i + 1);
clock_tick(&counter);
print_counter_status(&counter);
// 模式1和模式5的再次触发
if ((mode == MODE1_HARDWARE_RETRIGGERABLE ||
mode == MODE5_HARDWARE_TRIGGERED_STROBE) && i == ticks / 2) {
printf("
模拟中间再次触发...
");
set_gate(&counter, false);
set_gate(&counter, true);
}
}
}
int main() {
printf("8254定时计数器工作模式模拟
");
printf("=========================
");
// 模拟各种工作模式
simulate_mode(MODE0_INTERRUPT_ON_TERMINAL_COUNT, 5, 10);
simulate_mode(MODE1_HARDWARE_RETRIGGERABLE, 5, 10);
simulate_mode(MODE2_RATE_GENERATOR, 5, 15);
simulate_mode(MODE3_SQUARE_WAVE_GENERATOR, 6, 20); // 偶数
simulate_mode(MODE3_SQUARE_WAVE_GENERATOR, 5, 20); // 奇数
simulate_mode(MODE4_SOFTWARE_TRIGGERED_STROBE, 5, 10);
simulate_mode(MODE5_HARDWARE_TRIGGERED_STROBE, 5, 10);
return 0;
}
这段代码详细模拟了8253/8254计数器在各种工作模式下的行为。通过运行可以观察不同模式下计数器的计数过程、输出信号的变化以及门控信号的影响。
9.1.3 8253/8254的编程接口与方法
8253/8254通过I/O端口与CPU通信,编程主要涉及控制字的设置和计数值的写入/读取。
端口分配:
通常在PC系统中,8253/8254的端口地址分配如下:
端口40h:计数器0端口41h:计数器1端口42h:计数器2端口43h:控制寄存器
控制字格式:
控制字是一个8位值,用于配置计数器的工作模式和操作方式:
apache
D7 D6 | D5 D4 | D3 D2 | D1 D0
SC1 SC0 | RW1 RW0 | M2 M1 M0 | BCD
SC1,SC0:选择计数器(00=计数器0, 01=计数器1, 10=计数器2, 11=控制命令)RW1,RW0:读/写方式(00=锁存命令, 01=只读/写低字节, 10=只读/写高字节, 11=先低后高字节)M2,M1,M0:工作模式(000=模式0, 001=模式1, 010/110=模式2, 011/111=模式3, 100=模式4, 101=模式5)BCD:计数方式(0=二进制计数, 1=BCD计数)
编程步骤:
向控制寄存器写入控制字,配置计数器根据控制字中的读/写方式,向对应计数器写入计数值如需读取计数值,可以直接读取计数器端口或先发送锁存命令
下面是一个完整的8253/8254编程示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
// 8254端口地址(模拟)
#define TIMER0_PORT 0x40
#define TIMER1_PORT 0x41
#define TIMER2_PORT 0x42
#define TIMER_CTRL_PORT 0x43
// 控制字位定义
#define SC_COUNTER_0 0x00 // 选择计数器0
#define SC_COUNTER_1 0x40 // 选择计数器1
#define SC_COUNTER_2 0x80 // 选择计数器2
#define SC_READ_BACK 0xC0 // 读回命令
#define RW_LATCH 0x00 // 锁存命令
#define RW_LSB_ONLY 0x10 // 只读/写低字节
#define RW_MSB_ONLY 0x20 // 只读/写高字节
#define RW_LSB_MSB 0x30 // 先低后高字节
#define MODE_0 0x00 // 模式0:计数终止
#define MODE_1 0x02 // 模式1:可编程单稳态
#define MODE_2 0x04 // 模式2:频率发生器
#define MODE_3 0x06 // 模式3:方波发生器
#define MODE_4 0x08 // 模式4:软件触发选通
#define MODE_5 0x0A // 模式5:硬件触发选通
#define COUNT_BINARY 0x00 // 二进制计数
#define COUNT_BCD 0x01 // BCD计数
// 模拟的8254状态
typedef struct {
uint16_t counter_values[3]; // 三个计数器的当前值
uint8_t control_reg; // 控制寄存器
uint8_t latch_status[3]; // 锁存状态(0=未锁存, 1=低字节锁存, 2=高字节锁存)
uint16_t latch_values[3]; // 锁存的值
} Timer8254State;
Timer8254State timer_state = {
.counter_values = {0xFFFF, 0xFFFF, 0xFFFF},
.control_reg = 0,
.latch_status = {0, 0, 0},
.latch_values = {0, 0, 0}
};
// 模拟outb - 向端口写入一个字节
void outb(uint16_t port, uint8_t value) {
printf("向端口0x%04X写入0x%02X
", port, value);
if (port == TIMER_CTRL_PORT) {
// 写入控制寄存器
timer_state.control_reg = value;
// 解析控制字
uint8_t counter_select = (value >> 6) & 0x03;
uint8_t rw_mode = (value >> 4) & 0x03;
uint8_t operation_mode = (value >> 1) & 0x07;
uint8_t bcd_mode = value & 0x01;
// 处理读回命令
if (counter_select == 3) { // 读回命令
printf("执行读回命令
");
return;
}
// 打印解析后的控制字信息
printf("设置计数器%d:", counter_select);
switch (rw_mode) {
case 0: printf("锁存操作, "); break;
case 1: printf("只读/写低字节, "); break;
case 2: printf("只读/写高字节, "); break;
case 3: printf("先低后高字节, "); break;
}
printf("模式%d, ", operation_mode);
printf("%s计数
", bcd_mode ? "BCD" : "二进制");
// 处理锁存命令
if (rw_mode == 0) {
timer_state.latch_values[counter_select] = timer_state.counter_values[counter_select];
timer_state.latch_status[counter_select] = 1; // 锁存低字节
printf("锁存计数器%d当前值: %u (0x%04X)
",
counter_select,
timer_state.latch_values[counter_select],
timer_state.latch_values[counter_select]);
}
} else if (port >= TIMER0_PORT && port <= TIMER2_PORT) {
// 写入计数器
uint8_t counter_num = port - TIMER0_PORT;
uint8_t rw_mode = (timer_state.control_reg >> 4) & 0x03;
// 根据读/写模式处理
if (rw_mode == 1) { // 只写低字节
timer_state.counter_values[counter_num] = (timer_state.counter_values[counter_num] & 0xFF00) | value;
printf("设置计数器%d低字节: 0x%02X, 当前值: %u (0x%04X)
",
counter_num, value,
timer_state.counter_values[counter_num],
timer_state.counter_values[counter_num]);
} else if (rw_mode == 2) { // 只写高字节
timer_state.counter_values[counter_num] = (timer_state.counter_values[counter_num] & 0x00FF) | (value << 8);
printf("设置计数器%d高字节: 0x%02X, 当前值: %u (0x%04X)
",
counter_num, value,
timer_state.counter_values[counter_num],
timer_state.counter_values[counter_num]);
} else if (rw_mode == 3) { // 先低后高字节
static uint8_t lsb_values[3] = {0, 0, 0};
static bool expecting_msb[3] = {false, false, false};
if (!expecting_msb[counter_num]) { // 期望低字节
lsb_values[counter_num] = value;
expecting_msb[counter_num] = true;
printf("设置计数器%d低字节: 0x%02X, 等待高字节
", counter_num, value);
} else { // 期望高字节
timer_state.counter_values[counter_num] = (value << 8) | lsb_values[counter_num];
expecting_msb[counter_num] = false;
printf("设置计数器%d高字节: 0x%02X, 当前值: %u (0x%04X)
",
counter_num, value,
timer_state.counter_values[counter_num],
timer_state.counter_values[counter_num]);
}
}
}
}
// 模拟inb - 从端口读取一个字节
uint8_t inb(uint16_t port) {
uint8_t value = 0;
if (port >= TIMER0_PORT && port <= TIMER2_PORT) {
uint8_t counter_num = port - TIMER0_PORT;
uint8_t rw_mode = (timer_state.control_reg >> 4) & 0x03;
// 检查是否有锁存值
if (timer_state.latch_status[counter_num]) {
if (timer_state.latch_status[counter_num] == 1) { // 读取锁存的低字节
value = timer_state.latch_values[counter_num] & 0xFF;
if (rw_mode == 3) { // 如果是先低后高,更新锁存状态
timer_state.latch_status[counter_num] = 2; // 下次读取高字节
} else {
timer_state.latch_status[counter_num] = 0; // 锁存完成
}
} else if (timer_state.latch_status[counter_num] == 2) { // 读取锁存的高字节
value = (timer_state.latch_values[counter_num] >> 8) & 0xFF;
timer_state.latch_status[counter_num] = 0; // 锁存完成
}
} else {
// 直接读取计数器值
if (rw_mode == 1) { // 只读低字节
value = timer_state.counter_values[counter_num] & 0xFF;
} else if (rw_mode == 2) { // 只读高字节
value = (timer_state.counter_values[counter_num] >> 8) & 0xFF;
} else if (rw_mode == 3) { // 先低后高字节
static bool read_lsb[3] = {true, true, true};
if (read_lsb[counter_num]) { // 读取低字节
value = timer_state.counter_values[counter_num] & 0xFF;
read_lsb[counter_num] = false;
} else { // 读取高字节
value = (timer_state.counter_values[counter_num] >> 8) & 0xFF;
read_lsb[counter_num] = true;
}
}
}
}
printf("从端口0x%04X读取0x%02X
", port, value);
return value;
}
// 设置计数器
void set_timer_counter(uint8_t counter, uint8_t mode, uint16_t count) {
uint8_t control_byte = 0;
// 构建控制字
switch (counter) {
case 0: control_byte |= SC_COUNTER_0; break;
case 1: control_byte |= SC_COUNTER_1; break;
case 2: control_byte |= SC_COUNTER_2; break;
default: return; // 无效计数器
}
control_byte |= RW_LSB_MSB; // 使用先低后高字节模式
switch (mode) {
case 0: control_byte |= MODE_0; break;
case 1: control_byte |= MODE_1; break;
case 2: control_byte |= MODE_2; break;
case 3: control_byte |= MODE_3; break;
case 4: control_byte |= MODE_4; break;
case 5: control_byte |= MODE_5; break;
default: return; // 无效模式
}
control_byte |= COUNT_BINARY; // 使用二进制计数
// 写入控制字
printf("
设置计数器%d为模式%d,计数值%u (0x%04X)
", counter, mode, count, count);
outb(TIMER_CTRL_PORT, control_byte);
// 写入计数值,先低后高
uint8_t low_byte = count & 0xFF;
uint8_t high_byte = (count >> 8) & 0xFF;
outb(TIMER0_PORT + counter, low_byte);
outb(TIMER0_PORT + counter, high_byte);
}
// 读取计数器当前值
uint16_t read_timer_counter(uint8_t counter) {
if (counter > 2) {
return 0; // 无效计数器
}
// 发送锁存命令
uint8_t control_byte = (counter << 6) | RW_LATCH;
printf("
读取计数器%d当前值
", counter);
outb(TIMER_CTRL_PORT, control_byte);
// 读取当前值
uint8_t low_byte = inb(TIMER0_PORT + counter);
uint8_t high_byte = inb(TIMER0_PORT + counter);
uint16_t value = (high_byte << 8) | low_byte;
printf("计数器%d当前值: %u (0x%04X)
", counter, value, value);
return value;
}
// 模拟计数器倒计数
void simulate_counting(uint8_t counter, int ticks) {
printf("
模拟计数器%d倒计数%d个时钟周期
", counter, ticks);
for (int i = 0; i < ticks; i++) {
if (timer_state.counter_values[counter] > 0) {
timer_state.counter_values[counter]--;
} else {
// 根据模式可能需要重载
uint8_t mode = (timer_state.control_reg >> 1) & 0x07;
if (mode == 2 || mode == 3) { // 模式2和3会自动重载
timer_state.counter_values[counter] = 0xFFFF; // 假设初值
printf("计数器%d重载
", counter);
}
}
}
}
int main() {
printf("8253/8254计时器编程示例
");
printf("=====================
");
// 配置计数器0为模式3(方波发生器)
set_timer_counter(0, 3, 1193); // PC标准配置:1.193182MHz/1000Hz = 1193
// 配置计数器1为模式2(频率发生器)
set_timer_counter(1, 2, 2000);
// 配置计数器2为模式0(计数终止)
set_timer_counter(2, 0, 5000);
// 模拟倒计数
simulate_counting(0, 500);
simulate_counting(1, 1000);
simulate_counting(2, 2000);
// 读取当前计数值
read_timer_counter(0);
read_timer_counter(1);
read_timer_counter(2);
return 0;
}
这个程序模拟了8253/8254定时器的编程接口,包括控制字的构建、计数值的设置和读取,以及基本的计数过程。在实际系统中,我们通过向特定端口读写数据来操作定时器,而这个程序则通过模拟函数展示了这个过程。
9.2 8253/8254在PC系统中的应用
8253/8254定时计数器在PC系统中扮演着至关重要的角色,为操作系统和应用程序提供了基本的时序控制和定时服务。
9.2.1 系统定时中断与周期性更新
在标准的PC系统中,8253/8254的计数器0被设置为模式3(方波发生器模式),产生约18.2Hz(精确值为1193182Hz/65536 ≈ 18.2065Hz)的时钟信号。这个信号连接到8259A中断控制器的IRQ0,生成系统的定时中断(INT 08h)。
操作系统使用这个定时中断来维护系统时钟、实现多任务调度和提供各种定时服务。每个定时中断都会增加系统时间计数器,系统通过这个计数器来获取当前时间。
下面是一个模拟PC系统定时中断处理的C语言程序:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
// 模拟PC系统环境
typedef struct {
uint32_t ticks; // 系统启动后的tick数
uint32_t day_ticks; // 从午夜开始的tick数
uint8_t timer_interrupt; // 定时中断号
bool timer_active; // 定时器是否激活
timer_t timer_id; // POSIX定时器ID
struct itimerspec timer_spec; // 定时器规格
} PCSystem;
PCSystem pc_system = {
.ticks = 0,
.day_ticks = 0,
.timer_interrupt = 0x08, // INT 08h
.timer_active = false
};
// 模拟BIOS数据区
typedef struct {
uint32_t tick_count; // 从午夜开始的低字节(0x0046C)
uint8_t day_rollover; // 天计数进位标志(0x00470)
uint8_t timer0_divisor[2]; // Timer 0的分频值(0x00471)
} BIOSDataArea;
BIOSDataArea bios_data = {
.tick_count = 0,
.day_rollover = 0,
.timer0_divisor = {0xA9, 0x04} // 1193(0x04A9)
};
// 时间结构
typedef struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
uint16_t millisecond;
} SystemTime;
// 模拟定时中断处理程序
void timer_interrupt_handler(void) {
// 更新系统tick计数
pc_system.ticks++;
// 更新BIOS数据区的tick计数
bios_data.tick_count++;
// 检查是否过了一天(1天 = 1573040个tick ≈ 24小时)
if (bios_data.tick_count >= 1573040) {
bios_data.tick_count = 0;
bios_data.day_rollover = 1;
}
}
// 模拟IRQ0中断处理过程
void handle_irq0(void) {
// 调用定时中断处理程序
timer_interrupt_handler();
}
// POSIX信号处理程序,用于模拟硬件中断
void timer_signal_handler(int signum) {
if (signum == SIGALRM) {
handle_irq0();
}
}
// 启动系统定时器
void start_system_timer(void) {
if (pc_system.timer_active) {
return; // 已经启动
}
// 设置POSIX信号处理
struct sigaction sa;
sa.sa_handler = timer_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
// 创建POSIX定时器
if (timer_create(CLOCK_REALTIME, NULL, &pc_system.timer_id) == -1) {
perror("timer_create");
return;
}
// 设置为约18.2Hz
pc_system.timer_spec.it_interval.tv_sec = 0;
pc_system.timer_spec.it_interval.tv_nsec = 55000000; // 约18.2Hz
pc_system.timer_spec.it_value.tv_sec = 0;
pc_system.timer_spec.it_value.tv_nsec = 55000000;
if (timer_settime(pc_system.timer_id, 0, &pc_system.timer_spec, NULL) == -1) {
perror("timer_settime");
return;
}
pc_system.timer_active = true;
printf("系统定时器已启动 (18.2Hz)
");
}
// 停止系统定时器
void stop_system_timer(void) {
if (!pc_system.timer_active) {
return; // 已经停止
}
// 停止POSIX定时器
pc_system.timer_spec.it_interval.tv_sec = 0;
pc_system.timer_spec.it_interval.tv_nsec = 0;
pc_system.timer_spec.it_value.tv_sec = 0;
pc_system.timer_spec.it_value.tv_nsec = 0;
if (timer_settime(pc_system.timer_id, 0, &pc_system.timer_spec, NULL) == -1) {
perror("timer_settime");
return;
}
pc_system.timer_active = false;
printf("系统定时器已停止
");
}
// 获取当前系统时间
SystemTime get_system_time(void) {
SystemTime time;
uint32_t total_seconds = pc_system.ticks / 18; // 近似每秒18个tick
time.hour = (total_seconds / 3600) % 24;
time.minute = (total_seconds / 60) % 60;
time.second = total_seconds % 60;
time.millisecond = (pc_system.ticks % 18) * (1000 / 18); // 每个tick约55ms
return time;
}
// 打印系统状态
void print_system_status(void) {
SystemTime time = get_system_time();
printf("
系统状态:
");
printf("Ticks: %u
", pc_system.ticks);
printf("BIOS Tick计数: %u
", bios_data.tick_count);
printf("天计数进位: %s
", bios_data.day_rollover ? "是" : "否");
printf("系统时间: %02d:%02d:%02d.%03d
",
time.hour, time.minute, time.second, time.millisecond);
}
int main() {
printf("PC系统定时中断模拟
");
printf("===============
");
// 显示Timer 0的分频值
uint16_t divisor = (bios_data.timer0_divisor[1] << 8) | bios_data.timer0_divisor[0];
printf("Timer 0分频值: %u
", divisor);
printf("时钟频率: 1193182 Hz / %u = %.2f Hz
",
divisor, 1193182.0 / divisor);
// 启动系统定时器
start_system_timer();
// 运行一段时间并监视系统状态
for (int i = 0; i < 10; i++) {
sleep(1); // 休眠1秒
print_system_status();
}
// 停止系统定时器
stop_system_timer();
return 0;
}
这个程序模拟了PC系统中的定时中断机制,包括18.2Hz的系统时钟、BIOS数据区的tick计数以及基于这些tick的系统时间计算。在实际的PC系统中,这些功能由BIOS和操作系统协同完成。
9.2.2 音频产生与扬声器控制
在早期的PC系统中,计数器2通常连接到PC扬声器,用于产生简单的音频信号。通过将计数器设置为模式3(方波发生器)并选择适当的分频值,可以生成不同频率的方波,驱动扬声器发出不同音调的声音。
PC系统中的扬声器控制端口(61h)用于控制扬声器的开关:
位0:计时器通道2门控(Timer 2 Gate)位1:扬声器数据(Speaker Data)位2:控制扬声器(通道1和2的输出)
下面是一个模拟PC扬声器控制的C语言程序:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
// 8254端口定义
#define TIMER2_PORT 0x42
#define TIMER_CTRL_PORT 0x43
// 扬声器控制端口
#define SPEAKER_PORT 0x61
// 模拟I/O端口读写函数
void outb(uint16_t port, uint8_t value);
uint8_t inb(uint16_t port);
// 模拟的系统状态
typedef struct {
uint16_t timer2_counter; // 计数器2的当前值
uint8_t speaker_control; // 扬声器控制端口的值
bool speaker_enabled; // 扬声器是否启用
} SystemState;
SystemState system_state = {
.timer2_counter = 0,
.speaker_control = 0,
.speaker_enabled = false
};
// 设置扬声器频率
void set_speaker_frequency(uint16_t frequency) {
if (frequency == 0) {
printf("错误: 频率不能为0
");
return;
}
// 计算分频值: 1193182 / frequency
uint16_t divisor = 1193182 / frequency;
printf("设置扬声器频率: %u Hz (分频值: %u)
", frequency, divisor);
// 设置计数器2为模式3(方波发生器)
outb(TIMER_CTRL_PORT, 0xB6); // 10 11 011 0 = 选择计数器2,先低后高字节,模式3,二进制计数
// 写入分频值
outb(TIMER2_PORT, divisor & 0xFF); // 低字节
outb(TIMER2_PORT, (divisor >> 8) & 0xFF); // 高字节
// 保存到模拟状态
system_state.timer2_counter = divisor;
}
// 启用扬声器
void enable_speaker(void) {
// 读取当前扬声器控制值
uint8_t speaker_value = inb(SPEAKER_PORT);
// 设置位0和位1以启用扬声器
speaker_value |= 0x03;
// 写回控制端口
outb(SPEAKER_PORT, speaker_value);
printf("扬声器已启用
");
system_state.speaker_enabled = true;
}
// 禁用扬声器
void disable_speaker(void) {
// 读取当前扬声器控制值
uint8_t speaker_value = inb(SPEAKER_PORT);
// 清除位0和位1以禁用扬声器
speaker_value &= 0xFC;
// 写回控制端口
outb(SPEAKER_PORT, speaker_value);
printf("扬声器已禁用
");
system_state.speaker_enabled = false;
}
// 播放一个音调
void play_tone(uint16_t frequency, int duration_ms) {
printf("
播放音调: %u Hz, 持续 %d ms
", frequency, duration_ms);
// 设置频率
set_speaker_frequency(frequency);
// 启用扬声器
enable_speaker();
// 模拟延时
printf("播放中");
for (int i = 0; i < duration_ms / 100; i++) {
printf(".");
fflush(stdout);
usleep(100000); // 100ms
}
printf("
");
// 禁用扬声器
disable_speaker();
}
// 播放一个简单的旋律
void play_melody(void) {
printf("
播放简单旋律
");
printf("=============
");
// 简单的音符频率表
const uint16_t C4 = 262; // 中央C
const uint16_t D4 = 294;
const uint16_t E4 = 330;
const uint16_t F4 = 349;
const uint16_t G4 = 392;
const uint16_t A4 = 440; // 标准A
const uint16_t B4 = 494;
const uint16_t C5 = 523; // 高八度C
// 播放《小星星》旋律
play_tone(C4, 500); // 闪
play_tone(C4, 500); // 闪
play_tone(G4, 500); // 亮
play_tone(G4, 500); // 亮
play_tone(A4, 500); // 小
play_tone(A4, 500); // 星
play_tone(G4, 1000); // 星
play_tone(F4, 500); // 眨
play_tone(F4, 500); // 眨
play_tone(E4, 500); // 眼
play_tone(E4, 500); // 睛
play_tone(D4, 500); // 放
play_tone(D4, 500); // 光
play_tone(C4, 1000); // 芒
printf("
旋律播放完成
");
}
// 模拟outb - 向端口写入一个字节
void outb(uint16_t port, uint8_t value) {
printf("向端口0x%04X写入0x%02X
", port, value);
// 模拟端口行为
switch (port) {
case TIMER_CTRL_PORT:
// 模拟控制寄存器
break;
case TIMER2_PORT:
// 模拟计数器2
static bool expecting_high_byte = false;
static uint8_t low_byte = 0;
if (!expecting_high_byte) {
low_byte = value;
expecting_high_byte = true;
} else {
system_state.timer2_counter = (value << 8) | low_byte;
expecting_high_byte = false;
}
break;
case SPEAKER_PORT:
// 模拟扬声器控制端口
system_state.speaker_control = value;
system_state.speaker_enabled = (value & 0x03) == 0x03;
break;
}
}
// 模拟inb - 从端口读取一个字节
uint8_t inb(uint16_t port) {
uint8_t value = 0;
// 模拟端口行为
switch (port) {
case SPEAKER_PORT:
value = system_state.speaker_control;
break;
}
printf("从端口0x%04X读取0x%02X
", port, value);
return value;
}
// 打印系统状态
void print_system_status(void) {
printf("
系统状态:
");
printf("Timer 2计数值: %u
", system_state.timer2_counter);
printf("频率: %.2f Hz
", system_state.timer2_counter ? 1193182.0 / system_state.timer2_counter : 0);
printf("扬声器控制值: 0x%02X
", system_state.speaker_control);
printf("扬声器状态: %s
", system_state.speaker_enabled ? "启用" : "禁用");
}
int main() {
printf("PC扬声器控制模拟
");
printf("==============
");
// 初始状态
print_system_status();
// 测试基本操作
set_speaker_frequency(1000); // 1kHz
print_system_status();
enable_speaker();
print_system_status();
printf("
模拟播放1kHz音调3秒...
");
for (int i = 0; i < 30; i++) {
printf(".");
fflush(stdout);
usleep(100000); // 100ms
}
printf("
");
disable_speaker();
print_system_status();
// 播放旋律
play_melody();
return 0;
}
这个程序模拟了PC系统中使用8254计数器2和扬声器控制端口来生成音频的过程。它展示了如何设置不同频率、启用/禁用扬声器以及播放简单的旋律。在实际的PC中,这种方法曾被广泛用于游戏和简单的声音提示。
9.2.3 可编程延时与时间测量
8253/8254定时器还可以用于实现精确的时间延迟和测量。通过将计数器设置为特定模式(如模式0或模式2)并加载适当的计数值,可以实现从微秒到秒级的各种延时。
下面是一个使用8254实现精确延时的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
// 8254端口定义
#define TIMER0_PORT 0x40
#define TIMER1_PORT 0x41
#define TIMER2_PORT 0x42
#define TIMER_CTRL_PORT 0x43
// 模拟I/O端口读写函数
void outb(uint16_t port, uint8_t value) {
// 在实际系统中,这会写入硬件端口
printf("写端口0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
// 在实际系统中,这会从硬件端口读取
uint8_t value = 0;
printf("读端口0x%04X: 0x%02X
", port, value);
return value;
}
// 使用Timer 1实现微秒级延时
void delay_us(uint32_t microseconds) {
if (microseconds == 0) return;
// 确保延时不超过定时器能处理的范围
if (microseconds > 54925) {
printf("警告: 延时太长,将被截断
");
microseconds = 54925; // 最大约55ms(65535/1.193182MHz)
}
// 计算计数值
uint16_t count = (uint16_t)((uint32_t)microseconds * 1193182 / 1000000);
printf("延时%u微秒,计数值=%u
", microseconds, count);
// 配置Timer 1为模式0(计数到0中断)
outb(TIMER_CTRL_PORT, 0x30); // 00 11 000 0 = 选择计数器0,读写低高字节,模式0,二进制
// 写入计数值
outb(TIMER1_PORT, count & 0xFF); // 低字节
outb(TIMER1_PORT, (count >> 8) & 0xFF); // 高字节
// 在实际系统中,我们会等待OUT引脚变为高电平
// 这里我们使用usleep来模拟延时
usleep(microseconds);
printf("延时完成
");
}
// 使用Timer 1测量事件执行时间
uint32_t measure_execution_time(void (*function)(void)) {
// 配置Timer 1为模式0(计数到0中断)
outb(TIMER_CTRL_PORT, 0x30); // 00 11 000 0 = 选择计数器0,读写低高字节,模式0,二进制
// 加载最大计数值
outb(TIMER1_PORT, 0xFF); // 低字节
outb(TIMER1_PORT, 0xFF); // 高字节
// 执行被测量函数
function();
// 读取计数器当前值
outb(TIMER_CTRL_PORT, 0x00); // 锁存计数器0
uint8_t low_byte = inb(TIMER1_PORT);
uint8_t high_byte = inb(TIMER1_PORT);
// 计算已经过去的计数
uint16_t counts_elapsed = 0xFFFF - ((high_byte << 8) | low_byte);
// 转换为微秒
uint32_t microseconds = (uint32_t)((uint64_t)counts_elapsed * 1000000 / 1193182);
return microseconds;
}
// 测试函数:执行一些消耗CPU时间的操作
void test_function(void) {
printf("执行测试函数...
");
// 模拟一些工作
volatile int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
printf("测试函数完成, 结果: %d
", sum);
}
// 使用Timer 2实现可变周期脉冲
void generate_pulse(uint16_t frequency, int duration_ms) {
printf("
生成频率为%uHz的脉冲,持续%dms
", frequency, duration_ms);
// 计算分频值
uint16_t divisor = 1193182 / frequency;
// 配置Timer 2为模式3(方波发生器)
outb(TIMER_CTRL_PORT, 0xB6); // 10 11 011 0 = 选择计数器2,读写低高字节,模式3,二进制
// 写入分频值
outb(TIMER2_PORT, divisor & 0xFF); // 低字节
outb(TIMER2_PORT, (divisor >> 8) & 0xFF); // 高字节
// 模拟脉冲生成
printf("生成脉冲中");
for (int i = 0; i < duration_ms / 100; i++) {
printf(".");
fflush(stdout);
usleep(100000); // 100ms
}
printf("
完成
");
}
// 使用时间测量和延时功能实现精确定时循环
void precise_timing_loop(int iterations, int interval_ms) {
printf("
开始精确定时循环:%d次迭代,间隔%dms
", iterations, interval_ms);
for (int i = 0; i < iterations; i++) {
// 记录循环开始时间
struct timespec start_time;
clock_gettime(CLOCK_MONOTONIC, &start_time);
// 执行一些操作
printf("迭代 %d: 执行操作
", i + 1);
// 计算剩余延时时间
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
int64_t elapsed_ns = (current_time.tv_sec - start_time.tv_sec) * 1000000000 +
(current_time.tv_nsec - start_time.tv_nsec);
int64_t target_ns = (int64_t)interval_ms * 1000000;
int64_t remaining_ns = target_ns - elapsed_ns;
if (remaining_ns > 0) {
// 使用延时函数等待剩余时间
int remaining_us = remaining_ns / 1000;
printf("等待剩余时间: %d微秒
", remaining_us);
// 在实际系统中会使用8254定时器等待
usleep(remaining_us);
} else {
printf("警告: 操作耗时超过间隔!
");
}
}
printf("定时循环完成
");
}
int main() {
printf("8254定时器延时与时间测量示例
");
printf("=======================
");
// 测试微秒级延时
printf("测试微秒级延时:
");
printf("--------------
");
delay_us(1000); // 1ms
delay_us(10000); // 10ms
delay_us(50000); // 50ms
// 测试执行时间测量
printf("
测试执行时间测量:
");
printf("----------------
");
uint32_t execution_time = measure_execution_time(test_function);
printf("函数执行时间: %u微秒 (%.2f毫秒)
", execution_time, execution_time / 1000.0);
// 测试脉冲生成
printf("
测试脉冲生成:
");
printf("------------
");
generate_pulse(100, 2000); // 100Hz, 2秒
generate_pulse(1000, 1000); // 1kHz, 1秒
// 测试精确定时循环
printf("
测试精确定时循环:
");
printf("----------------
");
precise_timing_loop(5, 500); // 5次,间隔500ms
return 0;
}
这个程序演示了如何使用8254定时器实现微秒级精确延时、测量执行时间、生成特定频率的脉冲以及实现精确的定时循环。在实际的PC系统中,这些技术被广泛用于各种需要精确时序控制的应用,如硬件驱动、实时控制系统和高精度计时。
9.3 定时计数器的扩展应用
除了基本的时钟信号生成和时间测量功能外,8253/8254定时计数器还可以用于更多创新性的应用。通过组合多个计数器或与其他硬件配合,可以实现更复杂的时序控制功能。
9.3.1 多通道同步定时控制
通过同时使用8253/8254的多个计数器,可以实现多通道同步定时控制,适用于需要精确协调多个设备或动作的场景。
下面是一个多通道同步定时控制的示例程序:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <time.h>
// 8254端口定义
#define TIMER0_PORT 0x40
#define TIMER1_PORT 0x41
#define TIMER2_PORT 0x42
#define TIMER_CTRL_PORT 0x43
// 模拟I/O端口读写函数
void outb(uint16_t port, uint8_t value) {
printf("写端口0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
static uint8_t counter_values[3] = {0, 0, 0};
static int counter_index = 0;
uint8_t value = 0;
if (port >= TIMER0_PORT && port <= TIMER2_PORT) {
counter_index = port - TIMER0_PORT;
value = counter_values[counter_index];
}
printf("读端口0x%04X: 0x%02X
", port, value);
return value;
}
// 定义多通道控制器
typedef struct {
uint16_t periods[3]; // 三个通道的周期(计数值)
uint16_t counters[3]; // 三个通道的当前计数值
bool outputs[3]; // 三个通道的输出状态
bool active[3]; // 三个通道的激活状态
uint32_t trigger_time_us[3]; // 三个通道的触发时间(微秒)
void (*callbacks[3])(void); // 三个通道的回调函数
} MultiChannelTimer;
// 全局实例
MultiChannelTimer timer_controller = {
.periods = {0, 0, 0},
.counters = {0, 0, 0},
.outputs = {false, false, false},
.active = {false, false, false},
.trigger_time_us = {0, 0, 0},
.callbacks = {NULL, NULL, NULL}
};
// 获取当前时间(微秒)
uint64_t get_current_time_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
// 配置单个计时器通道
void configure_timer_channel(uint8_t channel, uint16_t frequency, void (*callback)(void)) {
if (channel > 2) {
printf("错误: 无效的通道号%d
", channel);
return;
}
printf("
配置通道%d: 频率=%uHz
", channel, frequency);
// 计算分频值
uint16_t divisor = frequency > 0 ? 1193182 / frequency : 0;
// 保存配置
timer_controller.periods[channel] = divisor;
timer_controller.counters[channel] = divisor;
timer_controller.outputs[channel] = false;
timer_controller.active[channel] = (frequency > 0);
timer_controller.callbacks[channel] = callback;
// 实际硬件配置
uint8_t command = (channel << 6) | 0x34; // xx 11 010 0 = 选择通道, 读写低高字节, 模式2(频率发生器), 二进制
outb(TIMER_CTRL_PORT, command);
outb(TIMER0_PORT + channel, divisor & 0xFF); // 低字节
outb(TIMER0_PORT + channel, (divisor >> 8) & 0xFF); // 高字节
printf("通道%d已配置: 分频值=%u
", channel, divisor);
}
// 启动所有通道的同步操作
void start_synchronized_timers(void) {
printf("
启动同步定时器
");
// 记录启动时间
uint64_t start_time = get_current_time_us();
// 设置所有通道的触发时间
for (int i = 0; i < 3; i++) {
if (timer_controller.active[i]) {
timer_controller.counters[i] = timer_controller.periods[i];
timer_controller.outputs[i] = false;
timer_controller.trigger_time_us[i] = start_time;
printf("通道%d已启动
", i);
}
}
}
// 停止所有通道
void stop_all_timers(void) {
printf("
停止所有定时器
");
for (int i = 0; i < 3; i++) {
timer_controller.active[i] = false;
printf("通道%d已停止
", i);
}
}
// 模拟定时器滴答
void simulate_timer_tick(void) {
uint64_t current_time = get_current_time_us();
for (int i = 0; i < 3; i++) {
if (!timer_controller.active[i] || timer_controller.periods[i] == 0) {
continue;
}
// 计算自上次触发以来经过的时间
uint64_t elapsed_us = current_time - timer_controller.trigger_time_us[i];
// 计算对应的时钟滴答数(假设每个滴答是1.193182MHz的周期)
uint64_t ticks = elapsed_us * 1193182 / 1000000;
// 更新计数器
if (ticks > 0) {
uint16_t ticks_to_apply = (ticks > 0xFFFF) ? 0xFFFF : (uint16_t)ticks;
if (ticks_to_apply >= timer_controller.counters[i]) {
// 计数器到达零,触发事件
timer_controller.outputs[i] = !timer_controller.outputs[i];
// 调用回调函数
if (timer_controller.callbacks[i] != NULL) {
printf("通道%d触发回调
", i);
timer_controller.callbacks[i]();
}
// 重装载计数器
uint16_t overflows = ticks_to_apply / timer_controller.periods[i];
uint16_t remainder = ticks_to_apply % timer_controller.periods[i];
if (overflows % 2 == 1) {
// 奇数次溢出,输出状态翻转
timer_controller.outputs[i] = !timer_controller.outputs[i];
}
timer_controller.counters[i] = timer_controller.periods[i] - remainder;
// 更新触发时间
timer_controller.trigger_time_us[i] = current_time -
remainder * 1000000 / 1193182;
} else {
// 更新计数器
timer_controller.counters[i] -= ticks_to_apply;
// 更新触发时间
timer_controller.trigger_time_us[i] = current_time;
}
}
}
}
// 打印定时器状态
void print_timer_status(void) {
printf("
定时器状态:
");
printf("----------
");
for (int i = 0; i < 3; i++) {
printf("通道%d: ", i);
printf("激活=%s, ", timer_controller.active[i] ? "是" : "否");
printf("周期=%u, ", timer_controller.periods[i]);
printf("计数值=%u, ", timer_controller.counters[i]);
printf("输出=%s
", timer_controller.outputs[i] ? "高" : "低");
}
}
// 各通道的回调函数
void channel0_callback(void) {
printf("通道0事件: 系统时钟滴答
");
}
void channel1_callback(void) {
printf("通道1事件: DRAM刷新请求
");
}
void channel2_callback(void) {
printf("通道2事件: 扬声器输出
");
}
// 进行事件循环,处理同步定时器事件
void event_loop(int duration_ms) {
printf("
开始事件循环 (%dms)
", duration_ms);
printf("------------------
");
uint64_t start_time = get_current_time_us();
uint64_t end_time = start_time + duration_ms * 1000;
while (get_current_time_us() < end_time) {
// 模拟定时器滴答
simulate_timer_tick();
// 每100ms打印一次状态
static uint64_t last_print_time = 0;
uint64_t current_time = get_current_time_us();
if (current_time - last_print_time >= 100000) {
print_timer_status();
last_print_time = current_time;
}
// 降低CPU使用率
usleep(10000); // 10ms
}
printf("
事件循环结束
");
}
int main() {
printf("多通道同步定时控制示例
");
printf("==================
");
// 配置三个通道
configure_timer_channel(0, 100, channel0_callback); // 100Hz - 系统时钟
configure_timer_channel(1, 1000, channel1_callback); // 1kHz - DRAM刷新
configure_timer_channel(2, 440, channel2_callback); // 440Hz - 标准A音调
// 打印初始状态
print_timer_status();
// 启动同步操作
start_synchronized_timers();
// 运行事件循环
event_loop(2000); // 运行2秒
// 停止所有通道
stop_all_timers();
// 打印最终状态
print_timer_status();
return 0;
}
这个程序演示了如何使用8254的三个通道实现同步定时控制。每个通道可以配置不同的频率和回调函数,但它们的操作是同步协调的。这种方法在需要多个定时信号协同工作的场景(如多轴控制系统、多通道数据采集等)中非常有用。
9.3.2 高精度PWM信号生成
通过特殊配置8253/8254计数器,可以生成具有可变占空比的PWM(脉宽调制)信号,用于电机控制、LED亮度调节等应用。
下面是一个使用8254生成PWM信号的示例程序:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <math.h>
// 8254端口定义
#define TIMER0_PORT 0x40
#define TIMER1_PORT 0x41
#define TIMER2_PORT 0x42
#define TIMER_CTRL_PORT 0x43
// 模拟I/O端口读写函数
void outb(uint16_t port, uint8_t value) {
printf("写端口0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
uint8_t value = 0;
printf("读端口0x%04X: 0x%02X
", port, value);
return value;
}
// PWM控制器结构
typedef struct {
uint8_t channel; // 使用的8254通道
uint16_t period; // PWM周期(计数值)
uint16_t duty_cycle; // 占空比(计数值)
uint8_t duty_percent; // 占空比百分比
bool output_state; // 当前输出状态
bool active; // 是否活动
} PWMController;
// 全局PWM控制器
PWMController pwm_controller = {
.channel = 2, // 默认使用通道2
.period = 0,
.duty_cycle = 0,
.duty_percent = 0,
.output_state = false,
.active = false
};
// 初始化PWM控制器
void init_pwm_controller(uint8_t channel, uint16_t frequency) {
if (channel > 2) {
printf("错误: 无效的通道号%d
", channel);
return;
}
printf("
初始化PWM控制器: 通道=%d, 频率=%dHz
", channel, frequency);
// 保存通道
pwm_controller.channel = channel;
// 计算周期值
pwm_controller.period = frequency > 0 ? 1193182 / frequency : 0;
pwm_controller.duty_cycle = 0;
pwm_controller.duty_percent = 0;
pwm_controller.output_state = false;
pwm_controller.active = false;
printf("初始化完成: 周期值=%d
", pwm_controller.period);
}
// 生成PWM信号,使用两种方法实现:
// 方法1:使用模式3方波,通过快速重新加载不同的计数值来改变占空比
void generate_pwm_mode3(uint8_t duty_percent) {
if (duty_percent > 100) {
duty_percent = 100;
}
// 保存占空比设置
pwm_controller.duty_percent = duty_percent;
// 计算占空比对应的计数值
pwm_controller.duty_cycle = (uint16_t)((uint32_t)pwm_controller.period * duty_percent / 100);
printf("
设置PWM占空比: %d%% (计数值=%d/%d)
",
duty_percent, pwm_controller.duty_cycle, pwm_controller.period);
// 配置通道为模式3(方波发生器)
uint8_t command = (pwm_controller.channel << 6) | 0x36; // xx 11 011 0
outb(TIMER_CTRL_PORT, command);
// 设置计数器的值
uint16_t count_value;
if (duty_percent == 0) {
count_value = 1; // 特殊情况:0%占空比
} else if (duty_percent == 100) {
count_value = 2; // 特殊情况:100%占空比
} else {
count_value = pwm_controller.duty_cycle;
}
outb(TIMER0_PORT + pwm_controller.channel, count_value & 0xFF);
outb(TIMER0_PORT + pwm_controller.channel, (count_value >> 8) & 0xFF);
pwm_controller.active = true;
pwm_controller.output_state = (duty_percent > 50);
printf("PWM信号已启动 (模式3)
");
}
// 方法2:使用模式0和外部中断实现更精确的PWM
void simulate_pwm_mode0(uint8_t duty_percent, int duration_ms) {
if (duty_percent > 100) {
duty_percent = 100;
}
// 保存占空比设置
pwm_controller.duty_percent = duty_percent;
// 计算占空比对应的计数值
pwm_controller.duty_cycle = (uint16_t)((uint32_t)pwm_controller.period * duty_percent / 100);
printf("
模拟PWM占空比: %d%% (计数值=%d/%d), 持续%dms
",
duty_percent, pwm_controller.duty_cycle, pwm_controller.period, duration_ms);
// 对于完全开/关的情况,简化处理
if (duty_percent == 0) {
printf("输出恒定低电平
");
pwm_controller.output_state = false;
return;
} else if (duty_percent == 100) {
printf("输出恒定高电平
");
pwm_controller.output_state = true;
return;
}
// 模拟PWM循环
int cycles = duration_ms / ((pwm_controller.period * 1000) / 1193182);
printf("模拟%d个PWM周期
", cycles);
pwm_controller.active = true;
for (int i = 0; i < cycles; i++) {
// 高电平部分
pwm_controller.output_state = true;
printf("周期%d: 输出高电平 (%d个单位)
", i + 1, pwm_controller.duty_cycle);
// 模拟高电平持续时间
usleep(pwm_controller.duty_cycle * 1000000 / 1193182);
// 低电平部分
pwm_controller.output_state = false;
printf("周期%d: 输出低电平 (%d个单位)
", i + 1, pwm_controller.period - pwm_controller.duty_cycle);
// 模拟低电平持续时间
usleep((pwm_controller.period - pwm_controller.duty_cycle) * 1000000 / 1193182);
}
pwm_controller.active = false;
printf("PWM模拟结束
");
}
// 生成渐变PWM效果
void generate_fade_effect(int cycles, int step_ms) {
printf("
生成PWM渐变效果: %d个循环, 步长%dms
", cycles, step_ms);
for (int cycle = 0; cycle < cycles; cycle++) {
printf("
循环 %d/%d:
", cycle + 1, cycles);
// 渐亮
printf("渐亮过程:
");
for (int duty = 0; duty <= 100; duty += 5) {
simulate_pwm_mode0(duty, step_ms);
}
// 渐暗
printf("渐暗过程:
");
for (int duty = 100; duty >= 0; duty -= 5) {
simulate_pwm_mode0(duty, step_ms);
}
}
printf("
渐变效果完成
");
}
// 生成特殊的呼吸灯效果
void generate_breathing_effect(int duration_seconds) {
printf("
生成呼吸灯效果: %d秒
", duration_seconds);
// 模拟时间
int steps = duration_seconds * 10; // 每秒10个采样点
for (int i = 0; i < steps; i++) {
// 使用正弦函数生成平滑的呼吸效果
double position = (double)i / steps * 2.0 * M_PI;
double sin_value = sin(position);
int duty = (int)((sin_value + 1.0) * 50); // 0-100范围
printf("时间点 %d/%d: 占空比 %d%%
", i + 1, steps, duty);
// 生成对应占空比的PWM
simulate_pwm_mode0(duty, 100); // 100ms每步
}
printf("
呼吸灯效果完成
");
}
// 模拟电机控制
void simulate_motor_control(void) {
printf("
模拟电机控制:
");
printf("-----------
");
printf("
1. 缓慢启动
");
for (int speed = 0; speed <= 100; speed += 10) {
printf("电机速度: %d%%
", speed);
simulate_pwm_mode0(speed, 500); // 500ms每步
}
printf("
2. 全速运行
");
simulate_pwm_mode0(100, 2000); // 2秒全速
printf("
3. 缓慢减速
");
for (int speed = 100; speed >= 0; speed -= 10) {
printf("电机速度: %d%%
", speed);
simulate_pwm_mode0(speed, 500); // 500ms每步
}
printf("
电机控制模拟完成
");
}
int main() {
printf("8254高精度PWM信号生成示例
");
printf("======================
");
// 初始化PWM控制器
init_pwm_controller(2, 100); // 通道2, 100Hz PWM
// 测试不同占空比
printf("
测试不同占空比:
");
printf("-------------
");
simulate_pwm_mode0(25, 1000); // 25%占空比,持续1秒
simulate_pwm_mode0(50, 1000); // 50%占空比,持续1秒
simulate_pwm_mode0(75, 1000); // 75%占空比,持续1秒
// 测试渐变效果
generate_fade_effect(2, 100); // 2个循环,每步100ms
// 测试呼吸灯效果
generate_breathing_effect(5); // 5秒
// 模拟电机控制
simulate_motor_control();
return 0;
}
这个程序演示了如何使用8254定时器生成PWM信号,并通过控制占空比来实现各种效果,如LED亮度渐变、呼吸灯和电机速度控制。通过改变占空比,可以精确控制输出功率,这在各种控制应用中非常有用。
9.3.3 频率计数与事件计时
8253/8254还可以用作频率计或事件计数器,测量外部信号的频率或计数事件的发生次数。
下面是一个使用8254实现频率计数和事件计时的示例程序:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
// 8254端口定义
#define TIMER0_PORT 0x40
#define TIMER1_PORT 0x41
#define TIMER2_PORT 0x42
#define TIMER_CTRL_PORT 0x43
// 模拟I/O端口读写函数
void outb(uint16_t port, uint8_t value) {
printf("写端口0x%04X: 0x%02X
", port, value);
}
uint8_t inb(uint16_t port) {
// 模拟返回随机值
uint8_t value = rand() & 0xFF;
printf("读端口0x%04X: 0x%02X
", port, value);
return value;
}
// 模拟频率计结构
typedef struct {
uint16_t gate_time_ms; // 门控时间(毫秒)
uint32_t reference_count; // 参考计数器值
uint32_t input_count; // 输入信号计数
double frequency; // 计算的频率
} FrequencyCounter;
// 全局频率计
FrequencyCounter freq_counter = {
.gate_time_ms = 1000, // 默认门控时间1秒
.reference_count = 0,
.input_count = 0,
.frequency = 0.0
};
// 配置频率计
void configure_frequency_counter(uint16_t gate_time_ms) {
printf("
配置频率计: 门控时间=%dms
", gate_time_ms);
freq_counter.gate_time_ms = gate_time_ms;
// 配置计数器0作为参考定时器
outb(TIMER_CTRL_PORT, 0x34); // 00 11 010 0 = 计数器0,先低后高字节,模式2,二进制
// 使用最大值作为计数值(用于参考计时)
outb(TIMER0_PORT, 0xFF);
outb(TIMER0_PORT, 0xFF);
// 配置计数器1作为事件计数器
outb(TIMER_CTRL_PORT, 0x74); // 01 11 010 0 = 计数器1,先低后高字节,模式2,二进制
// 使用最大值作为计数值
outb(TIMER1_PORT, 0xFF);
outb(TIMER1_PORT, 0xFF);
printf("频率计配置完成
");
}
// 读取计数器值
uint16_t read_counter(uint8_t counter) {
// 锁存计数器的当前值
outb(TIMER_CTRL_PORT, 0x00 | (counter << 6));
// 读取计数值
uint8_t low = inb(TIMER0_PORT + counter);
uint8_t high = inb(TIMER0_PORT + counter);
return (high << 8) | low;
}
// 测量频率
double measure_frequency(uint32_t input_signal_hz) {
printf("
开始频率测量: 输入信号=%uHz
", input_signal_hz);
// 在实际系统中,我们会读取计数器的初始值,然后等待门控时间,再读取最终值
// 这里我们使用模拟数据
// 计算参考计数器在门控时间内的变化
uint32_t reference_ticks = (uint32_t)(1193182UL * freq_counter.gate_time_ms / 1000);
freq_counter.reference_count = reference_ticks;
// 计算输入信号在门控时间内的计数
freq_counter.input_count = (uint32_t)(input_signal_hz * freq_counter.gate_time_ms / 1000);
// 计算频率: 输入计数 * (1193182 / 参考计数)
freq_counter.frequency = (double)freq_counter.input_count * 1193182.0 / reference_ticks;
printf("测量结果:
");
printf("参考计数: %u
", freq_counter.reference_count);
printf("输入计数: %u
", freq_counter.input_count);
printf("计算频率: %.2f Hz
", freq_counter.frequency);
return freq_counter.frequency;
}
// 事件计数器结构
typedef struct {
uint8_t counter; // 使用的计数器
uint16_t initial_value; // 初始计数值
uint16_t current_value; // 当前计数值
uint32_t total_events; // 总事件数
bool active; // 是否活动
} EventCounter;
// 全局事件计数器
EventCounter event_counter = {
.counter = 2, // 使用计数器2
.initial_value = 0,
.current_value = 0,
.total_events = 0,
.active = false
};
// 初始化事件计数器
void init_event_counter(uint8_t counter, uint16_t preset) {
if (counter > 2) {
printf("错误: 无效的计数器编号%d
", counter);
return;
}
printf("
初始化事件计数器: 计数器=%d, 预设值=%d
", counter, preset);
event_counter.counter = counter;
event_counter.initial_value = preset;
event_counter.current_value = preset;
event_counter.total_events = 0;
event_counter.active = false;
// 配置计数器为模式0(计数器模式)
outb(TIMER_CTRL_PORT, (counter << 6) | 0x30); // xx 11 000 0 = 计数器x, 先低后高, 模式0, 二进制
// 设置初始值
outb(TIMER0_PORT + counter, preset & 0xFF);
outb(TIMER0_PORT + counter, (preset >> 8) & 0xFF);
printf("事件计数器已初始化
");
}
// 开始事件计数
void start_event_counting(void) {
printf("
开始事件计数
");
// 重置计数器到初始值
event_counter.current_value = event_counter.initial_value;
// 写入初始值
outb(TIMER0_PORT + event_counter.counter, event_counter.initial_value & 0xFF);
outb(TIMER0_PORT + event_counter.counter, (event_counter.initial_value >> 8) & 0xFF);
event_counter.active = true;
printf("事件计数已开始
");
}
// 停止事件计数并获取结果
uint32_t stop_event_counting(void) {
if (!event_counter.active) {
printf("
错误: 事件计数器未启动
");
return 0;
}
printf("
停止事件计数
");
// 读取当前计数值
uint16_t final_value = read_counter(event_counter.counter);
// 计算发生的事件数
uint32_t events = event_counter.initial_value - final_value;
event_counter.total_events += events;
event_counter.current_value = final_value;
event_counter.active = false;
printf("计数结果:
");
printf("初始值: %u
", event_counter.initial_value);
printf("最终值: %u
", final_value);
printf("本次事件数: %u
", events);
printf("总事件数: %u
", event_counter.total_events);
return events;
}
// 模拟产生事件
void simulate_events(int event_count, int interval_ms) {
printf("
模拟%d个事件,间隔%dms
", event_count, interval_ms);
for (int i = 0; i < event_count; i++) {
printf("事件 %d/%d
", i + 1, event_count);
// 模拟事件发生导致计数器递减
if (event_counter.current_value > 0) {
event_counter.current_value--;
}
usleep(interval_ms * 1000);
}
}
// 测量时间间隔
uint32_t measure_time_interval(void (*function)(void), const char *name) {
printf("
测量时间间隔: %s
", name);
// 配置计数器0为模式0(计数器模式)
outb(TIMER_CTRL_PORT, 0x30); // 00 11 000 0 = 计数器0, 先低后高, 模式0, 二进制
// 设置最大初始值
outb(TIMER0_PORT, 0xFF);
outb(TIMER0_PORT, 0xFF);
// 读取初始值
uint16_t start_value = read_counter(0);
// 执行被测量函数
function();
// 读取最终值
uint16_t end_value = read_counter(0);
// 计算时间间隔(微秒)
uint32_t counts = start_value - end_value;
uint32_t microseconds = (uint32_t)((uint64_t)counts * 1000000 / 1193182);
printf("时间测量结果:
");
printf("初始计数: %u
", start_value);
printf("最终计数: %u
", end_value);
printf("计数差: %u
", counts);
printf("时间间隔: %u微秒 (%.2f毫秒)
", microseconds, microseconds / 1000.0);
return microseconds;
}
// 用于测量的测试函数
void test_function_short(void) {
printf("执行短耗时操作...
");
usleep(50000); // 50ms
}
void test_function_long(void) {
printf("执行长耗时操作...
");
usleep(500000); // 500ms
}
int main() {
printf("8254频率计数与事件计时示例
");
printf("========================
");
// 初始化随机数生成器
srand(time(NULL));
// 1. 频率计测量
configure_frequency_counter(1000); // 1秒门控时间
// 测量不同频率
measure_frequency(1000); // 1kHz
measure_frequency(10000); // 10kHz
measure_frequency(100000); // 100kHz
// 2. 事件计数
init_event_counter(2, 100); // 计数器2,初始值100
// 开始计数
start_event_counting();
// 模拟事件发生
simulate_events(50, 20); // 50个事件,间隔20ms
// 停止计数并获取结果
stop_event_counting();
// 再次启动计数器,累计更多事件
start_event_counting();
simulate_events(25, 40); // 25个事件,间隔40ms
stop_event_counting();
// 3. 时间间隔测量
measure_time_interval(test_function_short, "短操作");
measure_time_interval(test_function_long, "长操作");
return 0;
}
这个程序演示了如何使用8254定时器实现频率测量、事件计数和时间间隔测量。通过这些功能,可以测量外部信号的频率、计数事件的发生次数以及测量操作的执行时间,这在许多测试、测量和控制应用中都非常有用。
习题9
简述8253/8254定时计数器的基本原理和主要功能。
8253/8254有哪几种工作模式?请解释每种模式的特点和应用场景。
在PC系统中,8253/8254的三个计数器分别有什么典型用途?
写一个C语言函数,配置8254的计数器0为方波发生器模式,频率为1kHz。
如何使用8254实现精确的毫秒级延时?给出实现代码。
简述如何使用8254产生可变占空比的PWM信号,并说明其应用场景。
在PC系统中,系统时钟是多少Hz?这个频率是如何通过8254配置产生的?
设计一个使用8254计数器的频率计,能够测量1Hz-1MHz范围内的输入信号频率。