Forum: Compiler & IDEs Deklaration von external memory


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Thomas P. (topla)


Lesenswert?

Hallo zusammen,
kurz zur Vorgeschichte: zur Zeit sitze ich am Redesign einer 20 Jahre 
alten Baugruppe, die dabei von einem Dallas 80C320 auf einen ATMega2560 
umgestellt wird. Hauptsächliche Gründe sind eine dringend benötigte 3. 
serielle Schnittstelle, der Wunsch nach Flash anstelle von EPROM und 
mehr als die derzeit vorhandenen 32k RAM. Programmiert ist das Ganze in 
Assembler, da bin ich hinreichend fit auf der 51er Strecke.
Jetzt ist die Hardware fertig und ich sinne darüber nach, ob ich die 
Portierung von 51er Assembler nach AVR-Assembler ausführe oder mal 
wieder versuche, mit C zu beginnen. Bis jetzt ist das immer erfolgreich 
gescheitert und spätestens an dem Punkt, wo der Hinweis kam, bei 
Problemen doch gefälligst im Assemblerlisting nachzuschauen, habe ich 
mir dann gesagt, dann kann ich das gleich in Assembler programmieren 
(und habe das dann auch getan).
Jetzt habe ich mit dem AVR Studio 4.18 mal ein C-Projekt angelegt und 
mich mit ersten Schritten versucht. Problem ist schon die Darstellung 
der Anbindung des externen RAMs und Unterscheidung in den internen 
RAM-Bereich 200h – 21FFh und den externen Bereich 2200h – FFFFh bei der 
Deklaration von Variablen. Der Quelltext sieht jetzt so aus (die 
Linkeroption steht mit dabei):
1
#define F_CPU 16000000UL
2
#include <avr/io.h>
3
#include <lcd.h>
4
#include <Initialisierung.h>
5
#include <eeprom.h>
6
7
#define EXTMEM __attribute__((section(".EXTMEM")))
8
/* Linker option:
9
-Wl,--section-start,.data=0x802200,--defsym=__heap_end=0x80ffff  
10
//memory is used for variables (.data/.bss) and heap (malloc()).
11
*/
12
13
static const char T000[] PROGMEM = "Hello World 1   ";
14
static const char T001[] PROGMEM = "Hello World 2   ";
15
unsigned char eeso00 EEMEM;
16
unsigned char eeso01 EEMEM;
17
unsigned char eeso02 EEMEM;
18
unsigned char eeso03 EEMEM;
19
unsigned char so00 EXTMEM;
20
unsigned char so01;
21
unsigned char so02;
22
unsigned char so03;
23
24
uint8_t my_array[100] EXTMEM;
25
26
int main(void){
27
28
init_io(); //Ports initialisieren
29
30
lcd_init(LCD_DISP_ON);
31
lcd_puts_p(&(*T000));
32
lcd_gotoxy(0, 1);
33
lcd_puts_p(&(*T001));
34
35
xbus_adr = eeprom_read_byte((uint8_t*)&eexbus_adr);
36
my_array[0]=xbus_adr;
37
so03 = xbus_adr;
38
39
40
while(1)
41
{
42
}
43
44
}
Die Linkeroption ist unter project - configuration options - custom 
options eingetragen.
Ich habe jetzt erwartet, dass die Deklarationen von so00 mit EXTMEM im 
Bereich ab 2200h landen, die anderen im Bereich 200h-21FFh. Der 
Stackpointer wird auf 21FFh gesetzt.

Frage: Was habe ich falsch gemacht?

Thomas

PS: Mir ist bewusst, dass ich hier eine prima Zielscheibe aufgestellt 
habe. Wenn die zu erwartenden Prügel in die richtige Richtung führen, 
nehme ich das in Kauf. Da ich im direkten Umfeld aber niemanden habe, 
der meinen Start mit C unterstützen kann, bleibt nur diese Möglichkeit, 
irgendwie die Spur mal aufzunehmen - oder es mal wieder bleiben zu 
lassen.

von Stefan E. (sternst)


Lesenswert?

Thomas P. schrieb:
> Frage: Was habe ich falsch gemacht?

Du hast .data nach 2200h gelegt, nicht .EXTMEM.

Darüber hinaus wäre es für zukünftige Fragen sinnvoll, wenn du das "Ich 
habe jetzt erwartet" auch um ein "Ich habe stattdessen bekommen" 
ergänzen würdest.

von Thomas P. (topla)


Lesenswert?

Hallo Stefan,

danke für den Hinweis, aber auch der Eintrag
-Wl,--section-start,.extmem=0x802200,--defsym=__heap_end=0x80ffff
führt nicht zum Ziel.
Alle Variablen landen im Bereich ab 200h, zuerst das array mit EXTMEM, 
dann die einzelne Variable mit EXTMEM und dann der Rest ohne explizite 
Anweisung. Da kommt gleich die nächste Frage hoch: Warum erfolgt die 
Anlage der Variablen (array, so00) nicht in der Reihenfolge der 
Deklaration?

Thomas

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> Hallo Stefan,
>
> danke für den Hinweis, aber auch der Eintrag
> -Wl,--section-start,.extmem=0x802200,--defsym=__heap_end=0x80ffff
> führt nicht zum Ziel.

.extmem ist nicht gleich .EXTMEM

von Thomas P. (topla)


Lesenswert?

Hallo Johann,

dickes Dankeschön, genau das war es.
Der Weg Richtung C ist steinig. Mal schauen, an welcher Klippe ich als 
nächstes scheitere.

Thomas

von Dennis (Gast)


Lesenswert?

Thomas P. schrieb:
> Der Weg Richtung C ist steinig. Mal schauen, an welcher Klippe ich als
> nächstes scheitere.

Nicht so sehr, wenn du schon mal Assember gut kannst. Die grundsätzliche 
Herangehensweise ist bei beiden gleich. C ist nich sooo weit weg von 
Assembler, wie das manch einer denkt :-)

Recht hilfreich fand ich dieses Buch zum Thema:

http://www.rrzn.uni-hannover.de/buch.html?&titel=ansi-c

von Peter S. (psavr)


Lesenswert?

>C ist nich sooo weit weg von Assembler, wie das manch einer denkt :-)
Stimmt! Ich betrachte C als Macro-Assembler!

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

Thomas P. schrieb:
> Hallo Johann,
>
> dickes Dankeschön, genau das war es.
> Der Weg Richtung C ist steinig. Mal schauen, an welcher Klippe ich als
> nächstes scheitere.

Das hat mit C an sich wenig zu tun, sondern mehr damit wie deine 
Werkzeuge funktionieren. Die Sprache C hat zu Thema 'externer Speicher' 
überhaupt nichts zu sagen. Solche Dinge sind immer plattformabhängig 
(wie vieles andere auch in C) und du musst eben lernen, wie deine 
Entwicklungsumgebung das handhabt.

> ... Problem ist schon ...

'schon' ist gut gesagt :-) Denn eigentlich bist du da schon in der hohen 
Schule der Sonderfälle und nicht beim Einstieg in C.

von Thomas P. (topla)


Lesenswert?

Karl Heinz schrieb:

> 'schon' ist gut gesagt :-) Denn eigentlich bist du da schon in der hohen
> Schule der Sonderfälle und nicht beim Einstieg in C.

Hmm, das war für mich im ASM immer selbstverständlich, dass ich den 
Speicher selber aufteile und die sinnvolle Anordnung festlege. Dieses 
Vorgehen hat es im vorliegenden Projekt ermöglicht, durch 
Pointermanipulation (Offset im High- und Low-Teil) sehr schnell auf die 
Inhalte mehrerer Arrays zugreifen zu können, was bei den 4MHz des 
DS80C320 den Programmlauf deutlich beschleunigt hat.

Thomas

von Thomas P. (topla)


Lesenswert?

So, ich muss nochmal nerven.
Folgende Fragen habe ich derzeit:

1. In der main.c habe ich wie oben angeraten folgendes Konstrukt 
angelegt:
   folge_t folge[255] EXTMEM;  // funktioniert auch
   Damit ich den Inhalt bequem byteweise transferieren kann, möchte ich
   quasi ein zweites Array in gleicher Größe genau darüber legen.
   Wie stelle ich das an? Oder macht man das ganz anders?

2. Auf das in der main.c definierte Arrays möchte ich aus einer anderen
   .c-Datei zugreifen. Dazu habe ich dort ein
   extern folge[];
   hinterlegt. Beim compilieren bekomme ich die Warnung
   :6: warning: type defaults to 'int' in declaration of 'folge'
   Was muss ich anders machen? Wie ist es richtig?

Danke für alle Hinweise.

Thomas

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

Thomas P. schrieb:
> So, ich muss nochmal nerven.
> Folgende Fragen habe ich derzeit:
>
> 1. In der main.c habe ich wie oben angeraten folgendes Konstrukt
> angelegt:
>    folge_t folge[255] EXTMEM;  // funktioniert auch
>    Damit ich den Inhalt bequem byteweise transferieren kann, möchte ich
>    quasi ein zweites Array in gleicher Größe genau darüber legen.
>    Wie stelle ich das an? Oder macht man das ganz anders?

