Hallo,
kann mir jemand eine Starthilfe geben, wie ich einen virtuellen USB Com
Port am PC, den ich zum Zeitpunkt des Programmlaufes noch nicht kenne
auslesen kann?
Konkreter formuliert:
Ich schreibe ein normales Konsolenprogramm mit dem GCC, welches die
Ausgabe eines Arduino, der über einen USB Konverter an den PC
angeschlossen ist auslesen soll als Textstrings, um diese dann zu
verarbeiten. Mit minicom habe ich soweit allesüberprüft, dass es stimmt,
was der Arduino sendet, jetzt geht es an die Auswertung des
Datenstromes.
Stecke ich den USB Stecker rein kriege ich mit lsusb die Meldung:
d.h. das Ding hat das Device File /dev/ttyUSB0
Stecke ich noch einen rein kriegt deer ttyUSB1 usw.
Welche Routinen bietet mir Linux, damit ich das auslesen kann? Bzw. da
ich nicht unbedingt pollen will vielleicht einen Interrupt, wenn zeichen
da sind, eine Info wie viele usw.
De Arduino sendet unregelmässig Strings, die Buchstaben und Zahlen
enthalten, Start ist eine Zeichenkette "AD:.....<Datensatz>" getrennt
durch Komma und abgeschlossen mit \0.
Bei einer CD ROM bindet man einfach den cdrom.h ein, erhält eine
Struktur für den Gerätetreiber und kann loslegen mit fest definierten
Schlüsselbegriffen, die ich mir aus man ioctl_list geholt habe:
PittyJ schrieb:> fopen("/dev/ttyUSB0","r");> fread(...);
Naja, ein bissi komplizierter ist es schon... Locking sollte man auch
machen.
Ich verwends in etwa so:
1
if((pid=serial_lock_port(Port))!=0){
2
if(pid==-1)
3
error("port %s could not be locked",Port);
4
else
5
error("port %s is locked by process %d",Port,pid);
Alternativ auch direkt an USB mit LibUsb
#define VENDOR_ID 9025
#define PRODUCT_ID 1
....
s_DeviceHandle = libusb_open_device_with_vid_pid(
NULL,
VENDOR_ID,
PRODUCT_ID);
...
i_Result = libusb_bulk_transfer(
s_DeviceHandle, // device
0x83, // endpoint
(unsigned char *) ca_QuestionBuffer, // pointer to data
buffer
PACKET_LENGHT, // length of data buffer
&i_Transferred, // int * transferred
TIMEOUT // timeout in milliseconds
);
....
Das geht dann ohne tty Device.
Da muss man aber wissen, wie das auf USB-Ebene alles geht.
PittyJ schrieb:> Alternativ auch direkt an USB mit LibUsb> Da muss man aber wissen, wie das auf USB-Ebene alles geht.
ich denke das kannst du vergessen - allein, wie wählst du eine Baud-rate
aus, ohne die "Innereien" des USB-Konverters zu kennen? und wenn du von
einem Konverter-Typ zum nächsten wechselst, kannst von vorne beginnen?
Abgesehen davon, dass du vorher den Kernel-ttyUSB-Treiber abhängen
müsstest.
Was glaubst du warum es das ttyUSB-Subsystem im kernel gibt?
Hallo,
danke, den Startpunkt habe ich ja nun gefunden :-) Auch hier:
http://www.tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html#AEN125
Dass es mit einem Serial.open(9600) wie bei Ard.... <Jehova!> nicht
getan sein wird ist mir klar.
Nochwas: Wie wird das mit dem Interrupt gelöst, so dass ich nicht pollen
muss, vor allem wegen Bufferüberlauf? Kann ich dem Kernel mitteilen,
dass er die INTs auf meine eigene Routine umleiten soll? Komme aus der
uC Welt, mit PC mache ich grad meine ersten erfahrungen unter Linux.
@Michael Reinelt: Kannst du die Lib vielleicht mal posten, wenn die die
Basisfunktionen vielleicht schon gekapselt hat? Ich würde mir das ganze
ohnehin auf 4,5 Routinen runterschreiben, denn mehr als initserial,
putchar und getchar braucht es ja nicht.
Ach ja, das Device Locking! :-( Wenn ein Programm abstürzt gibt er das
Device oft nicht frei, auch unter Windows ist das nervig bei der Ard...
IDE. Startet man das Programm neu ist das Device dann blocked. Kann man
es von der Konsole evtl unblocken?
Christian J. schrieb:> Nochwas: Wie wird das mit dem Interrupt gelöst, so dass ich nicht pollen> muss, vor allem wegen Bufferüberlauf? Kann ich dem Kernel mitteilen,> dass er die INTs auf meine eigene Routine umleiten soll? Komme aus der> uC Welt, mit PC mache ich grad meine ersten erfahrungen unter Linux.
Das musst du nicht, das macht alles der Kernel für dich. Interrupts im
Userspace gibts nicht (und wenn dann nur sehr sehr sehr selten)
Christian J. schrieb:> @Michael Reinelt: Kannst du die Lib vielleicht mal posten, wenn die die> Basisfunktionen vielleicht schon gekapselt hat? Ich würde mir das ganze> ohnehin auf 4,5 Routinen runterschreiben, denn mehr als putchar und> getchar braucht es ja nicht.
here you are...
'flags' für serial_open sind im normalfall "keine", also 0 (null)
Michael Reinelt schrieb:> Das musst du nicht, das macht alles der Kernel für dich. Interrupts im> Userspace gibts nicht (und wenn dann nur sehr sehr sehr selten)
Hi,
das verstehe ich nicht. Mein Programm muss doch auf externe Ereignisese
reagieren. Da gibs sogar bei Windows Programmierung einen Handler, den
ich umleiten kann auf meine eigene Routine.
Das Programm saust zwar nur einer Loop herum aber wie kriege ich denn
mit, wenn was da ist?
PS: Die Lib ist ja bombig !!!!!
Christian J. schrieb:> Das Programm saust zwar nur einer Loop herum aber wie kriege ich denn> mit, wenn was da ist?
"meine" serial_read() Funktion wartet auf Daten von der seriellen
Schnittstelle, wenn keine kommen gibts einen Timeout-Fehler. Die
solltest also in der Form nur verwenden, wenn du aktuell bzw. regelmäßig
Daten erwartest.
Tust du das nicht, sondern die Daten kommen "irgendwann", müsstest du
das anders aufbauen. serial_read() ist aber nicht allzu komplex, basis
ist die read() funktion, und die liefert 0 wenns nix zu lesen gibt, <0
im Fehlerfall, wobei du den EAGAIN-Fehler noch speziell behandeln
solltest.
Wenn du nur auf die serielle warten wolltest, wäre select() dein Freund.
Michael Reinelt schrieb:> ich denke das kannst du vergessen - allein, wie wählst du eine Baud-rate> aus, ohne die "Innereien" des USB-Konverters zu kennen? und wenn du von> einem Konverter-Typ zum nächsten wechselst, kannst von vorne beginnen?> Abgesehen davon, dass du vorher den Kernel-ttyUSB-Treiber abhängen> müsstest.>> Was glaubst du warum es das ttyUSB-Subsystem im kernel gibt?
Ich habe mit libusb die Kommunikation realisiert. Ich kann den
kompletten Code schicken. Baudrate war einfach, kurz mit Wireshark
nachgeschaut, wie das geht.
Mein alter Linux-PowerPC hat leider kein ttyUSB, da ging nur diese
Lösung, um mit dem Arduino zu sprechen.
Läuft 24/7, das ganze Jahr.
Michael Reinelt schrieb:> Christian J. schrieb:>> @Michael Reinelt: Kannst du die Lib vielleicht mal posten, wenn die die>> Basisfunktionen vielleicht schon gekapselt hat? Ich würde mir das ganze>> ohnehin auf 4,5 Routinen runterschreiben, denn mehr als putchar und>> getchar braucht es ja nicht.>> here you are...>> 'flags' für serial_open sind im normalfall "keine", also 0 (null)
Hi Michael,
da du den Code nicht kommentiert hast, könntest du evtl, wenn du Zeit
hast in groben Zügen die init Routine mal erklären? Nur ganz grob. Da
ist einiges mit Tempfiles, Strincopy Funktionen usw. und ich bin grad
dabei das alles an meinen "Stil" anzupassen und einiges zu verändern.
Finde den Codeaufwand hoch für "Lock", daher frage ich:
Hallo Christian,
erstmal zur Info: der Code ist mehr als 10 Jahre alt, kann gut sein dass
man gewisse Dinge heute anders macht, oder es fertige Libs dafür gibt.
Gab es damals nicht oder ich konnte sie nicht verwenden, da unter
uClinux nicht/schlecht verfügbar. Der Code wird in der Form auch in
lcd4linux verwendet, und verrichtet dort seit vielen Jahren klaglos
seinen Dienst, also ganz falsch kann er nicht sein...
Ja, das Locking ist kompliziert, aber das passt so, weil Locking ist
kompliziert :-) Ist halt "zu Fuß" ausprogrammiert (genauso wie eine
saubere daemonize-Routine in lcd4linux)
Als erstes wird der Name des Lockfiles ermittelt. Wenn unter dev dann
dev weglassen. Für Subdirectories (damals gab es noch sowas wie
/dev/tty/USB0) wird der slash nach underscore gewandelt (tty/USB0 =>
tty_USB0)
"Erzeugt" wird das lockfile zuerst unter einem temporären Namen
(tempfile), mkstemp() liefert dir einen garantiert unbenutzten
temporären Namen.
Ins tempfile wird dann die aktuelle Process-Id geschrieben, und das
Tempfile wieder geschlossen.
Danach wird versucht, vom Tempfile einen Hardlink auf das eigentliche
Lockfile zu erzeugen. Da das eine atomare Operation ist, kann auch
theoretisch keine Überschneidung mit einem anderen Prozess erfolgen
(zwei Prozesse hätten dann dasselbe Lockfile).
Der Rename-Trick darf hier nicht verwendet werden, weil ich das alte
lockfile noch gerne verwendet hätte (zumindest um die PID auszulesen und
ins Logfile zu schreiben)
Wenn link() fehlschlägt, bedeutet dies dass das lockfile schon
existiert. Dann wird das bereits bestehende Lockfile geöffnet und die
PID des sperrenden Prozesses rausgelesen.
Ist diese PID unsere, hat der Programmierer einen Bock geschossen und
lock_port zweimal aufgerufen ("already locked by us. uh-oh...")
mit kill (pid, 0) wird überprüft ob der sperrende Prozess noch
existiert. Wenn ja, ist der Port tatsächlich von einem anderen Prozess
gesperrt, und wir geben auf.
Wenn nicht, handelt es sich um ein "übriggebliebenes" Lockfile
(vermutlich eh von uns selbst, von einem vorhergehenden Programmlauf,
der "hart" abgebrochen wurde, und deshalb keine Gelegenheit mehr hatte
sein Lockfile sauber zu löschen)
die restliche serial_open() funktionalität ist hier recht gut
beschrieben: https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2Christian J. schrieb:> und ich bin grad> dabei das alles an meinen "Stil" anzupassen und einiges zu verändern.
Das kannst du natürlich gerne (übrigens, vergessen: der Code unterliegt
der GPL), ich gebe nur zu bedenken: Der Code ist (mit lcd4linux) schon
von vielen Augen reviewt worden, und sollte daher einigermaßen korrekt,
robust, frei von Bugs und Race-Conditions sein. Jede (gröbere) Änderung
birgt die Gefahr einer Regression...
Natürlich kannst du sagen "das ist alles viel zu kompliziert, das brauch
ich alles nicht", spätestens wenn du parallel zu deiner Anwendung mit
einem Terminal-Programm auf den Port gehst (um was auszuprobieren) bis
du froh um funktionierendes, standardkonformes Locking.
Anyway, wenn du änderst, wäre ich an deinen Änderungen interessiert.
lg Michi
Hallo Michel,
erstmal danke für deine Mühe das alles zu erklären. Ich werde mich der
Sache später nochmal widmen, da ich aktuell an einem Z80 Projekt dran
bin für das diese Routinen gebraucht werden. Ich bn noch zu neu in der
Linux programmierung um das alles auf einmal zu verstehen. Da muss ich
mich erst allein mit befassen und bombensicheren Code muss ich nicht
schreiben, da nur ich das progamm verwenen werde um mit dem Z80 zu
reden.
So, erstmal hier linken, damit ich es auch wiederfinde.
Gruss,
Christian