8253/4 定时器芯片实现时钟中断处理例程

实验要求

使用 nasm 汇编编程实现一个 MBR 程序,每隔 1 秒钟左右输出一个 (组)字符,具体内容包括:

1)实现一个时钟中断处理例程,按规定时间间隔输出字符(可使用 BIOS 提供的 int 10h 实现字符输 出);

2)修改中断向量表,将时钟中断处理例程起始地址填入中断向量表对应表项;

3)设置 8253/4 定时器芯片,每隔 20ms 左右(自定 < 1 秒)产生一次时钟中断。

基本数据设置

TIMER_FREQ equ 1193182
TARGET_FREQ equ 50
DIVISOR equ TIMER_FREQ / TARGET_FREQ

[BITS 16]
[ORG 0x7C00]

这段代码是用于设置定时器频率的。首先定义了两个常数:TIMER_FREQ(定时器频率)和 TAR_GET_FREQ(目标频率)。然后计算这两个频率之间的比率,并将结果存储在 DIVISOR(除数)变量 中。

[BITS 16] 表示汇编器以 16 位模式工作。在这种模式下,寄存器的大小为 16 位,寻址范围为 0x0000 到 0xFFFF。

[ORG 0x7C00] 表示代码段从地址 0x7C00 开始。在实模式下,程序从地址 0x0000 开始,但是由于 BIOS 将 CS:IP 初始化为 0x0000:0x0000,所以程序实际上从地址 0x7C00 开始执行。

基本设置

clear:;清屏
    mov ax, 0x600               ;ah中输入功能号
    mov bx, 0x700               ;设置上卷行属性,0x70表示用黑底白字的属性填充空白行
    mov cx, 0                   ;左上角: (0, 0)
    mov dx, 0x184f	            ;右下角: (80,25),
    int 0x10                    ;int 0x10

mouse:;让光标处于最左上角
    mov ah, 2		            ;输入: 2号子功能是设置光标位置,需要存入ah寄存器
    mov bh, 0		            ;bh寄存器存储的是待设置光标的页号
    mov dh, 0		            ;dh寄存器存储的是行号
    mov dl, 0		            ;dl寄存器存储的是列号
    int 0x10		            ;输出: 无

因为我们需要在屏幕上打印字符串,而原先的页面上有 booting from hard disk 等等内容,所以我们 需要先将原先的页面清屏,之后我们需要将光标移到左上角,这样子可以使得打印字符串从页面的最上面 开始打印。

初始化中断处理程序

set_interrupt_handler_assembly:   ;初始化中断处理程序
    CLI
    xor ax, ax
    mov ds, ax ; 将ds设置为0,因为IVT位于内存的最低端
    mov word [8*4], handle_interrupt_assembly ; 设置中断处理程序的偏移地址
    mov word [8*4+2], cs ; 设置中断处理程序的段地址
    STI ; 重新开启中断

首先,关闭中断(CLI),然后将一些寄存器清零,并将数据段(DS)设置为 0。这是因为中断向量 表(IVT)位于内存的最低端,所以我们需要将段地址(CS)设置为 0。接下来,它将中断处理程序的偏 移地址(timer_interrupt)写入 IVT 中的位置 84,并将中断处理程序的段地址(CS)写入 IVT 中的位 置 84+2。最后,重新开启中断(STI).

初始化计时器

init_timer:
    mov al, 0x36 ; 控制字到0x43
    out 0x43, al
    mov ax, DIVISOR ; 加载除数值
    out 0x40, al ; 通道0,用于产生时钟中断
    mov al, ah
    out 0x40, al

这段代码是用于初始化一个定时器。定时器用于产生时钟中断,从而实现时间控制。

计数器

wait_for_interrupt:
    hlt ; 等待中断
    jmp wait_for_interrupt ; 无限循环

handle_interrupt_assembly:
    INC CX
    CMP CX, 50
    JE Print
    mov al, 0x20 ; AL = 0x20 表示发送EOI信号给PIC
    out 0x20, al ; 发送EOI信号
    IRET ; 从中断返回

这段代码实现了一个简单的定时器中断。

首先,程序进入 wait_for_interrupt 函数,这里使用 hlt 指令使 CPU 暂停,然后使用 jmp 指令无限 循环等待中断。

当定时器中断发生时,程序会跳转到 timer_interrupt 函数。首先,程序会递增计数器 CX 的值,然 后比较 CX 和 50 的大小。如果 CX 等于 50,那么就跳转到 Print 函数。

在 timer_interrupt 函数中,程序将 AL 寄存器的值设置为 0x20,表示发送 EOI 信号给 PIC(可编 程中断控制器)。然后使用 out 指令将 EOI 信号发送给 PIC。最后,使用 IRET 指令从中断返回。

打印函数

Print:
    CLI
    XOR CX, CX
    mov ah, 0x0E ; teletype输出
    mov al, 'I' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, ' ' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 'l' ; 显示字符
    int 0x10 ; BIOS视频服务

这边选择了逐个字符打印“I love OS”,最后打印一个换行即可,当然也可以将需要打印的data整体传入打印。

实现效果