Man könnte natürlich mit unions arbeiten
1
union {
2
  folge_t folge[255];
3
  unsigned char folgeBytes[ sizeof folge ];
4
} data EXTMEM;

Ich denke, so müsste das klappen mit dem EXTMEM.

Was man natürlich immer machen kann, das ist einen Pointer auf diesen 
Speicher einrichten und dann bei den Transferfunktionen anstelle des 
Arrays übrall mit dem Pointer arbeiten
1
folge_t folge[255] EXTMEM;
2
folge_t * EXTMEM dataPtr = &folge;

oder aber du kannst ganz einfach auch in den Bearbeitungsfunktionen in 
denen du byteweise hantiiren möchtest, den Datentyp des Arrays auf einen 
unsigend char Pointer niedercasten.

> 2. Auf das in der main.c definierte Arrays möchte ich aus einer anderen
>    .c-Datei zugreifen. Dazu habe ich dort ein
>    extern folge[];
>    hinterlegt. Beim compilieren bekomme ich die Warnung
>    :6: warning: type defaults to 'int' in declaration of 'folge'
>    Was muss ich anders machen? Wie ist es richtig?

logisch.
Schau
Der COmpiler muss ja bei der Verwendung immer noch wissen, was das für 
einen Datentyp hat. Mit
1
    extern folge[];
hast du ja nur gesagt, dass es ein Array ist. Schon. Aber von welchem 
Typ denn? Sind das int, sind das double. Oder vielleicht Character. Oder 
ganz was anderes?
Du musst ihm schon sagen: folge ist ein Array. UNd zwar ein Array aus 
folge_t. Das kann ja der Compiler nicht riechen
1
extern folge_t folge[];

: Bearbeitet durch Moderator
von Thomas P. (topla)


Lesenswert?

Vielen Dank, auf die Lösung bei 2. hätte ich auch selbst kommen können 
schäm.

Das:
1
union {
2
  folge_t folge[255];
3
  unsigned char folgeBytes[ sizeof folge ]; << Zeile 291
4
} data EXTMEM;
mag er nicht, da wirft der Compiler immer
../main.c:291: error: 'folge' undeclared here (not in a function)

Da war ich schon dicht dran, kann den Fehler aber nicht einordnen.

Thomas

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

Thomas P. schrieb:
> Vielen Dank, auf die Lösung bei 2. hätte ich auch selbst kommen können
> schäm.
>
> Das:
>
1
> union {
2
>   folge_t folge[255];
3
>   unsigned char folgeBytes[ sizeof folge ]; << Zeile 291
4
> } data EXTMEM;
5
>
> mag er nicht, da wirft der Compiler immer
> ../main.c:291: error: 'folge' undeclared here (not in a function)

klar.
weil die Variable ja auch jetzt 'data' heisst. folge ist nur noch einer 
der beiden Member der union.

Vielleicht doch mal ein C-Buch durcharbeiten? Es programmiert sich 
einfach besser, wenn man die Möglichkeiten seiner Programmiersprache und 
wie man sie benutzt nicht nur zu 5% kennt.

von Thomas P. (topla)


Lesenswert?

Karl Heinz schrieb:

> Vielleicht doch mal ein C-Buch durcharbeiten? Es programmiert sich
> einfach besser, wenn man die Möglichkeiten seiner Programmiersprache und
> wie man sie benutzt nicht nur zu 5% kennt.

Bin ich sofort dafür. An Literatur habe ich schon einiges angehäuft, 
aber immer dann, wenn es an die etwas spezielleren Fälle geht, ist Ende 
im Gelände oder das schon etwas ältere Köpfchen rafft es nicht. Ist auch 
der Grund, dass ich immer wieder reumütig beim Assembler gelandet bin. 
Da habe ich im Kopf, was ich jetzt mühsam versuche, dem Compiler 
beizubiegen.
Welche Literatur ist denn zu empfehlen, die auch auf solche Sachverhalte 
allgemeinverständlich eingeht?


>> Das:
>>> union {
>>   folge_t folge[255];
>>   unsigned char folgeBytes[ sizeof folge ]; << Zeile 291
>> } data EXTMEM;
>> > mag er nicht, da wirft der Compiler immer
>> ../main.c:291: error: 'folge' undeclared here (not in a function)
>
> klar.
> weil die Variable ja auch jetzt 'data' heisst. folge ist nur noch einer
> der beiden Member der union.

Bei mir ist nix klar, ich raffe es immer noch nicht.
Ich definiere eine union mit Namen "data", bestehend aus einem Array des 
Typs folge_t mit Namen "folge" und 255 Elementen. Darüber wird ein 
zweites Array aus unsigned char mit Namen "folgebytes" gelegt. Die Größe 
(sizeof) des gerade definierten Arrays "folge" kennt der Compiler aber 
nicht. Und ich begreife nicht, warum?
Ich glaube, der Zeitpunkt für einen Rückfall zum Assembler ist nicht 
mehr weit. Dann kann ich in eineem Speicher von 32768 Bytes auch wieder 
ein Array mit 256 Sätzen zu 128 Bytes anlegen....

Thomas

von Karl H. (kbuchegg) (Moderator)


Lesenswert?

Thomas P. schrieb:

> Bei mir ist nix klar, ich raffe es immer noch nicht.

Mein Fehler. Ist halt immer besser, wenn man selber 'an den Kontrollen 
sitzt' :-)

An dieser Stelle kann ich mich noch nicht auf den Member folge beziehen 
und kann daher auch noch nicht sizeof benutzen.
1
union {
2
  folge_t folge[255];
3
  unsigned char folgeBytes[ 255 * sizeof(folge_t)  ];
4
} data EXTMEM;


ich nehm bei einem sizeof immer lieber den Variablennamen als den 
Dtaentypnamen. Denn wenn eine Variable mal ihren Typ wechselt, hab ich 
so weniger Arbeit in der Anpassung. Aber hier gehts nicht anders. Einzig 
die 2-fache Erwähnung von 255 ist mir noch ein Dorn im Auge
1
#define FOLGE_SIZE 255
2
3
union {
4
  folge_t folge[FOLGE_SIZE];
5
  unsigned char folgeBytes[ FOLGE_SIZE * sizeof(folge_t)  ];
6
} data EXTMEM;

von Daniel A. (daniel-a)


Lesenswert?

Warum ist die Grösse von folgeBytes überhaupt relevant? Die union ist so 
gross wie der grösste member, und alle Member beginnen an der selbe 
Adresse:
1
union {
2
  folge_t folge[255];
3
  unsigned char folgeBytes[0];
4
} data EXTMEM;

von Thomas P. (topla)


Lesenswert?

Ist nicht relevant, das habe ich auch gerade durch probieren 
herausgefunden. So langsam kommt Licht in das Dunkel an dieser Stelle; 
wird aber nicht lange dauern, dann wird es an anderer Stelle wieder 
finster bei mir.
Vielen Dank für die Hinweise.

Thomas

von Thomas P. (topla)


Lesenswert?

Der Compiler, das unbekannte Wesen:
Ich dachte, dass ich mit der Datendefinition nun durch bin. Theoretisch 
ja, aber...
In den Datendefinitionen steckt unter anderem der Teil:
1
{
2
  unsigned int rest_oben : 6;
3
  unsigned int rm_nr : 10;
4
} lm_dat_t;
Dabei hatte ich mir gedacht, dass die Anordnung der Daten dazu führt, 
dass ein Datentransfer aus dem EXTRAM mit wenigen Befehlen möglich wird. 
Pustekuchen, es ergibt sich:

  so03 = folgedata.folge[250].lm_dat[3].rm_nr;
 1ee:  90 91 56 9f   lds  r25, 0x9F56
 1f2:  92 95         swap  r25
 1f4:  96 95         lsr  r25
 1f6:  96 95         lsr  r25
 1f8:  93 70         andi  r25, 0x03  ; 3
 1fa:  20 91 57 9f   lds  r18, 0x9F57
 1fe:  82 2f         mov  r24, r18
 200:  88 0f         add  r24, r24
 202:  88 0f         add  r24, r24
 204:  89 2b         or  r24, r25
 206:  22 95         swap  r18
 208:  26 95         lsr  r18
 20a:  26 95         lsr  r18
 20c:  23 70         andi  r18, 0x03  ; 3
 20e:  80 93 04 02   sts  0x0204, r24
 212:  20 93 05 02   sts  0x0205, r18

Ändert man die Definition in:
1
{
2
  unsigned int rm_nr : 10;
3
  unsigned int rest_oben : 6;
4
} lm_dat_t;
dann wird der Code deutlich kompakter:

  so03 = folgedata.folge[250].lm_dat[3].rm_nr;
 1f0:  90 91 56 9f   lds  r25, 0x9F56
 1f4:  80 91 57 9f   lds  r24, 0x9F57
 1f8:  83 70         andi  r24, 0x03  ; 3
 1fa:  90 93 04 02   sts  0x0204, r25
 1fe:  80 93 05 02   sts  0x0205, r24

Wenn solche Probleme öfters vorkommen, sehe ich selbst mit einem 
16MHz-ATMega2560 unter C gegen den alten 4MHz-DS80C320 mit Assembler 
keinen Stich.

