Difference between revisions of "Hardware Access"

From Free Pascal wiki
Jump to navigationJump to search
(Deleted categories from page (Embedded and Robotics moved to template))
(58 intermediate revisions by 18 users not shown)
Line 1: Line 1:
{{硬件访问}}
+
{{Hardware Access}}
 
__TOC__
 
__TOC__
==概览==
+
==Overview==
本页是关于在Lazarus上访问硬件设备的教程的开始。这些设备包括,但不限于:ISA,PCI,USB,并行端口,串行端口。
+
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.
  
RTL或LCL没有实现统一的多平台硬件设备的访问。因此本教程将基本覆盖不同平台上的硬件访问方法。在不同环境下可以使用条件编译来编译代码,像这样:
+
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:
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  uses
 
  uses
 
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
 
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
Line 15: Line 15:
 
   ports;
 
   ports;
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
此时还不知道Mac OS X/x86是否会允许HW访问。它可能不允许,虽然那种情形下我假设像io.dll的驱动器将会及时出现。
+
==Parallel and Serial Comparison==
 +
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.
  
==并行和串行的比较==
+
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.
ISA卡,PCI卡和并行端口使用“并行”协议与计算机进行通信。串行端口和USB设备使用“串行”协议。因为处理器和编程语言都通过并行方式处理数据,在软件端比较容易实现对这类协议的访问。例如,访问一个整型变量时,可以仅用一个命令就能访问它的值。然而,使用串行协议,一次仅能知道一位(bit),需要将所有片断(piece)放在一起才能理解数据。
 
  
串行通信比较难于直接实现,如果使用预制(pre-made)组件,可能会稍微容易些。在硬件端也比较困难,因此许多设备使用专门的集成电路或甚至微控制器来实现。
+
Now a brief comparison of hardware access protocols will be given:
 
 
现在来做一个硬件访问协议的简要比较:
 
 
 
{| border=2 width="100%"
 
 
 
|-
 
!
 
! 速度
 
! 硬件实现难度
 
  
 +
{| class="wikitable" width="100%"
 +
! !! Speed !! Hardware implementation difficulty
 
|-
 
|-
! 串行端口
+
! Serial Port
| align="center" | 非常慢 (< E5 bit/s)
+
| align="center" | Very slow (< E5 bit/s)
| align="center" | 中等
+
| align="center" | Medium
  
 
|-
 
|-
! 并行端口
+
! Parallel Port
| align="center" | (~ E6 bit/s)
+
| align="center" | Slow (~ E6 bit/s)
| align="center" | 容易
+
| align="center" | Easy
  
 
|-
 
|-
  
! ISA卡
+
! ISA Card
| align="center" | 中等 (~ E7 bit/s)
+
| align="center" | Medium (~ E7 bit/s)
| align="center" | 中等
+
| align="center" | Medium
  
 
|-
 
|-
 
! USB
 
! USB
| align="center" | 中等 (~ E7 bit/s)
+
| align="center" | Medium (~ E7 bit/s)
| align="center" | 困难
+
| align="center" | Hard
  
 
|-
 
|-
! PCI卡
+
! PCI Card
| align="center" | 非常快 (> E9 bit/s)
+
| align="center" | Very Fast (> E9 bit/s)
| align="center" | 非常难
+
| align="center" | Very Hard
  
 
|}
 
|}
  
==并行通信==
+
==Parallel Communication==
  
===为Windows使用inpout32.dll===
+
===Using inpout32.dll for Windows===
在9x系列和NT系列,Windows有不同的方法来访问硬件设备。在9x系列(95,98,Me),程序可以直接访问硬件,正如在DOS上一样。然而,NT系列(Windows NT和XP)不允许这种方式。在该架构上,所有与硬件端口的通信必须通过一个设备驱动器。这是一种安全机制,但是为一个小项目开发一个驱动器可能会花费太多的时间和金钱。
+
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.
  
幸运地是有一个库解决了这个问题。如果检测到Windows NT,它解压HWInterface.sys内核设备驱动器并安装。如果检测到Windows 9x,它简单地使用汇编操作码访问硬件。
+
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.
  
但是如何使用这个库呢?非常简单!它仅有2个函数:Inp32和Out32,它们的使用是比较直观的。
+
The library has only two functions, Inp32 and Out32, and their use is quite intuitive.
  
我们将动态加载该库,因此让我们首先定义这2个函数:
+
The library will be loaded dynamically, so define both functions first:
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  type
 
  type
 
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
 
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
 
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
 
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
</delphi>
+
</syntaxhighlight>
 +
 
 +
* 'Address' represents the address of the port to be accessed
 +
* 'Out32' sends data to the port specified by 'Address'
 +
* 'Inp32' returns a byte from the port specified by 'Address'
  
* Address代表期望访问的端口地址
+
Now the library can be loaded. This may be implemented in the 'OnCreate' method of the program's main form:
* Out32发送数据到指定地址的端口
 
