Forum: Mikrocontroller und Digitale Elektronik Erste Schritte mit ARM SAMD20


von Rudolph (Gast)


Lesenswert?

Hi,

ich habe heute ein "Atmel SAM D20 Xplained Pro" erhalten und es sogleich 
seinem Zweck zuführen wollen -> mal afangen mit was ARM basiertem zu 
spielen.

Als erstes habe ich mir dazu im Atmel Studio 6.1 mal ein Beispiel 
Projekt aufgerufen, LED_Toogle.

samd20_led_toggle_app_intro Introduction
 This application demonstrates a simple example to turn on the
 board LED when a button is pressed, using a variety
 of methods and modules within the device.

Erklärt wird da natürlich erstmal garnichts wirklich.
Geradezu erschreckend fand ich dann aber das hier:

Program Memory Usage : 2996 bytes 1,1 % Full
Data Memory Usage: 9368 bytes 28,6 % Full

Wie auch immer, das "einfache" Beispiel erschlägt mich quasi schon mit 
xxxx Dateien und weiss der Henker was da eigentlich passiert.

Geht sowas nicht auch in Einfach?

Auf dem AVR wäre die Funktion ja komplett so erschlagen:

---
#include <avr/io.h>

#define TASTER0 PB0
#define LED0 PB1

int main(void)
{

 DDRB = (1<<LED0); // Pin für LED auf Ausgang setzen
 PORTB = (1<<TASTER0); // Pullup-Widerstand für Taster an

 while(1)
{
 if(PINB & (1<<TASTER0)) == 0)
 {
  PORTB |= (1<<LED0);
 }
 else
 {
  PORTB &= ~(1<<LED0);
 }
}
---

Wie würde sowas jetzt in aller Kürze für das "Atmel SAM D20 Xplained 
Pro" aussehen?

Oder anders, wie kommt man in das Thema rein wenn man kein Freund davon 
ist sich seine Programme zusammen zu klicken?

von Lothar (Gast)


Lesenswert?

Rudolph schrieb:
> Atmel SAM D20 Xplained Pro

Kenne ich jetzt nicht aber beim ARM sieht LED Toggle erst mal genau so 
aus wie beim AVR. Atmel verwendet aber wahrscheinlich von Anfang an die 
CMSIS und dann kommt das dabei raus. Es hilft wohl nur im Manual die 
Register zu suchen. Bei NXP ARM würde das ganz ohne Library und Includes 
z.B. so aussehen:

#define DIR0            0xA0002000
#define PIN0            0xA0002100

void delay(volatile unsigned long cycles)
{
  while (cycles) {cycles--;}
}

void main(void)
{
  // P0_4 set to output
  *((unsigned long *) DIR0) |= 1<<(4);

  // P0_4 set to high (LED off)
  *((unsigned long *) PIN0) |= 1<<(4);

  while (1)
  {
    // P0_4 toggle
    *((unsigned long *) PIN0) ^= 1<<(4);

    delay(500000);
  }
}

Natürlich ist das noch kein effektiver Code, besser wäre Bitbanding.

von Rudolph (Gast)


Lesenswert?

CMSIS ist da auf jeden Fall mit im Spiel und inzsichen habe ich das 
AFS-Handbuch für den SAMD20 gefunden, das ist auch hilfreich.
Die 3k FLASH sind ja auch erstmal nicht schlimm, über 9k SRAM finde ich 
krank.

Dein Code macht übrigens was ganz anderes und sieht irgendwie Scheisse 
aus dafür was er macht. :-)
Es geht mir ja keineswegs um absolut minimalistisch. :-)

Mein erster Eindruck von den ARMs ist jedenfalls das die überflüssig 
komplex sind für das was ich damit machen wollen würde.
Und was ich bisher so an Datenblättern gesehen habe orientiert sich 
leider nicht daran wie man die Dinger programmieren soll.

von Deppenhauer (Gast)


Lesenswert?

Rudolph schrieb:
> Mein erster Eindruck von den ARMs ist jedenfalls das die überflüssig
> komplex sind für das was ich damit machen wollen würde.

Stimmt, deine Aufgabenstellung löst sich so:
http://elektroniktutor.de/analogverstaerker/astabil.html

von Rudolph (Gast)


Lesenswert?

Jaja, und jetzt geh bitte in Dein Loch zurück in dem Du Linux Apps durch 
zusammenklicken von Libs erstellst.

Durch das AFS klicken kann ich mich auch, wie das alles funktioniert 
lernt man dabei nur nicht.

von Lothar (Gast)


Lesenswert?

Rudolph schrieb:
> Es geht mir ja keineswegs um absolut minimalistisch. :-)

Es gibt nun mal NXP ARM mit 1KB RAM, da kann man nicht mit der CMSIS 
anrücken. Es haben sich auch bereits Leute mit minimalen Libraries 
beschäftigt z.B.:

https://github.com/microbuilder/LPC810_CodeBase

von Rudolph (Gast)


Lesenswert?

Ich habe gerade einfach mal ein nacktes Projekt aufgemacht mit dem 
Template für das Eva-Board.

Und da stand dann das hier in der main.c:

---
#include <asf.h>

int main (void)
{
  system_init();

  // Insert application code here, after the board has been initialized.

  // This skeleton code simply sets the LED to the state of the button.
  while (1) {
    // Is button pressed?
    if (port_pin_get_input_level(BUTTON_0_PIN) == BUTTON_0_ACTIVE) {
      // Yes, so turn LED on.
      port_pin_set_output_level(LED_0_PIN, LED_0_ACTIVE);
    } else {
      // No, so turn LED off.
      port_pin_set_output_level(LED_0_PIN, !LED_0_ACTIVE);
    }
  }
}
---

Also so ziemelich genau, was ich sehen wollte.
Funktionen zu benutzen um einzelne Bits einzulesen oder auszugeben finde 
ich jetzt etwas seltsam, aber naja.

Program Memory Usage : 2940 bytes   1,1 % Full
Data Memory Usage : 9328 bytes   28,5 % Full

Das bleibt weiterhin seltsam, das kann doch garnicht stimmen?

Wie sieht das denn dahinter aus?

