Forum: PC-Programmierung /dev/ttyACM0 macht nichts


von Lothar (Gast)


Lesenswert?

Habe einen uC programmiert, über USB VCOM Messwerte als Text 
rauszuschreiben.

Unter Windows funktioniert es d.h. COM Port erscheint und Text kann 
ausgelesen werden.

Unter Linux erscheint auch der COM Port, nachdem USB eingesteckt wurde:

$sudo stty -F /dev/ttyACM0
stty: /dev/ttyACM0: No such file or directory

$sudo stty -F /dev/ttyACM0
speed 9600 baud; line = 0;
-brkint -imaxbel

Aber dann kommt hier kein Text:

$sudo tail -f /dev/ttyACM0

Baudrate ist natürlich default = 9600

von Georg A. (georga)


Lesenswert?

tail ist evtl. nicht unbedingt die beste Lösung, da das Zeilen puffert. 
Versuch mal nur "cat /dev/ttyACM0".

von Lothar (Gast)


Lesenswert?

Der uC ist zwar kein Arduino - aber unter Windows funktioniert es - 
natürlich - auch mit dem Arduino IDE Serial Monitor.

Daher habe ich jetzt mal testweise unter Linux auch die Arduino IDE 
installiert. Und wie beschrieben:

$ls -l /dev/ttyACM*
crw-rw---- 1 root dialout 166, 0 Jan  1 22:03 /dev/ttyACM0
sudo usermod -a -G dialout xxxxxx

Das funktioniert nicht - warum auch immer. Damit funktioniert es:

sudo arduino

Somit sollte jetzt auch das funktionieren - aber auch nach Beenden ist 
der COM Port noch blockiert:

$sudo tail -f /dev/ttyACM0
tail: cannot open '/dev/ttyACM0' for reading: Permission denied
tail: no files remaining

von Lothar (Gast)


Lesenswert?

Georg A. schrieb:
> Versuch mal nur "cat /dev/ttyACM0"

Da tut sich was, es werden einige Zeichen gelesen, danach nichts mehr.

Lothar schrieb:
> sudo usermod -a -G dialout xxxxxx

Das wurde jetzt durch einen Neustart wirksam.

von which man (Gast)


Lesenswert?

Lothar schrieb:
> Georg A. schrieb:
>> Versuch mal nur "cat /dev/ttyACM0"
>
> Da tut sich was, es werden einige Zeichen gelesen, danach nichts mehr.
>
> Lothar schrieb:
>> sudo usermod -a -G dialout xxxxxx
>
> Das wurde jetzt durch einen Neustart wirksam.

logout u. login

oder

(exec) newgrp dialout

man newgrp
 The newgrp command is used to change the current group ID during a 
login session.

von Lothar (Gast)


Lesenswert?

Die Lage ist jetzt so, dass es mit dem Arduino IDE Serial Monitor 
funktioniert.

Im Terminal funktioniert es einige Zeit, dann nicht mehr.

Im Arduino Forum steht, man muss dann den Treiber resetten. Unter 
Windows reicht aus- und einstecken. Unter Linux nicht. Habe schon einige 
Methoden zum Treiber resetten versucht, bisher ging nichts. Nur nach 
Neustart geht es wieder.

von Georg A. (georga)


Lesenswert?

Lothar schrieb:
> Im Arduino Forum steht, man muss dann den Treiber resetten. Unter

Jaja, Arduino. Experten@work ;)

> Windows reicht aus- und einstecken. Unter Linux nicht. Habe schon einige
> Methoden zum Treiber resetten versucht, bisher ging nichts. Nur nach
> Neustart geht es wieder.

Ich hab schon viel und lang mit USB/RS232 verschiedenster (auch 
zweifelhafter) Herkunft gewurschtelt, aber sowas eigentlich noch nie 
gehabt. So gut wie immer ist bei den simplen Treibern eigentlich das 
Device selbst schuld. Ist das nach dem Ausstecken 100% stromlos?

Evtl. sollte man noch das Handshake mit stty -crtscts abschalten.

Achja, das Linux-TTY-System hat im Gegensatz zu Windows ein paar 
Besonderheiten:

- Man kann dasselbe Device einfach so mehrfach öffnen. Damit kann man 
zB. Lesen und Schreiben in getrennte Prozesse auslagern, tw. sehr 
praktisch. Zwei++mal lesen geht aber auch, aber wer dann die Daten 
bekommt, ist undefiniert. Schau mal die lsof-Ausgaben an, ob da 
noch/schon jemand an ttyACM0 hängt.

- Wenn Ausgaben nicht rausgehen (zB. wg. Handshake) kann ein nächstes 
open hängen. Das Device muss man dann nonblocking öffnen, sonst wirds 
nix mehr. Schau mal mit "strace cat /dev/ttyACM0" nach, wie weit du 
eigentlich kommst.

