Forum: Compiler & IDEs UART RS485 Problem


von DrWright (Gast)


Lesenswert?

Hallo, ich möchte eine bidirektionale Kommunikation von einem Mega328 
mit einem Mega 168 herstellen.
Als Treiber benutze ich einen SN75176, meine Sprache ist C.

Nun habe ich festgestellt, das ich zwischen den Umschalten von Senden 
auf Empfang einen Delay einfügen muss, da sonst Bytes abgeschnitten 
werden.
Der UART Code inclusive Delays ist auf beiden µC gleich.

Die Kommunikation beobachte ich über einen RS485 auf RS232 Wandler auf 
meinem Laptop mit HTERM. Dort sehe ich auch, das zuerst alles 
funktioniert, und nach einer kleinen Laufzeit (wärme?) überträgt er 
nurnoch Mist. Ich vermute das er nicht schnell genug auf Empfang 
wechselt, und deshalb Bits abgeschnitten werden.

Ich empfange über eine ISR in einen Ringpuffer, was auch ohne weiteres 
funktioniert.
1
#define BAUD 9600UL    // Baudrate 
2
3
#define DIRBIT_DDR DDRD
4
#define DIRBIT_PORT PORTD
5
#define DIRBIT_DDI PIND
6
#define DIRBIT_PIN PD2
7
8
// Berechnungen
9
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)   // clever runden
10
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))     // Reale Baudrate
11
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) // Fehler in Promille, 1000 = kein Fehler.
12
 
13
#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
14
  #error Systematischer Fehler der Baudrate grösser 1% und damit zu hoch! 
15
#endif 
16
17
#define buffersize 16
18
19
void SetTempHum(uint8_t tmp, uint8_t hum);
20
void SetWaterSim(uint8_t status, uint8_t pps);
21
void SendWaterStatus(void);
22
void SaveToEEPROM(void);
23
24
uint8_t txd_buffer[buffersize] = "",txd_buffer_cnt = 0,txd_do_cnt = 0;
25
26
void uart_init(void)
27
{
28
  UBRR0H = UBRR_VAL >> 8;
29
  UBRR0L = UBRR_VAL & 0xFF;
30
  UCSR0C = (1<<UCSZ01)|(1<<UCSZ00); // Asynchron 8N1 
31
  UCSR0B |= (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);  // UART RX, TX und RX Interrupt einschalten
32
  DIRBIT_DDR |= (1<<DIRBIT_PIN); // DIRBIT auf Ausgang
33
  DIRBIT_PORT &= ~(1<<DIRBIT_PIN); // Auf Empfang schalten
34
}
35
36
void uart_txd_Do(void)
37
{
38
  while (txd_do_cnt != txd_buffer_cnt)  // Daten zum Senden vorhanden?
39
  {
40
    if (UCSR0A & (1<<UDRE0)) // Sendepuffer bereit?
41
    {
42
      if (!(DIRBIT_DDI & (1<<DIRBIT_PIN)))
43
      {
44
        _delay_us(150);
45
        DIRBIT_PORT |= (1<<DIRBIT_PIN); // DIRBIT auf Senden
46
        _delay_us(100);        
47
      }      
48
      UDR0 = txd_buffer[txd_do_cnt];  
49
      while (!(UCSR0A & (1<<TXC0)));  //  Warte bis Zeichen gesendet ist  
50
      UCSR0A |= (1<<TXC0);  //  Gesendet Bit löschen      
51
      txd_do_cnt++;
52
      if (txd_do_cnt == buffersize){txd_do_cnt = 0;}
53
    }
54
  }
55
  
56
  if (DIRBIT_DDI & (1<<DIRBIT_PIN))  // Empfangsbit schon umgeschaltet?
57
  {  
58
    _delay_us(100);      
59
    DIRBIT_PORT &= ~(1<<DIRBIT_PIN); // Auf Empfang schalten
60
    _delay_us(150);
61
  }
62
}
63
64
uint8_t uart_SendByte(uint8_t in)
65
{  
66
  txd_buffer[txd_buffer_cnt] = in;  
67
  txd_buffer_cnt++;
68
  if (txd_buffer_cnt == buffersize){txd_buffer_cnt = 0;}
69
  return in;
70
}

