Forum: Mikrocontroller und Digitale Elektronik Raspberry Pi: UART - Blocking Write/Transmission Complete


von Matthias (Gast)


Lesenswert?

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_t data = 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

: Verschoben durch Moderator
von Matthias (Gast)


Angehängte Dateien:

Lesenswert?

Sorry, da ging die Dateiendung der Grafiken vergessen.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

@ Matthias: "Projekte und Code" ist für fertige Projekte gedacht. 
Siehe Screenshot von der Überschrift...

von Peter II (Gast)


Lesenswert?

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.

von suri (Gast)


Lesenswert?

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?

von Matthias (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Matthias (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Matthias (Gast)


Lesenswert?

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.

von suri (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Matthias (Gast)


Angehängte Dateien:

Lesenswert?

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_t lsr;
2
uint8_t data = 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

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.