将该汇编程序编译成bin文件后,用qemu运行该bin文件,实现效果如下:

程序源码如下:

TIMER_FREQ equ 1193182
TARGET_FREQ equ 50
DIVISOR equ TIMER_FREQ / TARGET_FREQ

[BITS 16]
[ORG 0x7C00]

clear:;清屏
    mov ax, 0x600               ;ah中输入功能号
    mov bx, 0x700               ;设置上卷行属性,0x70表示用黑底白字的属性填充空白行
    mov cx, 0                   ;左上角: (0, 0)
    mov dx, 0x184f	            ;右下角: (80,25),
    int 0x10                    ;int 0x10

mouse:;让光标处于最左上角
    mov ah, 2		            ;输入: 2号子功能是设置光标位置,需要存入ah寄存器
    mov bh, 0		            ;bh寄存器存储的是待设置光标的页号
    mov dh, 0		            ;dh寄存器存储的是行号
    mov dl, 0		            ;dl寄存器存储的是列号
    int 0x10		            ;输出: 无

set_interrupt_handler_assembly:   ;初始化中断处理程序
    CLI
    xor ax, ax
    mov ds, ax ; 将ds设置为0,因为IVT位于内存的最低端
    mov word [8*4], handle_interrupt_assembly ; 设置中断处理程序的偏移地址
    mov word [8*4+2], cs ; 设置中断处理程序的段地址
    STI ; 重新开启中断

init_timer:
    mov al, 0x36 ; 控制字到0x43
    out 0x43, al
    mov ax, DIVISOR ; 加载除数值
    out 0x40, al ; 通道0,用于产生时钟中断
    mov al, ah
    out 0x40, al

wait_for_interrupt:
    hlt ; 等待中断
    jmp wait_for_interrupt ; 无限循环

handle_interrupt_assembly:
    INC CX
    CMP CX, 50
    JE Print
    mov al, 0x20 ; AL = 0x20 表示发送EOI信号给PIC
    out 0x20, al ; 发送EOI信号
    IRET ; 从中断返回

Print:
    CLI
    XOR CX, CX
    mov ah, 0x0E ; teletype输出
    mov al, 'I' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, ' ' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 'l' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 'o' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 'v' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 'e' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, ' ' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 'O' ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出mov ah, 0x0E ; teletype输出
    mov al, 'S' ; 显示字符
    int 0x10 ; BIOS视频服务
    
    ;换行
    mov ah, 0x0E ; teletype输出
    mov al, 0x0D ; 显示字符
    int 0x10 ; BIOS视频服务

    mov ah, 0x0E ; teletype输出
    mov al, 0x0A ; 显示字符
    int 0x10 ; BIOS视频服务

    STI
mov al, 0x20 ; AL = 0x20 表示发送EOI信号给PIC
out 0x20, al ; 发送EOI信号
IRET ; 从中断返回   

TIMES 510-($-$$) DB 0
DW 0xAA55

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于STM32定时器中断,控制LED闪烁的程,带有详细注释: ```c #include "stm32f10x.h" //包含STM32F10x系列芯片的头文件 void GPIO_Configuration(void); //GPIO配置函数声明 void TIM_Configuration(void); //定时器配置函数声明 int main(void) { GPIO_Configuration(); //调用GPIO配置函数 TIM_Configuration(); //调用定时器配置函数 while (1); //程序无限循环 } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //指定GPIOB的Pin0引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输出推挽模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOB的Pin0引脚 } void TIM_Configuration(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义定时器初始化结构体 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能TIM4时钟 TIM_TimeBaseStructure.TIM_Period = 499; //设定自动重装载寄存器的值 TIM_TimeBaseStructure.TIM_Prescaler = 7199; //设定时钟预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设定时钟分割值 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //设定计数器计数模式为向上计数 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //初始化定时器4 TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //使能TIM4中断 TIM_Cmd(TIM4, ENABLE); //使能TIM4 } void TIM4_IRQHandler(void) //定时器4中断服务函数 { if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) //检查TIM4更新中断是否发生 { GPIO_WriteBit(GPIOB, GPIO_Pin_0, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0))); //翻转LED输出状态 TIM_ClearITPendingBit(TIM4, TIM_IT_Update); //清除TIM4更新中断标志 } } ``` 在上述代码中,我们使用了定时器4和GPIOB的Pin0引脚控制LED的闪烁。具体实现如下: 在GPIO配置函数中,我们使能了GPIOB时钟,然后指定了GPIOB的Pin0引脚,设定为输出推挽模式,速度为50MHz。 在定时器配置函数中,我们使能了TIM4时钟,然后设定了自动重装载寄存器的值为499,预分频值为7199,时钟分割值为0,计数器计数模式为向上计数。这里的计数频率为1MHz,即每秒钟计数1百万次。我们还使能了TIM4中断,并使能了定时器4。 在定时器4中断服务函数中,我们首先检查TIM4更新中断是否发生,如果发生,则翻转LED的输出状态,并清除TIM4更新中断标志。这里我们使用GPIO_WriteBit函数来翻转LED的输出状态,它的作用是将GPIOB的Pin0引脚的输出状态翻转。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值