刚刚开始接触dos,由于要求工控机的I/O口发送出1K-10K的方波,在windows下无法实现,故改用dos7.1(在论坛下载,十分感谢),准备用8253的定时器来做。
看了几个例子,就是不知道怎么跳进这个8253中断的,目前的测试程序就这么长,请高手解答,帮忙看下缺了什么。
#include“dos.h”
void interrupt intp()
{
enable();
}
void main()
{
disable();
outportb(0x43 ,0x11); //选中8253计数器0,工作于方式0
outportb(0x40, 50); //是设置计数初值为50
enable();
}
Just started to get in touch with DOS. Since the requirement is that the I/O port of the industrial control computer sends a square wave of 1K - 10K, which cannot be realized under Windows, so I switched to DOS 7.1 (downloaded from the forum, thank you very much), and plan to use the timer of 8253 to do it.
I read a few examples, but I just don't know how to jump into this 8253 interrupt. The current test program is so long. Please help the master to answer and see what is missing.
#include "dos.h"
void interrupt intp()
{
enable();
}
void main()
{
disable();
outportb(0x43, 0x11); // Select 8253 counter 0, work in mode 0
outportb(0x40, 50); // Is to set the count initial value to 50
enable();
}
LLM 解释
代码问题与关键缺失分析
你的代码存在核心逻辑错误和关键步骤缺失,无法触发8253定时器中断。以下是具体问题与技术解释:
1. 8253定时器中断的触发前提(你未满足)
8253的计数器0是系统定时器(IRQ0)的来源,其输出连接到中断控制器8259的IRQ0引脚。要触发中断,必须满足:
- 计数器0工作在方式2(速率波)或方式3(方波)(方式0是“计数结束中断”,仅触发一次,且默认被系统用于时钟中断,不应修改);
- 计数初值需正确设置(需考虑8253的二进制/BCD编码和16位初值的分两次写入);
- 必须注册中断处理函数到IRQ0对应的中断向量(即中断号0x08);
- 若修改了计数器0,需确保系统时钟中断的兼容性(否则DOS系统会崩溃,因为DOS依赖IRQ0维护时间片、进程切换等)。
2. 你的代码的具体错误
-
错误1:工作方式选择错误
outportb(0x43, 0x11) 表示:计数器0(0x10)、只写低8位(0x01)、方式0(0x00)、二进制编码(0x00)。方式0是“计数到0后输出高电平”,仅触发一次中断,且无法持续产生方波。若要产生1K-10K方波,应选择方式3(方波),控制字应为 0x36(计数器0、16位初值、方式3、二进制编码)。
-
错误2:未注册中断向量
你定义了 intp() 中断函数,但未通过 setvect(0x08, intp) 将其注册到IRQ0的中断向量(0x08),因此即使计数器触发中断,CPU也不会执行你的处理函数。
-
错误3:计数初值设置不完整
8253的16位初值需分两次写入(低8位→高8位)。你仅写了低8位 outportb(0x40, 50),高8位未设置,导致实际初值为 0x0032(而非你预期的50),且若初值>255会完全错误。
-
错误4:系统兼容性问题
直接修改计数器0会覆盖DOS的系统时钟中断,导致系统时间混乱、进程调度失败,最终崩溃。若要产生方波,更合理的选择是使用计数器2(连接到扬声器,IRQ9,不影响系统时钟)。
3. 正确的方波生成方案(基于计数器2,不影响系统)
若目标是通过I/O口(如扬声器)输出方波,应使用8253的计数器2(而非计数器0),因为计数器2的输出连接到扬声器(通过8255的PB1引脚控制开关)。以下是关键步骤:
- 设置计数器2为方式3(方波),控制字
0xB6(计数器2、16位初值、方式3、二进制编码);
- 写入16位计数初值(计算公式:
初值 = 1.19318MHz / 目标频率,1.19318MHz是8253的输入时钟);
- 通过8255的PB1引脚(端口0x61)打开扬声器开关。
示例代码(非中断方式,直接控制方波输出):
#include <dos.h>
// 输出指定频率的方波(通过计数器2和扬声器)
void play_square_wave(unsigned int freq) {
unsigned int count = 1193180 / freq; // 1.19318MHz / 目标频率
disable();
// 步骤1:设置计数器2为方式3(方波)
outportb(0x43, 0xB6); // 0xB6 = 1011 0110 → 计数器2、16位、方式3、二进制
// 步骤2:写入16位计数初值(低8位→高8位)
outportb(0x42, count & 0xFF); // 低8位
outportb(0x42, (count >> 8) & 0xFF); // 高8位
// 步骤3:打开扬声器(设置PB1和PB0为1)
outportb(0x61, inportb(0x61) | 0x03);
enable();
}
// 关闭方波输出
void stop_square_wave() {
disable();
outportb(0x61, inportb(0x61) & 0xFC); // 关闭PB1和PB0
enable();
}
void main() {
play_square_wave(1000); // 输出1KHz方波
delay(1000); // 持续1秒
stop_square_wave(); // 停止输出
}
4. 若必须使用中断方式(如控制I/O口输出)
若需通过中断触发I/O口输出(而非直接控制扬声器),应:
- 使用计数器1(未被系统占用,输出不连接中断,但可通过查询或外部电路触发中断);
- 或使用计数器2配合8259的IRQ9(需手动将计数器2的输出连接到IRQ9,需硬件支持);
- 注册中断处理函数时,需保存原中断向量,并在程序结束时恢复(否则会导致系统不稳定)。
当年常见误解的原因
你对“如何跳进8253中断”的困惑,源于当年资料对8253与8259的硬件连接关系描述不清晰,且很多例子未强调“系统定时器(计数器0)不可随意修改”。此外,8253的控制字编码(4位功能字段)和16位初值的写入方式,容易因文档翻译误差或示例代码省略注释而被误解。
补充建议
若你的工控机需要输出1K-10K方波,优先使用计数器2+扬声器的方案(无需中断,简单可靠)。若需通过其他I/O口输出,可在计数器2的中断处理函数中控制I/O口电平翻转(但需注意中断频率与方波频率的关系:方波周期=2×中断周期)。
另外,论坛中后续若有提到“保存原中断向量”“使用方式3”“分两次写初值”的回复,均是正确的关键补充。