Die Frage: Wie kann man dem Compiler helfen, besseren Code zu erzeugen? 
Wie macht ihr das? Gibt es dazu Literatur/Tutorials?
Kann ja nicht im Snne des Erfinders sein, das .lss-File auf solche 
Probleme zu flöhen.

Thomas

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> Wie macht ihr das?

Erstmal programmiert man nur die Funktion.
Und wenn das läuft, guckt man, wo es klemmt. Also was muß besonders 
schnell sein oder was wird besonders oft aufgerufen.

Versuche in Funktion zu denken.
D.h. versuche nicht das Asssemblerprogramm Zeile für Zeile in C 
nachzuschreiben.

Am besten schaltet man den PC ab und erstellt erstmal einen 
Programmablaufplan mit Papier und Bleistift.

von Thomas P. (topla)


Lesenswert?

Einen Programmablaufplan habe ich schon, wenn auch noch aus der Zeit der 
Entstehung des Assembler-Programms. Auch in Funktionen habe ich damals 
gedacht, bin aber beim Programmieren immer so herangegangen, dass jede 
Funktion programmiert und getestet und dann nicht mehr angefasst wurde. 
Die Funktionen waren dann schon nahe beim Optimum, da meinereiner immer 
zu faul war, viele Zeilen Coding zu schreiben, wenn es auch einfacher 
ging. Und in Assembler korreliert die Anzahl der Befehle im Allgemeinen 
mit der Laufzeit.
Und so wollte ich auch hier wieder vorgehen: Funktion schreiben, testen 
und weglegen/benutzen. Die Neugier hat mich zum Betrachten des Listings 
getrieben und das Ergebnis war erschütternd. Eine Zeile Coding, 40 Bytes 
Assembler für eine Sache, die problemlos mit 18 Bytes zu erschlagen wäre 
- und das nur wegen der Reihenfolge der Datendefinition. Ich würde gerne 
mal eine Baustelle (hier Datendefinition) endgültig abschließen um mich 
dann in Ruhe der nächsten zu widmen, ohne am Schluss des Projektes 
wieder von vorne anzufangen. Aber so langsam glaube ich zu verstehen, 
warum hier viele unter einem ARM nicht mehr anfangen.

Thomas

von Markus F. (mfro)


Lesenswert?

Ich muß vorausschicken, daß ich von AVR-Assembler keine Ahnung habe, 
aber wenn Du das mit Assembler programmiert hättest, hättest Du deine 
Datenstruktur doch wahrscheinlich auch gleich so hingeschrieben, daß der 
10-Bit Teil "rechtsbündig" gestanden wäre, damit Du an die oberen 2 Bit 
durch einfaches Ausmaskieren kommst?

von Thomas P. (topla)


Lesenswert?

Natürlich, aber eigentlich definiert man doch von oben nach unten, 
respektive von links nach rechts. Wenn also in einem Bereich von 16 Bits 
zuerst 6 ungenutzte Bits stehen und dann eine Adresse mit 10 Bit, dann 
bin ich bisher davon ausgegangen, dass die 10 Bits schon rechtsbündig 
stehen > error. Man muss erst einmal herausfinden, dass der Compiler 
sehr eigene Ansichten dazu hat.
Nach längerer Suche zu einer Beschreibung habe ich nach "das Verhalten 
ist compilerspezifisch" den Versuch aufgegeben. Bei drei oder mehr 
Member der struct habe ich keine Logik in der Anordnung entdeckt, bin 
aber durch Probieren und anschließendes Betrachten des 
Assembler-Listings zu einer zufriedenstellenden Anordnung gekommen. 
Hoffentlich ändert der Compiler in einer neuen Version nicht seine 
Ansichten... Das wäre u.U. tödlich für abgelegte Daten bzw. die 
Folgeprozesse.

Thomas

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> Eine Zeile Coding, 40 Bytes
> Assembler für eine Sache, die problemlos mit 18 Bytes zu erschlagen wäre

Da geht jedem Umsteiger so, aber das muß man schnell ablegen. C braucht 
ab und zu etwas mehr Zyklen, das ist normal.
Die Frage ist doch nur, spielt es überhaupt eine Rolle?
22 Byte = 11 Befehle = 0,55µs[20MHz], reißt es das wirklich raus?

Vielleicht kannst Du ja mal diese super kritische Funktion beschreiben, 
die so absolut sauschnell sein muß.
Oftmals findet sich ja eine andere Herangehensweise, die alles 
entschärft.

Ich hatte das auch öfter, daß durch nochmal nachdenken plötzlich das C 
Programm Däumchen drehte, wo in Assembler nichtmal mehr ein NOP 
reinpaßte.

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> Wenn also in einem Bereich von 16 Bits
> zuerst 6 ungenutzte Bits stehen und dann eine Adresse mit 10 Bit

CPUs mögen Vielfache von einem Byte.
Wenn der RAM nicht super knapp ist, kann es was bringen, wenn man 
aufrundet, also 10 auf 16 und 6 auf 8 Bit.

Du wirst oft in anderen AVR-Programmen sehen, daß Flags ein ganzes Byte 
groß sind. Das ist effektiver, da der AVR keine Bitbefehle kennt, wie 
sie der 8051 hat.

von Bitflüsterer (Gast)


Lesenswert?

@Thomas

Wenn Du erlaubst, möchte ich Dir raten, die aus Deiner Sicht "seltsamen" 
Dinge in C nur zur Kenntnis zu nehmen (evtl. 'ne kleine Liste machen) 
und erstmal keine Wertungen zu treffen.

So schaffst Du die Möglichkeit C einmal insgesamt und in einem 
praxisrelevanten Bezug zu sehen. Dazu musst Du natürlich erstmal C 
lernen.

Wenn ich bei jedem kleinen Schritt auf die Hindernisse achte, dann ist 
die Welt (egal ob ASM oder C) voller Probleme. Verdamme C meinetwegen am 
Ende, aber dann bitte fundiert. - Würde mich nicht wundern, wenn Du aber 
dann doch mal ab und zu C verwendest.

Ich benutzte übrigens beides. Ich glaube so wird es im Endeffekt einer 
Menge Leute gehen.

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> Man muss erst einmal herausfinden, dass der Compiler
> sehr eigene Ansichten dazu hat.

Nö, man muß nur wissen, daß es nicht definiert ist. Daher wirst Du kaum 
Programme finden mit Datenbreiten != n*8Bit.
Wenn es darauf ankommt, maskiert man die Werte selber mit & bzw. | 
heraus.

von Thomas P. (topla)


Lesenswert?

Erstmal danke für alle Hinweise.
Ich habe schon begriffen, dass die Herangehensweise unter C eine andere 
sein sollte und man auch eine gewisse Gelassenheit entwickeln muss.
Das sind für mich als Hobbyprogrammierer aber schon gravierende 
Umstellungen, da ich gewohnt bin, ein ganzes Stück voraus zu denken.

@Bitflüsterer:
Eine Liste habe ich schon ;-)) und mit deren Hilfe versuche ich, die mir 
ungewohnten Verhaltensweisen im Blick zu behalten. Eine Wertung im Sinn 
"ist so, gefällt mir aber ganz und gar nicht" gibt es aber auch schon.
Es geht mir auch nicht darum, C zu verdammen. Die Vorteile sind mir 
schon klar, sonst hätte ich damit garnicht erst beschäftigt, aber leider 
wächst im Moment die Seite mit den (aus meiner begrenzten Sicht) 
Absonderlichkeiten an.

@ Peter Dannegger:
Ich hatte unter der alten Hardware 32kByte RAM zur Verfügung und die 
waren gut ausgenutzt. Ohne Bitschubserei im RAM hätte die Software dort 
garnicht funktionieren können. Am liebsten wäre mir auch die neue 
Hardware unter einem 8051-Derivat gewesen - eben wegen den 
leistungsfähigen Bitbefehlen. Da ich da aber nichts gefunden habe, was 
meine Anforderungen erfüllt und ich zwischendurch im 
AVR-Assemblerbereich Erfahrungen gesammelt habe, fiel die Wahl auf einen 
ATMega und auf Grund der benötigten 3 seriellen Schnittstellen auf einen 
ATMega2560. Hardware ist fertig und Assembler war gesetzt, aber dann kam 
die Idee, es doch mal mit C zu versuchen.

Ich habe derzeit ein Array[64] (in Zukunft [255]) in Verwendung, welches 
für jeden Satz 32 Aktionen enthält. Die Entscheidung über Aktiv oder 
Inaktiv wird über Bedingungen getroffen, die zu jedem Satz mit zweimal 
uint16_t [4] hinterlegt sind. Bei einem Durchlauf muss ich also im worst 
case 255*4*2 Mal diese Bitmanipulation vornehmen und das Ergebnis 
untersuchen und weiter verarbeiten.
Daneben gilt es noch 2 serielle Schnittstellen permanent zu betreiben, 
zeitnah ein LCD zu bedienen und eine Tastatur zu überwachen sowie 
diverse Kleinigkeiten mehr. Eine weitere Ausbaustufe wäre der Betrieb 
der 3. seriellen Schnittstelle. Unter asm wäre das unter Zeitaspekten 
auch noch mit dem DS80C320 lösbar gewesen, allerdings war der RAM alle 
und die 3. serielle Schnittstelle nicht da.
Und aus diesen Gründen habe ich schon das Gefühl, nicht gleich am Anfang 
mit "Was-kostet-die-Welt" heranzugehen zu können und den 
Ressourcenverbrauch möglichst gering zu halten. Bisher habe ich mich mit 
dem anderen Prozessor auf der sicheren Seite gewähnt, werde aber langsam 
unsicher, ob das auch wirklich so ist.