- Wenn ein Device rausgezogen wird, während der Filedeskriptor dazu noch 
offen ist, führt das Einstecken zu einem neuen Devicenamen (zB. 
ttyACM1). Schau in "dmesg" nach, ob das Anstecken wirklich den 
Devicenamen erzeugt hat, den du vermutest.

von 🐧 DPA 🐧 (Gast)


Lesenswert?

Schau eventuell auch noch mit lsof nach, ob nicht noch was anderes 
darauf zugreift (z.B. modemmanager, gpsd, getty.service, etc.)

von Lothar (Gast)


Lesenswert?

Georg A. schrieb:
> Ich hab schon viel und lang mit USB/RS232 verschiedenster (auch
> zweifelhafter) Herkunft gewurschtelt, aber sowas eigentlich noch nie
> gehabt. So gut wie immer ist bei den simplen Treibern eigentlich das
> Device selbst schuld

Das Device ist hier nicht schuld. Es ist ein LPC845 der über UART 
rausschreibt. Schuld ist - falls überhaupt - der vom Board-Hersteller 
als USB-UART Bridge programmierte LPC11U35:

https://mcuoneclipse.com/2019/02/02/tutorial-transforming-the-nxp-lpc845-brk-into-a-cmsis-dap-debug-probe/

Der Hersteller wird mir bestimmt sagen, es funktioniert doch unter 
Windows, und ich soll Linux halt lassen. Aber ich muss es nun mal unter 
Linux testen.

Übrigens, auf stackoverflow gibt es haufenweise Beiträge, dass USB-CDC 
was unter Windows problemlos läuft, unter Linux instabil ist. Es wird 
dann ernsthaft zur Ethernet-UART Bridge geraten.

Bei Arduino ist die Lage ähnlich. Habe inzwischen ein MEGA getestet. 
Hier ist auch als USB-UART Bridge ein ATMEGA16U2 drauf. Den Sourcecode 
gibt es:

https://github.com/arduino/ArduinoCore-avr/tree/master/firmwares/atmegaxxu2/arduino-usbserial

Dennoch konnte das Problem wohl bisher nicht zu 100% gelöst werden.

von PittyJ (Gast)


Lesenswert?

Also ich verwende täglich auf der Arbeit Usb-Uart Adapter unter Linux. 
Sowohl auf großen X86 Maschinen, als auch auf Raspis.
Das geht problemloser als auf Windows. Von daher kann ich die Probleme 
überhaupt nicht nachvollziehen.
Ich habe dann die Devices /dev/ttyS0, /dev/ttyS1 .. je nachdem wie viele 
ich reinstecke. Komisch, dass du andere Devices bekommst?

Eine prima GUI-Anzeige ist das Putty Programm. Geht sowohl auf X86 als 
auch auf Arm, und es ist einfach über apt-get installierbar.

Damit arbeite ich seit Jahren.

von tt2t (Gast)


Lesenswert?

PittyJ schrieb:
> Ich habe dann die Devices /dev/ttyS0, /dev/ttyS1

Das ist aber eher untypisch, bei USB-Adaptern heissen die meistens
/dev/ttyUSB0 ... oder /dev/ttyACM0 ...

von Lothar (Gast)


Lesenswert?

Habe es jetzt in C programmiert und da funktioniert es halbwegs:

USB = open("/dev/ttyACM0", O_RDWR | O_NOCTTY);
fcntl(USB, F_SETFL, O_NONBLOCK);

Somit kann ich mich nun mit dem nächsten Problem beschäftigen:

Unter Windows kann ich in C einen seriellen Interrupt Handler 
definieren, der aufgerufen wird, wenn ein Zeichen angekommen ist.

Gibt es sowas unter Linux auch? Bisher bin ich am Pollen:

num = read(USB, &ch, 1);
if (num == 0) break;

von S. R. (svenska)


Lesenswert?

Pollen, entweder über ein blocking read(), besser mit poll() oder 
select() ist eigentlich die übliche Vorgehensweise. Damit verschwendet 
man auch keine CPU-Zeit.

Vielleicht könnte man ein Signal definieren, aber die Interaktion von 
signal handlers und dem restlichen Programm ist eher schlecht definiert 
und wird noch schlimmer, wenn man Multithreading benutzt. Signale sollte 
man besser meiden.

von Rolf M. (rmagnus)


Lesenswert?

Lothar schrieb:
> fcntl(USB, F_SETFL, O_NONBLOCK);

> Gibt es sowas unter Linux auch? Bisher bin ich am Pollen:
>
> num = read(USB, &ch, 1);
> if (num == 0) break;

