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?
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.
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.
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.
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
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...
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 :-) ).
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 :-)
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.
> 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:
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.
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 ...
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.
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.
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?
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.
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. ;-)
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.
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]);
}
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);
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.
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
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.
Die Register kann man auch anders schreiben, das gefällt mir so
eigentlich noch besser:
1
#include"sam.h"
2
3
intmain(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?
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.
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. :-)
// 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
intmain(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.
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:
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 unschlagbar8051 ist schneller:
CPL P0.4
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).
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:86b9out0x06,r24;6
2
b2:86b9out0x06,r24;6
3
b4:fdcfrjmp.-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:
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.
Markus H. schrieb:> Dabei gelernt:
Sind aber zu großen Teilen keineARM-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.
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.
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-calculatorRudolph 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.
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]