Thomas

von Markus F. (mfro)


Lesenswert?

Thomas P. schrieb:
> Wenn also in einem Bereich von 16 Bits
> zuerst 6 ungenutzte Bits stehen und dann eine Adresse mit 10 Bit, dann
> bin ich bisher davon ausgegangen, dass die 10 Bits schon rechtsbündig
> stehen > error

Naheliegend wäre für mich gewesen, wenn mich 6 Bits nicht interessieren, 
die auch nicht hinzuschreiben - ich würde dann davon ausgehen, daß der 
Compiler mit dem Rest so umgeht, wie es für ihn am günstigsten ist. 
Meine Compiler machen das auch so.

Wenn man allerdings wirklich sicher gehen will, sollte man - wie schon 
richtig geschrieben - die Sache selber in die Hand nehmen.

von Bitflüsterer (Gast)


Lesenswert?

Thomas P. schrieb:

> @Bitflüsterer:
> Eine Liste habe ich schon ;-)) und mit deren Hilfe versuche ich, die mir
> ungewohnten Verhaltensweisen im Blick zu behalten. Eine Wertung im Sinn
> "ist so, gefällt mir aber ganz und gar nicht" gibt es aber auch schon.
> Es geht mir auch nicht darum, C zu verdammen. Die Vorteile sind mir
> schon klar, sonst hätte ich damit garnicht erst beschäftigt, aber leider
> wächst im Moment die Seite mit den (aus meiner begrenzten Sicht)
> Absonderlichkeiten an.

Das Wort "verdammen" war zunächst nur als Synonym zu "abschliessend als 
unbrauchbar beurteilen" gemeint. Ich bedauere diesen Scherz, der 
anscheinend so nicht angekommen ist.

Es geht mir weniger um die Liste an sich und auch nicht um Deine 
Entscheidung, als darum, Dich dahin zu bringen, ein Urteil nicht anhand 
einzelner Punkte und aufgrund begrenzter Sicht zu fällen. Ob Du C nun 
ablehnst oder nicht, ist mir gleichgültig.

Aber ich wollte Dich anregen dies aufgrund fundierter Kenntnis und 
Erfahrung zu machen.

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> zeitnah ein LCD zu bedienen und eine Tastatur zu überwachen sowie
> diverse Kleinigkeiten mehr.

Solange das kein GLCD ist, sind das nur Peanuts.
Du hast zwar viel erzählt, das entscheidende fehlt aber, nämlich die 
Zeit.
Ohne Zeitvorgaben klingt das alles nicht nach hoher CPU-Last.

Thomas P. schrieb:
> Unter asm wäre das unter Zeitaspekten
> auch noch mit dem DS80C320 lösbar gewesen, allerdings war der RAM alle

Das verstehe ich nicht.
Der DS80C320 kann 64kB XDATA adressieren + 256B IDATA.
Beim ATmega2560 ist es sogar weniger (64kB - 512B für IO).

von Thomas P. (topla)


Lesenswert?

Ja, ein DS80C320 'kann' mehr Speicher adressieren, in der alten Hardware 
sind aber nur 32kByte verbaut. Auf Grund der Pinknappheit dieser Dinger 
hatte man damals vor mehr als 20 Jahren Register 74HC573 Memory-mapped 
mit unvollständiger Decodierung verwendet. Der Ram war batteriegestützt 
für den Erhalt der Stammdaten.
Diese Gegebenheiten sollten modernisiert werden und dabei auch der 
externe EPROM verschwinden (Kopierschutz light). Heute gibt es eben z.B. 
als RAM einen HM628512 und die drei Bits für A16-A18 waren am Prozessor 
auch noch frei - ohne zusätzliches Latch.
So ganz allein bin ich bei den Entscheidungen zur Hardware nicht auf der 
Welt und mir wäre aus Sicht der Programmierung auch ein DS89C450 lieber 
gewesen. Allerdings traue ich Maxim bei der Lieferfähigkeit nicht so 
recht über den Weg und mindestens 13€ teurer sind die Dinger auch noch. 
Und eine dritte (und ev. vierte) serielle Schnittstelle direkt im Chip 
ist weder für Geld noch für gute Worte zu bekommen - das hätte wieder 
neue externe Hardware bedeutet.

Zu den Zeitvorgaben:
Kann ich nicht genau sagen (ich weiß: "ganz schlecht"), da abhängig von 
den Ereignissen in den externen Geräten über beide (über drei in 
Zukunft) serielle Schnittstellen Daten einlaufen, die analysiert, 
abgespeichert und ggf. an eine andere Schnittstelle weitergegeben und 
angezeigt werden müssen. Da kann minutenlang mal garnichts passieren und 
dann kommt es fortlaufend hintereinander.
In der alten Hardware wurde die Tastenbedienung gefühlt ab und an recht 
"träge" und da hatte ich mal versucht, eine Led so einzubinden, dass 
damit bestimmte kritische Abschnitte der Software signalisiert werden. 
Die Led war nicht oft aus... (Und ja, ich habe mir das mit einem Oszi 
angeschaut, nicht dass mir jetzt noch einer erklärt, dass ich das 
optisch garnicht richtig wahrnehmen kann).
Dazu kommt auch noch, dass ich kein Informatiker sondern Maschinenbauer 
bin, mir also bestimmte theoretische Voraussetzungen für das 
Ausquetschen eines Prozessors bis auf das letzte Prozent Leistung fehlen 
werden.

Thomas

PS: Fällt mir gerade wieder ein: Ein weiteres Problem der Dallas-MC war 
die Taktbereitstellung der seriellen Schnittstellen, mit 62,5k und 9,6k 
gab es nicht viel Spielraum bei der Wahl des Systemtakts.
Und bevor die Notwendigkeit dazu wieder in Frage gestellt wird: Das sind 
Vorgaben durch externe Geräte, deren Designänderung absolut keine Option 
ist.
Irgendwann hatte keiner mehr Lust auf die Dinger und ein 8051 mit der 
Ausstattung eines ATMega2560 ist uns nicht bekannt.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Thomas P. (topla)

>Kann ich nicht genau sagen (ich weiß: "ganz schlecht"),

EBEN!

> da abhängig von
>den Ereignissen in den externen Geräten über beide (über drei in
>Zukunft) serielle Schnittstellen Daten einlaufen, die analysiert,
>abgespeichert und ggf. an eine andere Schnittstelle weitergegeben und
>angezeigt werden müssen.

Klingt nicht so dramatisch.

Welche Baudrate?
Welche Datenpaketgröße?
Was muss ungefähr analysiert werden?
Was muss ungefähr angezeigt werden? Wie oft?

> Da kann minutenlang mal garnichts passieren und
>dann kommt es fortlaufend hintereinander.

Wie oft? 1 Datenpaket/s?

So what. Packt man halt einen Software-FIFO an jeder Schnittstelle. 
Peter Fleury & Co lassen grüßen. Das man hier auch das Thema 
Interrupt beherrschen muss, ist selbstredend.

>In der alten Hardware wurde die Tastenbedienung gefühlt ab und an recht
>"träge"

Schlechte Programmierung.

>Dazu kommt auch noch, dass ich kein Informatiker sondern Maschinenbauer
>bin, mir also bestimmte theoretische Voraussetzungen für das
>Ausquetschen eines Prozessors bis auf das letzte Prozent Leistung fehlen
>werden.

Das kommt dem Problem schon näher. Aber niemand muss hier die CPU bis 
auf das letzte Prozent ausquetschen. Ein AVR @ 20 MHz hat soviel Power, 
das können sich die meisten Bastler/Quereinsteiger gar nicht vorstellen!
Das Hauptproblem ist das richtige Konzept bzw. Grundlagenwissen. Das da 
lautet Multitasking und Statemachine. Damit kriegt man 
eigentlich alles sauber und flüssig hin.
Mal als Beispiel. Ich hab hier vor einiger Zeit einen DMX-Rekorder 
gebaut, dort wurden DMX-Daten mit 22kB/s auf eine SD-Karte geschrieben, 
nebenbei lief noch ein Menu mit Drehgeberbedienung. Nix hat gehakt, lief 
immer flüssig, auch bei stellenweise 90% CPU-Last. Man muss nur wissen 
wie.

>Irgendwann hatte keiner mehr Lust auf die Dinger und ein 8051 mit der
>Ausstattung eines ATMega2560 ist uns nicht bekannt.

Nimm den AVR und mach es in C. Auch wenn der Umstieg mit Anstrengung 
verbunden ist, wenn es fertig ist wirst du die Eleganz und 
Leistungsfähigkeit sehen und du hast dann viel gelernt!

Viel Spaß und Erfolg
Falk