---
static inline bool port_pin_get_input_level(
    const uint8_t gpio_pin)
{
  PortGroup *const port_base = port_get_group_from_gpio_pin(gpio_pin);
  uint32_t pin_mask  = (1UL << (gpio_pin % 32));

  return (port_base->IN.reg & pin_mask);
}
---

---
static inline PortGroup* port_get_group_from_gpio_pin(
    const uint8_t gpio_pin)
{
  return system_pinmux_get_group_from_gpio_pin(gpio_pin);
}
---

---
static inline PortGroup* system_pinmux_get_group_from_gpio_pin(
    const uint8_t gpio_pin)
{
  uint8_t port_index  = (gpio_pin / 128);
  uint8_t group_index = (gpio_pin / 32);

  /* Array of available ports. */
  Port *const ports[PORT_INST_NUM] = PORT_INSTS;

  if (port_index < PORT_INST_NUM) {
    return &(ports[port_index]->Group[group_index]);
  } else {
    Assert(false);
    return NULL;
  }
}
---

Geil, also durch drei Funktionen um einen Pin einzulesen.
Auf die Art bringt es irgendwie wenig wenn der µC auf 48MHz läuft.

---
static inline void port_pin_set_output_level(
    const uint8_t gpio_pin,
    const bool level)
{
  PortGroup *const port_base = port_get_group_from_gpio_pin(gpio_pin);
  uint32_t pin_mask  = (1UL << (gpio_pin % 32));

  /* Set the pin to high or low atomically based on the requested level 
*/
  if (level) {
    port_base->OUTSET.reg = pin_mask;
  } else {
    port_base->OUTCLR.reg = pin_mask;
  }
}
---

Yup, der Werbe-Slogan von Atmel passt, die SAMD20 sind so leicht 
benutzbar wie die AVR...

von Random .. (thorstendb) Benutzerseite


Lesenswert?

bitte werft die CMSIS nicht mit den Hersteller-Driverlibs in einen Topf.

CMSIS standardisiert u.a. den Aufbau der MCU Headerfiles (die 
Peripherie-structs / mapping), das SVD (XML-)File (Register Viewer 
Daten, (=SystemViewer im MDK)) sowie den Device Startup incl. PLL Setup. 
Mittels SVDConv kann aus dem Device SVD file für einen Chip auch ein 
"plain" (also Driverlib-freies) C Headerfile erstellt werden.

Im zuge des Device startup wird vor dem Aufruf von pre-main (__main) die 
Funktion SystemInit() aus system_<device>.c aufgerufen, welche sich um 
das initiale PLL setup kümmert.

Ist man in main() angekommen, kann man mit der Hersteller-Lib 
weitermachen, oder sich ins Datenblatt stürzen und mit Registerzugriffen 
die Peripherie ansprechen (mein Favorit :-) ).

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Zu den abgedruckten codes oben:

Nutzt man die Atmel DriverLib, so kann man mWn. den gleichen Code für 
alle Atmels nutzen. Allerdings auch mit dem entsprechenden Overhead.

Geht es nur um einen speziellen Chip, schreibt das Zeug mit 
Registerzugriffen selbst, das wird wesentlich schneller und kompakter.

Es sei denn, Ausführungszeit & Codegrösse sind uninteressant :-)

: Bearbeitet durch User
von Rudolph (Gast)


Lesenswert?

Nochmal, mir geht es um einen Einstieg in die Controller, dazu gehört 
halt auch zu verstehen wie der Hersteller sich so im allgemeinen 
vorstellt wie man die Dinger benutzen soll.

Erstmal ja, das Ding wird system_init() irgendwie initialisiert,
da bin ich nichtmal drauf eingegangen.

Treiber vom Hersteller sind sicher eine tolle Sache, vor allem für 
komplexere Funktionen.

Aber auf dem Level auf dem ich gerade mit dem Ding rumspiele wirkt das 
seltsam.
Dann ist das ja auch ein CortexM0+, von Atmel ja auch als Alternative zu 
den AVRs positioniert für so Fälle wo es unbedingt ein ARM sein muss im 
Design, also ganz am unteren Ende und mit "nur" 48 MHz.
Wie lange dauert das Einschalten der LED in dem Beispiel? 20 Takte? 30? 
mehr?

Immerhin ist so zu sehen, dass man auch direkt

port_base->OUTSET.reg = (1UL << (LED_0_PIN % 32));

benutzen kann um die LED einzuschalten.

Wobei ich überhaupt noch kein Verständnis aufbringe für das Verpacken 
von Registern in Strukturen.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

> Wobei ich überhaupt noch kein Verständnis aufbringe für das Verpacken
> von Registern in Strukturen.

z.B. "Intellisense" :-)

Ich finde das wesentlich übersichtlicher als die #define Suppen, die es 
vielfach gab.

frage mich gerade, was der zusätzliche Level bei dir ist. Ich verwende 
auf SAM3X:
1
#define PIO_WRPROT        0x50494F00
2
3
#define LED_PWR_PORT        PIOB              // IO
4
#define LED_PWR_PIN         PIO_PB17
5
6
#define LED_PWR_ON            LED_PWR_PORT->PIO_CODR = LED_PWR_PIN
7
#define LED_PWR_OFF           LED_PWR_PORT->PIO_SODR = LED_PWR_PIN
8
#define LED_PWR_TOGGLE        { if (LED_PWR_PORT->PIO_PDSR & LED_PWR_PIN) LED_PWR_ON; else LED_PWR_OFF; }
9
10
11
  LED_PWR_PORT->PIO_WPMR = PIO_WRPROT | 0;       // Disable PIO write protection
12
  LED_PWR_PORT->PIO_PER  = LED_PWR_PIN;
13
  LED_PWR_PORT->PIO_OER  = LED_PWR_PIN;
14
  LED_PWR_PORT->PIO_PUDR = LED_PWR_PIN;
15
  LED_PWR_PORT->PIO_OWER = LED_PWR_PIN;
16
  LED_PWR_ON;

wobei die #defines der Portierbarkeit dienen.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rudolph schrieb:
> Wobei ich überhaupt noch kein Verständnis aufbringe für das Verpacken
> von Registern in Strukturen.

