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?
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.
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.
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.
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.
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.
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.
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?
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.
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 | }
|
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.

