Forum: Mikrocontroller und Digitale Elektronik DCC signal durch AVR


von Florian P. (chillkroedde)


Lesenswert?

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

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

>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...

von Florian P. (chillkroedde)


Lesenswert?

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

von Hubert G. (hubertg)


Lesenswert?

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.

von Ulrich (Gast)


Lesenswert?

Du kennst schon opendcc ?

von Florian P. (chillkroedde)


Lesenswert?

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

von Hubert G. (hubertg)


Lesenswert?

Ich habe nicht nachgerechnet, aber prinzipiell richtig wenn es ein 8bit 
Timer ist. Ansonst schau dir noch das CTC an. Clear timer on 
comparematch.

von Florian P. (chillkroedde)


Lesenswert?

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.

von Florian P. (chillkroedde)


Lesenswert?

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

von Kachel-Heinz (Gast)


Lesenswert?

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

von Florian P. (chillkroedde)


Lesenswert?

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

von Kachel-Heinz (Gast)


Lesenswert?

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

von Florian P. (chillkroedde)


Lesenswert?

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
Noch kein Account? Hier anmelden.