Erfahrungen und Tipps zum ATXmega

    english page is missing - you can help the project with a translation!

Analogsystem

    Beim Gleisbesetztmelder wird der Strom des jeweiligen Abschnittes durch einen Widerstand (begrenzt durch anitparallele Schottky-Dioden) geleitet. Der entstehende Spannungsabfall im Bereich -0,4V bis +0,4V wird zur Messung verwendet.
    Der ATXmega bietet einen AD-Wandler, welcher auch ins 'Negative' messen kann. Das habe ich in einen Test vermessen:
  • Einstellungen:
      2 MHz Samplerate
      Single Measurement, Signed Mode, Resolution 12 Bit
      interne Bandgap Referenz mit 1V, Gain 1
      Chip mit Längsregler 3V3 versorgt
      Einspeisung der Meßspannung aus (längsgeregeltem) Labornetzteil via 1k
      Kontrolle der Spannung mit 4-1/2 stelligem DVM.
  • Ergebnisse:
      Rauschen: so ca. 4 Digit, d.h. 2mV.
      Meßbereich: 0 .. 1V, danach geht der Wandler bei Code 2047 sauber in die Sättigung;
      DC-Offset ca. 3mV, Nichtlinearität ca. 3mV.
      Negative Spannungen: linearer Meßbereich: 0 .. -340mV, dannach wird der Wandler nichtlinear und sättigt etwa bei -400mV
  • Besondere Optimierungen

    Timer

      Beim ATXmega sind alle Timer gleich, jeder kann alles: 8 Bit, 16 Bit, Compareregister, Capture, usw. Zusätzlich sind ein paar Dinge eingebaut, welche bis data ziemliches Aufstand verursacht haben:
    • Wenn ein Waveform Generation Mode gewählt ist, kann man trotzdem noch auf die Pins zugreifen - nützlich z.B. einer H-Brücke, welche in der Richtung umgesteuert wird oder kurz aus dem PWM raus soll (z.B. für die Cutout von DCC BiDi).
    • Es gibt ein extra Register, um Timer-Ereignisse auszulösen. Man kann über dieses Register den Timer neu starten, einen Update provozieren (also z.B. mehrere Compareregister parallel umladen).

    Xmega und DCC

    DCC Dekodieren

      Der Xmega hat 8 interne Event-Leitungen, man kann frei definieren, wer da sendet und wer da empfängt. Das kann benutzen, um DCC elegant zu dekodieren:
    • Der DCC Eingang wird so programmiert, daß er auf Flanke (steigend oder fallend) reagiert. Diese Ereignis wird dann auf die Event-Leitung gelegt.
    • Ein Timer wird freilaufend programmiert, ein Ausgangs-Compare auf die Zeitspanne von 87µs (=DCC Period_1 * 0,75= programmiert. In Event-Aktions-Register des Timer wird nun 'RESTART' auf das obige Event eingetragen.
      Dadurch wird beim Flankenwechsel des DCC-Signals der Timer zurückgesetzt, und exakt nach 87µs erfolgt das Auslösen des Compares.
    • Nun kann man da entweder eine Interruptroutine anhängen und den Port einlesen oder wiederum ein Event auslösen, welches dann den DMA-Controller veranlaßt, den Port einzulesen und im Speicher abzulegen.
    • Events erlauben also sehr exaktes Timing und Abarbeiten von Abläufen ohne CPU-Interaktion.

    DCC erzeugen

      Bei DCC wird jedes Bit als eine Folge aus zwei Leitungszuständen (je ein Puls high, ein Puls low) kodiert, eine 1 wird dabei durch 58µs high + 58µs low, eine 0 durch 116µs high und 116µs low dargestellt.

      Jetzt kann man hier verschiedene Ansätze zum Erzeugen diese Signals verwenden, nochfolgend ist mal ein Ansatz dargestellt, welcher ohne strenge Interruptforderungen auskommt und nur sehr geringe CPU-Last verursacht.

      Es wird ein Timer in Frequency Mode verwendet: hier läuft der Zähler bis zu einem einstellbaren Endwert hoch und beginnt dann wieder von vorne. Bei jedem Endwert wechselt der Ausgang die Polarität. Wenn man nun diesen Endwert bei jedem Zählerumlauf neu beschreibt, so kann man beliebige Pulsketten erzeugen. Das Beschreiben geht am Besten mit dem DMA Controller. Zu beachten ist, das Frequency Mode nur auf dem Compareregister A und dessen zugehörigem Ausgang verfügbar ist.

      Beginnen wir mit dem Timer. Der Vorteiler wird auf 64 gestellt, damit ergibt sich bei 32 MHz ein Zeitraster von 2µs. Des weiteren stellt man die Timer auf Bytemode, damit belegen alle Zeiteinstellungen nur 8 Bit, auch der DMA braucht dann nur je ein Byte nachladen.
            static void init_timer_d0(void)
              {
                TCD0.CNT = 0;
                TCD0.PER = 0;
                TCD0.CCA = TIME_DCC_HALF1;
                TCD0.CTRLE = (1 << TC0_BYTEM_bp);       // Byte mode: 1 run as 8-bit counter
                TCD0.CTRLA = ( TCD0.CTRLA & ~TC0_CLKSEL_gm ) | TC_CLKSEL_DIV64_gc;
                TCD0.CTRLB = (0 << TC0_CCDEN_bp)
                           | (0 << TC0_CCCEN_bp)
                           | (0 << TC0_CCBEN_bp)
                           | (1 << TC0_CCAEN_bp)        // enable A (dcc)
                           | TC_WGMODE_FRQ_gc;
                TCD0.INTCTRLA = ( TCD0.INTCTRLA & ~TC0_OVFINTLVL_gm ) | TC_OVFINTLVL_OFF_gc;
              }


      Dann muß man den DMA Controller aufsetzen. Hier werden die Kanäle 2 und 3 verwendet und dabei auch der DBUFMODE aktiviert: nach der Übertragung des Kanals 2 wechselt der DMA-Controller automatisch auf Kanal 3 und umgekehrt. Übertragen wird je ein Byte (wir haben ja den Timer auf Byte-Mode laufen), SRC wird inkrementiert, DEST bleibt immer gleich. Der Reload des SRC-Pointers soll je Block (bei uns eine DCC-Message) erfolgen.
           static void init_dma(void)
              {
                DMA.CTRL = (1 << DMA_ENABLE_bp)
                         | (0 << DMA_RESET_bp)
                         | DMA_PRIMODE_CH0123_gc
                         | DMA_DBUFMODE_CH23_gc;
                DMA.CH2.REPCNT = 0;                        // run forever
      
                DMA.CH2.CTRLA = (0 << DMA_CH_ENABLE_bp)    //  Channel Enable
                              | (0 << DMA_CH_RESET_bp)   // Channel Software Reset
                              | (1 << DMA_CH_REPEAT_bp)   // Channel Repeat Mode bit position.
                              | (0 << DMA_CH_TRFREQ_bp)   // Channel Transfer Request bit position.
                              | (1 << DMA_CH_SINGLE_bp)   // Channel Single Shot Data Transfer bit position.
                              | DMA_CH_BURSTLEN_1BYTE_gc;
                DMA.CH2.CTRLB = DMA_CH_ERRINTLVL_OFF_gc
                              | DMA_CH_TRNINTLVL_LO_gc;  // transaction complete
      
                DMA.CH2.ADDRCTRL = DMA_CH_SRCRELOAD_BLOCK_gc  // Source address reload mode : _NONE, BLOCK, BURST, TRANSACTION
                                 | DMA_CH_SRCDIR_INC_gc       // Source addressing mode: FIXED, INC, DEC
                                 | DMA_CH_DESTRELOAD_NONE_gc  // Destination adress reload mode: NONE, BLOCK, BURST, TRANSACTION
                                 | DMA_CH_DESTDIR_FIXED_gc;   // Destination adressing mode: FIXED, INC, DEC
      
                DMA.CH2.TRIGSRC = DMA_CH_TRIGSRC_TCD0_CCA_gc;   // Counter D, comp. A
      
                src.as_uint32 = dcc_buffer_0;
                DMA.CH2.SRCADDR0 = src.low8;
                DMA.CH2.SRCADDR1 = src.high8;
                DMA.CH2.SRCADDR2 = src.upper8;
      
                dest.as_uint32 = &TCD0.CCAL;
                DMA.CH2.DESTADDR0 = dest.low8;
                DMA.CH2.DESTADDR1 = dest.high8;
                DMA.CH2.DESTADDR2 = dest.upper8;
      
                DMA.CH3.REPCNT = 0;
      
                DMA.CH3.CTRLA = (0 << DMA_CH_ENABLE_bp)    //  Channel Enable
                              | (0 << DMA_CH_RESET_bp)     // Channel Software Reset
                              | (1 << DMA_CH_REPEAT_bp)   // Channel Repeat Mode bit position.
                              | (0 << DMA_CH_TRFREQ_bp)   // Channel Transfer Request bit position.
                              | (1 << DMA_CH_SINGLE_bp)   // Channel Single Shot Data Transfer bit position.
                              | DMA_CH_BURSTLEN_1BYTE_gc;
                DMA.CH3.CTRLB = DMA_CH_ERRINTLVL_OFF_gc
                              | DMA_CH_TRNINTLVL_LO_gc;  // transaction complete
      
                DMA.CH3.ADDRCTRL = DMA_CH_SRCRELOAD_BLOCK_gc  // Source address reload mode : _NONE, BLOCK, BURST, TRANSACTION
                                 | DMA_CH_SRCDIR_INC_gc       // Source addressing mode: FIXED, INC, DEC
                                 | DMA_CH_DESTRELOAD_NONE_gc  // Destination adress reload mode: NONE, BLOCK, BURST, TRANSACTION
                                 | DMA_CH_DESTDIR_FIXED_gc;   // Destination adressing mode: FIXED, INC, DEC
      
                DMA.CH3.TRIGSRC = DMA_CH_TRIGSRC_TCD0_CCA_gc;   // Counter D, comp. A
      
                src.as_uint32 = dcc_buffer_1;
                DMA.CH3.SRCADDR0 = src.low8;
                DMA.CH3.SRCADDR1 = src.high8;
                DMA.CH3.SRCADDR2 = src.upper8;
      
                dest.as_uint32 = &TCD0.CCAL;
                DMA.CH3.DESTADDR0 = dest.low8;
                DMA.CH3.DESTADDR1 = dest.high8;
                DMA.CH3.DESTADDR2 = dest.upper8;
      
                dma_active_channel = 2;
      
                // and fire the DMA (we start with channel 2)
      
                DMA.CH2.CTRLA = (1 << DMA_CH_ENABLE_bp)     //  Channel Enable
                              | (0 << DMA_CH_RESET_bp)      // Channel Software Reset
                              | (1 << DMA_CH_REPEAT_bp)     // Channel Repeat Mode
                              | (0 << DMA_CH_TRFREQ_bp)     // Channel Transfer Request
                              | (1 << DMA_CH_SINGLE_bp)     // Channel Single Shot Data Transfer
                              | DMA_CH_BURSTLEN_1BYTE_gc;
              }


      Wenn ein DMA Durchlauf fertig ist, erfolgt ein Interrupt. In diesem Interrupt wird der Pointer auf die aktive DCC Nachricht um eins weitergeschoben und auch das Neuberechnen der Kurvenform für die nächste DCC-Nachricht angestoßen.
            ISR(DMA_CH2_vect)
              {
                DMA.INTFLAGS = DMA_CH2ERRIF_bm  | DMA_CH2TRNIF_bm;
      
                dma_active_channel = 3;
                actual_index++;
                actual_index &= 0x3;  // modulo 4
                isr_set_task_ready(TASK_DCC_GEN);
              }
      
            ISR(DMA_CH3_vect)
              {
                DMA.INTFLAGS = DMA_CH3ERRIF_bm  | DMA_CH3TRNIF_bm;
      
                dma_active_channel = 2;
                actual_index++;
                actual_index &= 0x3;  // modulo 4
                isr_set_task_ready(TASK_DCC_GEN);
              }


      Die in DMA-ISR freigegebene Task berechnet dann den Pulszug für das nächste DCC Signal:
         #define WRITE_1 {*dcc_buffer++ = TIME_DCC_HALF1;*dcc_buffer++ = TIME_DCC_HALF1;}
         #define WRITE_0 {*dcc_buffer++ = TIME_DCC_HALF0;*dcc_buffer++ = TIME_DCC_HALF0;}
      
         static void prepare_dma_buffer(t_dcc_message* dcc_message)
           {
             uint16_t size; uint8_t* dcc_buffer;
             uint8_t i,prebits; uint8_t my_xor = 0; // dcc checksum
      
             if (dma_active_channel == 2) dcc_buffer = dcc_buffer_1;
             else dcc_buffer = dcc_buffer_0;
      
             // ----- now fill this buffer with timing data
      
             if (dcc_message->type == is_prog) prebits = MAX_PREAMBLE_BITS;
             else prebits = NORMAL_PREAMBLE_BITS;
      
             for (i=0; i<prebits; i++)  {  WRITE_1; }                // preamble
             WRITE_0;                                                    // dcc starts
             for (i=0; i<dcc_message->size; i++)
               {
                 uint8_t current_byte = dcc_message->dcc[i];
                 my_xor ^= current_byte;
                 for (uint8_t mask=0x80; mask; mask >>= 1)  // msb first
                   {
                     if (current_byte & mask) { WRITE_1; }
                     else  { WRITE_0; }
                   }
                 WRITE_0;                                                // byte delimiter
               }
             for (uint8_t mask=0x80; mask; mask >>= 1)
               {
                 if (my_xor & mask) { WRITE_1; }
                 else  { WRITE_0; }
               }
             WRITE_1;                                                    // packet end bit
      
             if (dma_active_channel == 2)                              // ---- load to isr / dma
               {
                 size = dcc_buffer - dcc_buffer_1;
                 DMA.CH3.TRFCNT = size;
               }
      
             else
               {
                 size = dcc_buffer - dcc_buffer_0;
                 DMA.CH2.TRFCNT = size;
               }
           }
      Dieses Konstrukt verursacht eine Interruptlast von etwa 10µs alle 6ms, das Neuberechnen der Pulswerte dauert etwa 40µs (bei 32MHz Atxmega128A1), so dass dieser DCC-Generator eine Last kleiner 1% verursacht. Zudem ist der Interrupt von der Zeitanforderung her nicht kritisch.

    Speichern von Daten im Flash

      Das permanente Speichern von Daten im Flash ist etwas vertrackt: Ins Flash kann man nur aus dem Bootloaderbereich schreiben (die SPM-Befehl sind von dort aus ausführbar), d.h. man braucht im Bootloaderbereich entsprechenden Code. Diesen Code muß man anspringen, von dort aus den Schreibvorgang durchführen und wieder zurückkommen.
      Dabei muß man beachten, dass der Bootbereich außerhalb der Reichweite der üblichen Calls bzw. Pointerzugriffe liegt: sowohl bei Call als auch bei Datenzugriff dorthin sind entsprechende Vorkehrungen zu treffen. (RAMPZ, EIND entsprechend umprogrammieren).
      Zudem wird in einer üblichen Installation im Bootbereich ein Bootloader liegen, d.h. beim FW-Update klammert man diesen Bereich aus, i.d.R. durch ein Hinzufügen von -R .boot zu den HEXFLAGS des Linkers im makefile.

      D.h. man muß da in den Bootbereich rein, kann es aber eigentlich nicht ... Ich habe das so gelöst, dass ich mir aus den Bootloader die Funktion 'write-Flash' rausgezogen habe, (Sektion .BOOT aus der sp_driver.c) die fest auf eine Adresse verlinkt habe (0x10Ef0) und diese Adresse dann aus der Applikation auch anspringe. (mit gesetztem EIND-Bit).
      Damit ich dabei nicht ins nirwana springe und mir der Prozessor 'abstürzt', prüfe ich beim Programmstart ab, ob passender Code an der Stelle liegt, an die ich hinspringen will. Wenn da nichts ist, dann werden schreibende Speicherzugriffe ins Flash aus der Applikation heraus unterbunden.
      Weiter muß man beachten, dass durch EIND und RAMPZ eventuelle die normale Adressierung des gcc daneben greift, sinnvoll ist hier ein Sperren der Interrupts vor der Speicherroutine und anschließende Wiederfreigabe.