mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik DCC signal durch AVR


Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Hubert G. (hubertg)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Ulrich (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du kennst schon opendcc ?

Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Hubert G. (hubertg)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
#include <avr\io.h>
#include <inttypes.h>
#include <avr\interrupt.h>
#include <util/delay.h>
#include <stdbool.h> 

#define DCC_ONE 116L          // 116 us für eine 1
#define DCC_ZERO 232L          // 232 us für eine 0
#define F_CPU 20000000UL        // 20 MHz Takt über exteren Quarz

/* DCC Paketaufbau
11111111111111 0 0AAAAAAA 0 01DCSSSS 0 EEEEEEEE 1

1-14     ist die Preamble
15     ist ein Nullbit welches das Startbit für die Zugadresse darstellt
16-24   stellt das Addressbyte dar, die As sind die Variablen der Zugadresse also max. 127
25    ist ein Nullbit welches das Startbit für die das Datenbyte darstellt
26-34  stellt das Datenbyte dar, die Variablen geben ab in welche Richtung und wie schnell der Zug fahren soll
35    ist ein Nullbit welches das Startbit für die Xor Prüfsumme darstellt
36-44  stellt die Xor Prüfsumme dar
45    ist ein Einsbit und stellt das Stopbit des Paketes dar
*/

// Ports initialisieren
void InitPorts()
{
  DDRB = 6;              //  PB1 und PB2 als Ausgänge
}

void InitTimer1 (void)          // Timer initialisieren
{
  TIFR1 |= (1<<OCF1A);        // Interrupt Request löschen (sicherheitshalber)
  TIMSK1 |= (1<<OCIE1A);        // Aktiviert den Output Compare Interrupt
  OCR1A = ???              // Comparezeit von 116 us [hoffe das dies richtig gedacht ist]
  TCNT1 = 0;
  TCCR1A |= (1<<COM1A0);        // OC1A togglen
  TCCR1B |= (1<<WGM12) | (1<<CS10);  // Prescaler 1, CTC Mode 4, Timer1 Start
}

ISR (TIMER1_COMPA_vect)
{
 // hier soll dann der Code stehen der besagt, dass bei einer auf den Ausgang zu sendene 1  116us anliegt
 // 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

Autor: Kachel-Heinz (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
// verwendet wird der ATMega 644

#include <avr\io.h>
#include <inttypes.h>
#include <avr\interrupt.h>
#include <util/delay.h>
#include <stdbool.h> 

/* DCC Paketaufbau
11111111111111 0 0AAAAAAA 0 01DCSSSS 0 EEEEEEEE 1

1-14     ist die Preamble
15     ist ein Nullbit welches das Startbit für die Zugadresse darstellt
16-24   stellt das Addressbyte dar, die As sind die Variablen der Zugadresse also max. 127
25    ist ein Nullbit welches das Startbit für die das Datenbyte darstellt
26-34  stellt das Datenbyte dar, die Variablen geben ab in welche Richtung und wie schnell der Zug fahren soll
35    ist ein Nullbit welches das Startbit für die Xor Prüfsumme darstellt
36-44  stellt die Xor Prüfsumme dar
45    ist ein Einsbit und stellt das Stopbit des Paketes dar
*/

#define F_CPU 20000000UL          // 20 MHz Takt über exteren Quarz

#define DCC_EIN (TCCR1A |= (1 << COM1A0))  // Startet die Ausgabe des Dcc Signals OC1A on Compare Match
#define DCC_AUS  (TCCR1A &= ~(1 << COM1A0))  // Stopt die Ausgabe des DCC Signals

#define ICE_ZUG 0x0A            // Lockadresse 10 stellt den ICE im Versuch dar
#define ICE_SPEED 0x74            // vorwärts, mit der Fahrstufe 5
#define ICE_XOR 0x7E            // Vorberechnete Prüfsumme der oben genannten Einstellung

volatile uint8_t dcc_adresse;        // Dcc Adressbyte (Adresse der Lok)
volatile uint8_t dcc_data;          // Dcc Databyte (Richtung und Geschwindigkeit der Lok)
volatile uint8_t dcc_xor;          // Dcc Prüfsumme

dcc_adresse = ICE_ZUG;            // Dieses
dcc_data = ICE_SPEED;            // gilt
dcc_xor = ICE_XOR;              // nur zum Testen

/* Wartefunktion
Warte 116 us
*/
void warte(void) 
{
  TCNT1 = 63215;              // TCNT1 vorladen (Aufwaertszaehler bis 65535)
  TCCR1A = 0x00;              // normale Einstellung
                      // 0C1A/OC1B ausgeschaltet- Timer-Mode 0
  TCCR1B |= (1 << CS10);           // F_CPU/1 (Kein Prescaler ausgewählt)
  while (! (TIFR & (1 << TOV1)));     // Solange das Bit TOV1 im Register TIFR noch 0
                      // ist / Warte auf Überlauf von Timer/Counter 1
  TIFR |= (1 << TOV1);           // läsche Überlauf-Flag
  TCCR1B &= ~0x07;             // Timer stoppt weil (CS12, CS11 und CS10  auf High gezogen werden)
}

void sendePreamble (void)          // Preamble = 14 mal 1
{                      // Berechnung: 14 * 116 us = 1624
  DCC_EIN;
  warte(); warte(); warte(); warte(); warte(); warte(); warte(); warte();
  warte(); warte(); warte(); warte(); warte(); warte(); warte(); warte();
}

void sendeEins (void)            // Eine Eins hat die länge von einem Wartezyklus, also 116 us
{
  DCC_EIN;
  warte();
}

void sendeNull (void)            // Eine Null hat die doppelte länge eine Eins, also 232 us
{
  DCC_EIN;
  warte(); warte();
}

void sendeStartbit (void)          // Ein Startbit ist eine Null welche ein nächstes Byste einleutet
{
  DCC_EIN;
  warte(); warte();
}

void sendeStopbit (void)          // Das Stopbit stellt das Ende eines DCC Paketes dar und ist eine EINS
{
  DCC_EIN;
  warte();
}

void sendeDccpaket (void)          // Senderoutine für das DCC Protokoll gemäß der Norm
{
  uint8_t bit = 0;            // Hilfsvariablen
  uint8_t data = 0;
  uint8_t bitzaehler = 0;
  
  sendePreamble();            // 14 Einsen sollen gesendet werden welche die Preamble darstellen
  sendeStartbit();            // das Nullbit als Statbit für das Adressbyte senden
  
  data = ICE_ZUG;              // Lokadresse des ICE
  for (bitcounter = 1; bitcounter < 9; bitcounter++) 
  {
    bit = ((data & 0x80) >> 7);
    if (bit == 0) 
    {
    sendeNull();
    }   else 
      {
        sendeEins();
      }
        data <<= 1;
  }
  
  sendeStartbit();            // das Nullbit als Startbit für das Datenbyte senden
  
  data = ICE_SPEED;            // Fahrtrichtung sowie Geschwindigkeit des Zuges
  for (bitcounter = 1; bitcounter < 9; bitcounter ++)
  {
    bit = ((data & 0x80) >> 7;
    if (bit == 0)
    {
    sendeNull();
    }  
      else
      {
        sendeEins();
      }
        data <<= 1;
  }
  
  sendeStartbit();            // das Nullbit als Startbit für die XOR Prüfsumme senden
  
  data = ICE_XOR;              // Prüfsumme des Vordefinierten ICE Zuges
  for (bitcounter = 1; bitcounter < 9; bitcounter ++)
  {
    bit = ((data & 0x80) >> 7;
    if (bit == 0)
    {
    sendeNull();
    }
      else
      {
        sendeEins();
      }
        data <<= 1;
  }
  
  sendeStopbit();
}

ISR (INT0_vect)
{
  
}

/* Timer0 für die DCC - Trägerfrequenz von 8620,7 Hz im CTC Modus
in dieser Trägerfrequenz enthalten sind die einzelnen Perioden für eine Null oder eine Eins
*/
int main (void)
{
  TCCR0A |= (1<<WGM01);          // Waveform Generator Mode; CTC Modus 2; TOP ist OCR0A
  TCCR0B |= (1<<CS01);          // Prescaler 8 da sonst die gewünschte Frequenz nicht erreicht werden kann
  
  OCR0A = 143;              // Berechnung: OCR0A = (Systemtakt / 2 * gewünschte freq. ) - 1
  
  DDRD |= (1<<DDB5);            // PD5 ( OC1A) als Ausgang definieren
  PORTD &= ~(1<<PD5);            // PD5 ist Low  
}

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

Autor: Kachel-Heinz (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Florian Pramme (chillkroedde)
Datum:

Bewertung
0 lesenswert
nicht 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?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.