UART RS485 Problem

Gast #3455712
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
}
Gast #3456806
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.
Gast #3456866
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.
Gast #3459880
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
Gast #3553303
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).
#3553688
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
Gast #3554806
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.
Angehängte Dateien:
Gast #3555105
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.
Gast #3555157
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren