Day12 定时器(1)
文章目录
1. 使用定时器(harib09a)
不同的cpu,执行指令的时钟周期不同,一个时钟周期是多久的时间取决于cpu的主频。例如,100Mhz主频的CPU,一个时钟周期是10ns;200Mhz主频的CPU,一个时钟周期是5ns。
定时器的原理就是每隔固定时间,就会发送一个中断信号给cpu。因此,程序只需要按照自己的步调节奏处理自己的事情就好了,至于经过了多长时间,只需要在中断处理程序中数一数定时器产生中断的次数。就算CPU处于HLT状态,也可以通过中断来唤醒,根本不需要程序去自己记忆时间。
管理定时器,需要设置PIT(“Programmable Interval Timer”可编程的间隔型定时器)。通过设定PIT,可以让定时器以固定时间间隔产生一次中断。因为PIT连接着IRQ(Interrupt request,参见第6章)的0号,所以只要设定PIT就可以设定IRQ0的中断间隔。
- IRQ0的中断周期变更:
- AL=0x34:OUT(0x43, AL);
- AL=中断周期的低8位:OUT(0x40, AL);
- AL=中断周期的高8位:OUT(0x40, AL);
- 如果指定中断周期为0,会被默认看作65536。中断产生的频率是单位时间时钟周期数(即主频)/设定的数值。例如,设定值为11932的话,中断产生的频率就是100Hz,即每10ms产生一次。
简单来说就是需要对OUT设置三次。11932的16进制表示为0x2ec9。在timer.c文件中编写init_pit函数,并在HariMain函数的最开始进行调用。这样的话,IRQ0就会在1秒内发生100次中断了。
/* timer.c */#define PIT_CTRL0x0043#define PIT_CNT00x0040void init_pit(void){io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);return;}/* IRQ0中断发生时的处理程序,调用频率10ms/次 */void inthandler20(int *esp){io_out8(PIC0_OCW2, 0x60);/* 把IRQ-00信号接收完了的信息通知给PIC *//* 暂时什么都不做 */return;}
类似键盘的处理,需要在naskfunc.nas中添加对inthandler20的调用,以及在dsctbl.c中将中断处理程序注册到IDT中。
/* naskfunc.nas中对inthandler20的调用 */_asm_inthandler20:PUSHESPUSHDSPUSHADMOVEAX,ESPPUSHEAXMOVAX,SSMOVDS,AXMOVES,AXCALL_inthandler20POPEAXPOPADPOPDSPOPESIRETD/* dsctbl.c */void init_gdtidt(void){// 略set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);// 新增set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);return;}
准备工作完成,此时运行的话,并不会有什么新的效果。
2. 计量时间(harib09b)
定义一个TIMECTL结构体,其中只有一个计数字段,每次中断后对计数累加。并在HariMain
函数中将计数值打印出来。
struct TIMERCTL {unsigned int count;};struct TIMERCTL timerctl;void init_pit(void){io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);timerctl.count = 0;return;}void inthandler20(int *esp){io_out8(PIC0_OCW2, 0x60);/* 把IRQ-00信号接受结束的信息通知给PIC */timerctl.count++;return;}void HariMain(void){for (;;) {sprintf(s, \"%010d\", timerctl.count);boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);sheet_refresh(sht_win, 40, 28, 120, 44);}
由于设定了中断产生的频率为100Hz,因此计数累加的速度期望为1s增加100。
3. 超时功能(harib09c)
struct TIMERCTL {unsigned int count;unsigned int timeout;// 记录离超时还有多久struct FIFO8 *fifo;// 到达超时时间之后就往FIFO8中写内容unsigned char data;};void init_pit(void){io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);timerctl.count = 0;timerctl.timeout = 0;return;}void inthandler20(int *esp){io_out8(PIC0_OCW2, 0x60);/* 把IRQ-00信号接受结束的信息通知给PIC */timerctl.count++;if (timerctl.timeout > 0) { /* 如果已经设定了超时 */timerctl.timeout--;if (timerctl.timeout == 0) {fifo8_put(timerctl.fifo, timerctl.data);/* 把data内容写到fifo */}}return;}void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data){int eflags;eflags = io_load_eflags();io_cli();timerctl.timeout = timeout;timerctl.fifo = fifo;timerctl.data = data;io_store_eflags(eflags);return;}
通过调用settimer(1000, &timerfifo, 1);
设置10s作为超时周期,10s后在屏幕上打印“10[sec]”字符串。
4. 设定多个定时器(harib09d)
// 定义结构体为最多500个定时器。#define MAX_TIMER500struct TIMER {unsigned int timeout;unsigned int flags;// 记录各个定时器的状态struct FIFO8 *fifo;unsigned char data;};struct TIMERCTL {unsigned int count;struct TIMER timer[MAX_TIMER];};#define TIMER_FLAGS_ALLOC1/* 已配置状态 */#define TIMER_FLAGS_USING2/* 定时器运行中 */void init_pit(void){int i;io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);timerctl.count = 0;for (i = 0; i < MAX_TIMER; i++) {timerctl.timer[i].flags = 0; /* 未使用 */}return;}struct TIMER *timer_alloc(void){int i;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timer[i].flags == 0) {timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;return &timerctl.timer[i];}}return 0; /* 未找到空闲定时器 */}void timer_free(struct TIMER *timer){timer->flags = 0; /* 未使用 */return;}void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data){timer->fifo = fifo;timer->data = data;return;}void timer_settime(struct TIMER *timer, unsigned int timeout){timer->timeout = timeout;timer->flags = TIMER_FLAGS_USING;/* 使用中 */return;}void inthandler20(int *esp){int i;io_out8(PIC0_OCW2, 0x60);/* 把IRQ-00信号接收结束的信息通知给PIC */timerctl.count++;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {timerctl.timer[i].timeout--;if (timerctl.timer[i].timeout == 0) {timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);}}}return;}
在HariMain函数中创建三个定时器,分别用作显示10s超时,3s超时和500ms光标闪烁。
5. 加快中断处理(harib09g)
当前的中断函数中,做的处理太多,严重影响系统的运行速度。模仿sheet.c中的做法,在结构体中添加一个*timers[]
成员,
#define MAX_TIMER500struct TIMER {unsigned int timeout, flags;struct FIFO8 *fifo;unsigned char data;};struct TIMERCTL {unsigned int count, next;unsigned int using;// 记录当前活动中的计时器数量struct TIMER *timers[MAX_TIMER];// 按某种顺序排列好的计时器地址struct TIMER timers0[MAX_TIMER];};void inthandler20(int *esp){int i, j;io_out8(PIC0_OCW2, 0x60);/* 把IRQ-00信号接收结束的信息通知PIC */timerctl.count++;if (timerctl.next > timerctl.count) {return;}for (i = 0; i < timerctl.using; i++) {/* timers里保存的定时器都处于工作中,所以不确认flags */if (timerctl.timers[i]->timeout > timerctl.count) {break;}/* 超时 */timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);}/* 正好有i个定时器超时了,其余的进行移位 */timerctl.using -= i;for (j = 0; j < timerctl.using; j++) {timerctl.timers[j] = timerctl.timers[i + j];}if (timerctl.using > 0) {timerctl.next = timerctl.timers[0]->timeout;} else {timerctl.next = 0xffffffff;}return;}void init_pit(void){int i;io_out8(PIT_CTRL, 0x34);io_out8(PIT_CNT0, 0x9c);io_out8(PIT_CNT0, 0x2e);timerctl.count = 0;timerctl.next = 0xffffffff; /* 初始化为没有正在运行的定时器 */timerctl.using = 0;for (i = 0; i < MAX_TIMER; i++) {timerctl.timers0[i].flags = 0; /* 未使用 */}return;}struct TIMER *timer_alloc(void){int i;for (i = 0; i < MAX_TIMER; i++) {if (timerctl.timers0[i].flags == 0) {timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;return &timerctl.timers0[i];}}return 0; /* 未找到 */}void timer_settime(struct TIMER *timer, unsigned int timeout){int e, i, j;timer->timeout = timeout + timerctl.count;timer->flags = TIMER_FLAGS_USING;e = io_load_eflags();io_cli();/* 搜索注册的位置 */for (i = 0; i < timerctl.using; i++) {if (timerctl.timers[i]->timeout >= timer->timeout) {break;}}/* i号之后全部后移一位 */for (j = timerctl.using; j > i; j--) {timerctl.timers[j] = timerctl.timers[j - 1];}timerctl.using++;/* 插入到空位上 */timerctl.timers[i] = timer;timerctl.next = timerctl.timers[0]->timeout;io_store_eflags(e);return;}