* Inp32从指定地址的端口返回一字节
 
  
现在可以加载该库了。这可能在一个类似程序主form的OnCreate方法的地方来实现:
+
<syntaxhighlight lang="pascal">
 +
uses
 +
....dynlibs...
  
<delphi>
 
 
  type
 
  type
 
   TMyForm = class(TForm)
 
   TMyForm = class(TForm)
Line 111: Line 107:
 
  {$ENDIF}
 
  {$ENDIF}
 
  end;
 
  end;
</delphi>
+
</syntaxhighlight>
  
如果在OnCreate加载了该库,那么不要忘记在OnDestroy卸载它:
+
If the library is loaded on 'OnCreate', it must be unloaded in 'OnDestroy':
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  procedure TMyForm.FormDestroy(Sender: TObject);
 
  procedure TMyForm.FormDestroy(Sender: TObject);
 
  begin
 
  begin
Line 122: Line 118:
 
  {$ENDIF}
 
  {$ENDIF}
 
  end;
 
  end;
</delphi>
+
</syntaxhighlight>
  
这里是一个如何使用Inp32函数的简单例子:
+
Here is a simple example of how to use the 'Inp32' function:
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  {$IFDEF WIN32}
 
  {$IFDEF WIN32}
 
   myLabel.Caption := IntToStr(Inp32($0220));
 
   myLabel.Caption := IntToStr(Inp32($0220));
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
 +
 
 +
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.
  
该代码在Windows XP上使用Lazarus 0.9.10,在端口$0220的一个自定义ISA卡上测试过。当然为了让该代码运行,你需要有一个有使用条款的Windows。为了部署,你仅需要把“inpout32.dll”包含在应用的相同目录下。
+
{{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:
 +
<pre>Filename: {sys}\rundll32.exe; Parameters: "inpout32.dll,IsInpOutDriverOpen"; WorkingDir: {app}; Flags: 32bit;</pre>
 +
}}
  