Das entspricht aber letztlich genau dem, wie die Hardware implementiert
ist: du hast eine Basisadresse (die wird irgendwo in einem Adressdekoder
verdrahtet) und verschiebliche Blöcke, die innerhalb dann jeweils
Offsets haben.  Sind mehrere gleichartige Blöcke implementiert, bleiben
die Offsets innerhalb des Blocks alle gleich, nur die Basisadresse
ändert sich.

Um eine UART also auf eine bestimmte Baudrate zu initialisieren, musst
du der init-Funktion nur den Basiszeiger auf den UART-Modul liefern.
Sowas beim (nicht-Xmega-)AVR zu machen, war eher ein Krampf, den man
höchstens über Präprozessortricks flexibel implementieren konnte.

von Rudolph (Gast)


Lesenswert?

Random ... schrieb:
> frage mich gerade, was der zusätzliche Level bei dir ist

Nicht zusätzlich, Schritt eins, Basic-I/O.


@Jörg

Auf dem AVR schreibe ich mir im Moment eine timer0_init(), timer1_init() 
und so weiter, genauso würde ich uart0_init() und uart1_init() anlegen.
Warum überhaupt Funktionen? Recycling.

FLASH ist eher nicht das Problem und durchlaufen wird das normalerweise 
nur einmal.
Im einfachsten Fall copy/paste und im wesentlichen xxxx0 Register auf 
xxx1 Register ändern plus eben die Konfiguration anpassen.

Mit dem Spass kann man sich also eine uart_init(unit, baud) schreiben in 
der man mehrere Einheiten behandeln kann.

Sorry, aber schmackhaft macht mir die Nummer sowas auch nicht da mir 
gerade nichts einfällt wo ich das machen wollen würde.

Ach ja, als Argument, die Hardware ist so organisiert.
Nur, wo in welcher Reihenfolge die Register liegen interessiert mich 
beim AVR ja auch schon nicht. Klar gehört zu PORTB und DDRB eine 
Adresse, welche das ist muss aber der Compiler wissen.

Schön, dass das beim ARM so aufgeräumt ist, der Adressraum gibt eben 
viel mehr her, da kann man dann auch mal PORTA auf 0x41004400 und PORTB 
auf 0x41004480 anfangen lassen.

port_base->OUTSET.reg = (1UL << (LED_0_PIN % 32));

Bedeutet doch im wesentlichen, dass man noch mehr immer gleichen Text 
ohne veränderliche Information tippen muss.
Wo kommt da überhaupt das ".reg" noch her?

OUTSETB = PB12;

So in der Art, das hätte mal was, vor allem auch das ganze Bit-schiebe 
Gefummel nicht immer wieder.
Das würde der Compiler mit den entsprechenden Includes auch hinbekommen.
Ich schweife ab. :-)

Was mir eigentlich fehlt ist immer noch eine Anleitung wie man das ganze 
so wie es gedacht ist benutzen können soll.
Und zwar halbwegs effizient und nicht 
wir-setzen-ein-Ausgangs-Bit-in-nur-20-Taktzyklen-und-sind-langsamer-als- 
ein-AVR-auf-4-MHz  ...

von Lothar (Gast)


Lesenswert?

Rudolph schrieb:
> port_base->OUTSET.reg = (1UL << (LED_0_PIN % 32));

Das ist vermutlich das genannte Bitbanding. Der AVR kann ja auf RAM wie 
auf Register zugreifen (wie schon der 8051), somit kann man dort direkt 
im Port ein Bit setzen (ist natürlich nicht wirklich RISC). Der ARM 
trennt aber Register und RAM, somit müsste man den Port auslesen, Bit 
setzen und wieder zurück schreiben.

Da aber der ARM einen riesigen Adressraum hat, kann man einfach jedes 
Port-Bit einer 32-Bit aligned Adresse zuweisen und dort dann 0 oder >0 
rein schreiben. Wird übrigens bei der ganzen ARM Peripherie so gemacht.

von Rudolph (Gast)


Lesenswert?

Nope, kein Bitbanding, der SAMD20 kann das.
Der hat pro Port etliche Register was erstmal sehr nett ist.

Und OUTSET ist das Register mit dem einfach die entsprechenden Bits auf 
1 setzen kann, also ohne read-modify-write.

von Lothar (Gast)


Lesenswert?

Rudolph schrieb:
> Der hat pro Port etliche Register was erstmal sehr nett ist.

Haben die NXP ARM auch (neben PIN0 auch SET0, CLR0, NOT0) aber eben auch 
Bitbanding, was wegen der Portierbarkeit auch üblicherweise genutzt 
wird. Hat das der SAMD20 wirklich nicht?

von Rudolph (Gast)


Lesenswert?

This register allows the user to set one or more output I/O pin drive 
levels high, without doing a read-modify-write
operation. Changes in this register willalso be reflected in the Data 
Output Value (OUT), Data Output Value Toggle
(OUTTGL) and Data Output Value Clear (OUTCLR) registers.

Da sind etliche getrennte 32 Bit Register im Satz drin.

von Deppenhauer (Gast)


Lesenswert?

Rudolph schrieb:
> OUTSETB = PB12;
>
> So in der Art, das hätte mal was

Dann mach es doch einfach:

(uint32_t)(*(BASE_OF_PORT + OFFSET_OUTSETB)) = BIT_22;

Du kannst beim ARM genauso weiter machen, wie im AVR Kindergarten. Du 
kannst aber auch am Fortschritt teilhaben. ;-)

von Markus H. (dasrotemopped)


Lesenswert?

Ich bin grade auch an meinem ersten samd20 Board dran, weil ich diesen 
ARM als noch recht übersichtlich einschätze. Beim atmega musste ich 
recht ressourcensparend jede Hardwarekomponente selbst initialisieren 
und eine Routine dafür schreiben. Hat mich ziemlich gebremst was den 
Programmieraufwand angeht. Mit ASF sind schon alle Hardwaretreiber 
geschrieben und ich kann mich schneller auf die eigentliche Funktion der 
Schaltung konzentrieren. Setzt bei mir allerdings ein Einarbeiten in ASF 
voraus und meine C-Kenntnisse müssen geschärft werden.
Mit dem Einbinden von ASF.h wird halt ein struct für jeden Registersatz 
von jeder Funktionseinheit reserviert, das kostet erst mal Platz. Und 
die Initialisierung  jeder Funktionseinheit erfolgt mit system.init() 
auf Defaultwerte. Die nötigen Routinen belegen auch etwas Platz. Aber 
alles was dann noch frei ist steht komplett meinem Programm zur 
Verfügung. Und das ist mehr als die meisten atmegas haben. +32 bit +48 
MHz