Naja, wenn du nicht gerade wie oben auf Nonblocking umschaltest, dann 
kehrt read() erst zurück, wenn was angekommen ist.
Aber ja, du kannst auch ein Signal generieren lassen, was so eine Art 
Userspace-Interrupt ist. Aktivieren mit
1
fcntl (USB, F_SETFL, O_ASYNC);

Und dann kannst du mit signal() oder sigaction() einen Handler für das 
Signal SIGIO registrieren, der bei ankommenden Daten aufgerufen wird. 
Aber in der Regel braucht man sowas nicht (zumindest hab ich es noch nie 
gebraucht).

: Bearbeitet durch User
von Martin B. (ratazong)


Angehängte Dateien:

Lesenswert?

🐧 DPA 🐧 schrieb:
> Schau eventuell auch noch mit lsof nach, ob nicht noch was anderes
> darauf zugreift (z.B. modemmanager, gpsd, getty.service, etc.)

Unter ubuntu greift der Modemmanager nach dem Einstecken auf ttyACMx 
devices zu. das macht er nicht bei ttyUSBx devices. Das kann man aber 
verhindern durch eine rules Datei im Verzeichnis /etc/udev/rules.d.
Ich habe mal ein Beispiel angehängt (ist für stm32 usb stack)
Du müsstest nur die Zahlen ändern (vendor id und devid)
Die bekommt man lsusb heraus.
für das zweite Gerät im Beispielfile liefert lsusb bei mir

Bus 001 Device 044: ID 0483:5740 STMicroelectronics STM32F407

Die Rechte werden nach dem Einstecken des devices auf die Gruppe user 
gesetzt. d.h. Du kannst direkt aus dem userland darauf zugreifen. Das 
andere Verfahren mit add to dialout braucht man dann gar nicht mehr.
Der Eintrag ENV{ID_MM_DEVICE_IGNORE}="1" bewirkt, dass der Modemmanager 
nach dem Einstöpseln nicht mehr darauf zugreift.
funktioniert bei mir bei ubuntu16.04 und 18.04. Andere habe ich nicht 
getested.


Martin

von Lothar (Gast)


Lesenswert?

S. R. schrieb:
> wird noch schlimmer, wenn man Multithreading benutzt

Also unter Windows mit .NET funktioniert das ohne Probleme:

Private Sub Serial_DataReceived(ByVal sender As System.Object, ByVal e 
As System.IO.Ports.SerialDataReceivedEventArgs) Handles 
Serial.DataReceived
  Me.BeginInvoke(Sub() Serial_handler())
End Sub

Private Sub Serial_handler()
  If Serial_open = True
    Dim size As Integer = Serial.BytesToRead
    Dim buffer As Byte() = New Byte(size - 1) {}
    Serial.Read(buffer, 0, size)

von S. R. (svenska)


Lesenswert?

Lothar schrieb:
>> wird noch schlimmer, wenn man Multithreading benutzt
> Also unter Windows mit .NET funktioniert das ohne Probleme:

Das sind ja auch keine Signal-Handler nach POSIX... witzich, wa?

von Lothar (Gast)


Lesenswert?

S. R. schrieb:
> Das sind ja auch keine Signal-Handler nach POSIX

Ja - deswegen muss ich jetzt auch noch das hier machen. Wenn der uC mal 
genau während read() keinen Strom mehr hat:

https://stackoverflow.com/questions/10522277/how-can-i-implement-timeout-for-read-when-reading-from-a-serial-port-c-c

Das tcflush(USB, TCIFLUSH) macht auch manchmal merkwürdige Sachen - habe 
noch nicht den vollen Durchblick ...

von Lothar (Gast)


Lesenswert?

Scheint zu funktionieren:

struct pollfd pollevents;
pollevents.fd = fd;
pollevents.events = POLLIN;

if ((poll(&pollevents, 1, 500)) == 0)
  break;
else
  num = read(fd, &ch, 1);

von PittyJ (Gast)


Lesenswert?

tt2t schrieb:
> PittyJ schrieb:
>> Ich habe dann die Devices /dev/ttyS0, /dev/ttyS1
>
> Das ist aber eher untypisch, bei USB-Adaptern heissen die meistens
> /dev/ttyUSB0 ... oder /dev/ttyACM0 ...

Stimmt ist /dev/ttyUSB0.
Der arbeitet aber problemlos.

von Lothar (Gast)


Lesenswert?

PittyJ schrieb:
> /dev/ttyUSB0 ... arbeitet aber problemlos

Das ist wohl nicht so überraschend:

/dev/ttyUSB0 = USB-UART Konverter
/dev/ttyACM0 = USB-CDC Programm

von S. R. (svenska)


Lesenswert?

Lothar schrieb:
>> Das sind ja auch keine Signal-Handler nach POSIX
>
> Ja - deswegen muss ich jetzt auch noch das hier machen.
> Wenn der uC mal genau während read() keinen Strom mehr hat:

