|
firstsail
高级用户
积分 668
发帖 295
注册 2005-7-26 来自 广东深圳
状态 离线
|
『楼 主』:
[转贴]DJGPP在实时高速数据采集中的应用
保护模式软件开发环境DJGPP在实时高速数据采集中的应用
http://www.bjx.com.cn/files/wx/xddzjs/2003-11/15.htm
高强业,王宪平,李圣怡,白奉天
(国防科技大学 机电工程与自动化学院湖南 长沙410073)
摘 要:介绍一种DOS下的32位C/C++保护模式软件开发环境DJGPP,他通过DPMI(DOS保护模式接口)实现DOS下32位保护模式软件的开发。结合嵌入式计算机PC/104及12位DAS模块PCM3718HG,具体说明DJGPP在实时、高速数据采集情况下的应用,有较强的实用性。
关键词:DPMI(DOS保护模式接口);实时;实模式;保护模式
Borland C是一款功能强大使用方便的DOS下的C语言开发环境,适用于大多数情形下DOS应用程序的开发。可是,当涉及到大量的高速数据采集时,用Borland C就不是很合适,因为他是工作在16位实模式下,编程时往往会因为1 MB的内存限制而碍手碍脚(用BC写32位程序并不是很方便),而DJGPP却可以很好地解决这个问题。DJGPP为一种DOS下的32位C/C++保护模式软件开发环境,他通过DPMI(DOS保护模式接口)接口实现DOS下32位保护模式软件的开发,很好地克服了1 MB内存的限制,这对于开发小型的实时高速数据采集系统不失为一种好的选择。
1DOS保护模式接口DPMI
Windows是工作在保护模式下的,他除了提供标准服务外,还支持一组特殊的DOS服务,称为DOS保护模式接口DPMI,由一些INT 2FH和INT 31H服务组成。他使应用程序能够访问PC系列计算机的扩充内存,同时维护系统的保护功能。INT 2FH为实模式下的调用,通过他可以获得实模式到保护模式的交换入口,调用实模式-保护模式的交换入口指针。INT 31H为保护模式下的调用, DPMI通过INT 31H来定义了一个新的接口,使得保护模式的应用程序能够用他作分配内存、修改描述符以及调用实模式软件等工作。
DPMI是由Microsoft,Intel,IBM,Lotus,Phar Lap,Rational systems,Borland,Quarterdeck及其他公司等联合建立的一种约定,他规定了一组服务程序,并可在保护模式下使用INT 31H来调用他们。这些服务程序的提供者,被称之为DPMI服务程序(server),如Microsoft Windows 3.0,Watcom的DOS/4GW,PMODE以及在将要介绍的DJGPP里要用到的CWSDPMI等,而这些服务程序的使用者,被称之为客户程序(client),如保护模式下的DOS扩展程序。
DPMI服务程序可以提供以下的服务:
(1)扩充内存的管理;
(2)描述符表的管理;
(3)中断及异常的管理;
(4)保护模式及实模式应用程序的通信;
(5)实模式调用的仿真;
(6)调试管理;
(7)DMA虚拟。
DPMI服务程序可由多种不同环境来建立,包括OS/2 2.0甚至UNIX,提供了在各种不同环境下,运行扩展的DOS应用程序的途径。
2DJGPP的安装及编制的集成开发环境RHIDE
DJGPP为DOS下32位C/C++保护模式软件开发环境,是一个GNU软件,即共享软件,可以通过网络得到他整个系统的源代码,并且可以任意修改。DJGPP的源程序按照内容被分割成许多压缩子文件,可以根据自己的需要,下载安装所需要的子源文件。
DJGPP的安装很简单,只要把所需的压缩子源文件解压到指定的路径即可,一般都安装到C盘根目录下的文件夹DJGPP中。可以在DOS下用pkunzip或unzip32来解压,也可以在Windows下解压,但是要注意,一定要用支持长文件名的解压程序,否则,在使用时可能找不到超过DOS的8+3文件格式的库文件和头文件。
DJGPP安装好以后,接下来就需装载DPMI服务程序,即保护模式引擎。DJGPP专用的DPMI服务程序有CWSDPMI,他是纯32位程序,使用线性地址;具有虚拟内存管理功能;可以使用所有硬件资源;可以直接对I/O口直接编程等。为了简化程序设计,CWSDPMI已经把大部分的DPMI接口融合到他的运行时库里,程序员只要调用C函数即可,而无需直接调用DPMI功能。CWSDPMI的装载方法同上,但解压后需在DOS下运行一次CWSDPMI.EXE,这样CWSDPMI才算正式装载完毕,接下来设置一下环境变量,就可以随便做自己想做的事情。
DJGPP拥有丰富的外围软件,尤其是专为DJGPP编制的集成开发环境RHIDE,他的界面是按照BORLAND C/C++3。1仿制而成,所有的操作及功能键都很雷同,他内置的DEBUG不仅功能超过了TURBO DEBUG,而且稳定性一流。同时这套IDE(集成开发环境)不仅支持C,而且支持C++,OBJ C,PASCAL ASM,ADA等多种语言,使得混合编程极为容易。最为难能可贵的是,在RHIDE界面的右上角还可以显示应用程序可以使用的虚拟内存和物理内存。例如,笔者使用的是内存为62 MB的嵌入式计算机PC/104,当使用RHIDE时,他的右上角会显示为184 M/57 M,即应用程序可以使用184 MB虚拟内存及高达57 MB的DPMI物理内存,这对实时高速数据采集非常重要。
目前,DJGPP还不是很完善,也有许多BUG,但比起其他商业化的C系统,他的发展要快得多,因为他是GNU软件,无数计算机高手在不断地完善他,为他注入新的活力,被发现的BUG也会在第一时间被去除。
3应用实例
我们用PC/104及12位DAS模块PCM3718HG进行A/D转换及数据采集,PCM3718HG为美国AD公司生产的A/D板,他通过PC总线接口接到PC/104上,可以接16个单端模拟输入或8个双端模拟输入。
我们的实验系统工作时间很短,所以采集数据的时间也很短(最短时仅为3 s),而且采集的数据量较大,因此,如果数据采集应用程序能够不受实模式下1 MB内存限制,可以使用扩充内存的话,将会极大地提高数据处理的速度,更好地保证系统的实时性。
鉴于DJGPP不受1 MB内存限制可以任意使用物理内存的特点,这里采用RHIDE来编制数据采集应用程序。可以先建立一个或几个很大的缓冲区(数组)来存放数据,等到数据采集完毕以后,再把采集的数据从内存中导出来。
他之所以在DOS下编程而不在Windows下:Windows的强大功能及良好的图形用户界面(GUI)是不可否认的,但是在Windows下并不能直接对I/O端口进行操作,而在DOS下却可以。在本例中,通过I/O端口直接对PCM3718HG中的寄存器进行读写,操作非常方便。
下面给出本例在RHIDE中用C语言编制的数据采集应用程序的部分源程序,这里我们用了DAS模块PCM3718HG的6个双端模拟输入端口,进行6路数据采集。源程序如下:
该程序与一般的DOS下的C程序基本一样,区别只是在于缓冲区(数组)的大小。在该程序里定义了6个2 MB的缓冲区,当然还可以更大一些,因为程序可以使用的DPMI物理内存达到57 MB,而在BC 3.1里这根本是不可能的。在RHIDE里,程序的编译、连接操作和在BC 3.1里一模一样。上述源程序在RHIDE中经过编译、连接后生成了SIXCHANNEXE可执行文件,该文件执行后,成功地实现了数据的采集并将数据导入到PC/104中的FLASH卡中。
4结语
鉴于Windows的强大功能及友好的图形用户界面,很多人选择在Windows下编制数据采集实时软件。但是如果大家能够回过头来,尝试一下在DOS下用DJGPP编制实时软件,说不定会给大家一个意外的惊喜。
参考文献
[1]Andrew Schulman,等.未公开的DOS核心技术[M].熊桂喜,等译北京:清华大学出版社,1992
[2]任大庆.保护模式DOS程序设计原理与方法[J].内蒙古大学学报,1996,(6)
[3]DJ Delorie. README1ST file for DJGPP Version 2.03
[ Last edited by firstsail on 2008-11-27 at 18:13 ]
|
|
2008-11-27 18:08 |
|
|
firstsail
高级用户
积分 668
发帖 295
注册 2005-7-26 来自 广东深圳
状态 离线
|
『第
2 楼』:
[转]DPMI下的硬件中断(Whowin的文章)
DPMI下的硬件中断
http://blog.csdn.net/whowin/archive/2008/05/08/2417532.aspx
实模式下的硬件中断有很多书籍中都有介绍,保护模式下的硬件中断要复杂一些,但也有一些资料。本网志中的大部分文章将介绍在DOS环境下使用DJGPP完成保护模式下的程序,编译出来的程序需要在DPMI的支持下运行在保护模式下,通常情况下,并没有什么,当你需要调用DOS下的实模式下的功能调用时,DPMI会帮助你切换到实模式,完成后再返回保护模式,基本上不需要编程者去做任何处理,但在处理硬件中断方面就比较微妙了,我们究竟应该在实模式下完成硬件中断还是在保护模式下,如果原有的硬件中断工作在实模式下,而我却编了一个保护模式下的硬件中断程序,按规则,在我的中断例程完成后,我必须再执行原有的硬件中断,显然这里有些问题。
本文旨在探讨DPMI环境下的硬件中断机制,以及在此环境下硬件中断的编写方法,本文并不探讨实模式下和保护模式下硬件中断的原理,但文中难免涉及一些这方面的知识,请读者自行阅读相关资料解决。在本文的最后将举一个硬件中断的编写实例。
本文主要讨论的环境为:DOS6.22 + DJGPP + CWSDPMI。CWSDPMI为DJGPP自带的DPMI服务,也是一个好用的且免费的DPMI,有关这个环境的搭建,请参阅我以前的博文。
1、DPMI下的硬件中断机理
首先,不论是在实模式下还是在保护模式下,硬件中断都会产生,当程序运行在DPMI下时,尽管你的程序是32位的,运行在保护模式下,但当你调用实模式下DOS的功能调用时,DPMI会切换到实模式下,可能一个硬件中断恰恰在这个时候产生了,但不管什么时候发生了硬件中断,只要你的程序运行在DPMI下,DPMI将截获硬件中断,并将首先传送给保护模式下的硬件中断程序执行,然后再转到实模式下的中断例程;但并不是所有的DPMI都将在两种模式下执行中断例程,比如我们本文讨论的环境,CWSDPMI下,DPMI截获中断并交给保护模式下的中断例程,只有在保护模式的中断例程不存在的情况下,DPMI才会去执行实模式下的中断例程,在CWSDPMI下,DPMI只执行一种模式下的中断例程;但WINDOWS 95下的DPMI就不一样,windows宣称会执行把两种模式下的中断例程都执行一遍(抱歉,我并没有验证这一说法);所以,一定要了解你所处的环境,在下面的叙述中,如果不特别说明,均只在CWSDPMI下,综上所述,在DPMI下,你可以只编写一个保护模式下的中断例程,而无须管实模式下的中断例程。
但是,我们毕竟是在DOS下编程,很多资源来自于实模式的DOS,所以如果你的中断程序中需要大量地调用DOS的资源时,你必须要考虑编写实模式的中断程序,否则在你的中断程序中DPMI将不断地在实模式和保护模式之间切换,这将十分麻烦。
2、DPMI下编写硬件中断的方法
保护模式下的中断程序,按下面方法设置。
用汇编语言编写的中断程序,可以按照下面的步骤进行设置:
取得原有的中断程序结构
调用__dpmi_get_protected_mode_interrupt_vector函数,并把返回值存在结构中,以便程序退出时可以恢复原来的中断程序
锁定内存
调用__dpmi_lock_linear_region函数锁定在中断程序中可能用到的数据变量,代码,调用的函数;锁定失败有可能造成你的程序崩溃;
设置新的中断程序
调用__dpmi_set_protected_mode_interrupt_vector,把新中断程序的选择子(selector)和偏移(offset)填入__dpmi_paddr中,传给函数,其中,pm_selector可以通过函数__dpmi_cs()获得
如果中断程序用C语言写成,可以按照下面方法设置:
取得原有的中断程序结构
调用_go32_dpmi_get_protected_mode_interrupt_vector,将返回值存入适当的结构中,以便程序退出时恢复原有的中断程序
对中断程序进行再包装
调用_go32_dpmi_allocate_iret_wrapper函数,按照要求传递参数,该函数将按照中断程序的规范建立一个小的汇编函数对你的C语言写的中断程序进行包装,该函数填充的结构_go32_dpmi_seginfo将作为下面函数调用的参数。
设置新的中断程序
调用_go32_dpmi_set_protected_mode_interrupt_vector函数,把上一步骤返回的_go32_dpmi_seginfo作为参数传给该函数。
链接原有中断
如果需要在执行完你的中断程序后执行原有的中断程序,不必执行前面两个步骤,直接调用_go32_dpmi_chain_protected_mode_interrupt_vector即可,就是说不用调用_go32_dpmi_allocate_iret_wrapper和_go32_dpmi_set_protected_mode_interrupt_vector这两个函数,_go32_dpmi_chain_protected_mode_interrupt_vector将完成所有的工作,在填结构_go32_dpmi_seginfo时,把中断程序的偏移放到pm_offset中,把_my_cs()的返回值放到pm_selector中即可;用这种方式设置的中断你只能通过DJGPP的退出代码进行释放,别无他法。
用C语言写中断程序的主要问题是你根本没有办法完全锁定你的中断程序所用到的存储器,所以,这种方法只适用于那些较小的程序,你可以确认你的程序不会被page到磁盘上去;这一点要特别引起注意,一般情况下,中断程序尽量使用汇编语言完成比较好。
3、DPMI下一个时钟中断的实例
老实说,没有什么很好的例子可以给大家演示中断程序,本来想结合前面博文中的AC97的内容编写一个PCI的中断例子,但复杂一些,涉及的问题也比较多,好在比较幸运的是我找到了一个int 8h的例子,个人感觉写的比较简单,好懂,对这段代码做了大量的修改,原程序接管了int 8h,我修改后的程序接管了int 8h和int 70h两个中断,我觉得这样更能说明问题。
int 8h(IRQ 0)是时钟中断,来自可编程的时钟芯片8254,现在的机器中已经没有了这颗芯片,给集成到了别的芯片中,不过保留了原来的I/O端口及8254的控制方法,所以仍然可以使用控制8254的方法控制时钟;要说明这个例子,我们不得不先简单介绍一下8254这颗芯片。
8254定时器这颗芯片有三个定时通道,每个通道有一个I/O地址,timer0--timer2对应的I/O地址为40h--42h,这个I/O通道是8位的,通过这个8位的I/O通道可以对8254进行编程,8254内部有一个16位的锁存寄存器,编程时送入的技术值首先进入这个锁存寄存器,然后被复制到计数寄存器,当接到8254上的时钟信号每来一个脉冲时,这个计数寄存器中的数值将减一,当减到0时,8254将输出一个脉冲,同时再次将锁存寄存器中的内容复制到计数寄存器中,开始下一轮的计数。
所以,8254芯片的每个通道有两个输入信号:一个是时钟信号,一个是门控信号,用于控制该通道工作或者不工作(后面我们会提到);一个输出信号,即技术完毕后的输出信号。
一般情况下,timer0用于产生每秒18.2次的系统时钟中断(int 8h),这就是我们下面例子中要编的中断;timer1用于存储器刷新(现在应该不用了?);timer2连接扬声器,可以产生方波使扬声器发声。这里说到的知识可能有点老,尤其是timer1的用法,不过不妨碍我们的例子。
8254还有一个控制寄存器,I/O地址为43h,8位通道,该寄存器的定义如下:
还是简单说明一下:
bit [6:7] 准备对那个定时器通道进行操作,timer0 or timer1 or timer2
bit [4:5] 读/写当前计数值
00:读当前计数值,先读低8位,后读高8位,要两次in指令
01:只写高8位,一次out指令
10:只写低8位,一次out指令
11:先写低8位,后写高8位,要两次out指令
bit [1:3] 工作模式,有6种工作模式,其中模式0(000)为计数结束后产生中断;
模式3(003)为方波频率发声器,这两种模式后面要用
bit 0 写入值得格式,0--二进制格式,1--BCD码格式
前面说到,8254的输入信号里还有一个门控信号,对于timer0和timer1,这个信号永远是选通的,但timer2的门控信号是可控的,由I/O端口61h的bit 0和bit 1控制,该I/O端口的bit0为1则关断timer2的门控信号,为0开启门控信号,bit1和可以控制timer2和扬声器的通断,我们不需要使用这个信号。
pc机种还有一个实时时钟中断,int 70h(IRQ 8),由以前的MC146818这颗芯片产生,现在这颗芯片也已经集成到其他芯片中了,不过操作和控制方法还是相同,MC146818可以产生多种类型的中断,在初始状态都是关闭的,我们仅用它的周期中断,缺省为每秒1024次,我们并没有更改任何参数,仅仅是在中断中增加了一个中断计数,我们希望以此中断来检验我们接管的int 8h的正确性。
好了,基本知识介绍完了。下面是程序清单:
001 #include <stdio.h>
002 #include <dos.h>
003 #include <string.h>
004 #include <dpmi.h>
005 #include <pc.h>
006 void pm_new8h(void);
007 void lock_pm_new8h(void);
008 void pm_new70h(void);
009 void lock_pm_new70h(void);
010 char *get_cmostime(void);
011 #define IRQ0 0x8 // 8254 time interrupt
012 #define IRQ8 0x70 // Real time interrupt
013 #define PIT0 0x40 // 8254 timer0
014 #define PITMODE 0x43 // 8254 control word
/* 8254 controll word
bit 7:6 timer number 00--timer0 01--timer1 10--timer2
bit 5:4 latch,read format;
00--latch current count;
01--write low byte(no latching);
10--write high byte(no latching);
11--write low,then high byte
bit 3:1 mode number;
000--interrupt on terminal count;
001--programmable one-shot;
010--rate generator
011--sequare wave generator;
100--software triggered strobe;
101--hardware triggered strobe
bit 0 count byte; 0--binary 1--bcd
*/
015 #define IMR0 0x21 // IMR of 8259-0
016 #define IMR1 0xa1 // IMR of 8259-1
017 #define RTC_ADDR 0x70
018 #define RTC_DATA 0x71
019 #define PITCONST 1193180L // Frequence of 8254
020 #define PIT0DEF 18.2067597 // ticks per sec of previous int
021 #define RT_MS_PER_TICK 0.9765625
022 #define NEW8H 1 // flag. new int 8h valiable.
023 static float tick_per_ms = 0.0182068; // ticks per ms
024 static float ms_per_tick = 54.9246551; // interval between two ticks
025 static float freq8h = 18.2067597; // frequency of system timer
026 static unsigned char flag8h = 0;
027 static _go32_dpmi_seginfo old_handler_8h, new_handler_8h;
028 static _go32_dpmi_seginfo old_handler_70h, new_handler_70h;
029 static _go32_dpmi_registers r;
030 unsigned long int ticks_8h = 0;
031 unsigned long int ticks_70h = 0;
/***********************************************
* New int 8h initialization
* It chains int routine of protected mode and
* real mode
***********************************************/
032 void pctimer_init(unsigned int Hz) {
033 unsigned int pit0_set, pit0_value;
034 if (flag8h != NEW8H) { // Current int 8h is old.
035 disable(); // Disable interrupts
036 lock_pm_new8h(); // Lock int routine in protected mode
037 lock_pm_new70h(); // Lock int routine in protected mode
// Lock some var that will be used in int routine
038 _go32_dpmi_lock_data(&ticks_8h, sizeof(ticks_8h));
039 _go32_dpmi_lock_data(&ticks_70h, sizeof(ticks_70h));
040 _go32_dpmi_lock_data(&r, sizeof(r));
// Get the previous int routine in protected mode
// and chain a new routine
041 _go32_dpmi_get_protected_mode_interrupt_vector(IRQ0, &old_handler_8h);
042 new_handler_8h.pm_offset = (int)pm_new8h;
043 new_handler_8h.pm_selector = _go32_my_cs();
044 _go32_dpmi_chain_protected_mode_interrupt_vector(IRQ0, &new_handler_8h);
045 _go32_dpmi_get_protected_mode_interrupt_vector(IRQ8, &old_handler_70h);
046 new_handler_70h.pm_offset = (int)pm_new70h;
047 new_handler_70h.pm_selector = _go32_my_cs();
048 _go32_dpmi_allocate_iret_wrapper(&new_handler_70h);
049 _go32_dpmi_set_protected_mode_interrupt_vector(IRQ8, &new_handler_70h);
// initial RTC
050 outportb(RTC_ADDR, 0x0b);
051 outportb(RTC_DATA, 0x42);
// initial 8259-2
052 outportb(IMR1, 0x0c);
// initial Timer0 of 8254
053 outportb(PITMODE, 0x36); // 8254 command register
// 00110110b.timer0,write low and high
// sequare wave generator,binary
054 pit0_value = PITCONST / Hz;
055 pit0_set = (pit0_value & 0x00ff);
056 outportb (PIT0, pit0_set);
057 pit0_set = (pit0_value >> 8);
058 outportb (PIT0, pit0_set);
059 // initial vars for int
060 ticks_8h = 0;
061 flag8h = NEW8H; // new int 8h
062 freq8h = Hz; // frequence of new int
063 tick_per_ms = freq8h / 1000; // ticks per ms
064 ms_per_tick = 1000 / freq8h; // ms of per tick
065 enable(); // enable interrupts
066 }
067 }
068 void pctimer_exit(void) {
069 unsigned int pit0_set, pit0_value;
070 unsigned long tick;
071 char *cmostime;
072 if (flag8h == NEW8H) {
073 disable();
074 outportb(PITMODE, 0x36);
075 outportb(PIT0, 0x00);
076 outportb(PIT0, 0x00);
077 outportb(RTC_ADDR, 0x0b);
078 outportb(RTC_DATA, 0x02);
079 outportb(IMR1, 0x0d);
080 _go32_dpmi_set_protected_mode_interrupt_vector(IRQ0, &old_handler_8h);
081 _go32_dpmi_free_iret_wrapper(&new_handler_70h);
082 _go32_dpmi_set_protected_mode_interrupt_vector(IRQ8, &old_handler_70h);
083 enable();
084 cmostime = get_cmostime();
085 tick = PIT0DEF *
(
(((float) *cmostime) * 3600) +
(((float) *(cmostime + 1)) * 60) +
(((float) *(cmostime + 2)))
);
086 biostime(1, tick);
087 flag8h = 0;
088 freq8h = PIT0DEF;
089 tick_per_ms = freq8h / 1000;
090 ms_per_tick = 1000 / freq8h;
091 }
092 }
/****************************************
* New int 8h routine in protected mode
****************************************/
093 void pm_new8h(void) {
094 ticks_8h++;
095 }
/************************************************
* Lock the memory of new int 8h routine region
* in protected mode
************************************************/
096 void lock_pm_new8h(void) {
097 _go32_dpmi_lock_code(pm_new8h, (unsigned long)(lock_pm_new8h - pm_new8h));
098 }
/****************************************
* New int 70h routine in protected mode
****************************************/
099 void pm_new70h(void) {
100 disable();
101 ticks_70h++;
102 outportb(RTC_ADDR, 0x0c);
103 inportb(RTC_DATA);
104 outportb(0xa0, 0x20);
105 outportb(0x20, 0x20);
106 enable();
107 }
/************************************************
* Lock the memory of new int 70h routine region
* in protected mode
************************************************/
108 void lock_pm_new70h(void) {
109 _go32_dpmi_lock_code(pm_new70h, (unsigned long)(lock_pm_new70h - pm_new70h));
110 }
/*****************************************
* Sleep delayms ms
*****************************************/
111 void pctimer_sleep(unsigned int delayms) {
112 unsigned long int delaytick;
113 delaytick = delayms * tick_per_ms + ticks_8h;
114 do {
115 } while (ticks_8h <= delaytick);
116 }
117 char *get_cmostime(void) {
118 char *buff;
119 static char buffer[6];
120 char ch;
121 buff = buffer;
122 memset(&r, 0, sizeof (r));
123 r.h.ah = 0x02;
124 _go32_dpmi_simulate_int(0x1a, &r);
125 ch = r.h.ch;
126 buffer[0] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10);
127 ch = r.h.cl;
128 buffer[1] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10);
129 ch = r.h.dh;
130 buffer[2] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10);
131 buffer[3] = r.h.dl;
132 buffer[4] = (char)(r.x.flags & 0x0001);
133 buffer[5] = 0x00;
134 return(buff);
135 }
// Main program
136 int main(void) {
137 int i;
138 unsigned long int start, finish;
139 int elapsedtime[20];
140 float average_error, sum = 0;
141 printf("Press any key to begin the test. ");
142 printf("(It lasts for 20 seconds.)\n\n");
143 getch();
144 printf("Test in progress. Please wait...\n");
145 pctimer_init(1000); // 1000Hz, 1000 ticks per sec
146 for (i = 0; i < 10; i++) {
147 start = ticks_70h;
148 pctimer_sleep(1000);
149 finish = ticks_70h;
150 elapsedtime = (int)(finish - start);
151 }
152 pctimer_exit();
153 printf("Test finished.\n");
154 printf("Press any key to display the result...\n");
155 getch();
156 for (i = 0; i < 10; i++) {
157 sum += (float)((elapsedtime - 1024) * RT_MS_PER_TICK);
158 printf("iteration:%2d expected:1024 observed:%d difference:%d\n",
i + 1, elapsedtime, elapsedtime - 1024);
159 }
160 average_error = (float)sum / 10;
161 printf("\ntotal error:%.2f ms average error:%.2f ms\n",
sum, average_error);
162 return 0;
163 }
为了说明方便,源程序中加了行号,所有的注释语句没有行号。
先大概说一下这个程序完成的事情,PC机系统的时钟中断是18.2次/秒,本程序重新编写了系统时钟中断int 8h,试图做到1ms中断一次,链接到原中断上,这样int 8h(IRQ 0)的中断频率将从18.2Hz变成1000Hz,这势必影响到PC机的时钟,因为很多资源是依赖这个时钟中断的,比如时间;同时我们接管了实时时钟中断int 70h(IRQ 8),并启动了它的周期中断,中断频率应该是1024Hz,在这个中断中我们仅仅作了一个中断计数;主程序中在启动中断后做了10次的循环,每次循环前记录int 70h的中断次数计数值,然后等待int 8h中断1000次(应该刚好1秒钟),然后再记录int 70h的中断次数计数值,理论上说,int 8h中断1000次,int 70h应该刚好中断1024次,如果数值吻合,说明int 8h完全按照我们的预期在运行。
常量、变量说明:
ticks_8h:int 8h的中断次数计数
ticks_70h:int 70h的中断次数计数
PIT0:8254 timer0的端口地址,我们要修改timer0的设置
PITMODE:8254的控制寄存器
IMR1:第二颗8259中断控制器的中断屏蔽寄存器地址,因为IRQ 8连在第2颗8259上
RTC_ADDR:实时时钟芯片(MC146818)地址寄存器地址
RTC_DATA:实时时钟芯片(MC146818)数据寄存器地址
主程序从136行开始,145行调用pctimer_init初始化两个硬件中断,传递的参数1000表明把int 8h的中断频率设成1000Hz;146行--151行是测试部分的主循环,前面有过介绍,pctimer_sleep(1000)可以等待int 8h中断1000次,最后把int 70h在此期间的中断次数记录在数组elapsedtime中,共后面显示结果用;循环完毕后,调用pctimer_exit恢复以前的中断,然后显示测试记录。
比较复杂的部分是函数pctimer_init,从32行--67行。
35行关中断
36、37行锁定了两个中断程序的代码
38--40行锁定了中断程序中要用到的数据
41--44行设置了int 8h(IRQ 0)
45--49行设置了int 70h(IRQ 8),注意这两个中断的设置方法是有所不同的,我们把int 8h与原来的中断链接到了一起,而int 70h没有链接原来的中断,读者可以通过这段代码体会其中的不同
50、51行设置了实时时钟芯片的状态寄存器B,目的是开启周期中断,读者可以参考与实时时钟芯片相关的资料
52行重新设置了第二颗8259中断控制器,目的是清除对IRQ 8的屏蔽,这里要注意的是,在我做测试的机器上,我读取了第一颗8259的中断屏蔽寄存器,知道其用于级联第二颗8259的IRQ 2并没有被屏蔽,所以我并没有设置第一颗8259的中断屏蔽寄存器,如果你的机器与我的不同,请注意设置第一颗8259的中断屏蔽寄存器,打开IRQ 2,另外,我知道我的机器第二颗8259的中断屏蔽寄存器的缺省设置值是0dh,刚好IRQ 8被屏蔽了,所以我直接送了一个0ch到第二颗8259的中断屏蔽寄存器,以打开IRQ 8,在你的机器上可能也有些不同,请按实际情况设置,只要打开IRQ 8即可,不要改变其他的屏蔽位,以免麻烦,由于此处并非本文的重点,所以在代码上没有作更多处理
54--58行初始化8254的timer0,请参考前面关于8254芯片的介绍部分,另外在14和15行之间的注释也有一个对8254的简单介绍
60--64行设置了一些变量,明白变量的含义就好了,源程序中有注释
65行打开中断,此时两个新的中断开始运行了
93--95行是int 8h的中断程序,没有什么好说的,只是不断递增ticks_8h这个变量,注意int 8h这个中断程序是要链接到原有中断上的,所以最后不需要向8259发出中断结束的指令
99--107行是int 70h的中断程序,需要做一些说明,首先102、103行读取了实时时钟芯片的状态寄存器C,这是必须要做的,否则芯片认为你没有响应前一个中断,不会发出下一个中断;另外,这个中断和int 8h不同,没有链接到原有中断,所以你必须向8259发出中断完成的指令,又因为IRQ 8连接在第二颗8259上,而第二颗8259通过第一颗8259级联,所以必须向两颗8259均发出中断完成的指令,这就是104、105行的作用
117--135行这段程序对本文不重要,这是因为我们改变了系统时钟中断(int 8h)的中断频率,这势必导致本机时间混乱,所以在退出时,读取以下实时时钟芯片中的实时时间,然后重新设置一下时间
68--92行这段程序基本上是pctimer_init这个函数的逆动作,也不需要过多说明
至此,程序解释完了,希望能给你一些帮助。
|
|
2008-11-27 18:31 |
|
|
firstsail
高级用户
积分 668
发帖 295
注册 2005-7-26 来自 广东深圳
状态 离线
|
『第
3 楼』:
[转]Watcom C/C++与DJGPP开发环境的对比
Watcom C/C++与DJGPP开发环境的对比http://www.xuyibo.org/kb/204.htm
1、Watcom C/C++(以下简称WC)和DJGPP都是dos环境下32位应用程序开发的工具;WC的优势在于高性能的代码生成,内联编译语言,完善的工具集及跨平台编译;
WC提供的函数库除了提供标准C\\c++库的函数外还拥有
Heap Functions / Process Primitive Functions / Process Environment / Operating System I/O Functions /Console I/O Functions / Default Windowing Functions / BIOS Functions /DOS-Specific Functions / Intel 80x86 Architecture-Specific Functions / Intel Pentium Multimedia Extension Functions等其他函数
WC的支持库包括:
图形库 : SciTech's MGL; wxWindows;WGT Watcom C/C++ ;Xlib Watcom C/C++; Watcom C/C++ ModeX;PD Curses; IFF Software;JLIB ;Zephyr's ZSVGA ;gkit ;StelSOFT GDK ;Allegro D-Flat;OWL NExt ;Turbo Vision+ ;libpng ;
音频库 : VAT Watcom C/C++ ;SMIX Watcom C/C++ ;DSIK Watcom C/C++
及其他工具库dllPower ;STLPort ;XBase ;zlib 等等。
DJGPP是GNU c/c++编译器gcc在dos extender中的移植版本,他是一款免费的编译器,在rhide等工具集成下成为一款很好的开发工具。DJGPP的主要特点是纯面向32位编程,由于开源的特性,使用它编出来的程序具有很好的健壮性。此外DJGPP的头文件十分清晰,接口说明齐全。DJGPP的函数库也十分丰富,gnu c/c++ 中的库函数在DJGPP中都找得到,此外还拥有内嵌的cwsdpmi接口函数库,等。DJGPP的支持库有allegro /grx-graphic library /unix curses emulator等
2、相同点:
两者都提供了内联函数编译功能:
DJGPP提供了内联编译功能通过直接在函数前加inline关键字来实现,当然在ANSI C 中也可以直接使用宏_INLINE_来限定函数在编译时作为内联函数。
WC 使用一个#pragma的专用指令,通过#pragma aux <函数名>=... 来定义一个函数为内联函数,并可以指定实模式和保护模式的数据传输。WC的pragma功能十分强大通过它可以指定特定的编译选项;可以指导代码生成器改变默认的生成规则为传递参数及返回值指定顺序;可以指定函数的特性用以为代码生成器生成更加有效的代码提供指示;同时利用#pragma可以实现内联机制,包括实现dos和BIOS功能调用等。
内存模型:
两者在32位编程方式下均提供的是plat memory model;
3、比较
编译速度:
WC要比DJGPP快
运行速度:
虽然WC编译的代码效率更高,但DJGPP是纯DPMI的运行环境,其运行速度和WC的相差不大,而且由于dos4gw的先天不足,可能有些时候WC还要稍逊一筹。
编译生成的执行文件大小对比:
'hello world'程序通过WC编译后生成的可执行文件大小为37.8k;
djgpp默认编译选项编译生成的可执行文件大小为68.2k;
加-s连接选项后编译生成的可执行文件大小为43k;
平台支持:
Watcom C/C++支持跨平台的编译,可以在window,os/2, Dos下生成应用程序.DJGPP仅仅面向DOS系统。
模式转换机制
DJGPP内嵌的是DPMI标准的一个免费版本cwsdpmi。他解决了在实模式上运行保护模式的问题,至于在实模式和保护模式间传递参数,djgpp采用了一个称为transfer buffer的机制来解决。transfer buffer在程序开始运行时被分配一个2-64k的内存区,任何需要被传递的参数均被放在这个buffer中,由djgpp的底层库函数负责将buffer中的数据与实模式内存间的复制工作。同时DJGPP也可以采用其他的DPMI来处理实模式同保护模式间的转换。
WC采用的DPMI是dos4gw,dos4gw是DPMI0.9标准的一个不完整的实现,他不能在实模式调用时返回精确的错误码,甚至连一些简单的错误返回都不完全支持。
Dos和bios系统中断调用
WC中的中断调用只有一种:
就是直接调用int386()或int386x()函数来实现;
DJGPP的中断调用可采用如下三种方法:
1.使用_go32_dpmi_系列函数:
典型的步骤为:定义_go32_dpmi_seginfo结构的两个变量new_handler、old_handler;
利用_go32_dpmi_get_protected_mode_interrupt_vector()函数取得当前isr地址并保存到older_handler中;
将你的中断处理函数地址填入new_handler中,并用_go32_dpmi_allocate_iret_wrapper()函数来分配一个中断向量,或者用 _go32_dpmi_chain_protected_mode_interrupt_vector()连接到一个中断向量中,这样可以在程序完成后恢复原来的中断向量;
的isr入口地址填写到中断向量表中;
最后程序运行时将取代老的ISR响应自定义的ISR;
2.也可以直接使用中断调用
利用_dpmi_regs来保存寄存器需要的功能号; 利用_dpmi_int来调用中断号
3.DJGPP还支持调用其他dmpi的函数来实现中断功能
4、综合评论:
由于两者的目标不一样,DJGPP的方向是尽量同GCC保持兼容,向开源和稳定性方面发展;而作为商业工具的WC在开发调试方便性和代码生成上面更胜一筹。
http://www.ds0101.com/Article/ShowInfo.asp?ID=18&Page=1
|
|
2008-11-27 18:48 |
|
|