跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第一节 控制光标
 
 

        需要说明的是,在我们进入保护模式之后,所有的BISO中断程序都不能够使用了,所以我们只能,通过I/O端口操作来操作显示器上的光标和显示字符。要在显示器上显示一个字符首先要确定光标的位置,在光标所在的位置显示字符,并将光标移到下一个位置。在显示器中有一系列的寄存器。先来看一下各个寄存器的功能:

寄存器名称

索引号
(Hex)

写端口
(EGA/VGA/TVGA)

读端口
(EGA)

读端口
(VGA/TVGA)

地址寄存器

--

0x3D5/0x3B5

 

0x3D4/0x3B5

水平扫描总时间

0x00

0x3D5/0x3B5

 

0x3D5/0x3B5

水平显示结束

0x01

0x3D5/0x3B5

 

0x3D5/0x3B5

水平消隐开始

0x02

0x3D5/0x3B5

 

0x3D5/0x3B5

水平消隐结束

0x03

0x3D5/0x3B5

 

0x3D5/0x3B5

水平回扫开始

0x04

0x3D5/0x3B5

 

0x3D5/0x3B5

水平回扫结束

0x05

0x3D5/0x3B5

 

0x3D5/0x3B5

垂直扫描总时间

0x06

0x3D5/0x3B5

 

0x3D5/0x3B5

溢出

0x07

 

 

0x3D5/0x3B5

行扫描预置

0x08

0x3D5/0x3B5

 

0x3D5/0x3B5

最大扫描行

0x09

0x3D5/0x3B5

 

0x3D5/0x3B5

光标起始

0x0A

0x3D5/0x3B5

 

0x3D5/0x3B5

光标结束

0x0B

0x3D5/0x3B5

 

0x3D5/0x3B5

显存起始地址(高)

0x0C

0x3D5/0x3B5

0x3D5/0x3B5

0x3D5/0x3B5

显存起始地址(低)

0x0D

0x3D5/0x3B5

0x3D5/0x3B5

0x3D5/0x3B5

光标位置(高位)

0x0E

0x3D50x3B5

0x3D50x3B5

0x3D50x3B5

光标位置(低位)

0x0F

0x3D50x3B5

0x3D50x3B5

0x3D50x3B5

垂直回扫开始

0x10

0x3D5/0x3B5

 

0x3D5/0x3B5

垂直回扫结束

0x11

0x3D5/0x3B5

 

0x3D5/0x3B5

光笔地址〔高位)

0x10

仅EGA有效只读

0x3D5/0x3B5

0x3D5/0x3B5

光笔地址(低位)

0x11

仅EGA有效只读

0x3D5/0x3B5

0x3D5/0x3B5

垂直显示结束

0x12

0x3D5/0x3B5

 

0x3D5/0x3B5

偏移/逻辑屏宽度

0x13

0x3D5/0x3B5

 

0x3D5/0x3B5

下划线位置

0x14

0x3D5/0x3B5

 

0x3D5/0x3B5

垂直消隐开始

0x15

0x3D5/0x3B5

 

0x3D5/0x3B5

垂直消隐结束

0x16

0x3D5/0x3B5

 

0x3D5/0x3B5

模式控制

0x17

0x3D5/0x3B5

 

0x3D5/0x3B5

行比较

0x18

0x3D5/0x3B5

 

0x3D5/0x3B5

        为了节省计算机的端口数,这些显示器寄存器采用了地址方式来进行读写。具体来说就是使用了一个地址寄存器,通过这个寄存器的索引来访问其它寄存器。比如:想要访问光标位置(高位,它的索引是14)这个寄存器,就先要向0x03d4输出它的索引14,然后再向0x3d5端口输出光标高位的值。想要访问光标位置(低位,它的索引是15)这个寄存器,就先要向0x03d4输出它的索引15,然后再向0x3d5端口输出光标低位的值。

        我们在工程中创建一个叫作printf的文件夹,并在其下创建一个printf.c文件,同时在include/kernel下创建一个printf.h的头文件。如下图所示:


 

        在printf.c中,我们实现了两个函数,分别是get_cursor和set_curso,取得当前光标位置和设置光标位置。代码如下:

/***
 * 取得光标位置
 * return: 光标的线性位置
 */
u16 get_cursor()
{
        //告诉地址寄存器要接下来要使用14号寄存器
        outb_p(14, 0x03d4);

        //从光标位置高位寄存器读取值
        u8 cursor_pos_h = inb_p(0x03d5);

        //告诉地址寄存器要接下来要使用15号寄存器
        outb_p(15, 0x03d4);

        //从光标位置高位寄存器读取值
        u8 cursor_pos_l = inb_p(0x03d5);

        //返回光标位置
        return (cursor_pos_h << 8) | cursor_pos_l;
}

        想要设置光标的位置也很简单,把outb_p修改成inb_p,由向端口输出改为从端口输入,分别向14号和15号寄存器输出光标位置的高8位数值和低8位数值即可:

/***
 * 设置光标位置
 * u16 x: 光标的横坐标
 * u16 y: 光标的纵坐标
 */
void set_cursor(u16 x, u16 y)
{
        //计算光标的线性位置
        u16 cursor_pos = y * 80 + x;

        //告诉地址寄存器要接下来要使用14号寄存器
        outb_p(14, 0x03d4);

        //向光标位置高位寄存器写入值
        outb_p((cursor_pos >> 8) & 0xff, 0x03d5);

        //告诉地址寄存器要接下来要使用15号寄存器
        outb_p(15, 0x03d4);

        //向光标位置高位寄存器写入值
        outb_p(cursor_pos & 0xff, 0x03d5);
}

        接下来在start_kernel显示Hello World!处加入光标控制函数,让显示器每显示一个字符就将光标移动到下一个位置:

//全局字符串指针变量
char *str = "Hello World!";

//内核启动程序入口
int start_kernel(int argc, char **args)
{
        //显存地址
        char *p = (char *) 0xb8000;

        //显示str的内容到显示器上
        for (int i = 0; str[i] != '\0'; i++)
        {
                //显示字符
                p[i * 2] = str[i];

                //取得光标位置
                u16 cursor_pos = get_cursor();

                //下一个光标位置
                cursor_pos++;

                //设置光标到新位置
                set_cursor(cursor_pos % 80, cursor_pos / 80);
        }

        //永无休止的循环
        for (;;)
        {
        }
        return 0;
}

        将printf.c加入到Makefile当中,并执行make all命令。运行虚拟机lidqos可以看到显示器的光标位置已经发生了改变,运行结果正确:


 

        源代码的下载地址为:

https       https://github.com/magicworldos/lidqos.git
git         git@github.com:magicworldos/lidqos.git
subverion   https://github.com/magicworldos/lidqos
branch      v0.7

 

    返回首页    返回顶部
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号