Sign in Join
Wiki copytemporary

Hardware Access: what changed

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

Title: Hardware Access

OldNewLine
1+## Overview
2+
3+This page describes various ways of accessing hardware devices on Lazarus. These devices include, but are not limited to: ISA, PCI, USB, parallel port, serial port.
4+
5+Uniform multi-platform access to hardware devices is not implemented by the Free Pascal Runtime Library (RTL) or by the LCL - the underlying operating systems are often different enough to make that very difficult. Therefore, this article will basically cover hardware access methods on different platforms. The code can be compiled on different environments using conditional compiles, like this:
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+## Parallel and Serial Comparison
19+
20+ISA cards, PCI cards and the Parallel Port communicate with the computer using a **parallel** protocol. The Serial Port and USB devices work with a **serial** protocol. Because the processor and thus programming languages all work on a parallel approach to data, access to these kinds of protocols is easier to be implemented on the software side. When an Integer variable is accessed for example, its value can be accessed with a single command. With a serial protocol however, only one bit at a time can be accessed and the pieces need to be "glued" together to understand the data.
21+
22+Serial communication is difficult to be implemented directly, but it can be pretty easy if pre-made component are used. It is also harder on the hardware side, so many devices use specialised integrated circuits or microcontrolers to implement it.
23+
24+Now a brief comparison of hardware access protocols will be given:
25+
26+| | Speed | Hardware implementation difficulty |
27+|---------------|-------------------------|------------------------------------|
28+| Serial Port | Very slow (\< E5 bit/s) | Medium |
29+| Parallel Port | Slow (~ E6 bit/s) | Easy |
30+| ISA Card | Medium (~ E7 bit/s) | Medium |
31+| USB | Medium (~ E7 bit/s) | Hard |
32+| PCI Card | Very Fast (\> E9 bit/s) | Very Hard |
33+
34+## Parallel Communication
35+
36+### Using inpout32.dll for Windows
37+
38+Windows has different ways to access hardware devices on the 9x and NT series. On the 9x series (95, 98, Me) programs can access the hardware directly, just like they did on DOS. The NT series (Windows NT and XP), however, do not allow this approach. On this architecture, all communication with hardware ports must be through a device driver. This is a security mechanism, but developing a driver for small projects can cost too much in terms of time and money.
39+
40+Fortunately there is a library that solves this problem. If Windows NT is detected, it decompresses the HWInterface.sys kernel device driver and installs it. If Windows 9x is detected, it simply uses assembler opcodes to access the hardware.
41+
42+The library has only two functions, Inp32 and Out32, and their use is quite intuitive.
43+
44+The library will be loaded dynamically, so define both functions first:
45+
46+``` pascal
47+ type
48+ TInp32 = function(Address: SmallInt): SmallInt; stdcall;
49+ TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
50+```
51+
52+- 'Address' represents the address of the port to be accessed
53+- 'Out32' sends data to the port specified by 'Address'
54+- 'Inp32' returns a byte from the port specified by 'Address'
55+
56+Now the library can be loaded. This may be implemented in the 'OnCreate' method of the program's main form:
57+
58+``` pascal
59+ uses
60+ ....dynlibs...
61+
62+ type
63+ TMyForm = class(TForm)
64+ .........
65+ private
66+ { private declarations }
67+ Inpout32: THandle;
68+ Inp32: TInp32;
69+ Out32: TOut32;
70+ .........
71+ implementation
72+ .........
73+ procedure TMyForm.FormCreate(Sender: TObject);
74+ begin
75+ {$IFDEF WIN32}
76+ Inpout32 := LoadLibrary('inpout32.dll');
77+ if (Inpout32 <> 0) then
78+ begin
79+ // needs overtyping, plain Delphi's @Inp32 = GetProc... leads to compile errors
80+ Inp32 := TInp32(GetProcAddress(Inpout32, 'Inp32'));
81+ if (@Inp32 = nil) then Caption := 'Error';
82+ Out32 := TOut32(GetProcAddress(Inpout32, 'Out32'));
83+ if (@Out32 = nil) then Caption := 'Error';
84+ end
85+ else Caption := 'Error';
86+ {$ENDIF}
87+ end;
88+```
89+
90+If the library is loaded on 'OnCreate', it must be unloaded in 'OnDestroy':
91+
92+``` pascal
93+ procedure TMyForm.FormDestroy(Sender: TObject);
94+ begin
95+ {$IFDEF WIN32}
96+ FreeLibrary(Inpout32);
97+ {$ENDIF}
98+ end;
99+```
100+
101+Here is a simple example of how to use the 'Inp32' function:
102+
103+``` pascal
104+ {$IFDEF WIN32}
105+ myLabel.Caption := IntToStr(Inp32($0220));
106+ {$ENDIF}
107+```
108+
109+This code was tested with a custom ISA card on port \$0220, using Lazarus 0.9.10 on Windows XP. Of course 'Windows' must be in the uses clause in order for this code to run.
110+
111+> **Note:** For deployment "inpout32.dll" must be in the same directory of the application. Also the library has to be registered in 'system' by the 'administrator' user on Windows NT/XP/2000 or elevated privileges on Windows Vista/7. This can be done by installation of a program such as InnoSetup:
112+>
113+> Filename: {sys}\rundll32.exe; Parameters: "inpout32.dll,IsInpOutDriverOpen"; WorkingDir: {app}; Flags: 32bit;
114+
115+### Using assembler on Windows 9x
116+
117+On Windows 9x assembler code can be used. Suppose one wants to write \$CC to the \$320 port. The code to do that is:
118+
119+``` pascal
120+ {$ASMMODE ATT}
121+ ...
122+ asm
123+ movl $0x320, %edx
124+ movb $0xCC, %al
125+ outb %al, %dx
126+ end ['EAX','EDX'];
127+```
128+
129+### Troubleshooting on Windows
130+
131+One possible source of trouble using parallel hardware that does not support Plug And Play on Windows is that Windows may assign the port utilized by the hardware to another device.
132+
133+### Using 'ioperm' to access ports on Linux
134+
135+The best way to access the hardware on Linux is through device drivers, but, due to the complexity of the task of creating a driver, sometimes a quick method is very useful.
136+
137+In order to use the "[[doc:rtl/ports|ports]]" unit under Linux the program must be run as root, and IOPerm must be called to set appropriate permissions on the port access. Documentation about the "[[doc:rtl/ports|ports]]" unit can be found here: <http://www.freepascal.org/docs-html/rtl/ports/index.html>
138+
139+The first thing to do is link to (g)libc and call IOPerm. A unit that links to the entire (g)libc exists on free pascal, but this unit gives problems when used directly by an application and linking statically to the entire (g)libc library is not good practise as it changes often between versions in an incompatible manner. Functions like ioperm, however, are unlikely to change.
140+
141+``` pascal
142+ {$IFDEF Linux}
143+ function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
144+ {$ENDIF}
145+```
146+
147+- "from" represents the first port to be accessed.
148+- "num" is the number of ports after the first to be accessed, so ioperm(\$220, 8, 1) will give access for the program for all ports between and including \$220 and \$227.
149+
150+After linking to IOPerm, port\[\<Address\>\] will access the ports.
151+
152+``` pascal
153+ {$IFDEF Linux}
154+ i := ioperm($220, 8, 1);
155+ port[$220] := $00;
156+ myLabel.Caption := 'ioperm: ' + IntToStr(i);
157+ i := Integer(port[$220]);
158+ myOtherLabel.Caption := 'response: ' + IntToStr(i);
159+ {$ENDIF}
160+```
161+
162+This code was tested with a custom ISA card on port \$0220, using Lazarus 0.9.10 on Mandriva Linux 2005 and Damn Small Linux 1.5
163+
164+### General UNIX hardware access
165+
166+``` pascal
167+{$IFDEF Unix}
168+Uses Clib; // retrieve libc library name.
169+{$ENDIF}
170+
171+{$IFDEF Unix}
172+function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
173+{$ENDIF}
174+```
175+
176+**Note**: FPC provides an abstraction for ioperm called "fpioperm" in [[doc:rtl/x86/index.html|unit x86]], and also defines fpIOPL and out-/inport functions. These functions are currently implemented for Linux/x86, Linux/x86_64 and FreeBSD/x86, FreeBSD/x86_64.
177+
178+It is not recommended to link to libc unless absolutely necessary due to possible deployment and portability functions. Also manual linking to libc (by declaring ad hoc libc imports for functions that are available elsewhere) like done above is not recommended (e.g. the above libc import line will unnecessarily fail if the standard C lib is not called libc, like e.g. libroot on BeOS, or on platforms with a non standard C symbol mangling).
179+
180+**Note 2**: Using **unit** libc is not recommended under any circumstances other than Kylix compatibility. See [[libc unit]]
181+
182+### Status and control
183+
184+Besides data lines, the parallel port also has status and control lines which are accessed using the status and control registers. While the base address accesses the data lines and reads or writes data bytes from/to them, the status register is accessed on the address offset by +1 and the control register is accessed on the offset +2. For example, LPT1 (first parallel port on a PC) has the base address \$378, so its status register is at \$379 and control register at \$380. To get individual status line states, read a byte from its address and its bits represent those lines. Setting control lines is similarly done by writing a byte with accordingly set bits to the control register.
185+
186+Newer bidirectional parallel port versions have more registers on higher offsets. More details about them, together with information which bits map to which lines can be found [here](http://www.ni.com/white-paper/3466/en).
187+
188+Most directly accessed hardware devices other than PC parallel ports are controlled in a similar way. Depending on the device in question, it is necessary to find out what registers are available (above mentioned control and status, but also address and other registers) and which bits represent which hardware functions.
189+
190+## Serial Communication
191+
192+### Device Setup
193+
194+Before starting, make sure to authorize your device on Linux. An option is to create a persistent [UDEV rule](https://wiki.archlinux.org/index.php/udev) for each device. For example, the following approach apply mode "0064" for the device specified by 'serial', 'idVendor' and 'idProduct' attributes:
195+
196+``` pascal
197+# create a new dev rule based on some constants
198+echo 'SUBSYSTEM=="tty", ATTRS{serial}=="A1004chl", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0664"' | sudo tee /etc/udev/rules.d/50-usb-serial.rules > /dev/null
199+
200+# reload rules
201+sudo udevadm control --reload-rules
202+
203+# re-add all devices
204+sudo udevadm trigger
205+
206+# test
207+ls -al /dev/ttyUSB0
208+
209+# you may want to reboot your system
210+sudo reboot
211+```
212+
213+### Synaser
214+
215+It is very easy to build a serial communication software using the [Synaser library](http://synapse.ararat.cz/doku.php). The example when used together with the [Synaser documentation](http://synapse.ararat.cz/doc/help/synaser.html) should be trivial to understand. The most important part is TBlockSerial.Config to configure the speed (in bits per second), data bits, parity bits, stop bits and handshake protocol, if any. The following code was tested with a serial mouse connected to COM1.
216+
217+``` pascal
218+program comm;
219+
220+{$apptype console}
221+
222+uses
223+ Classes, SysUtils, Synaser;
224+
225+var
226+ ser: TBlockSerial;
227+begin
228+ ser:=TBlockSerial.Create;
229+ try
230+ ser.Connect('COM1');
231+ ser.config(1200, 7, 'N', SB1, False, False);
232+ while True do
233+ Write(IntToHex(ser.RecvByte(10000), 2), ' ');
234+ finally
235+ ser.free;
236+ end;
237+end.
238+```
239+
240+The following code-example is an alternative version of the example above. The example above has a critical bug in its main concept, to be exact, it is the part with "while true do...". On the test system (Asus A6T laptop with Digitus USB to RS232 Adapter, Ubuntu 8.04.1), this part caused the following error: the application ran only one time successfully per session, when the application was started again, the application was unable to connect to the serial port. Thus, a reboot was necessary everytime the user tried to relaunch the application.
241+
242+The reason is not difficult to understand: The application is in the while true do - loop, which is, to be more precise, an endless loop. There is no abort-condition, so the only way to close the application is to close the terminal or to press CTRL-C. But if the application is aborted this way, the important part with "ser.free", which frees the serial port, will never be called. This problem is described in the following thread in the German Lazarus-Forum [<http://www.lazarusforum.de/viewtopic.php?f=10&t=2082>](http://www.lazarusforum.de/viewtopic.php?f=10&t=2082)
243+
244+There is a bit of code around the main application to make it clear to the user not to press CTRL-C. /dev/ttyUSB0 is used for the com-port due to the USB to Serial Adapter (from Digitus) on the test-system. With a built-in serial port, use the 'Com0' - declaration like in the code - example above.
245+
246+``` pascal
247+
248+program serialtest;
249+
250+{$mode objfpc}{$H+}
251+
252+uses
253+ {$IFDEF UNIX}{$IFDEF UseCThreads}
254+ cthreads,
255+ {$ENDIF}{$ENDIF}
256+ Classes,SysUtils,Synaser,Crt
257+ { add other units after this };
258+
259+ var l:boolean;
260+
261+ function check_affirmation():boolean;
262+ var k:string;
263+ begin
264+ Writeln('To quit the application do NOT use CTRL-C! Instead, press any key to quit the application! '+
265+ 'Confirm this notification to continue the application! '+
266+ '[0]=Quit, [1]=Confirm, continue! ');
267+ Writeln('Your choice: ');
268+ Read(k);
269+ if StrtoInt(k) = 1 then
270+ begin
271+ check_affirmation:=true;
272+ Writeln('OK, application continues ...');
273+ end
274+ else
275+ begin
276+ check_affirmation:=false;
277+ Writeln('Abort');
278+ end
279+ end;
280+
281+ procedure RS232_connect;
282+ var
283+ ser: TBlockSerial;
284+ begin
285+ ser:=TBlockSerial.Create;
286+ try
287+ ser.Connect('/dev/ttyUSB0'); //ComPort
288+ Sleep(1000);
289+ ser.config(1200, 7, 'N', SB1, False, False);
290+ Write('Device: ' + ser.Device + ' Status: ' + ser.LastErrorDesc +' '+
291+ Inttostr(ser.LastError));
292+ Sleep(1000);
293+ repeat
294+ Write(IntToHex(ser.RecvByte(10000), 2), ' ');
295+ until keypressed; //Important!!!
296+ finally
297+ Writeln('Serial Port will be freed...');
298+ ser.free;
299+ Writeln('Serial Port was freed successfully!');
300+ end;
301+ end;
302+
303+ begin
304+ l:=check_affirmation();
305+ if l=true then
306+ RS232_connect()
307+ else
308+ Writeln('Program quit! ');
309+ end.
310+```
311+
312+Also, the [[Hardware Access|External Links]] section has UNIX and Windows serial port tutorials.
313+
314+Note the function of the TBlockSerial.LinuxLock parameter under linux. When set to default of True, a connect will try to create a lock file (eg. "LCK..ttyUSB0") under /var/lock and fail if a lock already exists for the requested port. The lock file will be left over if Free was not called. Setting LinuxLock to False will make Synaser ignore port locking.
315+
316+There are alternatives to Synaser; see below.
317+
318+### Visual Synapse
319+
320+[Visual Synapse](https://sourceforge.net/projects/visualsynapse/) has lots of component wrappers for many parts of Synapse serial and networking library. TvsComPort is a visual component wrapper around Synaser.
321+
322+### 5dpo
323+
324+There is also a visual component [5dpo](http://wiki.lazarus.freepascal.org/5dpo) based on Synaser.
325+
326+### TLazSerial
327+
328+Based on 5dpo (and therefore Synapse): <http://forum.lazarus.freepascal.org/index.php/topic,20481.0.html>
329+
330+### FPC built in Serial unit
331+
332+Another [[Serial unit]] is part of FreePascal since version 2.2.2: just put 'Serial' in the Uses list. However there does not seem to be any documentation other than the Serial.pp source file and some discussions.
333+
334+An example is [[Serial unit|here]].
335+
336+### TDataPort
337+
338+[DataPort](https://github.com/serbod/dataport) is a thread-safe abstract port for data exchange. It is used for communication over networks (TCP/UDP/HTTP), serial ports (UART, COM-port, FTDI), device files (ioctl supported) and conventional file named pipes. For serial communication it has TDataPortSerial and TDataPortFtdi descendants. Small example can be found in [demo](https://github.com/serbod/dataport/tree/master/demo) and [here](http://forum.lazarus.freepascal.org/index.php/topic,36523.msg245030.html#msg245030).
339+
340+### PXL
341+
342+[PXL (Platform eXtended Library)](http://wiki.freepascal.org/Lazarus_on_Raspberry_Pi#PXL_.28Platform_eXtended_Library.29_for_low_level_native_access_to_GPIO.2C_I.C2.B2C.2C_SPI.2C_PWM.2C_UART.2C_V4L2.2C_displays_and_sensors) for low level native access to serial ports, GPIO, I²C, SPI, PWM, V4L2, displays and sensors.
343+
344+### Serial port names on Windows
345+
346+COM ports are named with a number on Windows 9x-based OSes (95, 98, ME), e.g. COM1, COM30.
347+
348+On Windows NT-based systems (NT, 2000, XP, Vista, Windows 7, Windows 8), COM ports are numbered too, but only for compatibility with DOS/Win9x.
349+
350+Use this code to get the real name:
351+
352+``` pascal
353+// ComNr is obviously the number of the COM port
354+if ComNr > 9 then
355+ Result := Format('\\\\.\\COM%d', [ComNr])
356+else
357+ Result := Format('COM%d', [ComNr]);
358+```
359+
360+## USB
361+
362+### libusb
363+
364+A cross platform possibility for Windows, Linux, BSDs and macOS is [libusb](http://libusb.sourceforge.net/).
365+
366+| name | author | version | date | link | remarks |
367+|----|----|----|----|----|----|
368+| libusb.pp | nowrap \| Uwe Zimmermann | 0.1.12 | nowrap \| 2006-06-29 | | |
369+| 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 |
370+| fpcusb | Joe Jared | 0.11-14 | nowrap \| 2006-02-02 | | |
371+| libusb.pp | Marko Medic | 1.0 | nowrap \| 2010-12-14 | <http://www.lazarus.freepascal.org/index.php/topic,11435.0.html> | |
372+
373+### FTDI
374+
375+If one of the chips from [FTDI](http://www.ftdichip.com/) is used, their pascal headers for their dll interface to the chips can be used.
376+
377+## Devices in general
378+
379+On Windows, devices can be managed from code, see here: [[Windows Programming Tips|Windows Programming Tips#Enabling and disabling devices]]
380+
381+## See also
382+
383+- [[Lazarus on Raspberry Pi]]
384+- [[Networking]]
385+- [[Target NativeNT]]
386+
387+## External Links
388+
389+- Communication Protocols speed comparison:
390+
391+1. [Wikipedia, Serial port](http://en.wikipedia.org/wiki/Serial_port#Speed)
392+2. [Wikipedia USB](http://en.wikipedia.org/wiki/USB#Transfer_Speed)
393+3. [Wikipedia PCI bus](http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications)
394+
395+- Serial Communication Links:
396+
397+1. On UNIX: [Unix serial port](http://www.easysw.com/~mike/serial/serial.html)
398+2. On Windows: [Microsoft msdn](http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp)
399+3. Synaser component: [synapse. CZ](http://synapse.ararat.cz/)
400+4. Comport Delphi package: [Sourceforge Delphi comport](http://sourceforge.net/projects/comport/)
401+
402+---
403+*From the Free Pascal wiki, page "Hardware Access", as it stood on 2022-09-19 after a change by Alextpp, 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