这是该库的主页:[http://www.logix4u.net/inpout32.htm www.logix4u.net/inpout32.htm]  *查看讨论*
 
  
===在Windows 9x上使用汇编===
+
This is the homepage for the library: [http://www.logix4u.net/inpout32.htm www.logix4u.net/inpout32.htm] (see discussion).
  
在Windows 9x上也可以使用汇编代码。假设希望把$CC发送到$320端口。下面代码可以实现:
+
===Using assembler on Windows 9x===
 +
On Windows 9x assembler code can be used. Suppose one wants to write $CC to the $320 port. The code to do that is:
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  {$ASMMODE ATT}
 
  {$ASMMODE ATT}
 
  ...
 
  ...
Line 148: Line 148:
 
         outb %al, %dx
 
         outb %al, %dx
 
     end ['EAX','EDX'];
 
     end ['EAX','EDX'];
</delphi>
+
</syntaxhighlight>
 
 
===在Windows上的疑难解答===
 
 
 
在Windows上使用不支持即插即用并行硬件的一个可能问题来源是,Windows可能将该硬件使用的端口分配给了另外的设备。你可以在下面的URL里找到关于如何告诉Windows不要将你设备的地址分配给即插即用设备的指示:
 
 
 
http://support.microsoft.com/kb/135168
 
  
===在Linux上使用ioperm访问端口===
+
===Troubleshooting on Windows===
 +
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. Instructions on how to tell Windows not to assign the address of a device to Plug And Play devices can be found at http://support.microsoft.com/kb/135168
  
在Linux上访问硬件的最好方法通过设备驱动器,但是考虑到创建一个驱动器任务的复杂性,有时候一个快速的方法是非常有用的。
+
===Using 'ioperm' to access ports on Linux===
 +
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.
  
为了使用Linux下的“[[doc:rtl/ports|ports]]”单元,程序必须以root身份运行,并且必须调用IOPerm设置端口访问的合适权限。你可以在[http://www.freepascal.org/docs-html/rtl/ports/index.html 这里]找到关于“[[doc:rtl/ports|ports]]”单元的文档。
+
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
  
要做的第一件事是链接(g)libc和调用IOPerm。一个链接整个(g)libc的单元存在于free pascal,但是当应用直接使用时该单元出现了问题,并且静态链接整个(g)libc库不是一个非常好的主意,因为在不同版本间它以不兼容的方式改变。然而,像ioperm类的函数不大可能改变。
+
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.
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  {$IFDEF Linux}
 
  {$IFDEF Linux}
 
  function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
 
  function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
 
* "from" represents the first port to be accessed.
 
* "from" represents the first port to be accessed.
 
* "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.
 
* "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.
  
After linking to IOPerm you can port[<Address>] to access the ports.
+
After linking to IOPerm, port[<Address>] will access the ports.
  
<delphi>
+
<syntaxhighlight lang="pascal">
 
  {$IFDEF Linux}
 
  {$IFDEF Linux}
 
   i := ioperm($220, 8, 1);
 
   i := ioperm($220, 8, 1);
Line 183: Line 179:
 
   myOtherLabel.Caption := 'response: ' + IntToStr(i);
 
   myOtherLabel.Caption := 'response: ' + IntToStr(i);
 
  {$ENDIF}
 
  {$ENDIF}
</delphi>
+
</syntaxhighlight>
  
 
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
 
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
  
===General UNIX Hardware Access===
+
===General UNIX hardware access===
  
<delphi>{$IFDEF Unix}
+
<syntaxhighlight lang="pascal">
 +
{$IFDEF Unix}
 
Uses Clib;  // retrieve libc library name.
 
Uses Clib;  // retrieve libc library name.
 
{$ENDIF}
 
{$ENDIF}
Line 195: Line 192:
 
{$IFDEF Unix}
 
{$IFDEF Unix}
 
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
 
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
{$ENDIF}</delphi>
+
{$ENDIF}
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''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.
 +
 
 +
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).
  
 +
'''Note 2''': Using '''unit''' libc is not recommended under any circumstances other than Kylix compatibility. See [[libc unit]]
  
'''Note''' that 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 and FreeBSD/x86.
+
===Status and control===
 +
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.  
  
It is not recommended to link to libc unless absolutely necessary due to possible deployment and portability functions.
+
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 [http://www.ni.com/white-paper/3466/en here].
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).
 
  
'''Note 2''' Using '''unit''' libc is not recommended under any circumstances other than Kylix compatibility. See [[libc unit]]
+
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.
  
 
==Serial Communication==
 
==Serial Communication==
 +
=== Device Setup ===
  
It is very easy to build a serial communication software using the [http://synapse.ararat.cz/doku.php Synaser library]. The example when used together with the [http://synapse.ararat.cz/doc/help/synaser.html Synaser documentation] 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 COM 1.
+
Before starting, make sure to authorize your device on Linux. An option is to create a persistent [https://wiki.archlinux.org/index.php/udev UDEV rule] for each device. For example, the following approach apply mode "0064" for the device specified by 'serial', 'idVendor' and 'idProduct' attributes:
  
<delphi>
+
<syntaxhighlight lang="pascal">
 +
# create a new dev rule based on some constants
 +
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
 +
 
 +
# reload rules
 +
sudo udevadm control --reload-rules
 +
 
 +
# re-add all devices
 +
sudo udevadm trigger
 +
 
 +
# test
 +
ls -al /dev/ttyUSB0
 +
 
 +
# you may want to reboot your system
 +
sudo reboot
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
=== Synaser ===
 +
It is very easy to build a serial communication software using the [http://synapse.ararat.cz/doku.php Synaser library]. The example when used together with the [http://synapse.ararat.cz/doc/help/synaser.html Synaser documentation] 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.
 +
 
 +
<syntaxhighlight lang="pascal">
 
program comm;
 
program comm;
  
Line 230: Line 257:
 
   end;
 
   end;
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
The following code-example is an alternative version of the example above. The example above seems to have a critically fault in its main concept, to be exactly, 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, which is/was a really annoying bug.  
+
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.  
  
The reason is not difficult to understand: The application is in the while true do - loop, which is, to be more precisely, 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 you quit the application 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]
+
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]
  
There is a bit code around the main application to make every user clear, not to press CTRL-C. If anyone is worrying, why /dev/ttyUSB0 is used for com-port: this is due to the USB to Serial Adapter (from Digitus) on the test-system. If you have an built-in serial port, please use the 'Com0' - declaration like in the code - example above.  
+
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.  
  
<delphi>
+
<syntaxhighlight lang="pascal">
  
 
program serialtest;
 
program serialtest;
Line 249: Line 276:
 
   {$ENDIF}{$ENDIF}
 
   {$ENDIF}{$ENDIF}
 
   Classes,SysUtils,Synaser,Crt
 
   Classes,SysUtils,Synaser,Crt
   { you can add units after this };
+
   { add other units after this };
  
 
   var l:boolean;
 
   var l:boolean;
Line 256: Line 283:
 
   var k:string;
 
   var k:string;
 
   begin
 
   begin
       Writeln('To quit the application please do NOT use CTRL-C! Instead, please press any key to quit the application! '+
+
       Writeln('To quit the application do NOT use CTRL-C! Instead, press any key to quit the application! '+
       'Please confirm this notification before the application continues! '+
+
       'Confirm this notification to continue the application! '+
       '[0]=Quit, [1]=Confirm, please continue! ');
+
       '[0]=Quit, [1]=Confirm, continue! ');
       Writeln('Your decision: ');
+
       Writeln('Your choice: ');
 
       Read(k);
 
       Read(k);
 
       if StrtoInt(k) = 1 then
 
       if StrtoInt(k) = 1 then
Line 275: Line 302:
 
   procedure RS232_connect;
 
   procedure RS232_connect;
 
   var
 
   var
    ser: TBlockSerial;
+
    ser: TBlockSerial;
 
   begin
 
   begin
      ser:=TBlockSerial.Create;
+
    ser:=TBlockSerial.Create;
      try
+
    try
          ser.Connect('/dev/ttyUSB0'); //ComPort
+
      ser.Connect('/dev/ttyUSB0'); //ComPort
          Sleep(1000);
+
      Sleep(1000);
          ser.config(1200, 7, 'N', SB1, False, False);
+
      ser.config(1200, 7, 'N', SB1, False, False);
          Write('Device: ' + ser.Device + '  Status: ' + ser.LastErrorDesc +' '+
+
      Write('Device: ' + ser.Device + '  Status: ' + ser.LastErrorDesc +' '+
          Inttostr(ser.LastError));
+
      Inttostr(ser.LastError));
          Sleep(1000);
+
      Sleep(1000);
          repeat
+
      repeat
                Write(IntToHex(ser.RecvByte(10000), 2), ' ');
+
        Write(IntToHex(ser.RecvByte(10000), 2), ' ');
          until keypressed; //Important!!!
+
      until keypressed; //Important!!!
      finally
+
    finally
              Writeln('Serial Port will be freed...');
+
      Writeln('Serial Port will be freed...');
              ser.free;
+
      ser.free;
              Writeln('Serial Port was freed successfully!');
+
      Writeln('Serial Port was freed successfully!');
      end;
+
    end;
 
   end;
 
   end;
  
 
   begin
 
   begin
    l:=check_affirmation();
+
    l:=check_affirmation();
    if l=true then
+
    if l=true then
    RS232_connect()
+
    RS232_connect()
    else
+
    else
    Writeln('Program quit! ');
+
    Writeln('Program quit! ');
 
   end.
 
   end.
  
</delphi>
+
</syntaxhighlight>
  
 
Also, the [[Hardware Access#External Links | External Links]] section has UNIX and Windows serial port tutorials.
 
Also, the [[Hardware Access#External Links | External Links]] section has UNIX and Windows serial port tutorials.
  
 +
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.
  
It is also worth noting 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.
+
There are alternatives to Synaser; see below.
  
Alternatives to Synaser:
+
=== Visual Synapse ===
 +
[https://sourceforge.net/projects/visualsynapse/ Visual Synapse] has lots of component wrappers for many parts of Synapse serial and networking library. TvsComPort is a visual component wrapper around Synaser.
  
There is also a Visual component [http://wiki.lazarus.freepascal.org/5dpo 5dpo] that is based in Synaser.
+
=== 5dpo ===
 +
There is also a visual component [http://wiki.lazarus.freepascal.org/5dpo 5dpo] based on Synaser.
  
Another very simple fpc Serial unit is now part of freepascal (at least in version 2.2.2), just put Serial in your Uses list however there does not seem to be any documentation other than the Serial.pp source file.
+
=== TLazSerial ===
 +
Based on 5dpo (and therefore Synapse): http://forum.lazarus.freepascal.org/index.php/topic,20481.0.html
 +
 
 +
=== FPC built in Serial unit ===
 +
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.
 +
 
 +
An example using FPC 2.6.2 on WIN7.
 +
 
 +
<syntaxhighlight lang="pascal">
 +
Program TestSerialPortCom;
 +
{
 +
  Usage options:
 +
  TestSerialPortCom  => uses default COM1
 +
  TestSerialPortCom 8 'Hello' => uses COM8 and output 'Hello' before waiting for an answer
 +
 
 +
  the program will open a serialport and output 'Hello', after that the code will wait until
 +
  a CR (#13) is received, or a key is pressed.
 +
}
 +
uses
 +
    serial, crt;
 +
 
 +
VAR
 +
  serialhandle : LongInt;
 +
  ComPortName  : String;
 +
  s,tmpstr,txt : String;
 +
  ComOut,ComIn : String;
 +
  ComPortNr    : integer;
 +
  writecount  : integer;
 +
  status      : LongInt;
 +
 
 +
  BitsPerSec  : LongInt;
 +
  ByteSize    : Integer;
 +
  Parity      : TParityType; { TParityType = (NoneParity, OddParity, EvenParity); }
 +
  StopBits    : Integer;
 +
  Flags        : TSerialFlags; { TSerialFlags = set of (RtsCtsFlowControl); }
 +
 
 +
 
 +
  ErrorCode    : Integer;
 +
 
 +
BEGIN
 +
      ComPortNr:=1;
 +
      tmpstr:='';
 +
      txt:='';
 +
 
 +
      writeln('Parameters ',ParamCount);
 +
      if (ParamCount>0) then
 +
      begin
 +
        tmpstr:= ParamStr(1);
 +
        val(tmpstr,ComPortNr,ErrorCode);
 +
 
 +
        if (ParamCount>1) then
 +
        begin
 +
          txt:= ParamStr(2);
 +
          {val(tmpstr,ComPortNr,ErrorCode);}
 +
        end;
 +
      end;
 +
 
 +
      str(ComPortNr,tmpstr);
 +
   
 +
      ComPortName:= 'COM'+tmpstr+':';
 +
      writeln('Using '+ComPortname);
 +
 
 +
      serialhandle := SerOpen(ComPortName);
 +
      Flags:= [ ]; // None
 +
      SerSetParams(serialhandle,9600,8,NoneParity,1,Flags);
 +
     
 +
      s:=txt; // use the input text
 +
      writeln('OUT '+s);
 +
      s:=s+#13+#10; { CR + LF }
 +
      writecount := length(s);
 +
 
 +
      status := SerWrite(serialhandle, s[1], writecount );
 +
 
 +
      // The next line is for debugging only!
 +
      writeln('status: ', status, '  writecount: ', writecount);
 +
 
 +
 
 +
      if status > 0 then
 +
      begin
 +
        writeln('Waiting for answer');
 +
        { wait for an answer }
 +
        s:='';
 +
        ComIn:='';
 +
        while (Length(Comin)<10) and (status>=0) and not keypressed do begin
 +
 
 +
          status:= SerRead(serialhandle, s[1], 10);
 +
          if (s[1]=#13) then status:=-1; { CR => end serial read }
 +
 
 +
          if (status>0) then ComIn:=ComIn+s[1];
 +
          if (status>0) then begin
 +
            writeln(status,' ',length(ComIn),' ASCII ',ord(s[1]),' INP ',ComIn);
 +
          end;
 +
 
 +
        end;
 +
 
 +
      end
 +
      else
 +
        writeln('ERROR - Unable to send.');
 +
 
 +
      SerSync(serialhandle); { flush out any remaining before closure }
 +
 
 +
      SerFlushOutput(serialhandle); { discard any remaining output }
 +
 
 +
      SerClose(serialhandle);
 +
END.
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== TDataPort ===
 +
[https://github.com/serbod/dataport 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 [https://github.com/serbod/dataport/tree/master/demo demo] and [http://forum.lazarus.freepascal.org/index.php/topic,36523.msg245030.html#msg245030 here].
 +
 
 +
=== PXL ===
 +
[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 PXL (Platform eXtended Library)] for low level native access to serial ports, GPIO, I²C, SPI, PWM, V4L2, displays and sensors.
 +
 
 +
=== Serial port names on Windows ===
 +
COM ports are named with a number on Windows 9x-based OSes (95, 98, ME), e.g. COM1, COM30.
 +
 
 +
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.
 +
 
 +
Use this code to get the real name:
 +
<syntaxhighlight lang="pascal">
 +
// ComNr is obviously the number of the COM port
 +
if ComNr > 9 then
 +
  Result := Format('\\\\.\\COM%d', [ComNr])
 +
else
 +
  Result := Format('COM%d', [ComNr]);
 +
</syntaxhighlight>
  
 
==USB==
 
==USB==
  
 
===libusb===
 
===libusb===
 
+
A cross platform possibility for Windows, Linux, BSDs and macOS is [http://libusb.sourceforge.net/ libusb].
A cross platform possibility for Linux, BSDs and Mac OS X is [http://libusb.sourceforge.net/ libusb].
 
  
 
Headers are listed in http://www.freepascal.org/contrib/db.php3?category=Miscellaneous:
 
Headers are listed in http://www.freepascal.org/contrib/db.php3?category=Miscellaneous:
  
{|cellpadding="4"
+
{| class="wikitable"
|-
 
 
! name !! author !! version !! date !! link !! remarks
 
! name !! author !! version !! date !! link !! remarks
 
|-
 
|-
 
| libusb.pp || nowrap | Uwe Zimmermann || 0.1.12 || nowrap | 2006-06-29 || http://www.sciencetronics.com/download/fpc_libusb.tgz ||  
 
| libusb.pp || nowrap | Uwe Zimmermann || 0.1.12 || nowrap | 2006-06-29 || http://www.sciencetronics.com/download/fpc_libusb.tgz ||  
 
|-
 
|-
| libusb.pas || Johann Glaser ||  || nowrap | 2005-01-14 || http://www.johann-glaser.at/projects/libusb.pas ||  
+
| 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
 
|-
 
|-
 
| fpcusb || Joe Jared || 0.11-14 || nowrap | 2006-02-02 || http://relays.osirusoft.com/fpcusb.tgz || nowrap | download link broken
 
| fpcusb || Joe Jared || 0.11-14 || nowrap | 2006-02-02 || http://relays.osirusoft.com/fpcusb.tgz || nowrap | download link broken
 +
|-
 +
| libusb.pp || Marko Medic || 1.0 || nowrap | 2010-12-14 || http://www.lazarus.freepascal.org/index.php/topic,11435.0.html ||
 
|}
 
|}
  
 
===FTDI===
 
===FTDI===
 +
If one of the chips from [http://www.ftdichip.com/ FTDI] is used, their pascal headers for their dll interface to the chips can be used.
  
If you use one of the chips from [http://www.ftdichip.com/ FTDI], you can use their pascal headers for their dll interface to the chips.
+
==Devices in general==
 +
On Windows, devices can be managed from code, see here: [[Windows Programming Tips#Enabling and disabling devices]]
  
==External Links==
+
==See also==
 +
* [[Lazarus on Raspberry Pi]]
 +
* [[Networking]]
 +
* [[Target NativeNT]]
  
Communication Protocols speed comparison:
 
  
 +
==External Links==
 +
* Communication Protocols speed comparison:
 
# http://en.wikipedia.org/wiki/Serial_port#Speed
 
# http://en.wikipedia.org/wiki/Serial_port#Speed
 
# http://www.lvr.com/jansfaq.htm - Jan Axelson's Parallel Port FAQ
 
# http://www.lvr.com/jansfaq.htm - Jan Axelson's Parallel Port FAQ
Line 348: Line 510:
 
# http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications
 
# http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications
  
Serial Communication Links:
+
* Serial Communication Links:
 
 
 
# On UNIX: [http://www.easysw.com/~mike/serial/serial.html]
 
# On UNIX: [http://www.easysw.com/~mike/serial/serial.html]
 
# On Windows: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
 
# On Windows: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
Line 355: Line 516:
 
# Comport Delphi package: http://sourceforge.net/projects/comport/
 
# Comport Delphi package: http://sourceforge.net/projects/comport/
  
ISA Digital Oscilloscope - A example of hardware access with full source included:
+
* [http://eletronicalivre.incubadora.fapesp.br/portal/english/oscilloscope/|ISA Digital Oscilloscope] - A example of hardware access with full source included.
 
 
[http://eletronicalivre.incubadora.fapesp.br/portal/english/oscilloscope/]
 
 
 
----
 
[[Networking]]
 

Revision as of 18:35, 17 December 2019

Deutsch (de) English (en) español (es) français (fr) magyar (hu) 日本語 (ja) 한국어 (ko) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

Overview

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.

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:

 uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls,
 {$IFDEF WIN32}
   Windows;
 {$ENDIF}
 {$IFDEF Unix}
   ports;
 {$ENDIF}

Parallel and Serial Comparison

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.

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.

Now a brief comparison of hardware access protocols will be given:

Speed Hardware implementation difficulty
Serial Port Very slow (< E5 bit/s) Medium
Parallel Port Slow (~ E6 bit/s) Easy
ISA Card Medium (~ E7 bit/s) Medium
USB Medium (~ E7 bit/s) Hard
PCI Card Very Fast (> E9 bit/s) Very Hard

Parallel Communication

Using inpout32.dll for Windows

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.

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.

The library has only two functions, Inp32 and Out32, and their use is quite intuitive.

The library will be loaded dynamically, so define both functions first:

 type
   TInp32 = function(Address: SmallInt): SmallInt; stdcall;
   TOut32 = procedure(Address: SmallInt; Data: SmallInt); stdcall;
  • 'Address' represents the address of the port to be accessed
  • 'Out32' sends data to the port specified by 'Address'
  • 'Inp32' returns a byte from the port specified by 'Address'

Now the library can be loaded. This may be implemented in the 'OnCreate' method of the program's main form:

 uses
 ....dynlibs...

 type
   TMyForm = class(TForm)
   .........
   private
     { private declarations }
     Inpout32: THandle;
     Inp32: TInp32;
     Out32: TOut32;
   .........
 implementation
   .........
 procedure TMyForm.FormCreate(Sender: TObject);
 begin
 {$IFDEF WIN32}
   Inpout32 := LoadLibrary('inpout32.dll');
   if (Inpout32 <> 0) then
   begin
     // needs overtyping, plain Delphi's @Inp32 = GetProc... leads to compile errors
     Inp32 := TInp32(GetProcAddress(Inpout32, 'Inp32'));
     if (@Inp32 = nil) then Caption := 'Error';
     Out32 := TOut32(GetProcAddress(Inpout32, 'Out32'));
     if (@Out32 = nil) then Caption := 'Error';
   end
   else Caption := 'Error';
 {$ENDIF}
 end;

If the library is loaded on 'OnCreate', it must be unloaded in 'OnDestroy':

 procedure TMyForm.FormDestroy(Sender: TObject);
 begin
 {$IFDEF WIN32}
   FreeLibrary(Inpout32);
 {$ENDIF}
 end;

Here is a simple example of how to use the 'Inp32' function:

 {$IFDEF WIN32}
   myLabel.Caption := IntToStr(Inp32($0220));
 {$ENDIF}

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.

Light bulb  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:

Filename: {sys}\rundll32.exe; Parameters: "inpout32.dll,IsInpOutDriverOpen"; WorkingDir: {app}; Flags: 32bit;


This is the homepage for the library: www.logix4u.net/inpout32.htm (see discussion).

Using assembler on Windows 9x

On Windows 9x assembler code can be used. Suppose one wants to write $CC to the $320 port. The code to do that is:

 {$ASMMODE ATT}
 ...
    asm
        movl $0x320, %edx
        movb $0xCC, %al
        outb %al, %dx
    end ['EAX','EDX'];

Troubleshooting on Windows

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. Instructions on how to tell Windows not to assign the address of a device to Plug And Play devices can be found at http://support.microsoft.com/kb/135168

Using 'ioperm' to access ports on Linux

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.

In order to use the "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 "ports" unit can be found here: http://www.freepascal.org/docs-html/rtl/ports/index.html

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.

 {$IFDEF Linux}
 function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';
 {$ENDIF}
  • "from" represents the first port to be accessed.
  • "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.

After linking to IOPerm, port[<Address>] will access the ports.

 {$IFDEF Linux}
   i := ioperm($220, 8, 1);
   port[$220] := $00;
   myLabel.Caption := 'ioperm: ' + IntToStr(i);
   i := Integer(port[$220]);
   myOtherLabel.Caption := 'response: ' + IntToStr(i);
 {$ENDIF}

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

General UNIX hardware access

{$IFDEF Unix}
Uses Clib;   // retrieve libc library name.
{$ENDIF}

{$IFDEF Unix}
function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external clib;
{$ENDIF}


Note: FPC provides an abstraction for ioperm called "fpioperm" in 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.

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).

Note 2: Using unit libc is not recommended under any circumstances other than Kylix compatibility. See libc unit

Status and control

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.

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.

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.

Serial Communication

Device Setup

Before starting, make sure to authorize your device on Linux. An option is to create a persistent UDEV rule for each device. For example, the following approach apply mode "0064" for the device specified by 'serial', 'idVendor' and 'idProduct' attributes:

# create a new dev rule based on some constants
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 

# reload rules
sudo udevadm control --reload-rules

# re-add all devices
sudo udevadm trigger

# test
ls -al /dev/ttyUSB0

# you may want to reboot your system
sudo reboot


Synaser

It is very easy to build a serial communication software using the Synaser library. The example when used together with the Synaser documentation 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.

program comm;

{$apptype console}

uses
  Classes, SysUtils, Synaser;

var
  ser: TBlockSerial;
begin
  ser:=TBlockSerial.Create;
  try
    ser.Connect('COM1');
    ser.config(1200, 7, 'N', SB1, False, False);
    while True do
      Write(IntToHex(ser.RecvByte(10000), 2), ' ');
  finally
    ser.free;
  end;
end.

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.

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

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.

program serialtest;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,SysUtils,Synaser,Crt
  { add other units after this };

  var l:boolean;

  function check_affirmation():boolean;
  var k:string;
  begin
       Writeln('To quit the application do NOT use CTRL-C! Instead, press any key to quit the application! '+
       'Confirm this notification to continue the application! '+
       '[0]=Quit, [1]=Confirm, continue! ');
       Writeln('Your choice: ');
       Read(k);
       if StrtoInt(k) = 1 then
       begin
            check_affirmation:=true;
            Writeln('OK, application continues ...');
       end
       else
       begin
            check_affirmation:=false;
            Writeln('Abort');
       end
  end;

  procedure RS232_connect;
  var
    ser: TBlockSerial;
  begin
    ser:=TBlockSerial.Create;
    try
      ser.Connect('/dev/ttyUSB0'); //ComPort
      Sleep(1000);
      ser.config(1200, 7, 'N', SB1, False, False);
      Write('Device: ' + ser.Device + '   Status: ' + ser.LastErrorDesc +' '+
      Inttostr(ser.LastError));
      Sleep(1000);
      repeat
        Write(IntToHex(ser.RecvByte(10000), 2), ' ');
      until keypressed; //Important!!!
    finally
      Writeln('Serial Port will be freed...');
      ser.free;
      Writeln('Serial Port was freed successfully!');
    end;
  end;

  begin
    l:=check_affirmation();
    if l=true then
    RS232_connect()
    else
    Writeln('Program quit! ');
  end.

Also, the External Links section has UNIX and Windows serial port tutorials.

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.

There are alternatives to Synaser; see below.

Visual Synapse

Visual Synapse has lots of component wrappers for many parts of Synapse serial and networking library. TvsComPort is a visual component wrapper around Synaser.

5dpo

There is also a visual component 5dpo based on Synaser.

TLazSerial

Based on 5dpo (and therefore Synapse): http://forum.lazarus.freepascal.org/index.php/topic,20481.0.html

FPC built in Serial unit

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.

An example using FPC 2.6.2 on WIN7.

Program TestSerialPortCom;
{
  Usage options:
  TestSerialPortCom  => uses default COM1
  TestSerialPortCom 8 'Hello' => uses COM8 and output 'Hello' before waiting for an answer
  
  the program will open a serialport and output 'Hello', after that the code will wait until
  a CR (#13) is received, or a key is pressed.
}
uses
     serial, crt;

VAR
   serialhandle : LongInt;
   ComPortName  : String;
   s,tmpstr,txt : String;
   ComOut,ComIn : String;
   ComPortNr    : integer;
   writecount   : integer;
   status       : LongInt;

   BitsPerSec   : LongInt;
   ByteSize     : Integer;
   Parity       : TParityType; { TParityType = (NoneParity, OddParity, EvenParity); }
   StopBits     : Integer;
   Flags        : TSerialFlags; { TSerialFlags = set of (RtsCtsFlowControl); }


   ErrorCode    : Integer;

BEGIN
      ComPortNr:=1;
      tmpstr:='';
      txt:='';

      writeln('Parameters ',ParamCount);
      if (ParamCount>0) then
      begin
        tmpstr:= ParamStr(1);
        val(tmpstr,ComPortNr,ErrorCode);

        if (ParamCount>1) then
        begin
          txt:= ParamStr(2);
          {val(tmpstr,ComPortNr,ErrorCode);}
        end;
      end;

      str(ComPortNr,tmpstr);
     
      ComPortName:= 'COM'+tmpstr+':';
      writeln('Using '+ComPortname);

      serialhandle := SerOpen(ComPortName);
      Flags:= [ ]; // None
      SerSetParams(serialhandle,9600,8,NoneParity,1,Flags); 
      
      s:=txt; // use the input text
      writeln('OUT '+s);
      s:=s+#13+#10; { CR + LF }
      writecount := length(s);

      status := SerWrite(serialhandle, s[1], writecount );

      // The next line is for debugging only!
      writeln('status: ', status, '   writecount: ', writecount);


      if status > 0 then
      begin
        writeln('Waiting for answer');
        { wait for an answer }
        s:='';
        ComIn:='';
        while (Length(Comin)<10) and (status>=0) and not keypressed do begin

          status:= SerRead(serialhandle, s[1], 10);
          if (s[1]=#13) then status:=-1; { CR => end serial read }

          if (status>0) then ComIn:=ComIn+s[1];
          if (status>0) then begin
            writeln(status,' ',length(ComIn),' ASCII ',ord(s[1]),' INP ',ComIn);
          end;

        end;

      end
      else
        writeln('ERROR - Unable to send.');

      SerSync(serialhandle); { flush out any remaining before closure }

      SerFlushOutput(serialhandle); { discard any remaining output }

      SerClose(serialhandle);
 END.

TDataPort

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 and here.

PXL

PXL (Platform eXtended Library) for low level native access to serial ports, GPIO, I²C, SPI, PWM, V4L2, displays and sensors.

Serial port names on Windows

COM ports are named with a number on Windows 9x-based OSes (95, 98, ME), e.g. COM1, COM30.

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.

Use this code to get the real name:

// ComNr is obviously the number of the COM port
if ComNr > 9 then
  Result := Format('\\\\.\\COM%d', [ComNr])
else
  Result := Format('COM%d', [ComNr]);

USB

libusb

A cross platform possibility for Windows, Linux, BSDs and macOS is libusb.

Headers are listed in http://www.freepascal.org/contrib/db.php3?category=Miscellaneous:

name author version date link remarks
libusb.pp Uwe Zimmermann 0.1.12 2006-06-29 http://www.sciencetronics.com/download/fpc_libusb.tgz
libusb.pas Johann Glaser 2012-09-23 https://github.com/hansiglaser/pas-libusb includes OOP wrapper, see branch "libusb-1.0" for libusb 1.0
fpcusb Joe Jared 0.11-14 2006-02-02 http://relays.osirusoft.com/fpcusb.tgz download link broken
libusb.pp Marko Medic 1.0 2010-12-14 http://www.lazarus.freepascal.org/index.php/topic,11435.0.html

FTDI

If one of the chips from FTDI is used, their pascal headers for their dll interface to the chips can be used.

Devices in general

On Windows, devices can be managed from code, see here: Windows Programming Tips#Enabling and disabling devices

See also


External Links

  • Communication Protocols speed comparison:
  1. http://en.wikipedia.org/wiki/Serial_port#Speed
  2. http://www.lvr.com/jansfaq.htm - Jan Axelson's Parallel Port FAQ
  3. http://en.wikipedia.org/wiki/USB#Transfer_Speed
  4. http://en.wikipedia.org/wiki/PCI#Conventional_PCI_bus_specifications
  • Serial Communication Links:
  1. On UNIX: [1]
  2. On Windows: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
  3. Synaser component: http://synapse.ararat.cz/
  4. Comport Delphi package: http://sourceforge.net/projects/comport/