Sign in Join
Wiki copytemporary

Hardware Access/zh CN: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Hardware Access/zh CN

OldNewLine
1+## 概览
2+
3+本页是关于在Lazarus上访问硬件设备的教程的开始。这些设备包括,但不限于:ISA,PCI,USB,并行端口,串行端口。
4+
5+RTL或LCL没有实现统一的多平台硬件设备的访问。因此本教程将基本覆盖不同平台上的硬件访问方法。在不同环境下可以使用条件编译来编译代码,像这样:
6+
7+``` pascal
8+ uses
9+ Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
10+ {$IFDEF WIN32}
11+ Windows;
12+ {$ENDIF}
13+ {$IFDEF Unix}
14+ ports;
15+ {$ENDIF}
16+```
17+
18+此时还不知道Mac OS X/x86是否会允许HW访问。它可能不允许,虽然那种情形下我假设像io.dll的驱动器将会及时出现。
19+
20+## 并行和串行的比较
21+
22+ISA卡,PCI卡和并行端口使用“并行”协议与计算机进行通信。串行端口和USB设备使用“串行”协议。因为处理器和编程语言都通过并行方式处理数据,在软件端比较容易实现对这类协议的访问。例如,访问一个整型变量时,可以仅用一个命令就能访问它的值。然而,使用串行协议,一次仅能知道一位(bit),需要将所有片断(piece)放在一起才能理解数据。
23+
24+串行通信比较难于直接实现,如果使用预制(pre-made)组件,可能会稍微容易些。在硬件端也比较困难,因此许多设备使用专门的集成电路或甚至微控制器来实现。
25+
26+现在来做一个硬件访问协议的简要比较:
27+
28+| | 速度 | 硬件实现难度 |
29+|----------|----------------------|--------------|
30+| 串行端口 | 非常慢 (\< E5 bit/s) | 中等 |
31+| 并行端口 | 慢 (~ E6 bit/s) | 容易 |
32+| ISA卡 | 中等 (~ E7 bit/s) | 中等 |
33+| USB | 中等 (~ E7 bit/s) | 困难 |
34+| PCI卡 | 非常快 (\> E9 bit/s) | 非常难 |
35+
36+## 并行通信
37+
38+### 在Windows操作系统中使用inpout32.dll
39+
40+在9x系列和NT系列,Windows有不同的方法来访问硬件设备。在9x系列(95,98,Me),程序可以直接访问硬件,正如在DOS上一样。然而,NT系列(Windows NT和XP)不允许这种方式。在该架构上,所有与硬件端口的通信必须通过一个设备驱动器。这是一种安全机制,但是为一个小项目开发一个驱动器可能会花费太多的时间和金钱。
41+
42+幸运地是有一个库解决了这个问题。如果检测到Windows NT,它解压HWInterface.sys内核设备驱动器并安装。如果检测到Windows 9x,它简单地使用汇编操作码访问硬件。
43+
44+但是如何使用这个库呢?非常简单!它仅有2个函数:Inp32和Out32,它们的使用是比较直观的。
45+
46+我们将动态加载该库,因此让我们首先定义这2个函数:
47+
48+``` pascal
49+ type
50+ TInp32 = function(Address: SmallInt): SmallInt; stdcall;
51+ TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
52+```
53+
54+- Address代表期望访问的端口地址
55+- Out32发送数据到指定地址的端口
56+- Inp32从指定地址的端口返回一字节
57+
58+现在可以加载该库了。这可能在一个类似程序主form的OnCreate方法的地方来实现:
59+
60+``` pascal
61+ type
62+ TMyForm = class(TForm)
63+ .........
64+ private
65+ { private declarations }
66+ Inpout32: THandle;
67+ Inp32: TInp32;
68+ Out32: TOut32;
69+ .........
70+ implementation
71+ .........
72+ procedure TMyForm.FormCreate(Sender: TObject);
73+ begin
74+ {$IFDEF WIN32}
75+ Inpout32 := LoadLibrary('inpout32.dll');
76+ if (Inpout32 <> 0) then
77+ begin
78+ // needs overtyping, plain Delphi's @Inp32 = GetProc... leads to compile errors
79+ Inp32 := TInp32(GetProcAddress(Inpout32, 'Inp32'));
80+ if (@Inp32 = nil) then Caption := 'Error';
81+ Out32 := TOut32(GetProcAddress(Inpout32, 'Out32'));
82+ if (@Out32 = nil) then Caption := 'Error';
83+ end
84+ else Caption := 'Error';
85+ {$ENDIF}
86+ end;
87+```
88+
89+如果在OnCreate加载了该库,那么不要忘记在OnDestroy卸载它:
90+
91+``` pascal
92+ procedure TMyForm.FormDestroy(Sender: TObject);
93+ begin
94+ {$IFDEF WIN32}
95+ FreeLibrary(Inpout32);
96+ {$ENDIF}
97+ end;
98+```
99+
100+这里是一个如何使用Inp32函数的简单例子:
101+
102+``` pascal
103+ {$IFDEF WIN32}
104+ myLabel.Caption := IntToStr(Inp32($0220));
105+ {$ENDIF}
106+```
107+
108+该代码在Windows XP上使用Lazarus 0.9.10,在端口\$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。
109+
110+这是该库的主页:[www.logix4u.net/inpout32.htm](http://www.logix4u.net/inpout32.htm) *查看讨论*
111+
112+### 在Windows 9x上使用汇编
113+
114+在Windows 9x上也可以使用汇编代码。假设希望把\$CC发送到\$320端口。下面代码可以实现:
115+
116+``` pascal
117+ {$ASMMODE ATT}
118+ ...
119+ asm
120+ movl $0x320, %edx
121+ movb $0xCC, %al
122+ outb %al, %dx
123+ end ['EAX','EDX'];
124+```
125+
126+### 在Windows上的疑难解答
127+
128+在Windows上使用不支持即插即用并行硬件的一个可能问题来源是,Windows可能将该硬件使用的端口分配给了另外的设备。你可以在下面的URL里找到关于如何告诉Windows不要将你设备的地址分配给即插即用设备的指示:
129+
130+<http://support.microsoft.com/kb/135168>
131+
132+### 在Linux上使用ioperm访问端口
133+
134+在Linux上访问硬件的最好方法通过设备驱动器,但是考虑到创建一个驱动器任务的复杂性,有时候一个快速的方法是非常有用的。
135+
136+为了使用Linux下的“[[doc:rtl/ports|ports]]”单元,程序必须以root身份运行,并且必须调用IOPerm设置端口访问的合适权限。你可以在[这里](http://www.freepascal.org/docs-html/rtl/ports/index.html)找到关于“[[doc:rtl/ports|ports]]”单元的文档。
137+
138+要做的第一件事是链接(g)libc和调用IOPerm。一个链接整个(g)libc的单元存在于free pascal,但是当应用直接使用时该单元出现了问题,并且静态链接整个(g)libc库不是一个非常好的主意,因为在不同版本间它以不兼容的方式改变。然而,像ioperm类的函数不大可能改变。
139+
140+``` pascal
141+ {$IFDEF Linux}
142+ function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
143+ {$ENDIF}
144+```
145+
146+- from 代表访问的第一个端口.
147+- num 是访问的端口数,因此ioperm(\$220, 8, 1)将为程序访问\$220——\$227之间(含)的所有端口。
148+
149+在链接IOPerm后,可以使用port\[\<Address\>\]访问端口。
150+
151+``` pascal
152+ {$IFDEF Linux}
153+ i := ioperm($220, 8, 1);
154+ port[$220] := $00;
155+ myLabel.Caption := 'ioperm: ' + IntToStr(i);
156+ i := Integer(port[$220]);
157+ myOtherLabel.Caption := 'response: ' + IntToStr(i);
158+ {$ENDIF}
159+```
160+
161+该代码在Mandriva Linux 2005和Damn Small Linux 1.5上使用Lazarus 0.9.10,在端口\$0220的一个自定义ISA卡上测试过。
162+
163+### 通用UNIX硬件访问
164+
165+``` pascal
166+{$IFDEF Unix}
167+Uses Clib; // retrieve libc library name.
168+{$ENDIF}
169+
170+{$IFDEF Unix}
171+function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
172+{$ENDIF}
173+```
174+
175+**注意** FPC在[[doc:rtl/x86/index.html|unit x86]]里为ioperm提供了一个叫做“fpioperm”的抽象,也定义了fpIOPL和输出/输入函数。这些函数当前是为Linux/x86和FreeBSD/x86实现的。
176+
177+不建议链接到的libc,除非绝对必要的,因为可能的部署和移植功能。 像上面那样(通过为别处有效的函数声明特设导入)手工链接libc也是不建议的(例如,如果标准C库不叫作libc,那么上面的libc导入行将不必要(unnecessarily)地失败,比如BeOS或非标准C符号扩展(symbol mangling)平台上的libroot)。
178+
179+**注意 2** 不建议在除了Kylix兼容性的任何环境下使用**unit** libc。参见[[libc unit]]
180+
181+## 串行通信
182+
183+使用[Synaser库](http://synapse.ararat.cz/doku.php)开发一个串行通信软件是非常容易的。当与[Synaser文档](http://synapse.ararat.cz/doc/help/synaser.html)一起使用时,例子应该很好理解。最主要的部分是TBlockSerial.Config设置速度(位/秒),数据位,奇偶校验位,停止位和握手协议,如果有的话。下面代码在一个连接到COM 1的串行鼠标上测试过。
184+
185+``` pascal
186+program comm;
187+
188+{$apptype console}
189+
190+uses
191+ Classes, SysUtils, Synaser;
192+
193+var
194+ ser: TBlockSerial;
195+begin
196+ ser:=TBlockSerial.Create;
197+ try
198+ ser.Connect('COM1');
199+ ser.config(1200, 7, 'N', SB1, False, False);
200+ while True do
201+ Write(IntToHex(ser.RecvByte(10000), 2), ' ');
202+ finally
203+ ser.free;
204+ end;
205+end.
206+```
207+
208+下面的代码例子是上面例子的一个替代版本。上面的例子看起来在主要概念上有严重错误,准确地是“while true do...”部分。在测试系统(Asus A6T Laptop with Digitus USB to RS232 Adapter, Ubuntu 8.04.1)上,该部分导致了下面错误:每个会话只能成功运行一次应用,当再次起动应用时,应用不能连接到串行端口。因此,每次用户试图重启应用时,需要重启(计算机),这是一个非常恼人的bug。
209+
210+原因不难理解:应用处于while true do循环,更准确地说是无限循环。没有退出条件,因此关闭应用的唯一办法是关闭终端或按CTRL-C。但是如果通过种办法退出应用,释放串行端口的重要部分“ser.free”从来不被调用。德语Lazarus论坛的下面贴子里描述了这个问题[<http://www.lazarusforum.de/viewtopic.php?f=10&t=2082>](http://www.lazarusforum.de/viewtopic.php?f=10&t=2082)
211+
212+为了让每个用户明白,而不是按CTRL-C,在主要应用周围有一些代码。也许有人担心,为什么/dev/ttyUSB0被用作com端口:这是由于在测试系统上使用的USB串行适配器。如果有内置串行端口,请使用“Com0”——像上面例子代码中声明。
213+
214+``` pascal
215+
216+program serialtest;
217+
218+{$mode objfpc}{$H+}
219+
220+uses
221+ {$IFDEF UNIX}{$IFDEF UseCThreads}
222+ cthreads,
223+ {$ENDIF}{$ENDIF}
224+ Classes,SysUtils,Synaser,Crt
225+ { you can add units after this };
226+
227+ var l:boolean;
228+
229+ function check_affirmation():boolean;
230+ var k:string;
231+ begin
232+ Writeln('To quit the application please do NOT use CTRL-C! Instead, please press any key to quit the application! '+
233+ 'Please confirm this notification before the application continues! '+
234+ '[0]=Quit, [1]=Confirm, please continue! ');
235+ Writeln('Your decision: ');
236+ Read(k);
237+ if StrtoInt(k) = 1 then
238+ begin
239+ check_affirmation:=true;
240+ Writeln('OK, application continues ...');
241+ end
242+ else
243+ begin
244+ check_affirmation:=false;
245+ Writeln('Abort');
246+ end
247+ end;
248+
249+ procedure RS232_connect;
250+ var
251+ ser: TBlockSerial;
252+ begin
253+ ser:=TBlockSerial.Create;
254+ try
255+ ser.Connect('/dev/ttyUSB0'); //ComPort
256+ Sleep(1000);
257+ ser.config(1200, 7, 'N', SB1, False, False);
258+ Write('Device: ' + ser.Device + ' Status: ' + ser.LastErrorDesc +' '+
259+ Inttostr(ser.LastError));
260+ Sleep(1000);
261+ repeat
262+ Write(IntToHex(ser.RecvByte(10000), 2), ' ');
263+ until keypressed; //Important!!!
264+ finally
265+ Writeln('Serial Port will be freed...');
266+ ser.free;
267+ Writeln('Serial Port was freed successfully!');
268+ end;
269+ end;
270+
271+ begin
272+ l:=check_affirmation();
273+ if l=true then
274+ RS232_connect()
275+ else
276+ Writeln('Program quit! ');
277+ end.
278+```
279+
280+另外,[[Hardware Access|外部链接]]节有UNIX和Windows串行端口教程。
281+
282+值得一提的是linux下TBlockSerial.LinuxLock参数的功能。当设置默认为True时,连接将试图在/var/lock下创建一个锁文件(例如,“LCK..ttyUSB0”),如果所请求的端口存在一个锁就会失败。如果没有调用释放,锁文件将一直存在。设置LinuxLock为False将使Synaser忽略端口锁定。
283+
284+替代Synaser:
285+
286+基于Synaser的可视组件[5dpo](http://wiki.lazarus.freepascal.org/5dpo)。
287+
288+另外一个非常简单的fpc单元现在是freepascal(至少版本2.2.2)的部分了,只要将Serial放到你的Uses清单里,然而除了Serial.pp源文件外还没有其它文档。
289+
290+## USB
291+
292+### libusb
293+
294+驱动开发向来是内核开发中工作量最多的一块,随着USB设备的普及,大量的USB设备的驱动开发也成为驱动开发者手头上做的最多的事情。本文主要介绍Linux平台下基于libusb的驱动开发,希望能够给从事Linux驱动开发的朋友带来些帮助,更希望能够给其他平台上的无驱设计带来些帮助。文章是我在工作中使用libusb的一些总结,难免有错误,如有不当的地方,还请指正。
295+
296+Linux 平台上的usb驱动开发,主要有内核驱动的开发和基于libusb的无驱设计。
297+
298+对于内核驱动的大部分设备,诸如带usb接口的hid设备,linux本身已经自带了相关的驱动,我们只要操作设备文件便可以完成对设备大部分的操作,而另外一些设备,诸如自己设计的硬件产品,这些驱动就需要我们驱动工程师开发出相关的驱动了。内核驱动有它的优点,然而内核驱动在某些情况下会遇到如下的一些问题:
299+
300+1 当使用我们产品的客户有2.4内核的平台,同时也有2.6内核的平台,我们要设计的驱动是要兼容两个平台的,就连makefile 我们都要写两个。
301+
302+2 当我们要把linux移植到嵌入平台上,你会发现原先linux自 带的驱动移过去还挺大的,我的内核当然是越小越好拉,这样有必要么。这还不是最郁闷的地方,如果嵌入平台是客户的,客户要购买你的产品,你突然发现客户设 备里的系统和你的环境不一样,它没有你要的驱动了,你的程序运行不了,你会先想:“没关系,我写个内核驱动加载一下不就行了“。却发现客户连insmod加载模块的工具都没移植,那时你就看看老天,说声我怎么那么倒霉啊,客户可不想你动他花了n时间移植的内核哦
303+
304+3 花了些功夫写了个新产品的驱动,挺有成就感啊,代码质量也是相当的有水准啊。正当你沉醉在你的代码中时,客服不断的邮件来了,“客户需要2.6.5内核的驱动,config文件我已经发你了” “客户需要双核的 2.6.18-smp 的驱动” “客户的平台是自己定制的是2.6.12-xxx “ 你恨不得把驱动的源代码给客户,这样省得编译了。你的一部分工作时间编译内核,定制驱动
305+
306+有问题产生必然会有想办法解决问题的人, libusb的出现给我们带来了某些方便,即节约了我们的时间,也降低了公司的成本。 所以在一些情况下,就可以考虑使用libusb的无驱设计了。
307+
308+下面我们就来详细讨论一下libusb, 并以写一个hid设备的驱动来讲解如何运用libusb,至于文章中涉及的usb协议的知识,限于篇幅,就不详细讲解了,相关的可自行查看usb相关协议。
309+
310+一 libusb 介绍
311+
312+libusb 设计了一系列的外部API 为应用程序所调用,通过这些API应用程序可以操作硬件,从libusb的源代码可以看出,这些API 调用了内核的底层接口,和kernel driver中所用到的函数所实现的功能差不多,只是libusb更加接近USB 规范。使得libusb的使用也比开发内核驱动相对容易的多。 Libusb 的编译安装请查看Readme,这里不做详解
313+
314+二 libusb 的外部接口
315+
316+2.1 初始化设备接口
317+
318+这些接口也可以称为核心函数,它们主要用来初始化并寻找相关设备。
319+
320+usb_init
321+
322+函数定义: void usb_init(void);
323+
324+从函数名称可以看出这个函数是用来初始化相关数据的,这个函数大家只要记住必须调用就行了,而且是一开始就要调用的.
325+
326+usb_find_busses
327+
328+函数定义: int usb_find_busses(void);
329+
330+寻找系统上的usb总线,任何usb设备都通过usb总线和计算机总线通信。进而和其他设备通信。此函数返回总线数。
331+
332+usb_find_devices
333+
334+函数定义: int usb_find_devices(void);
335+
336+寻找总线上的usb设备,这个函数必要在调用usb_find_busses()后使用。以上的三个函数都是一开始就要用到的,此函数返回设备数量。
337+
338+usb_get_busses
339+
340+函数定义: struct usb_bus *usb_get_busses(void);
341+
342+这个函数返回总线的列表,在高一些的版本中已经用不到了,这在下面的实例中会有讲解
343+
344+2.2 操作设备接口
345+
346+usb_open
347+
348+函数定义: usb_dev_handle *usb_open(struct *usb_device dev);
349+
350+打开要使用的设备,在对硬件进行操作前必须要调用usb_open 来打开设备,这里大家看到有两个结构体 usb_dev_handle 和 usb_device 是我们在开发中经常碰到的,有必要把它们的 结构看一看。在libusb 中的usb.h和usbi.h中有定义。
351+
352+这里我们不妨理解为返回的 usb_dev_handle 指针是指向设备的句柄,而行参里输入就是需要打开的设备。
353+
354+usb_close
355+
356+函数定义: int usb_close(usb_dev_handle *dev);
357+
358+与usb_open相对应,关闭设备,是必须调用的, 返回0成功,\<0 失败。
359+
360+usb_set_configuration
361+
362+函数定义: int usb_set_configuration(usb_dev_handle *dev, int configuration);
363+
364+设置当前设备使用的configuration,参数configuration 是你要使用的configurtation descriptoes中的bConfigurationValue, 返回0成功,\<0失败( 一个设备可能包含多个 configuration,比如同时支持高速和低速的设备就有对应的两个configuration,详细可查看usb标准)
365+
366+usb_set_altinterface
367+
368+函数定义: int usb_set_altinterface(usb_dev_handle *dev, int alternate);
369+
370+和名字的意思一样,此函数设置当前设备配置的interface descriptor,参数alternate是指interface descriptor中的bAlternateSetting。返回0成功,\<0失败
371+
372+usb_resetep
373+
374+函数定义: int usb_resetep(usb_dev_handle *dev, unsigned int ep);
375+
376+复位指定的endpoint,参数ep 是指bEndpointAddress,。这个函数不经常用,被下面介绍的usb_clear_halt函数所替代。
377+
378+usb_clear_halt
379+
380+函数定义: int usb_clear_halt (usb_dev_handle *dev, unsigned int ep);
381+
382+复位指定的endpoint,参数ep 是指bEndpointAddress。这个函数用来替代usb_resetep
383+
384+usb_reset
385+
386+函数定义: int usb_reset(usb_dev_handle *dev);
387+
388+这个函数现在基本不怎么用,不过这里我也讲一下,和名字所起的意思一样,这个函数reset设备,因为重启设备后还是要重新打开设备,所以用usb_close就已经可以满足要求了。
389+
390+usb_claim_interface
391+
392+函数定义: int usb_claim_interface(usb_dev_handle *dev, int interface);
393+
394+注册与操作系统通信的接口,这个函数必须被调用,因为只有注册接口,才能做相应的操作。
395+
396+Interface 指 bInterfaceNumber. (下面介绍的usb_release_interface 与之相对应,也是必须调用的函数)
397+
398+usb_release_interface
399+
400+函数定义: int usb_release_interface(usb_dev_handle *dev, int interface);
401+
402+注销被usb_claim_interface函数调用后的接口,释放资源,和usb_claim_interface对应使用。
403+
404+2.3 控制传输接口
405+
406+usb_control_msg
407+
408+函数定义:int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout);
409+
410+从默认的管道发送和接受控制数据
411+
412+usb_get_string
413+
414+函数定义: int usb_get_string(usb_dev_handle *dev, int index, int langid, char *buf, size_t buflen);
415+
416+usb_get_string_simple
417+
418+函数定义: int usb_get_string_simple(usb_dev_handle *dev, int index, char *buf, size_t buflen);
419+
420+usb_get_descriptor
421+
422+函数定义: int usb_get_descriptor(usb_dev_handle *dev, unsigned char type, unsigned char index, void *buf, int size);
423+
424+usb_get_descriptor_by_endpoint
425+
426+函数定义: int usb_get_descriptor_by_endpoint(usb_dev_handle *dev, int ep, unsigned char type, unsigned char index, void *buf, int size);
427+
428+2.4 批传输接口
429+
430+usb_bulk_write
431+
432+函数定义: int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);
433+
434+usb_interrupt_read
435+
436+函数定义: int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);
437+
438+2.5 中断传输接口
439+
440+usb_bulk_write
441+
442+函数定义: int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);
443+
444+usb_interrupt_read
445+
446+函数定义: int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);
447+
448+A cross platform possibility for Linux, BSDs and Mac OS X is [libusb](http://libusb.sourceforge.net/).
449+
450+Headers are listed in <http://www.freepascal.org/contrib/db.php3?category=Miscellaneous>:
451+
452+| 文件名 | 作者 | 版本 | 日期 | 链接 | 备注 |
453+|----|----|----|----|----|----|
454+| libusb.pp | nowrap \| Uwe Zimmermann | 0.1.12 | nowrap \| 2006-06-29 | <http://www.sciencetronics.com/download/fpc_libusb.tgz> | |
455+| libusb.pas | Johann Glaser | | nowrap \| 2012-09-23 | <https://github.com/hansiglaser/pas-libusb> | includes OOP wrapper, see branch "libusb-1.0" for libusb 1.0 |
456+| fpcusb | Joe Jared | 0.11-14 | nowrap \| 2006-02-02 | <http://relays.osirusoft.com/fpcusb.tgz> | nowrap \| download link broken |
457+| libusb.pp | Marko Medic | 1.0 | nowrap \| 2010-12-14 | <http://www.lazarus.freepascal.org/index.php/topic,11435.0.html> | |
458+
459+### FTDI
460+
461+If you use one of the chips from [FTDI](http://www.ftdichip.com/), you can use their pascal headers for their dll interface to the chips.
462+
463+## 外部链接
464+
465+通信协议速度比较:
466+
467+1. <http://en.wikipedia.org/wiki/Serial_port#Speed>
468+2. <http://www.lvr.com/jansfaq.htm> - Jan Axelson's Parallel Port FAQ
469+3. <http://en.wikipedia.org/wiki/USB#Transfer_Speed>
470+4. <http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications>
471+
472+串行通信链接:
473+
474+1. On UNIX: [1](http://www.easysw.com/~mike/serial/serial.html)
475+2. On Windows: <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp>
476+3. Synaser component: <http://synapse.ararat.cz/>
477+4. Comport Delphi package: <http://sourceforge.net/projects/comport/>
478+
479+ISA数字示波器——一个硬件访问例子的全部源代码包含在:
480+
481+[2](http://eletronicalivre.incubadora.fapesp.br/portal/english/oscilloscope/)
482+
483+---
484+*From the Free Pascal wiki, page "Hardware Access/zh CN", as it stood on 2020-02-16 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history