Re: ttyS0-Unit



Hallo Thomas,

Du schriebst am Mon, 19 Mar 2007 18:09:09 +0100:

ich mache hier meine ersten Versuche mit Freepascal unter Linux und suche
eine unit oder einfach ein paar Tips um die serielle Schnittstelle lesen
und schreiben zu koennen, die Rechte dazu natürlich vorausgesetzt.

Tips zum Programmieren, spez. der seriellen Schnittstelle, unter Linux?

Immer wieder beliebt, sozusagen die No. 1, unter diesen sind - wer hätt's
gedacht? - die immer genannten und anscheinend nie gelesenen

man pages, Section 2 & 3

und da speziell alles, was mit Files und I/O zu tun hat.

Bei google fand ich nur welche für TP und die funktionieren unter
Freepascal nicht.

Stimmt _so_ mit Sicherheit nicht. Auch wenn ein paar kleinere Anpassungen
nötig sind, funktionieren TP-Units fast "wortwörtlich" auch unter fpc,
sogar dann, wenn sie einige Spezialitäten aus dem systemnahen Bereich
enthalten.
Du meinst sicher eher, Du hättest eine Menge solcher Units für DOS und
einige für Windows gefunden, aber nix für Linux? Das kann schon sein.

Unter Linux ist das im einfachsten Fall garkein Aufwand: Du machst ein
"normales" Textfile auf, das "zufällig" den Namen der Schnittstelle hat,
und kannst, wenn Du's für Lesen _und_ Schreiben benutzt, darüber schon
"kommunizieren". Allerdings mußt Du immer drauf achten, Deine Blöcke sauber
abzuschließen, so daß die Systemroutinen auch immer "rechtzeitig" die
richtige Richtung freigeben.
Wenn Du das nicht sicherstellen kannst (und bei einer Schnittstelle mit
undefinierten angeschlossenen Geräten kann man das nicht), mußt Du die
diversen Synchronisationsfunktionen des Systems zuhilfenehmen, die Dir
erlauben auch mal "außer der Reihe" und ohne Freigabe durch den Partner von
Lesen auf Schreiben zu wechseln. Da gibt's ein gute Auswahl unter Linux, zu
nennen sind da "select", "poll" und "epoll", dazu gibt's noch ein paar
"Hilfen".
Für den Anfang, weil ich das noch 'rumfliegen habe, häng' ich mal eine
alte, noch DOS-stämmige Unit an, die ich mehrfach konvertiert und vielfach
benutzt habe, und die ich hier mal auf den Linuxteil gestutzt habe. Dazu
ein kleines, primitivstes Testprogramm ohne jeden Komfort, damit Du die
auch mal ausprobieren kannst, ohne gleich eine volle Analyse machen zu
müssen.
Eins _mußt_ Du allerdings: die volle Verantwortung für jede Art von Nutzung
dieser Daten _selber_ übernehmen - ich zeichne für _nichts_ verantwortlich,
was damit, dadurch oder daraus entsteht!