Ich finde auch, das ASF komplex ist, aber es erleichtert das 
Programmiererleben. Es erspart aber nicht das Lesen des Datasheets, denn 
man muss wissen, was der uC kann und mit welchen Registerwerten in den 
Structs ich die Hardware einrichten muss. Und ich muss mich mit 
Zeigerarithmetik in C auskennen, das habe ich auf dem ATmega bis jetzt 
nicht gebraucht.

Gruß,

dasrotemopped.

von Rudolph (Gast)


Lesenswert?

Hmm, wenn das ASF wirklich erstmal alles anlegt könnte das den kranken 
SRAM Verbrauch erklären, alleine jeder Port hat ja schon 13 Register.

Allerdings, in dem leeren Projekt sind quasi keine Treiber drin.
Es finden sich unter ASF/sam0/drivers gerade mal port und system.
Die stumpfe initialisierung alle Komponenten macht auch keinen Sinn weil 
die Register sowieso Reset-Default Werte haben.

>Aber alles was dann noch frei ist steht komplett meinem
>Programm zur Verfügung.

Ja okay, aber 29% SRAM per Default weg?.
Auf dem Xplained ist ja auch der dickste SAMD20J drauf.
Die gleichen Steine gibt es aber auch mit 2/4/8 KB SRAM, das würde
so ja nichtmal für ein leeres Projekt reichen.

>Und das ist mehr als die meisten atmegas haben.
>+32 bit +48 MHz

32 Bit muss man erstmal brauchen, das ist keine Wunderwaffe die alles 
schneller und besser macht.
Und 48 MHz sind auch nicht so richtig nutzbar wenn man wie von Atmel 
vorgeschlagen die Treiber benutzt die für eine atomare Bit-Operation 
durch drei Funktionen und zurück laufen.

Zeigerarithmetik, sowas habe ich zuletzt beim Amiga gemacht und musste 
später lernen das man sowas nach diversen Kodierungsrichtlinien nicht 
machen darf, also das direkte manipulieren von Zeigern.

Also sowas darf man nicht:
for (T* p = arr; p != arr + numElements; ++p) {
    *p = foo(*p);
}

Während das hier okay ist:
for (size_t i = 0; i != numElements; ++i) {
    arr[i] = foo(arr[i]);
}

von davichii (Gast)


Lesenswert?

Rudolph schrieb:
> das ASF

ist ein Konzept von Atmel. Das gibt es für AVRs und für ARMs. Man muss 
es nicht nehmen. ;-P

Rudolph schrieb:
> Also sowas darf man nicht:
> for (T* p = arr; p != arr + numElements; ++p) {
>     *p = foo(*p);
> }

Natürlich nicht, denn es ist nicht das, was der Designer wünscht:
p != arr + numElements;
Wie groß ist sizeof(T)? Wann bricht die Schleife ab?

for (T* p = arr; p < (arr + (numElements * sizeof (T))); p++)
  *p = foo (*p);

von Lothar (Gast)


Lesenswert?

Rudolph schrieb:
> Ja okay, aber 29% SRAM per Default weg?

Die system_init() dürfte das Problem sein, hier könnte alles 
initialisiert werden (vielleicht sogar RAM für FIFO, DMA). Atmel hat 
wahrscheinlich das Konzept von den grossen SAM3/4 mit reichlich RAM auf 
die Kleinen übertragen.

von Steffen (Gast)


Lesenswert?

Hallo allerseits,

ich hatte dasselbe Problem mit dem stark belegten RAM ohne viel Code.

Lest mal folgende Appnote von Atmel: "AT08569: Optimizing ASF Code Size 
to Minimize Flash and RAM Usage."

Darin steht, dass standardmäßig 25% des RAM als Stack-Vorhalt reserviert 
werden. Man kann die Größe per Compilerschalter ändern, es wird auch 
erklärt, wie...

Gruß, Steffen

von Rudolph (Gast)


Lesenswert?

Danke für den Hinweis.
1
#include "sam.h"
2
3
int main(void)
4
{
5
  uint32_t x;
6
  
7
    /* Initialize the SAM system */
8
    SystemInit();
9
  
10
  PORT->Group[0].DIRSET.reg = PORT_PA14;
11
12
    while (1) 
13
    {
14
    PORT->Group[0].OUTTGL.reg = PORT_PA14;
15
    
16
    for(x=0; x<50000;x++)
17
    {
18
     asm volatile ("nop");
19
    }
20
     
21
/*    
22
     if (PORT->Group[0].IN.reg & PORT_PA15)
23
     {
24
       PORT->Group[0].OUTSET.reg = PORT_PA14;
25
     }
26
     else
27
     {
28
       PORT->Group[0].OUTCLR.reg = PORT_PA14;
29
     }
30
*/
31
  }
32
}

Das sieht schon etwas freundlicher aus mit 
"-Wl,--defsym,__stack_size__=0x1000" in den Linker-Optionen.
Da "verschwindet" zwar immer noch 1k SRAM irgendwo, aber der grösste 
Happen ist damit sauber erklärt und kontrollierbar.

von Rudolph (Gast)


Lesenswert?

Die Register kann man auch anders schreiben, das gefällt mir so 
eigentlich noch besser:
1
#include "sam.h"
2
3
int main(void)
4
{
5
 SystemInit();
6
  
7
 REG_PORT_DIRSET0 = PORT_PA14;
8
  
9
 while (1) 
10
 {
11
  REG_PORT_OUTTGL0 = PORT_PA14;
12
 }
13
}

Es geht also doch erheblich einfacher mit den mitgelieferten Includes. 
:-)

Damit wackelt der Pin jetzt mit um die 100kHz.

