实验日期 | 实验项目 |
---|---|
2020.10.22 | 第4天 C语言和画面显示的练习 |
(1).内容概要
使用汇编语言编写一个write_mem8函数来实现对指定地址中数据的写入,wirte_mem8函数的参数1是地址,参数2是写入该地址的数据值。调用函数时,会在内存中开辟函数栈空间,函数参数从栈顶指针esp+4的位置开始存储,有如下对应关系:
第一个参数的存放地址 | ESP+4 |
第二个参数的存放地址 | ESP+8 |
第三个参数的存放地址 | ESP+12 |
另外,指定内存地址的地方,可以自由使用寄存器EAX,ECX,EDX,其他寄存器因为在C语言编译生成的机器语言中用于记忆非常重要的值,因此只能使用其值,不能改变其值。
(2).关键代码分析
naskfunc.nas代码分析
[FORMAT "WCOFF"]
[INSTRSET "i486p"] ;告诉nask,程序是给486使用的
[BITS 32]
[FILE "naskfunc.nas"]
GLOBAL _io_hlt ,_write_mem8;
[SECTION .text]
_write_mem8: ;void write_mem8(int addr,int data)
MOV ECX,[ESP+4] ;[ESP+4]存放的是地址
MOV AL ,[ESP+8] ;[ESP+8]存放的是数据
MOV [ECX],AL
RET
wirte_mem8函数是使用汇编语言写的,将地址和写入数据存放到对应的寄存器中,再利用相应的间接寻址的方式把数据写入指定地址中去。之和的实验中会使用C语言直接对内存写入,现在先暂时用汇编实现。
bootpack.c代码分析
void io_hlt(void);
void write_mem8(int addr,int data);
void HariMain(void)
{
int i;
for(i=0xa0000;i<=0xaffff;i++)//0xa0000-0xaffff这64k大小的内存中用来设定颜色
{
write_mem8(i,15);//将第2个参数的值写入第1个参数表示的地址中
}
for(;;)
{
io_hlt();
}
}
这部分代码在for循环中调用write_mem8函数将64k大小的图形缓冲区中每个像素换成15,即白色所对应的像素数值。替换完成后,运行代码,显示屏就会变成白色。
(1).内容概要
为了改变显示在屏幕上的图案,绘制出不同的图案效果。可以使用C语言中的逻辑运算符去改变颜色的色号。图形数据处理方式总结如下:
和1相或(OR) | 使特定位变1 |
和0相与(AND) | 使特定位变0 |
和1相异或(XOR) | 使特定位反转 |
(2).关键代码分析
Bootpack.c代码分析
void io_hlt(void);
void write_mem8(int addr,int data);
void HariMain(void)
{
int i;
for(i=0xa0000;i<=0xaffff;i++)
{
write_mem8(i,i & 0x0f);//i和0x0f相与,得到16种不同的色号
}
for(;;)
{
io_hlt();
}
}
这部分代码主要是用来绘制条纹图案。将i和0x0f相与,将会是i的值从0—f开始循环,对应在画面上的效果就是0-f对应 16种颜色,按照列依次排列形成条纹图案。
代码实现的效果如下:
(1).内容概要
在汇编语言中,可以省略BYTE等的情况:源和目的操作数位数相同;源和目的操作数均是寄存器。当我们使用指针(地址的专用变量)时,可以相应用于BYTE,WORD类地址。对应关系如下:
char *p | 用于BYTE类地址,1个字节 |
short *p | 用于WORD类地址,2个字节 |
int *p | 用于DWORD类地址,4个字节 |
引入指针后,对于MOV ECX,I;MOV BYTE [ECX], (I &0x0f)两句汇编语言可以用C语言替代,声明一个指针变量p,指针变量指针地址ECX,*p表示该地址中存放的值,使用赋值语句即可改变地址p中存放的数值。
下表是对将数据写入内存的几种方式的总结:
方式 | 用法(使用的语句) |
---|---|
汇编语言MOV指令 | MOV ECX,地址;MOV AL,数据;MOV [ECX],AL |
类型转换 | *((char *) i)=数据,i为写入的位置 |
指针 | i[p]=数据;p[i]=数据; *(p+i)=数据;这3种方式中p为写入的首地址,需要提前指定,i为相对于首地址的偏移量p=(char *)i , *p=数据 |
(2).关键代码分析
bootpack.c代码分析
void io_hlt(void);
void write_mem8(int addr,int data);
void HariMain(void)
{
int i;
char *p;
for(i=0xa0000;i<=0xaffff;i++)
{
p=(char *)i;//这样写是为了消除警告
*p=i & 0x0f;
}
for(;;)
{
io_hlt();
}
}
这部分代码是使用指针向内存写入数据,实现之前的图形绘制。其中p=(char )i用到了类型转换。类型转换是改变数值类型的命令,一般不必每次都注意类型转换,但是这里的给指针变量赋值时需要先强制类型转换为对应类型,不然会存在警告,因为不是每个数值都可以用来作为内存地址。在for循环中内容也可以相应使用类型转换将数据写入内存的语句替换,如((char *) i)=i & 0x0f,或者使用指针的其他写法,如i[p]=i &0x0f,p[i]=i &0x0f, *(p+i)=i & 0x0f。
(1).内容概要
本次实验中使用320*200的8位颜色模式,色号使用8位(二进制)数表示,但是这种方式可以指定的颜色很少。一般指定颜色是采用#ffffff的方式,使用6位16进制的数,用2位16进制的数表示R,2位16进制的数表示G,2位16进制的数表示B,也就是RGB的色彩表示方法。实验中用到的0-15号码的颜色如下:
调色板的访问步骤:
a.设置中断屏蔽
b.将设定的调色板号码写入0x03c8,将颜色按照RGB的顺序写入0x03c9。当设定多个调
色板号码时,可以省略调色板号码,直接写入颜色。
c.如果想要读出调色板的状态,则首先将调色板号码写入0x03c7,在从0x03c9中依次读
出RGB。
d.取消中断屏蔽。
汇编指令:IN指令是从设备取得电气信号,OUT指令是向设备发送电信号。IN和OUT指令可以实现CPU和设备之间的端口通信。PUSHFD指令是将标志位按照双字节压入栈,即PUSH EFLAGS,POPFD指令是将标志位按照双字节弹出栈,即POP EFLAGS。
EFLAGS寄存器的介绍:EFLAGS 是一组用来存放进位标志和中断标志等标志的存储器。进位标志可以用JC或者JNC来判断进位标志是否为0.。对于中断标志,需要读入EFLAGS,检查第9位的情况,第9位为0,忽视中断请求,否则则是立即处理中断请求。
(2).关键代码分析
_io_cli: ; void io_cli(void);//设置中断位为0
CLI
RET
_io_sti: ; void io_sti(void);//设置中断位为1
STI
RET
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {//定义16种颜色的数组
0x00, 0x00, 0x00, /* 0:黒 */
0xff, 0x00, 0x00, /* 1:亮红 */
…
0x84, 0x84, 0x84 /* 15:暗灰 */
};
set_palette(0, 15, table_rgb);//设置调色板
return;
/* C语言中static char 语句只能用于数据,相当于汇编中的DB指令*/
}
这部分代码是初始化调色板,首先定义一个16*3的二维数据,每行表示一种颜色,将0-15号颜色按照RGB的顺序调用set_palette写入调色板
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 记录中断许可标志的值 */
io_cli(); /* 将中断许可标志置为0,禁止中断 */
io_out8(0x03c8, start);
for (i = start; i <= end; i++) {
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); /* 复原中断许可标志 */
return;
}
这部分代码是设定的颜色写入对应的设备号码中。首先使用io_load_sflags()函数记录中断许可标志,接着调用io_cli()函数设置中断屏蔽,将设定的调色板号码写入0x03c8,使用for循环从start到end按照RGB的顺序分别将end-start+1钟颜色写入0x03c9。最后使用io_store_aflags()取消中断屏蔽。
(1).内容概要
(2).关键代码分析
#define COL8_000000 0
…
#define COL8_848484 15
采用宏定义不同的色号
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c;
}
return;
}
这部分代码是绘制矩形的代码。输入4个坐标参数,分别表示矩形左上角的坐标和矩形右下角的坐标,从而确定整个矩形。y*xsize+x表示按照列的方式进行访问,vram[y * xsize + x] = c表示给坐标为(x,y)的像素设置值为c。
void HariMain(void)
{
char *p;
init_palette(); /*设定调色板*/
p = (char *) 0xa0000; /* 颜色设定的首地址*/
boxfill8(p, 320, COL8_FF0000, 20, 20, 120, 120);//绘制矩形左上角坐标为(20,20),右下角坐标为(120,120)
boxfill8(p, 320, COL8_00FF00, 70, 50, 170, 150);
boxfill8(p, 320, COL8_0000FF, 120, 80, 220, 180);
for (;;) {
io_hlt();
}
}
这部分代码是绘制三个不同颜色矩形的代码。绘制系统界面的代码类型,关键在于去确定矩形的位置。坐标系中y轴正方向水平向下,x轴正方向水平向右。以下是对系统界面的绘制的分析。
boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29);
//系统界面的上半部分
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28);
boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27);
//上部分界面和下部分界面的分界
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1);
//界面下半部分
boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24);
boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5);
//左下角的方框
boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3);
boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);
//左下角方框的阴影效果
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3);
//右下角方框
教材第77页,set_palette函数中rgb[0]/4、rgb[1]/4、rgb[2]/4为什么要除以4?
起初原本的想法是除以4之后,是为了提高颜色的灰度。经过试验发现确实如此,当增大除数的时候,颜色显示会变得越来越暗,除以一个数相当于是进行右移操作,颜色的像素值会变小,对应的颜色也会逐渐加深,但是对于除以4的解释还是不够全面。后来通过同学的启示,从图形显示模式入手,查阅资料得到如下原因。
本次实验绘制图形是在3202008的八位彩色图形模式下进行的。通过查阅资料可以知道VGA指定调色板颜色时,一个颜色频道只有6位,而我们使用的24位颜色模式中8位表示R,8位表示G,8位表示B,已经超出了颜色频道的表示位数,所以需要舍弃2位。考虑到颜色偏差不能太大,选择右移操作舍弃低2位,对应的操作就是除以4。
运用本次所学的图形显示技术,绘制了一个黄色的小人(以下简称小黄人),通过循环控制坐标的变化,可以使小黄人的双臂摆动,向屏幕前的你打招呼。背景颜色更换为亮灰色。
矩形绘制代码和原代码一致
圆的绘制代码
圆弧的绘制代码和圆的绘制代码基本一致,不过因为是绘制圆弧,需要对x,y两个坐标进行位置的限制,使其绘制出的结果是一个弧形。
主体函数部分
使用ly和ry控制手上下挥动,m用来控制挥手速度,k用来控制抬手的高度