Hallöchen ich hätte mal eine Frage und zwar möchte ich mittels des ATMega 644 ein DCC-Protokoll erzeugen und weiss allerdings noch nicht so wirklich, wie ich dieses realisieren soll. Würde testweise einfach gern etwas in C definieren den AVR an einen Booster anschließen und hoffen das sich ein Zug rührt. Hat schon jemand soetwas gemacht oder kann mir zumindest einen anstups geben? Technisch gesehen bräuchte ich nur einen Pin der das signal kontinuierlich rausleitet. Hab blos noch keinen blassen schimmer wie ich die zeiten für eine 0 oder für eine 1 einhalten soll. Realisiert das man einfach mit einem Zähler oder mittels den Timern? Die 10 bit Preamble würde ich einfach als eine Konstante definieren. Das Adressbyte, Datenbyte und X0R byte jeweils als Variablen. Aber was macht man mit den einzelnen Start und Stopbits? Sollten diese ebenfalls als Konstante definiert werden? Wäre wirklich super wenn mir hier irgendwie geholfen werden kann. Danke im Voraus
>ATMega 644 ein DCC-Protokoll erzeugen... >Hab blos noch keinen blassen schimmer wie ich >die zeiten für eine 0 oder für eine 1 einhalten soll. Kauf dir ein STK500 und programmiere selber ein Lauflicht (in C oder Assembler), dann ein komplizierteres Lauflicht, und dann ein frei konfigurierbares Lauflicht. Dann solltest du so langsam wissen, wie man irgendwelche Zeiten programmiert, und danach ist das DCC-Protokoll einfach zu implementieren... Und wenn es dann Probleme gibt, dann darfst du dich (in ca. 1 Monat) gerne wieder melden ;-) Dann sind auch die Fragen etwas anspruchsvoller...
erst einmal danke für die rasche Antwort. Mir ist schon klar wie ich es in etwa aufzubauen habe. Möchte es auch in C schreiben. Möchte für eine logische 1 einen Timer bauen, der alle 58 us auslöst. Und für eine logische 0 soll ein Timer gebaut werden, der alle 100 us auslöst. Habe im tutorial auch die Timergeschichte gelesen, sogar verstanden ;). Teile ich meinen Systemtakt (20 MHz) nun durch die 1024 so erhalte ich ca. 19531,25 Hz als Takt. Dies entspricht einer Periodenläge von 51,2 us. Gibt es irgendwie möglichkeiten, dass ich den Timer auf meine 58 bzw. 100 us hochschrauben kann oder habe ich (wie ich hoffe und vermute) einfach nur einen Gedankenfehler eingebaut. Möchte halt ein relativ konstanten Timer, relativ daher, da dass DCC Protocoll genügend spielraum für tolleranzen bietet. Danke im voraus Florian
Du kannst den Systemtakt durch 128 teilen oder noch weniger und den Timer zählen lassen. Bei dem vorgegebenen Zählerwert sagt der Timer "jetzt" und du hast deine gewünschten 58 oder 100µsec.
joa hab mir das etwas angesehen. Möchte aber gern selbst etwas basteln. Im grunde ist es auch nicht schwierig. Die norm gibt alle informationen die benötigt werden. und ich hoffe das sich das Timerproblem nun auch gelöst hat. Rechenbeispiel: Wenn ich meinen Systemtakt (20Mhz) durch den Teilungsfaktor 8 teile. Erhalte ich einen takt von 2,5 MHz und eine periodenlänge von 0,4 us. Wenn ich nun den Startwert 111 wähle, müsste der Timer ja bis 256 hochzählen und wieder bei 111 beginnen. Also genau bis 145 zählen. 145 * 0,4 us ergeben genau die 58 us die von mir gewünscht worden sind. Ist dieses korrekt? Danke im Voraus
Ich habe nicht nachgerechnet, aber prinzipiell richtig wenn es ein 8bit Timer ist. Ansonst schau dir noch das CTC an. Clear timer on comparematch.
Hubert G. wrote: > Ich habe nicht nachgerechnet, aber prinzipiell richtig wenn es ein 8bit > Timer ist. Ansonst schau dir noch das CTC an. Clear timer on > comparematch. Richtig genau, den werde ich mir jetzt nochmal ansehen. Klingt nämlich für den zweck deutlich sinnvoller. allerdings auch deutlich komplizierter ;) Sollte ich nicht mit zurecht kommen werde ich nochmals fragen müssen. Bislang vielen Dank für die netten Stöße an meinen Hinterkopf.
1 | #include <avr\io.h> |
2 | #include <inttypes.h> |
3 | #include <avr\interrupt.h> |
4 | #include <util/delay.h> |
5 | #include <stdbool.h> |
6 | |
7 | #define DCC_ONE 116L // 116 us für eine 1
|
8 | #define DCC_ZERO 232L // 232 us für eine 0
|
9 | #define F_CPU 20000000UL // 20 MHz Takt über exteren Quarz
|
10 | |
11 | /* DCC Paketaufbau
|
12 | 11111111111111 0 0AAAAAAA 0 01DCSSSS 0 EEEEEEEE 1
|
13 | |
14 | 1-14 ist die Preamble
|
15 | 15 ist ein Nullbit welches das Startbit für die Zugadresse darstellt
|
16 | 16-24 stellt das Addressbyte dar, die As sind die Variablen der Zugadresse also max. 127
|
17 | 25 ist ein Nullbit welches das Startbit für die das Datenbyte darstellt
|
18 | 26-34 stellt das Datenbyte dar, die Variablen geben ab in welche Richtung und wie schnell der Zug fahren soll
|
19 | 35 ist ein Nullbit welches das Startbit für die Xor Prüfsumme darstellt
|
20 | 36-44 stellt die Xor Prüfsumme dar
|
21 | 45 ist ein Einsbit und stellt das Stopbit des Paketes dar
|
22 | */
|
23 | |
24 | // Ports initialisieren
|
25 | void InitPorts() |
26 | {
|
27 | DDRB = 6; // PB1 und PB2 als Ausgänge |
28 | }
|
29 | |
30 | void InitTimer1 (void) // Timer initialisieren |
31 | {
|
32 | TIFR1 |= (1<<OCF1A); // Interrupt Request löschen (sicherheitshalber) |
33 | TIMSK1 |= (1<<OCIE1A); // Aktiviert den Output Compare Interrupt |
34 | OCR1A = ??? // Comparezeit von 116 us [hoffe das dies richtig gedacht ist] |
35 | TCNT1 = 0; |
36 | TCCR1A |= (1<<COM1A0); // OC1A togglen |
37 | TCCR1B |= (1<<WGM12) | (1<<CS10); // Prescaler 1, CTC Mode 4, Timer1 Start |
38 | }
|
39 | |
40 | ISR (TIMER1_COMPA_vect) |
41 | {
|
42 | // hier soll dann der Code stehen der besagt, dass bei einer auf den Ausgang zu sendene 1 116us anliegt
|
43 | // und eine auf den Ausgang zu sendene 0 die doppelte Zeit 232 us anliegt.
|
habe mir den Timer1 angesehen, erscheint mir auch alles ziemlich schlüssig. Gerade diese CTC funktion wäre von interesse. Nun steh ich nun etwas auf dem Schlauch, wie ich diesen Comparewert einsetzen soll. Am liebsten würde ich ja einfach diesen 45 bit String vergleichen lassen. Wenn 1 dann das signal am ausgang für 116 us halten, wenn eine 0 dann die doppelte zeit. Aber ob dieses überhaupt möglich ist weiss ich nicht. Vom prinzip her dürfte es nicht anders funktionieren als das aussehend von Fernbedienungscodes die sind auch intern gespeichert und werden dann einfach rausgegeben. Hoffe, dass ich wiedermal nur zu wenig über den Tellerrand schaue. Danke im Voraus
CTC würde ich da nicht nehmen, CTC blockiert nur unnötig die anderen Interrupt-Möglichkeiten des Timers. In der OC1A-ISR würde ich folgendes tun: - Pin toggeln - Zeitstempel aus OCR1a holen (nicht aus TCNT1!!), - Intervall dazu addieren und - nächsten Interrupt-Termin in OCR1A schreiben Nun weiß der Timer, wann er das nächste mal interrupten muss... - wenn Pin L ist, dann - Bits im Telegramm um eine Bitposition schieben und dabei (anhand des Carry) ermitteln, ob das Intervall der nächsten beiden Interrupts klein oder groß sein muss und dieses Intervall setzen. - Durch geeignetes Mittel (z.B. Zählvariable) erkennen, wann das Telegramm durch ist und das nächste Telegramm in die Shiftvariable holen. Mit OC1B würde ich den 1ms-Takt (festes Intervall auf OCR1B aufaddieren) für die Ausgabe an das LCD (1 Zeichen aus dem lokalen Bildschirmspeicher an das LCD senden) und das Pollen der Drehgeber erzeugen und jede 16. Runde die Taster entprellen. KH
1 | // verwendet wird der ATMega 644
|
2 | |
3 | #include <avr\io.h> |
4 | #include <inttypes.h> |
5 | #include <avr\interrupt.h> |
6 | #include <util/delay.h> |
7 | #include <stdbool.h> |
8 | |
9 | /* DCC Paketaufbau
|
10 | 11111111111111 0 0AAAAAAA 0 01DCSSSS 0 EEEEEEEE 1
|
11 | |
12 | 1-14 ist die Preamble
|
13 | 15 ist ein Nullbit welches das Startbit für die Zugadresse darstellt
|
14 | 16-24 stellt das Addressbyte dar, die As sind die Variablen der Zugadresse also max. 127
|
15 | 25 ist ein Nullbit welches das Startbit für die das Datenbyte darstellt
|
16 | 26-34 stellt das Datenbyte dar, die Variablen geben ab in welche Richtung und wie schnell der Zug fahren soll
|
17 | 35 ist ein Nullbit welches das Startbit für die Xor Prüfsumme darstellt
|
18 | 36-44 stellt die Xor Prüfsumme dar
|
19 | 45 ist ein Einsbit und stellt das Stopbit des Paketes dar
|
20 | */
|
21 | |
22 | #define F_CPU 20000000UL // 20 MHz Takt über exteren Quarz
|
23 | |
24 | #define DCC_EIN (TCCR1A |= (1 << COM1A0)) // Startet die Ausgabe des Dcc Signals OC1A on Compare Match
|
25 | #define DCC_AUS (TCCR1A &= ~(1 << COM1A0)) // Stopt die Ausgabe des DCC Signals
|
26 | |
27 | #define ICE_ZUG 0x0A // Lockadresse 10 stellt den ICE im Versuch dar
|
28 | #define ICE_SPEED 0x74 // vorwärts, mit der Fahrstufe 5
|
29 | #define ICE_XOR 0x7E // Vorberechnete Prüfsumme der oben genannten Einstellung
|
30 | |
31 | volatile uint8_t dcc_adresse; // Dcc Adressbyte (Adresse der Lok) |
32 | volatile uint8_t dcc_data; // Dcc Databyte (Richtung und Geschwindigkeit der Lok) |
33 | volatile uint8_t dcc_xor; // Dcc Prüfsumme |
34 | |
35 | dcc_adresse = ICE_ZUG; // Dieses |
36 | dcc_data = ICE_SPEED; // gilt |
37 | dcc_xor = ICE_XOR; // nur zum Testen |
38 | |
39 | /* Wartefunktion
|
40 | Warte 116 us
|
41 | */
|
42 | void warte(void) |
43 | {
|
44 | TCNT1 = 63215; // TCNT1 vorladen (Aufwaertszaehler bis 65535) |
45 | TCCR1A = 0x00; // normale Einstellung |
46 | // 0C1A/OC1B ausgeschaltet- Timer-Mode 0
|
47 | TCCR1B |= (1 << CS10); // F_CPU/1 (Kein Prescaler ausgewählt) |
48 | while (! (TIFR & (1 << TOV1))); // Solange das Bit TOV1 im Register TIFR noch 0 |
49 | // ist / Warte auf Überlauf von Timer/Counter 1
|
50 | TIFR |= (1 << TOV1); // läsche Überlauf-Flag |
51 | TCCR1B &= ~0x07; // Timer stoppt weil (CS12, CS11 und CS10 auf High gezogen werden) |
52 | }
|
53 | |
54 | void sendePreamble (void) // Preamble = 14 mal 1 |
55 | { // Berechnung: 14 * 116 us = 1624 |
56 | DCC_EIN; |
57 | warte(); warte(); warte(); warte(); warte(); warte(); warte(); warte(); |
58 | warte(); warte(); warte(); warte(); warte(); warte(); warte(); warte(); |
59 | }
|
60 | |
61 | void sendeEins (void) // Eine Eins hat die länge von einem Wartezyklus, also 116 us |
62 | {
|
63 | DCC_EIN; |
64 | warte(); |
65 | }
|
66 | |
67 | void sendeNull (void) // Eine Null hat die doppelte länge eine Eins, also 232 us |
68 | {
|
69 | DCC_EIN; |
70 | warte(); warte(); |
71 | }
|
72 | |
73 | void sendeStartbit (void) // Ein Startbit ist eine Null welche ein nächstes Byste einleutet |
74 | {
|
75 | DCC_EIN; |
76 | warte(); warte(); |
77 | }
|
78 | |
79 | void sendeStopbit (void) // Das Stopbit stellt das Ende eines DCC Paketes dar und ist eine EINS |
80 | {
|
81 | DCC_EIN; |
82 | warte(); |
83 | }
|
84 | |
85 | void sendeDccpaket (void) // Senderoutine für das DCC Protokoll gemäß der Norm |
86 | {
|
87 | uint8_t bit = 0; // Hilfsvariablen |
88 | uint8_t data = 0; |
89 | uint8_t bitzaehler = 0; |
90 | |
91 | sendePreamble(); // 14 Einsen sollen gesendet werden welche die Preamble darstellen |
92 | sendeStartbit(); // das Nullbit als Statbit für das Adressbyte senden |
93 | |
94 | data = ICE_ZUG; // Lokadresse des ICE |
95 | for (bitcounter = 1; bitcounter < 9; bitcounter++) |
96 | {
|
97 | bit = ((data & 0x80) >> 7); |
98 | if (bit == 0) |
99 | {
|
100 | sendeNull(); |
101 | } else |
102 | {
|
103 | sendeEins(); |
104 | }
|
105 | data <<= 1; |
106 | }
|
107 | |
108 | sendeStartbit(); // das Nullbit als Startbit für das Datenbyte senden |
109 | |
110 | data = ICE_SPEED; // Fahrtrichtung sowie Geschwindigkeit des Zuges |
111 | for (bitcounter = 1; bitcounter < 9; bitcounter ++) |
112 | {
|
113 | bit = ((data & 0x80) >> 7; |
114 | if (bit == 0) |
115 | {
|
116 | sendeNull(); |
117 | }
|
118 | else
|
119 | {
|
120 | sendeEins(); |
121 | }
|
122 | data <<= 1; |
123 | }
|
124 | |
125 | sendeStartbit(); // das Nullbit als Startbit für die XOR Prüfsumme senden |
126 | |
127 | data = ICE_XOR; // Prüfsumme des Vordefinierten ICE Zuges |
128 | for (bitcounter = 1; bitcounter < 9; bitcounter ++) |
129 | {
|
130 | bit = ((data & 0x80) >> 7; |
131 | if (bit == 0) |
132 | {
|
133 | sendeNull(); |
134 | }
|
135 | else
|
136 | {
|
137 | sendeEins(); |
138 | }
|
139 | data <<= 1; |
140 | }
|
141 | |
142 | sendeStopbit(); |
143 | }
|
144 | |
145 | ISR (INT0_vect) |
146 | {
|
147 | |
148 | }
|
149 | |
150 | /* Timer0 für die DCC - Trägerfrequenz von 8620,7 Hz im CTC Modus
|
151 | in dieser Trägerfrequenz enthalten sind die einzelnen Perioden für eine Null oder eine Eins
|
152 | */
|
153 | int main (void) |
154 | {
|
155 | TCCR0A |= (1<<WGM01); // Waveform Generator Mode; CTC Modus 2; TOP ist OCR0A |
156 | TCCR0B |= (1<<CS01); // Prescaler 8 da sonst die gewünschte Frequenz nicht erreicht werden kann |
157 | |
158 | OCR0A = 143; // Berechnung: OCR0A = (Systemtakt / 2 * gewünschte freq. ) - 1 |
159 | |
160 | DDRD |= (1<<DDB5); // PD5 ( OC1A) als Ausgang definieren |
161 | PORTD &= ~(1<<PD5); // PD5 ist Low |
162 | }
|
An der ISR bin ich noch am schrauben. noch nicht hinzugefügt ist, dass zwischen 2 Paketen 5 ms Pause sein sollen. Und das ein DCC paket in einer gewissen Zeit wiederholt werden soll. Was das letze angeht so bin ich noch etwas ratlos da es durchaus 127 stück werden können. Realisieren möchte ich vorest aber nur eine maximale anzahl von 5 paketen (also 5 züge mit dessen Geschwindigkeit) Derweil würde ich gern die Daten abspeichern um sie mittels eines Potentiometers verändern zu können. Für schneller und Langsamer in bezug auf einen ausgewählten zug. Hoffe das ich auf dem richtigen weg bin. Wenn man irgendetwas eleganter lösen kann bitte ich dieses mir mitzuteilen. Danke im Voraus Florian
Dass Du mehrere verschiedene Telegramme senden musst, ist ja klar. Was hindert Dich daran, ein Array mit zu sendenden Telegrammen anzulegen? Die Senderoutine kann dann die Telegramme reihum senden. Potentiometer für Tempo ist zwar machbar, aber hat enorme Nachteile. Du kannst ein Poti nämlich nicht für mehrere Loks einsetzen. Mit Drehgebern geht das besser, ein Drehgeber dient zum verändern des Tempos, ein zweiter (oder Up/Down-Tasten) zur Auswahl der Lok (-Adresse). Mit Poti würde jedes Umschalten der Lokadresse einen Temposprung verursachen weil das Poti seit dem Verlassen der Adresse zum Steuern einer anderen Lok verstellt wurde. Eine vollwertige Zentrale wirst Du sowiso nicht realisieren können, schau Dir Uhlenbrocks Intelli-Box oder vergleichbare Zentralen (Twin-Center) an, dann wirst Du sehen, dass es mit einem AVR nicht getan ist. Auch die Software ist nicht mal eben nebenher geschrieben, da sind seit Jahren hochqualifizierte Ingenieure mit beschäftigt. Du wirst also von vorn herein Einschränkungen einplanen müssen. Fang damit an, festzulegen, wieviele Loks das System unterstützen soll. Ich denke, dass 8 oder 16 Loks ausreichen. Somit kannst Du für 8 oder 16 Loks ein Array anlegen, in dem neben Tempo, Fahrtrichtung und Funktionen auch noch die Adresse eingetragen ist. Beim Steuern änderst Du nur Tempo, Richtung und Funktionen, für das Einstellen der Adresse je Speicherplatz machst Du einen eigenen Dialog. Somit musst Du lokal nur 8 (16) Loks verwalten, die reihum gesendet werden. Zwischen den Lok-Telegrammen wird geschaut, ob Zubehör-Telegramme gesendet werden müssen (die müssen nicht wiederholt werden). Für diese legst Du dann ein eigenes Array an. Nach dem Senden eines Zubehör-Telegramms wird dieses dann aus dem Array gelöscht (Idle-Telegramm), um Platz für neue Telegramme zu machen. Der Bedien-Dialog zum Schalten der Weichen und Signale sucht dann den ersten freien Platz im Array und legt dort das Telegramm rein. Du solltest auch von Anfang an über das Bedien-Interface nachdenken. Ich würde mit mehreren Drehgebern (mit Taster) und einigen Tastern (Matrix über ADC-Eingang) arbeiten. Als Feedback dann ein LCD mit ausreichend Anzeigeplatz, das Adressen, Fahrstufen, Funktionen der Loks anzeigen kann, aber auch die Adresse der per Tasten steuerbaren Magnetartikel und deren (im RAM mitgeloggte) aktuelle Lage. Vermutlich wird das alles einfacher und überschaubarer, wenn Du zwei getrennte Zentralen für Loks und Zubehör baust und für Zubehör einen eigenen DCC-Strang ziehst. Und wenn das alles fertig ist, kannst Du damit immer noch keine Loks programmieren. Da bietet sich aber eine PC-Lösung mit Booster an der COM-Schnittstelle an, denn das Implementieren aller Programmierbefehle in den AVR ist gar nicht mal so einfach. Mach's wie Egon, mach Dir erstmal einen Plan... Zum C-Quältext sage ich mangels C-Kompetenz nichts. KH
bin derweil noch munter am machen aber eins bereitet mir irgendwie kopfschmerzen. Ich möchte 2 popelige Bytes miteinander XOR´en um das ergebnis als Prüfsumme dem Paket anzuhängen. Gibt es in C einen einfachen befehl dafür oder muss ich mich der Logic bedienen und eine funktion schreiben?
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.