Der SAMD20J18 läuft dabei wahrscheinlich noch lange nicht mit den 48MHz 
die der kann.
Die 70 Seiten Datenblatt auf denen die Clock-Konfiguration beschrieben 
ist verwirren mich aber gerade noch ein wenig.
Hat mal bitte wer ein paar Zeilen mit Register-Zugriff anhand derer ich 
mir ansehen kann, wie man das Ding einstellt?

von Rudolph (Gast)


Lesenswert?

Hmm, ich habe jetzt das Beispiel-Programm aus dem Atmel-Studio für die 
Takt-Einstellung benutzt, clock_quick_start oder so.

"\brief SAM System Clock Driver Quick Start"

Blöderweise wird da so überhaupt nichts erklärt.
Hunderte Zeilen Code ohne Erklärung dazu.
In der qs_clock_source.c passiert irgendwas, etliche Zeilen sind aber 
auskommentiert.

Bekomme ich irgendwie raus, auf welchem Takt der SAMD20 jetzt wirklich 
läuft?

Das Wackeln mit dem Pin passiert jetzt mit 3,81 MHz.
Ist der SAMD20 wirklich so langsam?
Sowas wie "system_flash_set_waitstates(2);" gibt mir ja auch zu denken.

Ein AVR der mit 16 MHz getaktet ist und nichts anderes machen soll kann 
einen Pin jedenfalls mit 4 MHz wackeln lassen.

von Stephan (Gast)


Lesenswert?

Hey Rudolph,

laut dieser Quelle sollte es 2 Funktionen geben mit der du Frequenzen 
auslesen kannst.

http://asf.atmel.com/docs/3.16.0/samd21/html/group__asfdoc__sam0__system__clock__group.html#ga8af98ce1562b894524dd6f5cd4db8f1d
1
uint32_t   system_gclk_gen_get_hz (const uint8_t generator)
2
   Retrieves the clock frequency of a Generic Clock generator. More...
3
 
4
uint32_t   system_gclk_chan_get_hz (const uint8_t channel)
5
   Retrieves the clock frequency of a Generic Clock channel. More...

ob es dir weiter hilft weiß ich aber nicht.

von Rudolph R. (rudolph)


Lesenswert?

Stephan schrieb:
> ob es dir weiter hilft weiß ich aber nicht.

Der Link ist hilfreich, vielleicht, verstehen tue ich das gerade 
trotzdem nicht weil mich das ganze Ding einfach mit Information 
erschlägt. :-)

Aber spontan hilft mir eine Funktion zum Auslesen leider nicht weiter,
weil ich das Ergebnis nirgendwo ausgeben kann. :-)

von Rudolph R. (rudolph)


Lesenswert?

Ich habe da gerade was zu gefunden:

http://www.at91.com/discussions/viewtopic.php/f,31/t,24666.html

Und gerade mal etwas umgestrickt:
1
#include "sam.h"
2
3
void clock_init(void)
4
{
5
  // Enable the XOSC32K and set the start up time
6
//  SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_STARTUP(6) | SYSCTRL_XOSC32K_EN32K | SYSCTRL_XOSC32K_XTALEN | SYSCTRL_XOSC32K_ENABLE;
7
  REG_SYSCTRL_XOSC32K = SYSCTRL_XOSC32K_STARTUP(6) | SYSCTRL_XOSC32K_EN32K | SYSCTRL_XOSC32K_XTALEN | SYSCTRL_XOSC32K_ENABLE;
8
  // Wait for the XOSC32K is stable and ready
9
//  while(!SYSCTRL->PCLKSR.bit.XOSC32KRDY);
10
  while((REG_SYSCTRL_PCLKSR & SYSCTRL_PCLKSR_XOSC32KRDY) == 0);
11
  // Enable the Generic Clock GEN 1 and Configure the XOSC32K as Clock Source for it
12
//  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID_GCLK1 | GCLK_GENCTRL_SRC_XOSC32K |    GCLK_GENCTRL_GENEN | GCLK_GENCTRL_IDC;
13
  REG_GCLK_GENCTRL = GCLK_GENCTRL_ID_GCLK1 | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_IDC;
14
  // Wait for the synchronization between clock domain is complete
15
//  while(GCLK->STATUS.bit.SYNCBUSY);
16
  while((REG_GCLK_STATUS & GCLK_STATUS_SYNCBUSY) != 0);
17
  // Enable the DFLL and set the operation mode as closed loop
18
//  SYSCTRL->DFLLCTRL.reg = SYSCTRL_DFLLCTRL_ENABLE | SYSCTRL_DFLLCTRL_MODE;
19
  REG_SYSCTRL_DFLLCTRL = SYSCTRL_DFLLCTRL_ENABLE | SYSCTRL_DFLLCTRL_MODE;
20
  // Wait for the synchronization between clock domain is complete
21
//  while(!SYSCTRL->PCLKSR.bit.DFLLRDY);
22
  while((REG_SYSCTRL_PCLKSR & SYSCTRL_PCLKSR_DFLLRDY) == 0);
23
  // Load the Multiply factor, Coarse Step and fine Step for DFLL
24
//  SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_CSTEP(0x1F/4) | SYSCTRL_DFLLMUL_FSTEP(0xFF/4) |  SYSCTRL_DFLLMUL_MUL(1465);
25
  REG_SYSCTRL_DFLLMUL = SYSCTRL_DFLLMUL_CSTEP(0x1F/4) | SYSCTRL_DFLLMUL_FSTEP(0xFF/4) |  SYSCTRL_DFLLMUL_MUL(1465);
26
  // Enable the Generic Clock GEN 1 as DFLL48 as Reference
27
//  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_ID(0);
28
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_ID(0);
29
  // wait for fine lock
30
//  while(!SYSCTRL->PCLKSR.bit.DFLLLCKF);
31
  while((REG_SYSCTRL_PCLKSR & SYSCTRL_PCLKSR_DFLLLCKF) == 0);
32
  // Set the NVM Read Wait States to 1, Since the operating frequency 48 MHz
33
  NVMCTRL->CTRLB.bit.RWS = 1;
34
//  REG_NVMCTRL_CTRLB != NVMCTRL_CTRLB_RWS(1);
35
  //  Enable the Generic Clock 0 and Configure the DFLL as Clock Source for it
36
//  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID_GCLK0 | GCLK_GENCTRL_SRC_DFLL48M |  GCLK_GENCTRL_GENEN | GCLK_GENCTRL_IDC;
37
  REG_GCLK_GENCTRL = GCLK_GENCTRL_ID_GCLK0 | GCLK_GENCTRL_SRC_DFLL48M |  GCLK_GENCTRL_GENEN | GCLK_GENCTRL_IDC;
38
  // Wait for the synchronization between clock domain is complete
39
//  while(GCLK->STATUS.bit.SYNCBUSY);
40
  while((REG_GCLK_STATUS & GCLK_STATUS_SYNCBUSY) != 0);
41
}
42
43
int main(void)
44
{
45
//  SystemInit();
46
  clock_init();
47
  
48
  REG_PORT_DIRSET0 = PORT_PA14;
49
  
50
  while (1)
51
  {
52
    REG_PORT_OUTTGL0 = PORT_PA14;
53
  }
54
}