Und hier der Code - zuerst die Unit, samt gesammelten Referenzen:
----------------------------
UNIT AsynUnit;
{
Textfile Device Driver for serial port, interrupt driven.
After Turbo 4.0 ASYNC.INC by Michael Quinlan
and ASYNC4U.PAS by Scott Gurvey, see below.

Main Procedure exported is

PROCEDURE AssignCOM (VAR F: text; Channel: PortString;
BaudRate: word; Parity: ParityDef;
DataBits, StopBits: byte);
with Parameters
F - the text file variable
Channel - the DOS port name 'COMn', n in the range 1..4.
BaudRate - the baud rate to set, values like MODE command.
Parity - specify one of (noPty, evenPty, oddPty)
for no, even or odd Parity, resp.
DataBits - number of data bits, may be 7 or 8.
StopBits - number of stop bits, may be 1 or 2.

Text file assigned may be opened, written to or read from with standard
TP routines; both input and output are interrupt driven.
Any neccessary initialisations, installations and deinstallations are
fully automatic.
Port parameters for input file always take preference!

Additional utility procedure:

FUNCTION COMready - tells if it's worthwhile to attempt
to read from the COM port file;
same basical function as KeyPressed.

Plus a couple of Constants and Variables:

AsyncHandshake - boolean, default value: false.
Defines initial setting for CTS/RTS
handshake. Can be changed before
assignment, will NOT be reset after
AssignCOM.
AsyncSetHandshake - boolean, default value: true.
Specifies whether to do initial setup
for DTR and RTS.
Can be used in conjunction with
AsyncHandshake to disable handshaking
completely.
AsyncTimeout - word, default value: 18 (about 1 sec).
Number of clock ticks to wait until
timeout error. Globally used.

{ References: }
{----------------------------------------------------------------------}
{ ASYNC4U.PAS }
{ }
{ This is a faithful translation of the famous ASYNC.INC by Michael }
{ Quinlan into a Turbo 4.0 unit. No extra frills, no modification of }
{ types, nothing fancy. But with this code you should be able to }
{ delete your $I ASYNC.INC directive, add a USES ASYNC4U statement, }
{ and recompile your existing program. If you want to add support }
{ for more ports, other computers, or change to use the new data }
{ types, all good ideas, go right ahead. With this you don't have to. }
{ }
{ Scott Gurvey, November 29 1987 }
{----------------------------------------------------------------------}
{ }
{ ASYNC.INC }
{ }
{ Async Communication Routines }
{ by Michael Quinlan }
{ with a bug fixed by Scott Herr }
{ made PCjr-compatible by W. M. Miller }
{ Highly dependant on the IBM PC and PC DOS 2.0 }
{ }
{ based on the DUMBTERM program by CJ Dunford in the January 1984 }
{ issue of PC Tech Journal. }
{ }
{----------------------------------------------------------------------}

{ Textfile Device Driver for Linux serial devices }

INTERFACE

USES Dos, OldLinux, SysUtils;

TYPE
{$PACKENUM 1}
ParityDef = (noPty, evenPty, oddPty);
PortString = string [5];

CONST
CS: ARRAY [5..8] OF longint = (CS5, CS6, CS7, CS8);
CSTOP: ARRAY [1..2] OF longint = (0, CSTOPB);
PAR: ARRAY [ParityDef] OF longint = (0, PARENB, PARENB OR PARODD);
CTL: ARRAY [boolean] OF longint = (CLOCAL, CRTSCTS);

CONST
MinBaud = 75; { minimum permissible baud rate }
MaxBaud = 115200; { maximum permissible baud rate }

UnusedHandle = -1; { handle value for inactive file }

AsyncHandshake: boolean = false; { may be set true for CTS/RTS
handshake } AsyncSetHandshake: boolean = true;
AsyncTimeout: longint = 1000{ms};

LockBase: PathStr = '/var/lock/LCK..';


FUNCTION COMready (VAR F: text): boolean;

FUNCTION reserveCOM (Channel: PortString): boolean;

PROCEDURE releaseCOM (VAR F: text);

PROCEDURE AssignCOM (VAR F: text; Channel: PortString; BaudRate: longint;
Parity: ParityDef; DataBits, StopBits: byte);


IMPLEMENTATION


CONST
BaudCount = 9;

BaudRate: ARRAY [0..BaudCount] OF longint =
(300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200);
BaudCode: ARRAY [0..BaudCount] OF word =
(B300, B600, B1200, B2400, B4800, B9600, B19200, B38400, B57600,
B115200);

TYPE
COMdata = PACKED RECORD
Data: byte;
Pty: ParityDef;
Stop: byte;
Handsh: boolean;
Baud: longint;
unused: ARRAY [9..32] OF byte;
END;


PROCEDURE MapError;
{
Convert ErrNo error to the correct Inoutres value
}
BEGIN
IF LinuxError = 0 THEN Exit; { Else it will go through all the cases }

CASE LinuxError OF
Sys_ENFILE,
Sys_EMFILE: InOutRes:= 4;
Sys_ENOENT: InOutRes:= 2;
Sys_EBADF: InOutRes:= 6;
Sys_ENOMEM,
Sys_EFAULT: InOutRes:= 217;
Sys_EINVAL: InOutRes:= 218;
Sys_EPIPE,
Sys_EINTR,
Sys_EIO,
Sys_EAGAIN,
Sys_ENOSPC: InOutRes:= 101;
Sys_ENAMETOOLONG,
Sys_ELOOP,
Sys_ENOTDIR: InOutRes:= 3;
Sys_EROFS,
Sys_EEXIST,
Sys_EACCES: InOutRes:= 5;
Sys_ETXTBSY: InOutRes:= 162;
END;
END;


PROCEDURE InputCOM (VAR F: TextRec);
VAR
waiting: longint;
COMset: fdSet;

BEGIN
WITH F DO BEGIN
waiting:= 1 {AsyncTimeout}; { minimal time out }
FD_ZERO (COMset); FD_Set (Handle, COMset);
waiting:= Select (succ (Handle), @COMset, NIL, NIL, waiting);

IF waiting > 0 THEN BEGIN
waiting:= BufSize;
BufEnd:= fdRead (Handle, BufPtr^, waiting {BufSize});
BufPos:= 0; MapError;
END (* IF waiting > 0 *)
ELSE InOutRes:= 161; { device read error }
END (* WITH F *);
END (* InputCOM *);


PROCEDURE OutputCOM (VAR F: TextRec);
VAR
Written: word;

BEGIN
WITH F DO BEGIN
Written:= fdWrite (Handle, BufPtr^, BufPos);
MapError;
IF Written <> BufPos THEN InOutRes:= 101;
BufPos:= 0;
END (* WITH F *);
END (* OutputCOM *);

{
FUNCTION IgnoreCOM (VAR F: TextRec): integer;
BEGIN IgnoreCOM:= 0; END;
}

PROCEDURE CloseCOM (VAR F: TextRec);
BEGIN
IF FLock (F.Handle, LOCK_UN)
THEN fdClose (F.Handle)
ELSE InOutRes:= LinuxError;
F.Handle:= UnusedHandle; MapError;
END (* CloseCOM *);


PROCEDURE OpenCOM (VAR F: TextRec);

FUNCTION BaudSel (Baud: longint): word;
VAR
i: integer;

BEGIN (* BaudSel *)
i:= BaudCount;
WHILE (i > 0) AND (Baud < BaudRate [i]) DO Dec (i);
BaudSel:= BaudCode [i];
END (* BaudSel *);


FUNCTION opened: boolean;
VAR
BaseMode: longint;
DeviceCtrl: TermIOS;

BEGIN (* opened *)
opened:= false;
BaseMode:= Open_NoCtty OR Open_Sync; { not controlling terminal }

IF F.Mode = fmInput
THEN BaseMode:= Open_RdOnly OR BaseMode
ELSE IF F.Mode = fmOutput
THEN BaseMode:= Open_WrOnly OR BaseMode
ELSE InOutRes:= 151; { unknown unit }

WITH F DO BEGIN
Handle { file handle }:= fdOpen (Name { file name }, BaseMode
{ attribute: read/write });

IF Handle >= 0 THEN BEGIN
IF FLock (Handle, LOCK_SH) THEN BEGIN
IF tcGetAttr (Handle, DeviceCtrl) THEN BEGIN
WITH DeviceCtrl, COMdata (UserData) DO BEGIN
c_iflag:= IGNBRK OR BRKINT;
c_oflag:= 0;
c_cflag:= CS [Data] OR CSTOP [Stop] OR PAR [Pty] OR
CTL [Handsh] OR CREAD; c_lflag:= 0;
cfSetISpeed (DeviceCtrl, BaudSel (Baud));
cfSetOSpeed (DeviceCtrl, BaudSel (Baud));
END (* WITH DeviceCtrl, COMdata (UserData) *);

opened:= tcSetAttr (Handle, TCSAFLUSH, DeviceCtrl);
END (* IF tcGetAttr (Handle, DeviceCtrl) *)
END (* IF FLock (Handle LOCK_SH) *)
END (* IF Handle >= 0 *)
END (* WITH F, COMdata (UserData) *)
END (* opened *);

BEGIN
WITH F DO
IF opened THEN BEGIN
IF Mode = fmInput THEN BEGIN
InOutFunc:= @InputCOM;
FlushFunc:= NIL; {@IgnoreCOM;}
BufPos:= 0; BufEnd:= 0;
END (* IF Mode = fmInput *)
ELSE IF Mode = fmOutput THEN BEGIN
InOutFunc:= @OutputCOM;
FlushFunc:= @OutputCOM;
END (* IF Mode = fmOutput *);

CloseFunc:= @CloseCOM;
MapError;
END (* IF opened *)
ELSE InOutRes:= 103; { file not open }
END (* OpenCOM *);


PROCEDURE releaseCOM (VAR F: text);
BEGIN
DeleteFile (LockBase+ BaseName (StrPas (TextRec (F).Name), ''));
END (* releaseCOM *);


FUNCTION reserveCOM (Channel: PortString): boolean;
VAR
H: longint;
P: string [11];

BEGIN
H:= fdOpen (LockBase+ Channel, Open_WrOnly OR Open_Creat OR Open_Excl);
IF H >= 0 THEN BEGIN
Str (GetPID: 10, P); P:= P+ ^J; fdWrite (H, P [1], Length (P));
fdClose (H);
END (* IF H >= 0 *);
reserveCOM:= H >= 0;
END (* reserveCOM *);


FUNCTION COMready (VAR F: text): boolean;
VAR
waiting: longint;

BEGIN
COMready:= true; { mal auf Verdacht annehmen }
WITH TextRec (F) DO
IF Mode = fmInput THEN BEGIN
IF BufPos >= BufEnd THEN BEGIN
IOCtl (Handle, FIONREAD, @waiting);
COMready:= waiting > 0;
END (* IF BufPos >= BufEnd *);
END (* IF Mode = fmInput *)
ELSE RunError (104); { file not open for input }
END (* COMready *);


PROCEDURE AssignCOM (VAR F: text; Channel: PortString; BaudRate: longint;
Parity: ParityDef; DataBits, StopBits: byte);
CONST
dev: ARRAY [1..5] OF char = '/dev/';

BEGIN
IF (Length (Channel) < 5) OR
(Pos ('tty', Channel) <> 1)
THEN RunError (151); { unknown unit }

IF NOT ((StopBits IN [1, 2]) AND (DataBits IN [5..8]) AND
(MinBaud <= BaudRate) AND (BaudRate <= MaxBaud))
THEN RunError (153); { unknown command }

WITH TextRec (F), COMdata (UserData) DO BEGIN
FillChar (F, SizeOf (TextRec), 0);
Handle:= UnusedHandle;
Mode:= fmClosed;
BufSize:= TextRecBufSize; { SizeOf (Buffer); }
BufPtr:= @Buffer;
OpenFunc:= @OpenCOM;
Baud:= BaudRate;
Pty:= Parity;
Data:= DataBits;
Stop:= StopBits;
HandSh:= AsyncHandshake;
FillChar (Name, SizeOf (Name), 0);
Move (dev, Name, SizeOf (dev));
Move (Channel [1], Name [5], Length (Channel));
END (* WITH TextRec (F) *);
END (* AssignCOM *);

END.
----------------------------
So, das war dieses, und dazu noch das versprochene Testprogrämmchen:
----------------------------
PROGRAM AsyncTest;

USES Dos, Crt, AsynUnit;

CONST
Esc = ^[;

VAR
trash: PortString;
inChar: char ABSOLUTE trash;
value: longint;
xxx: integer;
COMin,
COMout: text;

CONST
COMport: PortString = 'ttyS0';
Pty: ParityDef = noPty;
Speed: longint = 9600;
Bits: byte = 8;
Stop: byte = 1;
Done: boolean = false;

BEGIN
IF (ParamCount >= 1) AND (ParamStr (1) <> '?')
THEN COMport:= ParamStr (1);

IF ParamCount >= 2 THEN BEGIN
Val (ParamStr (2), value, xxx);
IF xxx = 0 THEN Speed:= value;
END (* IF ParamCount >= 2 *);

IF ParamCount >= 3 THEN BEGIN
trash:= ParamStr (3); inChar:= trash [1];
CASE UpCase (inChar) OF
'E': Pty:= evenPty;
'N': Pty:= noPty;
'O': Pty:= oddPty;
END (* CASE UpCase (inChar) *);
END (* IF ParamCount >= 3 *);

IF ParamCount >= 4 THEN BEGIN
Val (ParamStr (4), value, xxx);
IF xxx = 0 THEN Bits:= value MOD 9;
END (* IF ParamCount >= 4 *);

IF ParamCount >= 5 THEN BEGIN
Val (ParamStr (5), value, xxx);
IF xxx = 0 THEN Stop:= value MOD 3;
END (* IF ParamCount >= 5 *);

Assign (output, ''); Rewrite (output);

IF NOT reserveCOM (COMport) THEN BEGIN
WriteLn ('Port ', COMport, ' nicht verf_gbar!'); Halt (103);
END (* IF NOT reserveCOM (COMport) *);

AsyncHandshake:= false; AsyncSetHandshake:= false;
AssignCOM (COMin, COMport, Speed, Pty, Bits, Stop); Reset (COMin);
AssignCOM (COMout, COMport, Speed, Pty, Bits, Stop); Rewrite (COMout);

REPEAT
IF COMready (COMin) THEN BEGIN
Read (COMin, inChar); Write (inChar);
END (* IF COMready (COMin) *);

IF KeyPressed THEN BEGIN
inChar:= ReadKey;
IF inChar <> #0
THEN Write (COMout, inChar)
ELSE Done:= ReadKey IN [#68 {F10}, #45 {Alt-X}];
END (* IF KeyPressed *);
UNTIL Done;

WriteLn; Close (COMout); releaseCOM (COMout);
END.
----------------------------
Viel Spaß damit, ich hoffe, es klappt alles...

----
--
(Weitergabe von Adressdaten, Telefonnummern u.ä. ohne Zustimmung
nicht gestattet, ebenso Zusendung von Werbung oder ähnlichem)
-----------------------------------------------------------
Mit freundlichen Grüßen, S. Schicktanz
-----------------------------------------------------------
.



Relevant Pages