von DrWright (Gast)


Lesenswert?

Achso, ich verwende bei beiden µC denselben Quarz von 9830400 und die 
Fuses sind auch richtig gesetzt.

von uli (Gast)


Lesenswert?

Nimm den txcoplete interrupt!

von (prx) A. K. (prx)


Lesenswert?

Ob per Interrupt oder nicht - jedenfalls muss man auf TXC warten, bevor 
man die Richtung umschaltet. Nicht schon, wenn das letzte Byte im Puffer 
gelandet ist.

: Bearbeitet durch User
von DrWright (Gast)


Lesenswert?

Es wartet ja schon bis der TXC gesetzt ist, bevor es weitergeht. Das ist 
das Bit was anzeigt das das Byze aus dem Schieberegister raus ist.

von (prx) A. K. (prx)


Lesenswert?

Stimmt, allerdings wäre das an anderer Stelle besser untergebracht.

von DrWright (Gast)


Lesenswert?

A. K. schrieb:
> Stimmt, allerdings wäre das an anderer Stelle besser
> untergebracht.

Ich bin den Ablauf schritt für schritt durchgegangen und sehe nicht, wo 
er vorzeitig auf Senden umschaltet. Wo meinst du denn wo es besser 
untergebracht wäre?

von (prx) A. K. (prx)


Lesenswert?

DrWright schrieb:
> Wo meinst du denn wo es besser untergebracht wäre?

Üblich ist es nach der while-Schleife vor der Richtungsumschaltung. TXC 
wird erst gesetzt, wenn Puffer und Schieberegister leer sind.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Wird der RX Ausgang des Transceivers mit abgeschaltet? Wenn ja: Ist 
sichergestellt, dass der RX Pin nicht floatet?

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Im gezeigten Code wird der Tx-Puffer Daten verlieren, wenn die Bytes 
schneller dort landen als sie übertragen werden. Wird das im nicht 
gezeigten Code irgendwie verhindert/vermieden?

von DrWright (Gast)


Lesenswert?

Ich habe keine Pullups oder einen 120 Ohm Abschlusswiderstand eingebaut. 
Hab mitm Oszi nachgemessen, und der Ausgang ist stabil, es floatet also 
nichts. Der TX Buffer ist groß genug, ich schicke nur alle 2 sek 4 byte.

Habe den Code daraufhin so geändert.
1
void uart_txd_Do(void)
2
{
3
  while (txd_do_cnt != txd_buffer_cnt)  // Daten zum Senden vorhanden?
4
  {
5
    if (UCSR0A & (1<<UDRE0)) // Sendepuffer bereit?
6
    {
7
      if (!(DIRBIT_DDI & (1<<DIRBIT_PIN)))
8
      {
9
        _delay_ms(5);
10
        DIRBIT_PORT |= (1<<DIRBIT_PIN); // DIRBIT auf Senden
11
        _delay_us(100);        
12
      }      
13
      UDR0 = txd_buffer[txd_do_cnt];            
14
      txd_do_cnt++;
15
      if (txd_do_cnt == buffersize){txd_do_cnt = 0;}
16
    }
17
  }
18
  
19
  if (DIRBIT_DDI & (1<<DIRBIT_PIN))  // Empfangsbit schon umgeschaltet?
20
  {  
21
    while (!(UCSR0A & (1<<TXC0)));  //  Warte bis Zeichen gesendet ist  
22
    UCSR0A |= (1<<TXC0);  //  Gesendet Bit löschen
23
    
24
    _delay_us(150);      
25
    DIRBIT_PORT &= ~(1<<DIRBIT_PIN); // Auf Empfang schalten
26
    _delay_us(100);
27
  }
28
}
Das hat leider auch nichts gebracht. Das merkwürdige ist ja das es 
einige Zeit funktioniert und dann langsam falsche bytes geschickt 
werden.
Ich versuche trotzdem mal Pullup/Down widerstände Einzulöten. Mal sehen 
ob es was bringt.