P S Ich wette mal, dass sich der AVR zu 99% langweilt.

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> nd eine dritte (und ev. vierte) serielle Schnittstelle direkt im Chip
> ist weder für Geld noch für gute Worte zu bekommen

Ja, da sind die 8051-er recht geizig mit.
Und mal eben ne Software-UART mit Input-Capture und Output-Compare wie 
beim AVR geht auch nicht so leicht.

Wir haben auch noch ein Altgerät mit DS80C320-QCL (33MHz), bei den 
Mondpreisen wird mir ganz schlecht.

Thomas P. schrieb:
> serielle Schnittstellen Daten einlaufen, die analysiert,
> abgespeichert und ggf. an eine andere Schnittstelle weitergegeben und
> angezeigt werden müssen.

Da kennst Du ja die Baudrate und die Anzahl Bytes für einen Befehl. 
Schneller geht es nicht.

Thomas P. schrieb:
> In der alten Hardware wurde die Tastenbedienung gefühlt ab und an recht
> "träge"

Fürs Tastenentprellen empfehle ich einen Timerinterrupt, der 
Ereignis-Flags setzt, die dann die Mainloop auswertet.
Damit gehen Drücke nicht verloren und die Mainloop wird nicht 
ausgebremst.

Thomas P. schrieb:
> Ausquetschen eines Prozessors bis auf das letzte Prozent Leistung

Laß das.
premature optimization is the root of all evil

von Peter D. (peda)


Lesenswert?

Falk Brunner schrieb:
> Nimm den AVR und mach es in C. Auch wenn der Umstieg mit Anstrengung
> verbunden ist, wenn es fertig ist wirst du die Eleganz und
> Leistungsfähigkeit sehen und du hast dann viel gelernt!

Und wenn Du nach einem Jahr an dem Projekt noch etwas hinzufügen sollst, 
wirst Du dich freuen, wie Bolle, daß es in C ist.
Die ganzen internen Assemblertricks hättest Du dann längst vergessen.

von Falk B. (falk)


Lesenswert?

@ Thomas P. (topla)

>Und so wollte ich auch hier wieder vorgehen: Funktion schreiben, testen
>und weglegen/benutzen. Die Neugier hat mich zum Betrachten des Listings
>getrieben und das Ergebnis war erschütternd. Eine Zeile Coding, 40 Bytes
>Assembler für eine Sache, die problemlos mit 18 Bytes zu erschlagen wäre
>- und das nur wegen der Reihenfolge der Datendefinition.

Vergiss diese Erbsenzählerrei!

https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung

> Ich würde gerne
>mal eine Baustelle (hier Datendefinition) endgültig abschließen um mich
>dann in Ruhe der nächsten zu widmen, ohne am Schluss des Projektes
>wieder von vorne anzufangen. Aber so langsam glaube ich zu verstehen,
>warum hier viele unter einem ARM nicht mehr anfangen.

Ach was, du malst nur den Teufel an die Wand. Du kümmerst dich viel 
zuviel um Kleingkeiten und klebst viel zuviel an deinem Assembler.

von Thomas P. (topla)


Lesenswert?

Falk Brunner schrieb:
> @ Thomas P. (topla)
> Klingt nicht so dramatisch.
>
> Welche Baudrate?
> Welche Datenpaketgröße?
> Was muss ungefähr analysiert werden?
> Was muss ungefähr angezeigt werden? Wie oft?

Dramatisch ist was anderes, dank effizienter Programmierung reißt es ja 
der DS80C320@4MHz im Allgemeinen auch.

- 62,5kB 9Bit
- Datenpaketgröße variabel bis 15 Byte
- Art des Paketes analysieren,
    - bei Relevanz Inhalt analysieren und in das Array schreiben
    - Relevanz für zweite Schnittstelle ermitteln, Daten zusammenbauen
      und in den Senderingpuffer stellen
    - Relevanz für Anzeige (momentan dargestellter Bereich) ermitteln
      und ggf. ausgeben

> Wie oft? 1 Datenpaket/s?

Nachrichten auf dem Bus laufen ca. aller 2ms, das sind sog. Token mit 
der Adresse (9.Bit). Über Int-Einstellungen werden alle nicht 
zutreffenden Token ausgefiltert. Abhängig von der Anzahl der 
Busteilnehmer wird das Gerät also zwischen 4 und max. 45ms angesprochen, 
Mittelwert 10ms.

> So what. Packt man halt einen Software-FIFO an jeder Schnittstelle.
> Peter Fleury & Co lassen grüßen. Das man hier auch das Thema
> Interrupt beherrschen muss, ist selbstredend.

Interruptgesteuert läuft das ohnehin, ich wüsste nicht, wie man das 
sonst beherrschen könnte.
Problem ist, dass die eingehenden Daten verschiedene Bedeutung haben 
können. Grob sind das das Token (als Sendeaufforderung), eine 
Information für das Gerät oder eine Information an alle Geräte. Bekommt 
das Gerät das Token, kann es eine Anfrage (gibt eine Antwort) oder eine 
Information senden (gibt keine Antwort). Bei einer Anfrage kann aber 
zwischen dieser und der zugehörigen Antwort auch eine allgemeine 
Information stecken.


> Schlechte Programmierung.

Das war ich nicht. Puh, mal Glück gehabt.


> Viel Spaß und Erfolg
> Falk

Danke, auf Grund der fertigen Hardware wird das wohl auch so werden.

> P S Ich wette mal, dass sich der AVR zu 99% langweilt.

Würde ich mal dagegen halten. ;-))

von Thomas P. (topla)


Lesenswert?

Falk Brunner schrieb:
> @ Thomas P. (topla)
> Vergiss diese Erbsenzählerrei!
>
> 
https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung

Ja, ist vielleicht ein Geburtsfehler bei mir. Ich mag auch vorzeigbares 
Innenleben, bei Geräten wie bei Software.

> Ach was, du malst nur den Teufel an die Wand. Du kümmerst dich viel
> zuviel um Kleingkeiten und klebst viel zuviel an deinem Assembler.

Hmm, mag sein, aber in diesem Zusammenhang geht mir folgendes Szenario 
durch den Kopf: Im externen EEPROM sind Stammdaten abgelegt, die beim 
Einschalten in den RAM geladen werden und ein Array füllen. Dabei bin 
ich zwingend darauf angewiesen, dass auch bei neuen Programmversionen 
die Datenlage im externen EEPROM exakt mit der vom Programm erwarteten 
im RAM übereinstimmt. Ist jetzt der Compiler mal anderer Meinung zur 
Datenanordnung, geht das voll in die Hose.

Peter Dannegger schrieb:
> Und wenn Du nach einem Jahr an dem Projekt noch etwas hinzufügen sollst,
> wirst Du dich freuen, wie Bolle, daß es in C ist.
> Die ganzen internen Assemblertricks hättest Du dann längst vergessen.

Diese Erkenntnis führte ja zur Entscheidung, es hier mal mit C zu 
versuchen.
Bis jetzt ging es mit Assembler ganz gut, dank übersichtlicher und 
modularer Programmierung (und bis zum Alzheimer ist es hoffentlich noch 
eine Weile hin). Nervig ist das Verwalten von Flagbits und Registern.

Thomas

von Falk B. (falk)


Lesenswert?

@Thomas P. (topla)

>- 62,5kB 9Bit
>- Datenpaketgröße variabel bis 15 Byte

Wenig.

>- Art des Paketes analysieren,
>    - bei Relevanz Inhalt analysieren und in das Array schreiben
>    - Relevanz für zweite Schnittstelle ermitteln, Daten zusammenbauen
>      und in den Senderingpuffer stellen
>    - Relevanz für Anzeige (momentan dargestellter Bereich) ermitteln
>      und ggf. ausgeben

Das muss aber sicher nicht im 2ms Raster passieren, dann das kann keiner 
lesen. 10 Hz Updaterate erscheinen da als Obergrenze sinnvoll, ggf. 
sogar weniger.

> Wie oft? 1 Datenpaket/s?

>Nachrichten auf dem Bus laufen ca. aller 2ms, das sind sog. Token mit
>der Adresse (9.Bit). Über Int-Einstellungen werden alle nicht
>zutreffenden Token ausgefiltert. Abhängig von der Anzahl der
>Busteilnehmer wird das Gerät also zwischen 4 und max. 45ms angesprochen,
>Mittelwert 10ms.

Bei 2ms Periodendauer, 15 Bytes/Paket und 62,5 kBaud stimmt was nicht.
Denn es gehen maximal 5682 Bytes/s über den Bus, macht in 2ms 
bestenfalls 11 Bytes. Also werden viele Pakete deutlich kleiner als 
15Bytes sein.

>Interruptgesteuert läuft das ohnehin, ich wüsste nicht, wie man das
>sonst beherrschen könnte.

Es geht auch über Polling und eine Statemachine.

>Problem ist, dass die eingehenden Daten verschiedene Bedeutung haben
>können.

Ist das nicht immer so?

> Grob sind das das Token (als Sendeaufforderung), eine
>Information für das Gerät oder eine Information an alle Geräte. Bekommt
>das Gerät das Token, kann es eine Anfrage (gibt eine Antwort) oder eine
>Information senden (gibt keine Antwort). Bei einer Anfrage kann aber
>zwischen dieser und der zugehörigen Antwort auch eine allgemeine
>Information stecken.

