Forum: Mikrocontroller und Digitale Elektronik LIN Telegramme mit Atmega und MCP2021 versenden


von Chris T. (chris0086)


Angehängte Dateien:

Lesenswert?

Hallo Leute,
ich benötige Hilfe bei der Umsetzung zum versenden von LIN Nachrichten 
per MCP2021 und einem Atmega.

Folgende Ausgangssituation:
Ich habe eine Abzugshaube deren Bedienteil als Master mit der 
Haubenelektronik kommuniziert.

Ich habe die Telegramme mit einem Peak LIN Adapter aufgezeichnet.
Es sind nur 4 unterschiedliche Telegramme die gesendet werden müssen.
In einem Telegramm steckt die einzustellende Stufe, der Rest ist immer 
gleich. (Ein Ausschnitt im Anhang)
Ich habe alle Zustände mitgeschnitten, sodass ich für jedes Telegramm 
auch die Checksumme habe. Muss also wahrscheinlich nicht mal so viel 
berechnen.
Ich muss nur die passenden Telegramme raus schicken.

Also die Hardware funktioniert erstmal denke ich, habe einfach per uart 
mal ein Hallo und eine Nachricht raus geschickt und den LA auf Async 
Serial gestellt. Auf der Lin Leitung wurde das was ich geschickt habe 
auch wieder gegeben (Siehe Anhang).

Jetzt geht es daran ein richtiges LIN Paket zu versenden, da wollte ich 
mal fragen ob jemand einen Codeschnipsel oder andere helfende Hinweise 
hat wie ich das am einfachsten angehe.