Ich muss dann morgen mal testen, wie schnell der SAMD20 damit den Pin 
wackeln lassen kann.

Interesant ist jetzt, dass gegenüber dem AFS-Treiber die 
Flash-Wait-States auf 1 statt auf 2 gesetzt werden.

Wobei ich "NVMCTRL->CTRLB.bit.RWS = 1;" gerne auch noch umgeschrieben 
hätte, dazu ist mir spontan aber nichts elegantes eingefallen.
Das wieder auskommentierte "REG_NVMCTRL_CTRLB != NVMCTRL_CTRLB_RWS(1);" 
haut ja so nicht hin.
Man müsste ja erstmal die Bits löschen bevor man Oder-verknüpft.
Der Zugriff mit den Strukturen ist vielleicht doch nicht so schlecht, 
dann kümmert sich wenigstens der Compiler um das Problem. :-)

Merkwürdig ist dann nur noch, dass das Syntax-Highlighting im Atmel 
Studio das "CTRLB.bit.RWS" nicht richtig einfärbt.

von Rudolph (Gast)


Lesenswert?

Hmm.

3,0 MHz mit -Os, 4,8 MHz mit -O3.

-Os:
1
  REG_PORT_DIRSET0 = PORT_PA17;
2
 21e:  2380        movs  r3, #128  ; 0x80