von DrWright (Gast)


Lesenswert?

Habe nun 2 kOhm Widerstände als Terminierung genommen. Leider hat sich 
das Problem damit nicht gelöst. Es scheint mir ein thermiches Problem zu 
sein. Beim ersten Test wenns noch kalt ist gehts ohne Probleme und nach 
einiger Zeit tritt der Fehler dann auf. Im HTERM kann ich beobachten das 
er bei manchen Bytes das erste Bit High setzt, obwohl es Low sein 
sollte.

von DrWright (Gast)


Lesenswert?

So, ich habe das Problem beheben können indem ich erstmal alle Delays 
entfernte und getestet habe welche überhaupt notwendig sind.
Es reicht ein 5ms Delay vor dem ersten Umschalten auf Sendebetrieb.
1
void uart_txd_Do(void)
2
{
3
  while (txd_do_cnt != txd_buffer_cnt)  // Daten zum Senden vorhanden?
4
  {
5
    if (UCSR0A & (1<<UDRE0)) // Sendepuffer bereit?
6
    {
7
      if (!(DIRBIT_DDI & (1<<DIRBIT_PIN)))
8
      {
9
        _delay_ms(5);        
10
        DIRBIT_PORT |= (1<<DIRBIT_PIN); // DIRBIT auf Senden
11
      }  
12
      UCSR0A |= (1<<TXC0);  //  Gesendet Bit löschen      
13
      UDR0 = txd_buffer[txd_do_cnt];        
14
      txd_do_cnt++;
15
      if (txd_do_cnt == buffersize){txd_do_cnt = 0;}
16
    }
17
  }
18
  
19
  if (DIRBIT_DDI & (1<<DIRBIT_PIN))  // Empfangsbit schon umgeschaltet?
20
  {    
21
    while (!(UCSR0A & (1<<TXC0)));  //  Warte bis Zeichen gesendet ist  
22
    UCSR0A |= (1<<TXC0);  //  Gesendet Bit löschen    
23
    DIRBIT_PORT &= ~(1<<DIRBIT_PIN); // Auf Empfang schalten
24
  }
25
}
26
27
uint8_t uart_SendByte(uint8_t in)
28
{  
29
  txd_buffer[txd_buffer_cnt] = in;  
30
  txd_buffer_cnt++;
31
  if (txd_buffer_cnt == buffersize){txd_buffer_cnt = 0;}
32
  return in;
33
}
Ganz wichtig ist auch das TX Complete Bit.
Vielen Dank für die Hilfe

von DrWright (Gast)


Lesenswert?