Ja und? Klingt dennoch nicht wirklich wild. Im Extremfall packt man das 
alles in große Tabellen, du hast ja mit dem neuen Prozessor mehr als 
genug Flash.

von Falk B. (falk)


Lesenswert?

@Thomas P. (topla)

>ich zwingend darauf angewiesen, dass auch bei neuen Programmversionen
>die Datenlage im externen EEPROM exakt mit der vom Programm erwarteten
>im RAM übereinstimmt. Ist jetzt der Compiler mal anderer Meinung zur
>Datenanordnung, geht das voll in die Hose.

Darum legt man diese Datenstruktur in einem struc an, dort DARF der 
Compiler nichts verschieben, er MUSS es EXAKT so abbilden, wie es 
vorgegeben ist.

>eine Weile hin). Nervig ist das Verwalten von Flagbits und Registern.

Und vieles mehr. Assembler ist heutzutage wirklich nur noch in GANZ 
kleinen Nischen sinnvoll, wo es WIRKLICH auf 100% volle Leistung bzw. 
absolut exaktes Timing ankommt. Wenn man ein paar grundlegende Konzepte 
zur sinnvollen Variablenauswahl und Verarbeitung kennt (Siehe mein Link 
weiter oben), ist C in 99% der Fälle vollkommen ausreichend.

von Thomas P. (topla)


Lesenswert?

Falk Brunner schrieb:
> @Thomas P. (topla)

> Das muss aber sicher nicht im 2ms Raster passieren, dann das kann keiner
> lesen. 10 Hz Updaterate erscheinen da als Obergrenze sinnvoll, ggf.
> sogar weniger.

Ja natürlich. Ich habe das jetzt nicht in epischer Breite ausgewalzt. 
Wird die eingetroffene bzw. ermittelte Information als relevant 
betrachtet, wird das Ergebnis in einen Ausgabepuffer geschrieben und im 
Zeitraster von ich-weiß-jetzt-nicht-genau-irgendwas um 15Hz ausgegeben.

>>Nachrichten auf dem Bus laufen ca. aller 2ms, das sind sog. Token mit
>>der Adresse (9.Bit). Über Int-Einstellungen werden alle nicht
>>zutreffenden Token ausgefiltert. Abhängig von der Anzahl der
>>Busteilnehmer wird das Gerät also zwischen 4 und max. 45ms angesprochen,
>>Mittelwert 10ms.
>
> Bei 2ms Periodendauer, 15 Bytes/Paket und 62,5 kBaud stimmt was nicht.
> Denn es gehen maximal 5682 Bytes/s über den Bus, macht in 2ms
> bestenfalls 11 Bytes. Also werden viele Pakete deutlich kleiner als
> 15Bytes sein.

Deswegen habe ich oben ja auch "bis" geschrieben. Je länger das 
Telegramm, umso größer auch der Abstand zum nächsten Telegramm, geht ja 
nicht anders. Verlängernd wirken natürlich auch allgemeine 
Informationen.

>>Problem ist, dass die eingehenden Daten verschiedene Bedeutung haben
>>können.
>
> Ist das nicht immer so?

Ich meine, dass die Art der einlaufenden Daten Einfluss auf die 
Sendedaten nimmt. Ich kann also nicht einfach alles, was ich loswerden 
will, hintereinander in einen Ringpuffer schreiben und abarbeiten, 
sondern muss da flexibler reagieren. Im Moment ist das so gelöst, dass 
das Programm halt auf die vollständige Abarbeitung einer Anfrage wartet. 
Haben wir schon öfters diskutiert, hat aber auch keiner eine brauchbare 
Lösung in den Ring geworfen. Wir sind aber alle keine Profis.

> Ja und? Klingt dennoch nicht wirklich wild. Im Extremfall packt man das
> alles in große Tabellen, du hast ja mit dem neuen Prozessor mehr als
> genug Flash.

Tabellen, variable Daten und Flash?
Jetzt hast Du mich total abgehängt.

Thomas

von Peter D. (peda)


Lesenswert?

Thomas P. schrieb:
> Dabei bin
> ich zwingend darauf angewiesen, dass auch bei neuen Programmversionen
> die Datenlage im externen EEPROM exakt mit der vom Programm erwarteten
> im RAM übereinstimmt.

Geht nur, wenn Du zufällig in Assembler die gleiche Byteorder benutzt 
hast, wie der AVR-GCC.
Wieviel Byte sind es denn?

Ich hatte bisher nie den Fall, daß ein anderes Tool den EEPROM 
beschreibt.
D.h. immer nur die Anwendung hat den EEPROM geschrieben und dann ist die 
Order egal.
Mir reichte bisher auch der internen EEPROM aus.

von Falk B. (falk)


Lesenswert?

@ Thomas P. (topla)

>Ich meine, dass die Art der einlaufenden Daten Einfluss auf die
>Sendedaten nimmt.

Sicher, sonst wären sie ja sinnlos ;-)

>Ich kann also nicht einfach alles, was ich loswerden
>will, hintereinander in einen Ringpuffer schreiben und abarbeiten,

Warum nicht?

>sondern muss da flexibler reagieren.

???

> Im Moment ist das so gelöst, dass
>das Programm halt auf die vollständige Abarbeitung einer Anfrage wartet.

Das klingt nicht gut, siehe Multitasking.

>Haben wir schon öfters diskutiert, hat aber auch keiner eine brauchbare
>Lösung in den Ring geworfen. Wir sind aber alle keine Profis.

Dann beschreibe doch dein Aufgabe hier mal detailierter.

>Tabellen, variable Daten und Flash?
>Jetzt hast Du mich total abgehängt.

Man kann im EXTREMFALL für jedes mögliche Eingangspaket exakt ein 
zugehöriges Ausgangspaket in eine feste Tabelle packen. JA, die wird 
dann riesig! Geht aber manchmal, ist halt ein ultimativer "Space for 
Time" Ansatz. Damit muss man nicht die Ausgangsdaten kompliziert und 
langwierig aus den Eingangsdaten berechnen/bitmanipulieren, sondern 
wählt einfach die fertige Antwort aus der riesigen Tabelle aus.

Ob und wie man das hier machen könnte, weiß ich nicht. Aber wenn im 
alten System ein 8051 Derivat mit 4 MHz die Aufgabe geschafft hat 
(Maschinentakt ist 1/4 Oszillatortakt, siehe

http://www.8052.com/320intro.php

), kann das ein AVR mit 20 MHz locker.

Ich meine, dein Problem ist nach wie vor der ungünstige Lösungsansatz, 
bedingt durch deine Quereinsteigerposition.

von Thomas P. (topla)


Lesenswert?

Peter Dannegger schrieb:
> Geht nur, wenn Du zufällig in Assembler die gleiche Byteorder benutzt
> hast, wie der AVR-GCC.
> Wieviel Byte sind es denn?

Unter anderem deshalb habe ich mit der Anordnung der Daten in den strutc 
so herumgerudert. Sind knapp 32k an Daten.

> Mir reichte bisher auch der internen EEPROM aus.

Ich weiß, ich grabe immer irgendwelchen Scheixx aus, aber ich finde ums 
Verrecken den toten Vogel in meinen Taschen nicht.

Thomas

von Daniel A. (daniel-a)


Lesenswert?

Thomas P. schrieb:
> Unter anderem deshalb habe ich mit der Anordnung der Daten in den strutc
> so herumgerudert. Sind knapp 32k an Daten.

Wenn es um Speicherplatz sparen geht, werden die structs gepackt, oder 
sind unbenutzte/redundante Informationen enthalten welche mit Unions 
überlagert werden könnten? Notfalls können die Werte im struct auch 
manuell ins EEPROM geschrieben werden.

von Thomas P. (topla)


Lesenswert?

Falk Brunner schrieb:
> @ Thomas P. (topla)
>> Im Moment ist das so gelöst, dass
>>das Programm halt auf die vollständige Abarbeitung einer Anfrage wartet.
>
> Das klingt nicht gut, siehe Multitasking.
> Dann beschreibe doch dein Aufgabe hier mal detailierter.

Ist DV-technisch nicht der Brüller (das weiß ich schon), macht aber 
technisch Sinn. Wenn ich eine Befehlsfolge abarbeite, dann bekomme ich 
keine Quittung über den erfolgreichen Empfang. Ich bekomme lediglich 
eine Rückmeldung zum Ergebnis der Ausführung meines Kommandos (oder auch 
nicht, da begrenzt dann eine timeout-Funktion die Wartezeit). Bekomme 
ich keine Antwort oder nicht die erwartete, wird der Vorgang für diese 
Folge abgebrochen (und ein Fehler gesetzt) und die nächste 
abzuarbeitende Folge gesucht.
In der Wartezeit kann ich jetzt weitere Token (als Sendemöglichkeit) 
bekommen, es können die erwarteten Informationen eintreffen, es kann 
eine Abfrage von Daten eintreffen (die sofort beantwortet werden muss) 
oder es können allgemeine Informationen eintreffen (also quasi 
unverlangte (aber relevante) Daten), die erstmal nur abgelegt werden 
müssen.
Würde ich jetzt ein weiteres Sendefenster nutzen, wäre ein Mechanismus 
zu implementieren, der die Zuordnung von gesendeten Daten zu empfangenen 
Daten und einem eigenen Timeoutzähler ermöglicht. Einen eindeutigen 
Identifikator zwischen Sende- und Empfangsdaten müsste man hinbekommen.
Fehlt die Antwort, wartet die weitere Ausgabe von Befehlen eben auf den 
Timeout oder die passende Antwort. Aber wie man das brauchbar 
gestaltet...?
Interruptgesteuert bediene ich die Ausgabe auf die zweite serielle 
Schnittstelle aus einem Ringpuffer, muss aber auch dort zyklisch im 
Polling Eingaben abholen und bearbeiten. Das Ergebnis könnte dann in den 
Ringpuffer der ersten Schnittstelle.