3
 220:  4a04        ldr  r2, [pc, #16]  ; (234 <main+0x20>)
4
 222:  029b        lsls  r3, r3, #10
5
 224:  6013        str  r3, [r2, #0]
6
  
7
    while (1) 
8
    {
9
    REG_PORT_OUTTGL0 = PORT_PA17;
10
 226:  4a04        ldr  r2, [pc, #16]  ; (238 <main+0x24>)
11
 228:  e7fc        b.n  224 <main+0x10>

-O3:
1
  REG_PORT_DIRSET0 = PORT_PA17;
2
 232:  4a06        ldr  r2, [pc, #24]  ; (24c <main+0x24>)
3
 234:  2380        movs  r3, #128  ; 0x80
4
 236:  029b        lsls  r3, r3, #10
5
 238:  6013        str  r3, [r2, #0]
6
  
7
    while (1) 
8
    {
9
    REG_PORT_OUTTGL0 = PORT_PA17;
10
 23a:  4a05        ldr  r2, [pc, #20]  ; (250 <main+0x28>)
11
 23c:  6013        str  r3, [r2, #0]
12
 23e:  6013        str  r3, [r2, #0]
13
 240:  e7fc        b.n  23c <main+0x14>

Das müssten dann entsprechend 8 Taktzyklen für die Schleife sein bei -Os 
und 5 bei -O3.

So richtig schnell finde ich das jetzt nicht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rudolph schrieb:
> So richtig schnell finde ich das jetzt nicht.

Der AVR ist auch nahezu unschlagbar, was das Wackeln mit den IO-Pins
betrifft. ;-)  Auch ein MSP430 ist da beispielsweise langsamer.

Wenn es dir also auf extreme IO-Pin-Performance ankommt, nimm lieber
einen Xmega.  Wenn du Rechenleistung brauchst, ist der ARM jedoch
besser …

Rudolph schrieb:
> Wo kommt da überhaupt das ".reg" noch her?

Weil .reg jeweils das ganze Register mit seinen 32 bits zugreifen
lässt, während es parallel noch .bit-Definitionen gibt für den
bitweisen Zugriff.  Du kannst also auch schreiben:
1
port_base->OUTSET.bit.OUTSET[LED_0_PIN] = 1;

Das ist zugegebenermaßen für die PIO-Ports nicht sehr lukrativ, aber
bei den Steuerregistern anderer Baugruppen macht sich es schon ganz
nett:
1
SERCOM0->USART.INTFLAG.bit.TXC = 1;

Gegenüber der Variante
1
SERCOM0->USART.INTFLAG.reg = SERCOM_USART_INTFLAG_TXC;

hast du dabei den Vorteil, dass du nicht aus Versehen versuchen kannst,
das Bit in einem falschen Register zu setzen:
1
SERCOM0->USART.CTRLA.reg = SERCOM_USART_INTFLAG_TXC; /* falsch, aber compiliert */
2
SERCOM0->USART.CTRLA.bit.TXC = 1; /* compiliert gar nicht erst */

von Lothar (Gast)


Lesenswert?

Die schon erwähnten NXP ARM haben zum Pin-Toggeln ein NOT-Register 
(sollten die SAMD20 aber auch haben) z.B. P0.4:

        MOVS R0, #10000B        ; R0=bit.Px_4
        LDR  R1, DIR0           ; R1=DIR0=0xA0002000
        STR  R0, [R1]           ; DIR0_bit.P0_4 = 1 (output)

        LDR  R1, NOT0           ; R1=NOT0=0xA0002300
loop
        STR  R0, [R1]           ; NOT0_bit.P0_4 = 1 (toggle)
        B    loop               ; loop

Jörg W. schrieb:
> Der AVR ist auch nahezu unschlagbar

8051 ist schneller:

        CPL  P0.4

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Lothar schrieb:
> 8051 ist schneller:

Ja, Umschalten zwischen Input mit Pullup und Output/low kann in der
Tat niemand so schnell wie ein 8051. ;-)

> haben zum Pin-Toggeln ein NOT-Register (sollten die SAMD20 aber auch
> haben

Ja, heißt dort OUTTGL (ein DIRTGL gibt's auch, dürfte man aber wohl
seltener brauchen).

von Rudolph (Gast)


Lesenswert?

Ich benutze doch schon das Output-Toggle:
1
  while (1)
2
  {
3
    REG_PORT_OUTTGL0 = PORT_PA17;
4
  }

Die Schleife braucht so 5 oder 8 Takt-Zyklen.

Und ja, okay, die absolute Performance ist mir gerade noch völlig egal,
das fällt alles noch unter rum spielen. :-)
Das Ziel war dann ja auch eigentlich, den SAMD20 auf die versprochenen 
48 MHz zu bringen.

Irgendwie fehlt mir auch immer noch ein Grund, von AVR auf ARM zu 
wechseln. :-)
Zumindest auf Cortex M0.
Wobei ich ja auf die SAMC21 schiele, wegen der zwei CANs.


Jörg W. schrieb:
> Der AVR ist auch nahezu unschlagbar, was das Wackeln mit den IO-Pins
> betrifft.

Naja, ich habe einen 90CAN32 auf 16MHz hiermit gefüttert:
1
    while(1)
2
    {
3
    PINC = (1<<PC0);
4
    }

Und bei -O3 kam das hier raus:
1
  b0:  86 b9         out  0x06, r24  ; 6
2
  b2:  86 b9         out  0x06, r24  ; 6
3
  b4:  fd cf         rjmp  .-6        ; 0xb0 <main+0x4>

Bei näherer Betrachtung müsste man den AVR wegen Cheatens 
disqualifizieren. :-)
Oder den Compiler, auf jeden Fall ist es seltsam, dass zweimal 
hintereinander ins Register geschrieben wird.

Andererseits, der ARM macht das ja auch:
1
 23c:  6013        str  r3, [r2, #0]
2
 23e:  6013        str  r3, [r2, #0]
3
 240:  e7fc        b.n  23c <main+0x14>

von Markus H. (traumflug)


Lesenswert?

Rudolph schrieb:
> Irgendwie fehlt mir auch immer noch ein Grund, von AVR auf ARM zu
> wechseln. :-)
> Zumindest auf Cortex M0.

Die letzten Wochen habe ich eine 3D-Drucker-Firmware ( = Geschwindigkeit 
zählt, sehr viel 32-Bit-Integer-Mathematik), die traditionell (fast) nur 
auf ATmegas zu Hause war, auf einen NXP LPC1114 portiert. Dabei gelernt:

1. Beim ARM ist das kompilierte Binary deutlich kleiner, nur etwas mehr 
als halb so gross.

2. Der ARM hat einen kleinen (16 Byte) Puffer am UART, der meist eine 
eigene Implementierung und damit einen Interrupt spart. Oder Interrupt 
nur alle 14 Bytes.

3. Will man mehrere analoge Pins auslesen, muss man beim AVR per 
Interrupt den Pin weiter schalten, der ARM kann das alleine und 
speichert die gelesenen Werte in jeweils ein eigenes Register. Spart 
noch einen Interrupt.

4. Lässt man MBED (noch abstraktere Bibliotheken sowieso) beiseite, ist 
die Sache auch schnell und ähnlich wie ein AVR zu programmieren. Eher 
etwas einfacher, weil die Register (z.B. Timer) nicht ganz so kryptisch 
sind.

5. Anders als MBED: CMSIS taugt.

6. Der Dreh- und Angelpunkt der Geschwindigkeit bei so einer Firmware 
ist der Bresenham-Algorithmus, also 32-Bit-Integer-Additionen und 
-Subtraktionen. Der braucht nach wie vor rund 300 CPU-Takte. Lediglich 
der 32-Bit-Timer hat etwas gespart, der wird auf AVR mit einem 
16-Bit-Timer emuliert, was einige Takte kostet. 32-Bitter sind also 
selbst bei 32-Bit-Integer-Mathematik nicht schneller pro Takt. Was dem 
ATmega an Registerbreite fehlt verbraucht der LPC offensichtlich mit 
Laden und wegspeichern von diesen Registern. Ob das bei den Cortex-M3 
und -M4 besser aussieht weiss ich nicht.

7. Der gcc lässt Optimierungs-Wünsche offen. -Os läuft am schnellsten, 
schneller als -O2 und -O3. Gucke ich in den Assembler, sehe ich meist 
auf Anhieb Möglichkeiten zu kleinen Verbesserungen.


Wen das interessiert, der Port ist hier:

https://github.com/Traumflug/Teacup_Firmware/commits/040e95b555bccdd45d6c1c55c82d1d4122bde1b8

Die letzten 87 commits. GPIO mit Bit-Banding ist auch dabei. Alles in 
kleinen Häppchen gemacht. Man kann recht gut äquivalenten Code auf 
ATmega und LPC gegenüber stellen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Markus H. schrieb:
> Dabei gelernt:

Sind aber zu großen Teilen keine ARM-Features als solches, sondern
Features der jeweiligen Peripherals, und die unterscheiden sich
zwischen den Herstellern teils erheblich.  So hat ein SAMD20 in der
SERCOM (mit der man die UART realisiert) zwar im Gegensatz zu deinem
NXP nur einen zweistufigen Empfangspuffer, andererseits könnte man sich
beim Nachfolger SAMD21 per DMA einen „automatischen“ Empfangspuffer
beliebiger Größe zimmern.

Interessant finde ich deine Feststellungen bezüglich der
32-bit-Ganzzahl-Arithmetik.  Das war ja nun lange Zeit eher nicht
die Stärke des AVR-GCC.

von Rudolph R. (rudolph)


Lesenswert?

Markus H. schrieb:
> 1. Beim ARM ist das kompilierte Binary deutlich kleiner,

Ich bekomme normalerweise schon die 8k Atmels nicht voll und könnte dann 
immer noch mindestens zwei Schritte aufrüsten.
Dagegen sind die ARMs aber so ziemlich immer üppig mit Speicher 
ausgestattet.

Oh ja, viel SRAM haben die ARMs auch, scheinbar brauchen die aber auch 
richtig viel davon für den Stack, das ist dann nicht mal für die 
Anwendung benutzbar.

> 2. Der ARM hat einen kleinen (16 Byte) Puffer am UART,

UART benutze ich fast nur für LIN und das ist soooo langsam. :-)

> 3. Will man mehrere analoge Pins auslesen, muss man beim AVR per
> Interrupt den Pin weiter schalten,

Das ist halt wie die Peripherie gestrickt ist, bei Freescales MC9S12 
kann man auch Sequenzen anlegen.

> 4. Lässt man MBED (noch abstraktere Bibliotheken sowieso) beiseite, ist
> die Sache auch schnell und ähnlich wie ein AVR zu programmieren.
> Eher etwas einfacher, weil die Register (z.B. Timer) nicht ganz so
> kryptisch sind.

Naja, es gibt von allem erheblich mehr.
Ich schrieb oben was von 70 Seiten zur Takt-Konfiguration und habe mich 
dabei noch vertan, das sind gut 100 Seiten.

Und dann bekommt es Atmel nicht mal hin zu dokumentieren wie man 
Register ansprechen soll.

> 5. Anders als MBED: CMSIS taugt.

Bezieht sich CMSIS nicht nur rein auf den Core?

> 6. Der Dreh- und Angelpunkt der Geschwindigkeit bei so einer Firmware
> ist der Bresenham-Algorithmus, also 32-Bit-Integer-Additionen und
> -Subtraktionen. Der braucht nach wie vor rund 300 CPU-Takte.

Es gibt immer mehrere Wege ans Ziel. :-)

> 7. Der gcc lässt Optimierungs-Wünsche offen.

Dabei würde es mich wundern, wenn nicht zig verschiedene GCC Versionen 
im Einsatz wären.
Mein Atmel Studio hat gerade Atmel ARM GNU Toolchain 4.8.4.1443 drunter 
mit GCC 4.8.4 mit CMSIS 4.2.0.
Aktuell wäre 4.8.5, 4.9.3 oder 5.2 und CMSIS 4.3.0.

Für den AVR ist es GCC 4.8.1 und für AVR32 gar 4.4.7.

Also der Zoo an Tools wird doch eigentlich immer bunter, obwohl vieles 
davon GCC ist.

von Lothar (Gast)


Lesenswert?

Rudolph R. schrieb:
> Ich bekomme normalerweise schon die 8k Atmels nicht voll

Es verkaufen sich allerdings 8051 mit 128k und Cortex mit 1M ganz 
ordentlich.

Rudolph R. schrieb:
> viel SRAM haben die ARMs auch, scheinbar brauchen die aber auch
> richtig viel davon für den Stack

Offensichtlich nur die von Atmel :-)

Rudolph R. schrieb:
> 70 Seiten zur Takt-Konfiguration

Grade nachgesehen, bei NXP nimmt "Configure the main clock and system 
clock" weniger als eine Seite sein. Was kann man schon groß machen: 
interner Oszillator oder externer Quarz, die PLL auf ganzzahliges 
Vielfaches von 12 MHz. Etwas schwierig sind nur krumme Frequenzen, das 
geht dann so:

https://www.lpcware.com/content/nxpfile/lpc11xx-main-pll-calculator

Rudolph R. schrieb:
> Bezieht sich CMSIS nicht nur rein auf den Core?

Bei NXP ist auch die Peripherie drin, mit direktem Registerzugriff, also 
kein signifikanter Overhead.

von Markus H. (traumflug)


Lesenswert?

Rudolph R. schrieb:
> Oh ja, viel SRAM haben die ARMs auch

Einige Typen haben ja mehr RAM als Flash. Da ist wohl vorgesehen, beim 
Startup den ganzen Flash ins RAM zu kopieren, um schneller darauf 
zugreifen zu können.

Rudolph R. schrieb:
>> 4. Lässt man MBED (noch abstraktere Bibliotheken sowieso) beiseite, ist
>> die Sache auch schnell und ähnlich wie ein AVR zu programmieren.
>> Eher etwas einfacher, weil die Register (z.B. Timer) nicht ganz so
>> kryptisch sind.
>
> Naja, es gibt von allem erheblich mehr.

Beim LPC1114 eigentlich nicht. Der kann ziemlich genau das, was ein 
ATmega328 auch kann. Klar, bei den ARMs gibt es nach oben ungleich mehr 
Optionen.

Rudolph R. schrieb:
>> 5. Anders als MBED: CMSIS taugt.
>
> Bezieht sich CMSIS nicht nur rein auf den Core?

Bei CMSIS, zumindest so wie ich das aus MBED raus gezogen habe, sind 
auch die ganzen Registerdefinitionen und -adressen mit drin. Damit hat 
man dann in etwa das Equipment, das der avr-gcc selbst liefert. Nur kein 
-mmcu, das muss man über eine lange Latte -I flags oder eben durch 
rauskopieren lösen.

Da wären noch eine ganze Reihe mehr Dateien, doch mit diesen sechs bin 
ich gut klar gekommen (der Prefix cmsis- ist von mir):

cmsis-core_cm0.h
cmsis-lpc1114.ld
cmsis-lpc11xx.h
cmsis-startup_lpc11xx.s
cmsis-system_lpc11xx.c
cmsis-system_lpc11xx.h

Praktisch ist, dass die Registergruppen als Strukte angelegt sind, da 
vertuddelt man sich nicht so leicht. Die Bezeichner der einzelnen Bits 
sind dagegen extrem lang, da blieb es dann doch bei ((1 << 0) | (1 << 
7)) & Co., ist im Handbuch schön so angegeben.

Rudolph R. schrieb:
> Dabei würde es mich wundern, wenn nicht zig verschiedene GCC Versionen
> im Einsatz wären.

Mit diesen IDEs habe ich es nicht so, auf Linux machen die alles nur 
umständlicher. Deswegen der gcc, der von Terry Guo (ein ARM-Mitarbeiter) 
zur Verfügung gestellt wird; der mit Ubuntu mitgelieferte ist nur wenig 
älter:

$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 4.9.3 20150529 
(release) [ARM/embedded-4_9-branch revision 224288]

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.