Ich melde mich nach einiger Zeit mal wieder, nachdem ich mit der letzen 
Version Probleme hatte, hier nun eine die läuft.
1
void uart_txd_Do(uint8_t overflow)
2
{
3
  while (txd_do_cnt != txd_buffer_cnt || overflow)  // Daten zum Senden vorhanden?
4
  {
5
    if (UCSR0A & (1<<UDRE0)) // Sendepuffer bereit?
6
    {
7
      if (!(DIRBIT_DDI & (1<<DIRBIT_PIN)))
8
      {        
9
        _delay_us(50);        
10
        DIRBIT_PORT |= (1<<DIRBIT_PIN); // DIRBIT auf Senden
11
      }  
12
      UCSR0A |= (1<<TXC0);  //  Gesendet Bit löschen        
13
      UDR0 = txd_buffer[txd_do_cnt++];
14
      if (txd_do_cnt == buffersize_rxd){txd_do_cnt = 0;}  
15
      overflow = 0;      
16
    }  
17
  
18
    if (DIRBIT_DDI & (1<<DIRBIT_PIN))  // Empfangsbit schon umgeschaltet?
19
    {    
20
      while (!(UCSR0A & (1<<TXC0)));  //  Warte bis Zeichen gesendet ist  
21
      DIRBIT_PORT &= ~(1<<DIRBIT_PIN); // Auf Empfang schalten
22
      UCSR0A |= (1<<TXC0);  //  Gesendet Bit löschen    
23
    }
24
  }
25
}
26
27
uint8_t uart_SendByte(uint8_t in)
28
{  
29
  txd_buffer[txd_buffer_cnt] = in;  
30
  txd_buffer_cnt++;
31
  if (txd_buffer_cnt == buffersize_rxd){txd_buffer_cnt = 0;}  
32
  if (txd_do_cnt == txd_buffer_cnt)  //Bei Puffer überlauf, leeren veranlassen
33
  {
34
    uart_txd_Do(1);
35
  }    
36
  return in;
37
}

Ich habe festgestellt, das ich nach jedem Byte ein Umschalten auf 
Empfang notwendig ist, da sonst ein Frame Error vom empfangenden Byte 
erzeugt wird.
(Ich kommuniziere mit einer Steuerung deren Code in Assembler 
geschrieben wurde, das ACK kommt sehr schnell).

von Frank K. (fchk)


Lesenswert?

Beschalte doch den 485-Transceiver so, dass Du immer empfängst, auch 
Deine eigenen Daten. Erstens weißt Du somit genau, wenn das Byte 
gesendet worden ist, und Du weißt genau, dass das Byte korrekt gesendet 
worden ist und nicht irgendwer anders den Bus belegt hat.

fchk

von Stefan (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

DrWright schrieb:
> Ich habe festgestellt, das ich nach jedem Byte ein Umschalten auf
> Empfang notwendig ist, da sonst ein Frame Error vom empfangenden Byte
> erzeugt wird.

sollte dies wirklich der Fall sein dann hast du was Grundlegendes falsch 
gemacht!

Ich hab jetzt alle vorhergehenden Mails überflogen und ich denke dein 
Problm ist weniger ein Timing-Problem als vielmehr ein Problem der 
"Stabilität".

Kurz: Du hast keine stabilen Verhältnisse in deiner Schaltung.

Ob dies der Fall ist oder nicht kannst du ganz einfach testen. Ein 
reines Umschalten von Empfang auf Senden und umgekehrt darf keinen 
Zustandswechsel auf den Leitungen DI, DO, A und B verursachen.
Ein Zustandswechsel würde Dir unweigerlich ein Startbit (und damit ein 
Zeichen) vorgaukeln.
Der Ruhezustand für DI und DO muss high sein und für A/B muss gelten 
A-B>200mV.
Damit der Ruhezustand auf diesen Leitungen sicher ist schlage ich dir 
obige Schaltung vor die du auch in diversen Foren wiederfindest.

von DrWright (Gast)


Lesenswert?

Das denke ich langsam auch, R1 und R3 habe ich bereits, auf die 
Terminierung habe ich verzictet, weil die Leitung grad mal 1 m lang ist. 
Den Pullup an RO habe ich noch nicht. Ich werde Testhalber mal die 
Richtung umschalten lassen im sekundentakt und dann einfach mal mitm 
Oszi Messen.

von DrWright (Gast)


Lesenswert?

OK, da stimmt definitiv was nicht. Ich schalte nur um und der 
USART_RX_vect Interrupt löst sofort aus.

von DrWright (Gast)


Lesenswert?

Ich denke das sollte es gewesen sein, vielen dank für die Info.

Ich hab jetz auf RXD den Pullup eingeschaltet, ohne Pullup ist der von 
0,8-4,6V beim Umschalten gefloatet, mit aktiven Pullup bleibt er starr 
bei 4,7V stehen.

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.