Du scheinst asynchrone Signalhandler nach POSIX mit einem 
Event-Dispatcher-System zu verwechseln.

Deswegen laufen die Antworten auf Stack Overflow auch alle auf "nimm 
poll() oder select()" hinaus, denn damit baut man sich sein Event-System 
erst auf. Dass die VB-Runtime in .net das für dich schon eingebaut hat, 
hat ja damit nichts zu tun.

> Das tcflush(USB, TCIFLUSH) macht auch manchmal merkwürdige
> Sachen - habe noch nicht den vollen Durchblick ...

Zugegeben, diese APIs haben tatsächlich was von schwarzer Magie bei den 
alten Römern. Ist aber den Geräten geschuldet, die bei der Einführung 
der APIs vorhanden waren. Windows hat da den Vorteil der spätereren 
Geburt.

Es hilft auch nicht besonders, dass die API an manchen Stellen 
wesentlich flexibler ist als die meisten modernen Geräte oder deren 
Treiber.

von Lothar (Gast)


Lesenswert?

pthread ist auch noch notwendig - da IO asynchron sein muss.

Und schon gibt es das nächste Problem:

delay() nutzt clock()

Und somit hat das was der synchrone Thread grade macht Auswirkung auf 
den asynchronen IO thread.

Das ist zwar eigentlich egal, denn der synchrone Thread übernimmt ja die 
Daten nur zu seinen Timings, aber irgendwie stört es mich :-)

Kann ein Thread seine "eigene" clock() bekommen?

von Rolf M. (rmagnus)


Lesenswert?

Lothar schrieb:
> pthread ist auch noch notwendig - da IO asynchron sein muss.

Warum?

> Und schon gibt es das nächste Problem:
>
> delay() nutzt clock()

Was für ein delay(), und wozu brauchst du das? Wieso nutzt es clock(), 
wenn das nicht geeignet ist?

von S. R. (svenska)


Lesenswert?

Lothar schrieb:
> pthread ist auch noch notwendig - da IO asynchron sein muss.

Nein. Wie kommst du darauf?

Lothar schrieb:
> Und somit hat das was der synchrone Thread grade macht
> Auswirkung auf den asynchronen IO thread.

Was bitte ist ein "synchroner Thread" und was ist ein "asynchroner 
Thread"?

Lothar schrieb:
> delay() nutzt clock()

Meine libc kennt kein delay(), dafür ist deren clock() die CPU-Zeit und 
nicht die reale Zeit. Du machst irgendwas sehr seltsames mit 
ungeeigneten Werkzeugen.

von Lothar (Gast)


Lesenswert?

S. R. schrieb:
> Was bitte ist ein "synchroner Thread" ... "asynchroner Thread"?

Stimmt - hatte ich nicht direkt erwähnt - die IO findet in einer GUI 
statt. Der Thread vom main() ist der synchrone Thread in dem die GUI 
Callbacks laufen. Das Problem ist nun, wenn der Nutzer z.B. ein Menü mit 
der Maus festhält, wird der synchrone Thread blocking, und IO da drin 
bleibt stehen. Deswegen wird, wenn der Nutzer im GUI auf Connect klickt, 
jetzt ein neuer asynchroner Thread gestartet, in dem dann die IO läuft, 
und von der GUI über globale Variablen abgeholt werden kann. Somit keine 
Mutex erforderlich:

int main(void) { ...
  while (RUNNING) {
    // event loop
    RUNNING = Fl::check(); } }

static void Connect_CB(Fl_Widget *, void *data) { ...
  pthread_t thread;
  pthread_create(&thread, NULL, VCOM_thread, NULL);
  VCOM_running = true; }

Wie schon geschrieben, in Windows ist das bei .NET alles schon 
"eingebaut"

von S. R. (svenska)


Lesenswert?

Lothar schrieb:
> Wie schon geschrieben, in Windows ist das bei .NET alles schon
> "eingebaut"

Solche Funktionalität ist in den größeren GUI-Toolkits normalerweise 
aber auch eingebaut.

Aber wenn du dein Problem gelöst hast, dann ist ja alles gut. :-)

von Lothar (Gast)


Lesenswert?

S. R. schrieb:
> Aber wenn du dein Problem gelöst hast, dann ist ja alles gut. :-)

Ja sieht jetzt stabil aus - delay() Problem auch gelöst - mal abwarten 
:-)

static void delay(double val, bool blocking) {
  // time-slice 10 msec
  for (int cnt=0; cnt<val*100; cnt++) {
    usleep(10000);
    // delay() called from synchronous thread ?
    if (! blocking)
        if (! (RUNNING = Fl::check())) return; } }

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.