> Man kann im EXTREMFALL für jedes mögliche Eingangspaket exakt ein
> zugehöriges Ausgangspaket in eine feste Tabelle packen. ....

Ist nicht notwendig und scheidet aus.

> Ich meine, dein Problem ist nach wie vor der ungünstige Lösungsansatz,
> bedingt durch deine Quereinsteigerposition.

Da wirst Du recht haben und deshalb versuche ich hier, so viele 
Informationen wie möglich aufzusammeln.

Thomas

von Thomas P. (topla)


Lesenswert?

Daniel A. schrieb:
> Wenn es um Speicherplatz sparen geht, werden die structs gepackt, oder
> sind unbenutzte/redundante Informationen enthalten welche mit Unions
> überlagert werden könnten? Notfalls können die Werte im struct auch
> manuell ins EEPROM geschrieben werden.

Redundante Informationen nicht, unbenutzte schon.
Allerdings ist dieses Thema ja vom Tisch, ein 32kEEPROM und ausreichend 
RAM machen es möglich.
Die Anordnung der Informationen im Speicher habe ich durch Probieren im 
Griff - hoffe ich mal.

Thomas

von Peter D. (peda)


Lesenswert?

Ist der EEPROM gesockelt, damit man ihn woanders programmieren kann?
Kann man nicht dieses Programmiertool an ein neue Format anpassen?

von Thomas P. (topla)


Lesenswert?

Damit alles hier etwas zusammen bleibt, habe ich den Thread wieder 
ausgebuddelt.

