Hallo,
ich möchte über die UART (/dev/ttyAMA0) des Raspberry Pi (Modell B v2.0)
ein Byte senden.
Konfiguration:
1
rpiFd=open(FILE_PATH,O_RDWR|O_NOCTTY);
2
3
if(tcgetattr(rpiFd,&rpiOpt)!=0)
4
5
// set baud rate
6
// -------------
7
cfsetispeed(&rpiOpt,B9600);
8
cfsetospeed(&rpiOpt,B9600);
9
10
// control options
11
// ---------------
12
rpiOpt.c_cflag&=~PARENB;
13
rpiOpt.c_cflag&=~PARODD;
14
rpiOpt.c_cflag&=~CSTOPB;
15
rpiOpt.c_cflag&=~CSIZE;
16
rpiOpt.c_cflag|=CS8;
17
rpiOpt.c_cflag|=CLOCAL;
18
rpiOpt.c_cflag|=CREAD;
19
20
// local options
21
// -------------
22
rpiOpt.c_lflag&=~ICANON;
23
rpiOpt.c_lflag&=~(ECHO|ECHOE);
24
rpiOpt.c_lflag&=~ISIG;
25
26
// input options
27
// -------------
28
rpiOpt.c_iflag|=INPCK;
29
rpiOpt.c_iflag&=~IGNPAR;
30
rpiOpt.c_iflag|=PARMRK;
31
rpiOpt.c_iflag&=~ISTRIP;
32
rpiOpt.c_iflag&=~IXON;
33
rpiOpt.c_iflag&=~IXOFF;
34
rpiOpt.c_iflag&=~IXANY;
35
rpiOpt.c_iflag&=~IGNBRK;
36
rpiOpt.c_iflag&=~BRKINT;
37
rpiOpt.c_iflag&=~INLCR;
38
rpiOpt.c_iflag&=~IGNCR;
39
rpiOpt.c_iflag&=~ICRNL;
40
rpiOpt.c_iflag&=~IUCLC;
41
rpiOpt.c_iflag|=IMAXBEL;
42
43
// output options
44
// --------------
45
rpiOpt.c_oflag&=~OPOST;
46
47
// control characters
48
// ------------------
49
rpiOpt.c_cc[VMIN]=0;
50
rpiOpt.c_cc[VTIME]=10;
51
52
tcflush(rpiFd,TCIOFLUSH);
53
tcsetattr(rpiFd,TCSANOW,&rpiOpt);
Code zum Senden:
1
uint8_tdata=0x00;
2
3
bcm2835_gpio_write(RPI_V2_GPIO_P1_11,HIGH);
4
write(rpiFd,&data,1);
5
// tcdrain(rpiFd);
6
bcm2835_gpio_write(RPI_V2_GPIO_P1_11,LOW);
Problem:
Ich möchte beim Senden des Bytes solange blockieren, bis das Byte
tatsächlich physikalisch über die UART gesendet wurde (inkl. Stoppbit).
Um dies zu überprüfen, ziehe ich einen GPIO vor dem Funktionsaufruf auf
HIGH und im Anschluss wieder auf LOW. Das Ergebnis ist im Anhang
(write.png) zu sehen. Oben sieht man die Übertragung des Bytes über die
UART, unten den GPIO. Wie zu erkennen ist, blockiert die write-Funktion
nicht einmal so lange, bis das Startbit übertragen wurde.
Als vermeintliche Lösung bin ich auf die Funktion tcdrain() gestoßen.
Diese soll warten, bis alle Daten gesendet wurden. Damit erhalte ich
dann das Ergebnis im Anhang (write_tcdrain.png). Wie zu erkennen ist,
blockiert tcrdain() in der Tat, allerdings um ein Vielfaches länger, als
es müsste - etwa 12ms anstatt ca. 1ms.
Frage:
Gibt es eine Möglichkeit nach dem Schreiben nur genau solange zu
blockieren, bis das Senden des Bytes vollständig abgeschlossen ist? (Bei
einem uC würde man einfach ein TX oder BUSY-Flag pollen oder auf einen
entsprechenden Interrupt warten...)
Danke im Voraus!
Gruß,
Matthias
Matthias schrieb:> Frage:> Gibt es eine Möglichkeit nach dem Schreiben nur genau solange zu> blockieren, bis das Senden des Bytes vollständig abgeschlossen ist?
ich hatte so ein ähnliches Problem auch schon. Aber ich habe dafür auch
keine schöne Lösung gefunden. Ich habe mir anhand der Anzahl der Bytes
und der Baudrate die Übertragungszeit ausgerechnet und vor dem Start der
Übertragung die Systemzeit geholt. und dann die Differenz zwischen den
beiden Zeiten gewartet.
Bei mir ging es aber um einen USB-Serial Adapter diese hat recht viel
Puffer gehabt.
Matthias schrieb:> Frage:> Gibt es eine Möglichkeit nach dem Schreiben nur genau solange zu> blockieren, bis das Senden des Bytes vollständig abgeschlossen ist? (Bei> einem uC würde man einfach ein TX oder BUSY-Flag pollen oder auf einen> entsprechenden Interrupt warten...)
Welchen Hintergrund hat das? Warum will man so ein Verhalten?
@Peter II:
Ja, so etwas habe ich mir auch schon überlegt. Ist natürlich - wie du
schon selbst gesagt hast - keine "schöne" und robuste Lösung.
Ich hätte auch nicht gedacht, dass es so schwer sein könnte, so eine
doch relativ einfache Information, die die UART-Hardware ja direkt
bereitstellt, durch das Betriebssystem bis in den eigenen Code
weiterzugeben...
Wenn man sich den UART-Treiber im Linux-Kernel anschaut
(linux/drivers/tty/serial/amba-pl011.c), dann gibt es da tatsächlich
sogar eine Funktion
1
pl011_console_write()
, die eigentlich genau das macht, was ich brauche - Byte an die UART
senden und dann das UART01x_FR_BUSY Flag pollen, bis das Byte versendet
wurde.
Leider wird diese Funktion beim Senden gar nicht benutzt und ich weiß
auch nicht, wie man die serielle Schnittstelle dafür konfigurieren
müsste.
@suri:
Der Hintergrund ist der Folgende: Nach dem Versenden des Bytes will ich
die UART schnellst möglichst umkonfigurieren. Und damit ich
umkonfigurieren kann muss ich sicher wissen, dass die vorangegangene
Operation, also das Versenden des Bytes, sicher abgeschlossen ist.
suri schrieb:> Welchen Hintergrund hat das? Warum will man so ein Verhalten?
Abschalten eines RS485 transceivers?
Abfragen lässt es sich jedenfalls. Im FR Register der zuständigen UART
gibts ein BUSY Bit.
A. K. schrieb:> Abfragen lässt es sich jedenfalls. Im FR Register der zuständigen UART> gibts ein BUSY Bit.
Ja genau. Die Hardware gibt es her. Jetzt suche ich noch eine
Möglichkeit diese Info auch oberhalb des Betriebssystems zu nutzen
Matthias schrieb:> Ja genau. Die Hardware gibt es her. Jetzt suche ich noch eine> Möglichkeit diese Info auch oberhalb des Betriebssystems zu nutzen
Man kann sich vom Anwenderprogramm aus direkt einen Zugriff auf die
Register des SOC verschaffen.
A. K. schrieb:> Man kann sich vom Anwenderprogramm aus direkt einen Zugriff auf die> Register des SOC verschaffen.
Um die Software portabel und hardware-unabhängig zu halten, möchte ich
genau das vermeiden! Ich dachte man könnte die gewünschte Information
auch über standardisierte Schnittstellen wzb. termios oder system calls
erfragen.
Matthias schrieb:> @suri:> Der Hintergrund ist der Folgende: Nach dem Versenden des Bytes will ich> die UART schnellst möglichst umkonfigurieren. Und damit ich> umkonfigurieren kann muss ich sicher wissen, dass die vorangegangene> Operation, also das Versenden des Bytes, sicher abgeschlossen ist.
Ok, das ist ein Grund. RS485-Transveiver umschalten auch.
Ich würde mit dem Anliegen incl. Begründung mal in die Kernel Mailing
Liste gehen. Allerdings ist ein Zeitverhalten durch den Kernel nicht
garantiert.
Oder da nachfragen ob die starke Verzögerung bei tcrdain() gewollt ist.
Matthias schrieb:> Ich dachte man könnte die gewünschte Information> auch über standardisierte Schnittstellen wzb. termios oder system calls> erfragen.
Es scheint dafür keinen Interrupt zu geben, was die Sache für den Kernel
nicht eben erleichtert.
Danke für die Rückmeldungen!
Ich habe soeben eine für mich ausreichend gute Lösung gefunden:
Mit der Betriebssystemfunktion ioctl() kann ich mir das Line Status
Register (LSR) der UART auslesen lassen. Dieses enthält ein Flag
(TIOCSER_TEMT) welches angibt, ob sich noch physikalisch Bits im
Ausgangsshiftregister der UART befinden. Dieses kann ich nun mit meinem
Code wunderbar pollen:
1
uint8_tlsr;
2
uint8_tdata=0x00;
3
4
bcm2835_gpio_write(RPI_V2_GPIO_P1_11,HIGH);
5
write(rpiFd,&data,1);
6
7
do
8
{
9
ioctl(fd,TIOCSERGETLSR,&lsr);
10
}
11
while(!(lsr&TIOCSER_TEMT));
12
13
bcm2835_gpio_write(RPI_V2_GPIO_P1_11,LOW);
Das liefert mir genau das gewünschte Verhalten ohne, dass ich selbst mit
Adressen direkt auf die UART-Hardwareregister zugreifen muss. Die
Schleife wird immer sauber sofort nach dem Stoppbit verlassen (siehe
Anhang)!
Grüße,
Matthias