Vorrangig geht's um die richtige Umsetzung des Break-Signal und des 
Syncfeldes.
Ich habe bereits gelesen das man das mit dem Herabsetzen der Baudrate 
hinbekommt,
sowie Beitrag "Uart mit LIN Protokoll"
und
Beitrag "LIN Bus mit Atmega als Master (Botschaft senden)"
durchgelesen aber noch nicht wirklich weiter gekommen. Leider richten 
die Infos für einen Ansatz noch nicht aus :(

Kann mir da jemand helfen?

von Harald A. (embedded)


Lesenswert?

Mit dem Umstellen der Baudrate geht es tatsächlich sehr gut! Wenn man 
per UART-Hardware raussendet, ist die Herausforderung, den Zeitpunkt zum 
Umstellen der Baudrate auf "Originalwert" herauszufinden. Das "UART 
Empty" kommt ja unmittelbar nach Absenden, das wäre zu früh. Man braucht 
ein "UART Busy" Flag, aber auch diese Info kommt manchmal etwas zu früh. 
Ob man zu diesem Zeitpunkt (sofern man daraus einen Interrupt ableiten 
kann) schon die Baudrate wieder umstellen kann, zeigt ein Test. Wenn das 
alles zu kompliziert wird, kann man auch einfach den UART "vom Portpin 
nehmen", die entsprechende Zeit Low einfach per Bitbanging erzeugen und 
die Kontrolle wieder an den UART übergeben. Frisst natürlich Rechenzeit.
Danach ist alles sehr einfach, Sync, ID, LEN, Daten und Checksumme 
nacheinander raussenden, ganz so als ob man eine "normale" UART vor sich 
hat. Mit der Checksumme musst Du mal nachrechnen, wie die das gemacht 
haben. Es gab zwischen den offiziellen LIN Versionen leichte 
Unterschiede, bei Nicht-Automotive wird auch gerne mal was anderes 
verwendet - muss man mal etwas nachrechnen und beobachten. Vielleicht 
sind das auch statische Botschaften, dann kannst du einfach kopieren.

Noch etwas: Der Master beginnt IMMER mit dem Break, Sync, ID und LEN, 
bei einer Antwort vom Slave muss dieser den Rest des Frames "auffüllen". 
Aber wie gesagt, Nicht-Automotive weichen gerne mal von Standards ab und 
verwenden eigene Logiken. Vielleicht schaust Du mit einem Logik-Analyzer 
an jeweils RX/TX des Bausteins Master und Slave, wer was tatsächlich 
sendet.

von Teee (Gast)


Lesenswert?

Würde auch die Baudrate umschalten. Da LIN nur eine 1-Draht-Leitung hat, 
könnte man auch erst das Zeichen wieder zurücklesen und dann umschalten. 
Man müsste mal schauen, ob der Zeitpunkt dann passt. Dann einfach die 
Baudrate umschalten und 0x55 fürs Syncfiel, gefolgt von dem Rest, 
senden.

Harald A. schrieb:
> Noch etwas: Der Master beginnt IMMER mit dem Break, Sync, ID und LEN,
> bei einer Antwort vom Slave muss dieser den Rest des Frames

Genau, nur das LEN-Field habe ich noch nie gesehen. Das gibt es 
zumindestens nicht in der offizellen LIN-Spec.

von Rudolph (Gast)


Lesenswert?

Wenn Du einen ATTiny87/ATTiny167 verwenden magst, die haben einen 
LIN-USART.
Der kümmert sich selber um solche Dinge wie Break, Sync und Checksumme.
Man muss auch die Daten nicht selber Byte-für-Byte auf den LIN werfen.

Dazu gibt es auch eine passende Application-Note bei Atmel.

von Harald (Gast)


Lesenswert?

Teee schrieb:
> Genau, nur das LEN-Field habe ich noch nie gesehen. Das gibt es
> zumindestens nicht in der offizellen LIN-Spec.

Ups, sorry, Teee hat natürlich recht. Das war alles nur aus der 
Erinnerung geschrieben, daher der Fehler.

von Chris T. (chris0086)


Lesenswert?

Hallo Leute,
danke für die Antworten. Ich musste erstmal einen anderen LIN 
Transceiver bestücken der auch mit Pegeln ab 5V zurecht kommt.
Jetzt hab ich mal meinen ersten Befehl raus geschickt und habe folgende 
Frage.
Wie muss ich die Baudratenumstellung machen damit das der UART auch 
'frisst'?
mit folgender Routine sende ich derzeit um die Kommunikation zu testen 
(Aufnahme mit dem LA um die Telgegramme zu vergleichen):
1
void USART_SendS(uint8_t *werte, uint8_t rep)
2
{
3
  //receive_string[0] = 1;
4
  uint8_t i;
5
6
  UCSR0B &= ~(1<<TXEN0);  // disable UART
7
    UBRR0H = UBRR_VAL_UH >> 8;
8
    UBRR0L = UBRR_VAL_UH & 0xFF;
9
    UCSR0B |= 1<<TXEN0;  // enable UART
10
    USART_Transmit(0x00);
11
    //_delay_us(400);
12
    UCSR0B &= ~(1<<TXEN0);  // disable UART
13
    UBRR0H = UBRR_VAL_U0 >> 8;
14
    UBRR0L = UBRR_VAL_U0 & 0xFF;
15
    UCSR0B |= 1<<TXEN0;  // enable UART
16
    USART_Transmit(0x55);
17
18
  for(i=0;i<rep;i++)
19
  {
20
    USART_Transmit(werte[i]);
21
  }
22
}

egal was ich für eine halbe Baudrate einstelle am LA ist der Headerbreak 
immer gleich lang (und das Telegramm wird vom LA nicht als Lin Telegramm 
erkannt), wenn ich natürlich das _delay_us einkommentiere passt die 
Länge und das Telegramm wird komplett als Lin Telegramm erkannt. Aber 
auf solche Methoden würde ich gerne verzichten.

von Rudolph R. (rudolph)


Lesenswert?

Einmal reicht schon und es muss auch nicht die halbe Baudrate sein, die 
muss nur niedrig genug sein, damit 8 Null-Bits bei nomineller Bitrate 
wie 13 Null-Bits aussehen. Also für 19200 reichen <11800.

Aber auf das Ende der Übertragung sollte man schon warten.

von Chris T. (chris0086)


Lesenswert?

Hallo Rudolph,
vielen Dank für deine Antwort.

Also auf das Ende der Übertragung warte ich doch eigentlich hier:
1
void USART_Transmit( char data )
2
{
3
  /* Wait for empty transmit buffer */
4
  while ( !( UCSR0A & (1<<UDRE0)) )
5
    ;
6
  /* Put data into buffer, sends the data */
7
  UDR0 = data;
8
}

oder meinst du etwas anderes?

von Rudolph (Gast)


Lesenswert?

Der Schnipsel hat gefehlt...

Du wartest so nicht wirklich auf das Ende der Übertragung.
Der UART ist mit einem Byte gepuffert in Sende-Richtung und wenn Du mit 
UDRE0 prüfst, dann ist das beim ersten zu sendenden Byte quasi 
unmittelbar wieder auf 1.
Zudem prüfst Du vor der Übertragung, das ist dann zusätzlich falsch bei 
dem Programm und würde auch schief gehen wenn das Warten an sich richtig 
wäre.

Ein
while ( !( UCSR0A & (1<<TXC0)) );
nach dem
USART_Transmit(0x00);

dürfte einen kräftigen Unterschied machen.

von Chris T. (chris0086)


Lesenswert?

Danke für die Korrektur :)