Nach Hinweisen hier habe ich eine eigene section für den externen RAM am 
ATMEGA2560 angelegt und die Linker-Option eingestellt.
#define EXTMEM __attribute__((section(".EXTMEM")))
/* Linker option:
-Wl,--section-start,.EXTMEM=0x802200,--defsym=__heap_end=0x80ffff

Mit kleinen Datenmengen hat das auch gut funktioniert, allerdings kam 
bei deutlicher Vergrößerung eines Arrays im .EXTMEM die Fehlermeldung, 
dass das Programm zu groß sei und nicht geflasht werden kann.
Die Betrachtung des hex-Files ergab, dass der Bereich des Arrays dort 
ebenfalls erscheint, aufgefüllt mit 0x00 und offensichtlich der 
Initialisierung dient. Lösche ich diesen Bereich aus dem hex-File, 
funktioniert alles wie gewünscht - ist nur etwas lästig nach jedem 
build.
Da ich den EXTMEM-Bereich nicht zu initialisieren brauche, wäre 
offensichtlich eine section .noinit die richtige Version gewesen.
Frage:
Muss ich zwingend die section EXTMEM in noinit ändern oder ist es 
möglich, der section EXTMEM zusätzlich einen Parameter "noint" zu 
verpassen? Und wenn ja, wie macht man das?

Thomas

von Stephan (Gast)


Lesenswert?

Hi,
 bin mir nicht sicher, aber sollte es nicht reichen:
-set-section-flags=.EXTMEM="NOLOAD"

zu setzen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> Im externen EEPROM sind Stammdaten abgelegt, die beim Einschalten
> in den RAM geladen werden und ein Array füllen. Dabei bin ich
> zwingend darauf angewiesen, dass auch bei neuen Programmversionen
> die Datenlage im externen EEPROM exakt mit der vom Programm erwarteten
> im RAM übereinstimmt. Ist jetzt der Compiler mal anderer Meinung zur
> Datenanordnung, geht das voll in die Hose.

Der Compiler ist nicht irgendeiner "Meinung", sondern folgt einem 
Sprachstandard.

Konkret: Die Reihenfolge, in der Variablen ablegegt werden, ist nicht 
spezifiziert und interessiert i.d.R auch nicht.

Wenn es interessiert, dann geht oft um einen Sack voll globaler 
Daten-Flöhe, auf die auf eine bestimmte Weise zuzugreigen ist, und das 
geht z.B. so:

Für die EEprom-Daten wird eine Struktur angelegt, welche die Daten 
origanisiert, ich nenn den mal eeprom_t.  Von diesem Ding gibt es dann 2 
Instanzen: Eine im EEMEM:
1
#include <avr/eeprom.h>
2
3
const volatile eeprom_t eeprom_content EEMEM = { ... };
sowie eine im RAM:
1
eeprom_t eeprom_werte;

Nun können wie Werte Problemlos zwischen EEPROM und RAM konsistent 
gehalten werden (modulo Problemen aufgrund der Spannungsversorgung), 
z.B. eeprom_read_block und eeprom_update_block.

: Bearbeitet durch User
von Thomas P. (topla)


Lesenswert?

Danke für Deinen Beitrag. Löst jetzt zwar nicht das akute Problem mit 
dem überlaufenden hex-File, erinnert mich aber an eine andere offene 
Baustelle, die ich erstmal weiter nach hinten geschoben habe.
Für die Anordnung im EEMEM ist mir die Vorgehensweise auch klar, da der 
Compiler den Ablauf für den Zugriff auf diese Daten kennt, aber wie 
verhält sich das, wenn diese Daten in einem SPI-EEPROM liegen? Was muss 
ich denn dann wie bereitstellen, damit der Compiler damit umgehen kann? 
Das würde ja dann eine neue section bedeuten, die aber weder im sram, im 
flash oder im eeprom liegt.

Thomas

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> [...] aber wie verhält sich das, wenn diese Daten in einem
> SPI-EEPROM liegen? Was muss ich denn dann wie bereitstellen,
> damit der Compiler damit umgehen kann?  Das würde ja dann eine
> neue section bedeuten, die aber weder im sram, im flash oder
> im eeprom liegt.

Ich nehme mal an, dass dein µC den SPI nicht per DMA abbildet, sondern 
dass das SPI "händisch" abläuft, d.h. es werden old-school bytes per SPI 
desendet / empfangen.  Wo erlche Daten hinkommen, implementiert dann die 
[De]Serialisierung C-Daten <-> SPI.

Eine neue Section könnte sinnvoll sein wenn, wenn das SPI-EEprom z.B. 
durch irgendwelche (Hardware-Magie) in den Adressraum gemappt würde. 
Abel selbst dann wäre es wahrscheinlich ausreichend, die einzelnen Bytes 
dieses RAM-gemappte SPI-EEproms wie SFRs zu behandeln.

von Thomas P. (topla)


Lesenswert?

Stephan schrieb:
> Hi,
>  bin mir nicht sicher, aber sollte es nicht reichen:
> -set-section-flags=.EXTMEM="NOLOAD"
>
> zu setzen.

Das heißt also, dass ich ein externes Makefile benutzen muss, richtig?
Ich habe jetzt das automatisch erzeugte Makefile um die angegebene Zeile 
im Bereich HEX_FLASH_FLAGS ergänzt, unter Optionen den Pfad zum Makefile 
eingestellt und die Verwendung eines externen Makefiles angehakt: 
Ergebnis ist, dass keinerlei Outputfiles erzeugt werden. Was habe ich 
denn nun wieder verbrochen? Fehlermeldungen gibt es keine.

Thomas

PS: Auch ohne die eingefügte Zeile passiert bei der Wahl "externes 
Makefile" garnichts.
Orischwernochbleedehier....

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

..ich verstehe diese EXTMEM nicht; wozu ist das gut?

Verstehe ich das richtig: Du willst keine Daten dort lokatieren, sondern 
nur ein definiertes Abbild zugreifen?

von Thomas P. (topla)


Lesenswert?

Johann L. schrieb:
> Ich nehme mal an, dass dein µC den SPI nicht per DMA abbildet, sondern
> dass das SPI "händisch" abläuft, d.h. es werden old-school bytes per SPI
> desendet / empfangen.  Wo erlche Daten hinkommen, implementiert dann die
> [De]Serialisierung C-Daten <-> SPI.

Richtig, kein DMA sondern SPI Byte für Byte. Dafür existiert eine union 
mit den Daten der 255 Sätze zu je 128 Byte / unsigned char im Ram über 
die immer auf einen kompletten Satz zugegriffen wird. Das Problem sehe 
ich eben an der Stelle, wenn die abgelegten Daten pro Satz bei einer 
neuen Programmversion z.B. die Reihenfolge ändern. Die Daten im Ram 
werden dann aus dem SPI-EEPROM falsch versorgt.

Thomas

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> Jetzt ist die Hardware fertig und ich sinne darüber nach,
> ob ich die Portierung [...] nach AVR-Assembler ausführe
> oder mal wieder versuche, mit C zu beginnen.

Das schliesst sich nicht aus. Und lass dir folgenden Ratschlag ans Herz 
legen:

Falls du dich für Assembler entscheidest, dann

1) Nimm auf jeden Fall den GNU-Assembler.

2) Implementiere dein Funktionen nach avr-gcc ABI.

Dann kannst du in der Zufunft ganz easy von Assembler nach C migrieren 
oder umgekehrt -- und zwar für jede einzelne Funktion wie es dir 
gefällt.

https://gcc.gnu.org/wiki/avr-gcc

3) Lass dir nicht einreden, diese ABI verwenden sei zu ineffizient.
   Ja, es ist nicht so gut wie kaputt-optimierter Assembler-Code.
   Aber das wird keine Rolle spielen — und wenn, hast du dich
   vermutlich nicht für die richtige Hardware entschieden.

Ein "leeres" Programm kann nich dir bei Bedarf geben.

von Thomas P. (topla)


Lesenswert?

EXTMEM ist ein externer SRAM und eine eigene section existiert aus dem 
Grund, dass nur so zu steuern ist, wo welche Daten landen. Ich möchte 
ganz gezielt Variablen im internen oder externen Ram ablegen können.

Thomas

von Thomas P. (topla)


Lesenswert?

Im Moment reite ich die C-Welle und es geht auch ganz brauchbar voran, 
wenn die kleinen, aber saugemeinen, Stolpersteine nicht dauernd vor die 
Füße geraten würden. Isr halt das erste C-Projekt.

Thomas

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> Johann L. schrieb:
>> [...]
> Richtig, kein DMA sondern SPI Byte für Byte. Dafür existiert eine union

Warum eine Union?

> Das Problem sehe ich eben an der Stelle, wenn die abgelegten Daten
> pro Satz bei einer neuen Programmversion z.B. die Reihenfolge ändern.
> Die Daten im Ram werden dann aus dem SPI-EEPROM falsch versorgt.

Selbt bei der gleichen Tool-Version ist die Reihenfolge immer noch 
nicht spezifiziert und hängt ab u.a. von der Verschalterung oder dem 
eigentlichen Progamm.

Nochmals: Vermeide globalen Datenmatsch, lies das ABI, modelliere dein 
Layout als Struct, und gut ist.  Falls du unbedingt Matsch magst, wirst 
du eine Lösung wählen müssen, die du später mal hassen wirst:

o Verwende ein eigenes Linker Description File und mach das
  Layout händisch.  Oder:

o Lege ein eigenes Assembler-Modul an, in dem die Daten definiert
  werden. Anbei ein Header mit den C-Deklarationen. Oder:

o Alle Variablen in ein C-Modul wie du sie haben willst.  Als
  Schalter mindestens -fno-toplevel-reorder, mehr sagt dir die
  gcc-Hilfe:

  http://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html

: Bearbeitet durch User
von Thomas P. (topla)


Lesenswert?

Johann L. schrieb:
> Warum eine Union?
> ...
> Nochmals: Vermeide globalen Datenmatsch, lies das ABI, modelliere dein
> Layout als Struct, und gut ist.  Falls du unbedingt Matsch magst, wirst
> du eine Lösung wählen müssen, die du später mal hassen wirst:

Ich glaube, soweit auseinander sind wir garnicht.
Die Daten sind schon nach Anleitung von hier als struct angelegt, die 
union dazu für den byteweisen Umgang mit dem SPI-EEPROM und dieser 
Schritt könnte zum Problem werden, wenn ich das richtig interpretiere:

> Selbt bei der gleichen Tool-Version ist die Reihenfolge immer noch
> nicht spezifiziert und hängt ab u.a. von der Verschalterung oder dem
> eigentlichen Progamm.

Das Ganze ist in einer definitions.h ausgelagert und funktioniert prima.

Thomas

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas P. schrieb:
> Damit alles hier etwas zusammen bleibt, habe ich den Thread wieder
> ausgebuddelt.
>
> Nach Hinweisen hier habe ich eine eigene section für den externen RAM am
> ATMEGA2560 angelegt und die Linker-Option eingestellt.
> #define EXTMEM __attribute__((section(".EXTMEM")))
> /* Linker option:
> -Wl,--section-start,.EXTMEM=0x802200,--defsym=__heap_end=0x80ffff
>
> Mit kleinen Datenmengen hat das auch gut funktioniert, allerdings kam
> bei deutlicher Vergrößerung eines Arrays im .EXTMEM die Fehlermeldung,
> dass das Programm zu groß sei und nicht geflasht werden kann.

Das wundert nicht, denn .data wird im Startup initialisiert aus dem 
Flash.  Wo steht das .EXTMEM im Linker-Script? Im Default-Skript find 
ich da nix, d.h. dein .EXTMEM ist eine Input-Section, der keine 
Output-Section zugeordnen ist (Orphan).

Weiteres Problem: Der Location-Pointer (.) des Linkers kann nicht 
rückwärts wandern.  Wenn er also ein Objekt nach .EXTMEM legt kann es 
sein dass andere Objekte danach lokatert werden und ebenfalls in 
.EXTMEM landen, was du dann vermutlich nicht willst.

Ergo: Du willst

o eine eigene Linker-Description, sehr wahrscheinlich mit eigener
  Output-Section für externen Speicher.

o falls es nur ein Objekt im externen Speicher ist und das auch so
  bleiben wird, dann ist folgendes abzuwägen:
1
      extern folge_t folgen[255];
  und Linken mit --Wl,-defsym=folgen=0x2200.  Für folgen[] gibt es
  keine Definition auf C-Ebene, oder

o es gibt ein lineares Speichermodell, in dem sich der externe
  Speicher nahtlos an den internen RAM anschließt.  Es ist egal,
  was in welchem Speicher landet, und explizites Attributieren
  einzelner Objekte ist nötig.

> Die Betrachtung des hex-Files ergab, dass der Bereich des Arrays dort
> ebenfalls erscheint, aufgefüllt mit 0x00 und offensichtlich der
> Initialisierung dient. Lösche ich diesen Bereich aus dem hex-File,
> funktioniert alles wie gewünscht - ist nur etwas lästig nach jedem
> build.

Klaro, s.o.  Händisch an Section-Flags rummachen würd ich abraten, 
insbesondere

o Wenn es keine Output-Section ist.  In es eine Output Section ist,
  steht eh im ld-Skript, wie diese zu behandeln ist)

o Wenn es eine Orphan ist.  In dem Falls bewirken andere Flags nur,
  dass der Linker anders rät.  Die Regeln für Orphans sind, naja
  loes eben selbst nach :-)

> Muss ich zwingend die section EXTMEM in noinit ändern oder ist es
> möglich, der section EXTMEM zusätzlich einen Parameter "noint" zu
> verpassen?

Mach dich mal vertraut damit, was Input- Output- und Orphan-Sections 
sind.

Zur Union:  Das verunstaltet die Quellen, denn jedes Objekt, dessen 
Adresse du einer Zugriffsfunktion übergibst, würde in eine Union gepackt 
werden müssen.  Stell dir vor, wie verunstaltet die Quellen wären, wenn 
das für jedes Objekt genacht werden würde, das mit pgm_read_* 
zugegriffen wird !

Ergo:
1
void read_block_from_eemem (void *vpram,
2
                            void const volatile *vpeeprom,
3
                            size_t n_bytes)
4
{
5
    uint8_t const *pram = (uint8_t const*) vpram;
6
    uint8_t const volatile *pram = (uint8_t const volatile*) vpeeprom;
7
  ...
8
}
9
10
void code (siet_t n, size_t m)
11
{
12
    read_block_from_eemem (& folgen[n].part, & ee_folgen[m].part, sizeof (part_t));
13
}

von Thomas P. (topla)


Lesenswert?

Hallo Johann,

vielen Dank für Deine ausführlichen Erklärungen.
Da ich jetzt nicht in die Niederungen des Linker-Scripts absteigen 
wollte, habe ich mit meinem angelesenen Halbwissen die Definition von 
EXTMEM wie folgt geändert:
#define EXTMEM __attribute__((section(".noinit")))
Dazu noch:
/* Linker option:
-Wl,--section-start,.noinit=0x802200,--defsym=__heap_end=0x80ffff

So funktioniert das einwandfrei, EXTMEM im batteriegepufferten 
SRAM-Bereich wird nicht initialisiert und damit sind auch die Tonnen von 
Nullbytes aus dem Flash (hex-File) verschwunden.

Johann L. schrieb:

> Zur Union:  Das verunstaltet die Quellen, denn jedes Objekt, dessen
> Adresse du einer Zugriffsfunktion übergibst, würde in eine Union gepackt
> werden müssen.  Stell dir vor, wie verunstaltet die Quellen wären, wenn
> das für jedes Objekt genacht werden würde, das mit pgm_read_*
> zugegriffen wird !

Ja, stimmt und Deinen Hinweis werde ich an der Stelle umsetzen.

Nochmals vielen Dank für Deine Hinweise.

Thomas

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]
  • [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.