Wenn man darüber nachdenkt hätte man auch slebst darauf kommen müssen.
Also das erste Telegramm läuft jetzt soweit und wird auch von meinem LIN 
Dongle erkannt.
Jetzt habe ich aber das Problem, dass das erste Telegramm kommplett 
durch geht und das bei den nachfolgenden Telegrammen die 
Baudratenumstellung irgendwie nicht mehr gemacht wird. Der Headerbreak 
ist zu kurz und die Telegramme werden nichtmehr als solche erkannt. 
Dachte irgendwie das mir der Timer dazwischen funkt und hab mal noch cli 
und sei eingefügt aber bringt nix.
Hier mal der Code
1
#define T0_ON      TIMSK |= (1<<TOIE0)
2
#define T0_OFF      TIMSK &= ~(1<<TOIE0); TCNT0 = 0
3
4
5
void USART_SendS(uint8_t *werte, uint8_t rep)
6
{
7
  //receive_string[0] = 1;
8
  uint8_t i;
9
  cli();
10
  UCSR0B &= ~(1<<TXEN0);  // disable UART
11
    UBRR0H = UBRR_VAL_UH >> 8;
12
    UBRR0L = UBRR_VAL_UH & 0xFF;
13
    UCSR0B |= 1<<TXEN0;  // enable UART
14
    USART_Transmit(0x00);
15
    while ( !( UCSR0A & (1<<TXC0)) );
16
17
    UCSR0B &= ~(1<<TXEN0);  // disable UART
18
    UBRR0H = UBRR_VAL_U0 >> 8;
19
    UBRR0L = UBRR_VAL_U0 & 0xFF;
20
    UCSR0B |= 1<<TXEN0;  // enable UART
21
    USART_Transmit(0x55);
22
23
  for(i=0;i<rep;i++)
24
  {
25
    USART_Transmit(werte[i]);
26
  }
27
  sei();
28
}
29
30
31
ISR(TIMER0_OVF_vect)
32
{
33
  sendframe_status = 1;
34
}
35
36
37
int main(void)
38
{
39
40
    //USART initialisieren
41
42
  //USART0
43
    UBRR0H = UBRR_VAL_U0 >> 8;
44
    UBRR0L = UBRR_VAL_U0 & 0xFF;
45
    //USART1
46
    UBRR1H = UBRR_VAL_KNX >> 8;
47
    UBRR1L = UBRR_VAL_KNX & 0xFF;
48
    //USART0
49
    UCSR0B |= (1<<TXEN0);                // UART TX einschalten
50
    UCSR0B |= (1<<RXEN0);
51
    UCSR0B |= (1<<RXCIE0);
52
    UCSR0C |= (1<<URSEL0)|(1<<UCSZ00)| (1<<UCSZ01);    // Asynchron 8N1
53
54
  //USART1
55
56
    UCSR1B |= (1<<TXEN1);                // UART TX einschalten
57
    UCSR1B |= (1<<RXEN1);
58
    UCSR1B |= (1<<RXCIE1);
59
    UCSR1C |= (1<<URSEL1)|(1<<UCSZ10)| (1<<UCSZ11)| (1<<UPM11);    // Asynchron 8N1 even
60
61
    //Timer 1 konfigurieren
62
    TCCR1B |= (1<<CS12) | (1<<CS10);
63
    TCNT1 = T_PRELOAD;
64
65
    //Timer 0 konfigurieren
66
    TCCR0 |= (1<<CS02) | (1<<CS00);
67
68
    sei();
69
70
    UART1_Transmit('H');
71
    UART1_Transmit('a');
72
    UART1_Transmit('l');
73
    UART1_Transmit('l');
74
    UART1_Transmit('o');
75
76
    sendstr[0] = werte[0].id;
77
    sendstr[1] = werte[0].byte0;
78
    sendstr[2] = werte[0].byte1;
79
    sendstr[3] = werte[0].byte2;
80
    sendstr[4] = werte[0].byte3;
81
    sendstr[5] = werte[0].byte4;
82
    sendstr[6] = werte[0].checksum;
83
    
84
    USART_SendS(sendstr,7);
85
86
    T0_ON;
87
  while(1)
88
  {
89
    if(sendframe_status >= 1)
90
    {
91
      T0_OFF;
92
      USART_SendS(sendstr,7);
93
      sendframe_status =0;
94
      T0_ON;
95
    }
96
}

von Chris T. (chris0086)


Lesenswert?

Habs jetzt noch mal mit dem Delay probiert.
Ich muss das schon auf 700 us stellen damit der Break lang genug ist.
1
void USART_SendS(uint8_t *werte, uint8_t rep)
2
{
3
  //receive_string[0] = 1;
4
  uint8_t i;
5
6
  UCSR0B &= ~(1<<TXEN0);  // disable UART
7
    UBRR0H = UBRR_VAL_UH >> 8;
8
    UBRR0L = UBRR_VAL_UH & 0xFF;
9
    UCSR0B |= (1<<TXEN0);  // enable UART
10
    USART_Transmit(0x00);
11
    while ( !( UCSR0A & (1<<TXC0)) );
12
    _delay_us(700);
13
    UCSR0B &= ~(1<<TXEN0);  // disable UART
14
    UBRR0H = UBRR_VAL_U0 >> 8;
15
    UBRR0L = UBRR_VAL_U0 & 0xFF;
16
    UCSR0B |= (1<<TXEN0);  // enable UART
17
    USART_Transmit(0x55);
18
19
  for(i=0;i<rep;i++)
20
  {
21
    USART_Transmit(werte[i]);
22
  }
23
24
}

Ich seh mal wieder nicht den Fehler

von Rudolph (Gast)


Lesenswert?

So richtig durchschaue ich den Code nicht, der ist aber auch immer noch 
nicht vollständig und somit compilierbar.

Das mit dem Timer-Overflow ist auch ein seltsames Konstrukt.
Mit "T0_OFF" läuft der Timer übrigens weiter, wie lang ist dann die Zeit 
zwischen "T0_ON" und Interrupt?

Am Ende sollte doch eigentlich raus kommen, dass die Botschaften gemäß 
des Schedule-Tables sauber getimed raus gehen, zum Beispiel alle 10 ms 
eine Botschaft.

Auf einem 90CAN habe ich das mal mit dem UART-Sende-Interrupt umgesetzt.

Scheduler...Arrays füllen...
if(sent == 0)...
UBRR0 = 78; // 12660 Baud @ 16 MHz - Sync langsamer übertragen
UDR0 = 0x00; // Übertragung anschieben!
sent = 1;
1
ISR (USART0_TX_vect) // UART Sende-IRQ
2
{
3
4
 if(sent == 1)
5
 {
6
  UBRR0 = 51; // 19200 Baud @ 16 MHz
7
  UDR0 = 0x55; // Sync
8
  sent++;
9
  return;
10
 }
11
12
 if(send_message_number == 0)
13
 {
14
  if(sent > 11) // break+sync+id+8+checksumme
15
  {
16
   sent = 0;
17
   return;
18
  }
19
  UDR0 = lin_data_0[sent-2]; // ID, Datenbytes 1..8, Checksumme der Botschaft
20
 }
21
22
 if(send_message_number == 1)
23
 {
24
  if(sent > 2) // break+sync+id
25
  {
26
   return; // nichts machen, sent muss durch den Empfangs-IRQ zurückgesetzt werden
27
  }
28
  UDR0 = lin_data_1[sent-2]; // ID
29
 }

Na so in der Art, den originalen und vollständigen Code kann ich nicht 
posten.

Auf einem ATMega16M1 oder ATTiny167 ist das durch den LIN-USART alles 
viel einfacher.

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.