Hallo,
ich habe einen Vektor mit 200 Stellen mit dem Datentyp int32_t. Eine ISR
greift auf diesen Vektor zu, d.h. ich muss diesen global definieren.
Das macht schon einmal 800byte SRAM. Das ist vermutlich für meinen
Atmega8 zuviel.
Der Vektor wird zur Laufzeit des Programms verändert, d.h. Werte
ausgelesen, verändert und wieder zurückgeschrieben.
Kann ich den Vektor auch in den PROGMEM auslagern? Welche Nachteile
ergeben sich daraus? Zugriffszeit?
Danke.
LG,
Sven
Sven schrieb:> Kann ich den Vektor auch in den PROGMEM auslagern? Welche Nachteile> ergeben sich daraus? Zugriffszeit?
Die Zugriffszeit wäre gar nicht mal so wild.
Der Hauptnachteil dürfte eher sein, dass man das PROGMEM, sprich das
Flash nicht zur Laufzeit beschreiben kann :-)
800 Bytes sind zwar schon heftig viel, aber es bleiben noch 224 Bytes.
Wenn man mit zu sehr damit umhaut, ist das immer noch eine Menge Holz.
Was steht den in den Daten? Kann man da mit cleverer Organisation
eventuell noch was rausholen?
Die Daten sind im Prinzip eine Tabelle, die durch Benutzerinteraktion
verändert werden kann (der Benutzer kann durch Konfigurationen die
Tabelle im Prinzip neu befüllen während der Laufzeit). Dort sind dann
Werte größer 16-bit hinterlegt.
Ich würde mit 16-bit vom Zahlenwert klarkommen (max. 65536), mein
Problem ist, dass das ganze aber signed ist. Daher benötige ich noch
1-bit für ein Vorzeichen. Vielleicht kann man hier mit cleverer
Programmiertechnik tricksen und Zahl von Vorzeichen geschickt trennen
und verwerten, aber dafür sind meine Kenntnisse zu wenig.
Der verbleibende SRAM ist aber ziemlich schnell aufgebraucht. Ich bin
jetzt bei ~97% nach dem compilieren mit den 32-bit Array. Zuzüglich die
dynamischen Variablen zur Laufzeit wird der Stack schnell dünn.
Sven schrieb:> Die Daten sind im Prinzip eine Tabelle, die durch Benutzerinteraktion> verändert werden kann (der Benutzer kann durch Konfigurationen die> Tabelle im Prinzip neu befüllen während der Laufzeit). Dort sind dann> Werte größer 16-bit hinterlegt.
Du hast mich missverstanden.
Was sind das für Werte? Welche Bedeutung haben sie?
> Ich würde mit 16-bit vom Zahlenwert klarkommen (max. 65536), mein> Problem ist, dass das ganze aber signed ist. Daher benötige ich noch> 1-bit für ein Vorzeichen. Vielleicht kann man hier mit cleverer> Programmiertechnik tricksen
Na ja.
Für eine
struct MyValue
{
uint8_t Sign;
uint16_t Value;
};
struct MyValue Values[200];
muss man nicht weiter tricksen. Und spart so schon mal auf einen Schlag
200 Bytes gegenüber einem reinen uint32_t Array ein.
> aber dafür sind meine Kenntnisse zu wenig.
dann musst du daran was ändern. Wer ein Haus bauen will muss mehr als
nur einen Hammer kennen.
Du nimmst zwei Arrays. Der eine enthält nur die Absolutwerte (besteht
also aus 200x uint16_t). Der andere nur die Vorzeichenbits. Und weil man
nur ein bit pro Eintrag braucht, nimmt man dafür einen Bitarray, der aus
200/8 uint8_t besteht. Das macht dann insgesamt 425 Byte.
Bitarrays tauchen hier immer wieder auf, du kannst z.B. mal hier gucken:
Beitrag "Bit Array in C"
oder schauen was die Suche zu "bitarray" bringt.
Hi,
Sven schrieb:> Ich würde mit 16-bit vom Zahlenwert klarkommen (max. 65536), mein> Problem ist, dass das ganze aber signed ist. Daher benötige ich noch> 1-bit für ein Vorzeichen. Vielleicht kann man hier mit cleverer> Programmiertechnik tricksen und Zahl von Vorzeichen geschickt trennen> und verwerten, aber dafür sind meine Kenntnisse zu wenig.
Nur 'ne Idee:
1
typedefstruct{
2
uint16_tvalue1;
3
uint16_tvalue2;
4
uint16_tvalue3;
5
uint16_tvalue4;
6
uint16_tvalue5;
7
uint16_tvalue6;
8
uint16_tvalue7;
9
uint16_tvalue8;
10
unsignedcharsign1:1;
11
unsignedcharsign2:1;
12
unsignedcharsign3:1;
13
unsignedcharsign4:1;
14
unsignedcharsign5:1;
15
unsignedcharsign6:1;
16
unsignedcharsign7:1;
17
unsignedcharsign8:1;
18
}val_t__attribute__((packed));
19
val_tarray[25];
20
(..)
21
array[3].value5=65535;// Betrag
22
array[3].sign5=1;// Vorzeichen
oder
1
typedefstruct{
2
uint16_tvalue[8];
3
uint8_tsign;
4
}vul_t__attribute__((packed));
5
vul_turray[25];
6
(..)
7
urray[3].value[5]=65535;// Betrag
8
urray[3].sign=(1<<5);// Vorzeichen
Kostet jeweils nur 450 Byte RAM, mußt aber entweder mit entsprechenden
Indizes (oder #defines) oder mit Bitoperatoren arbyten. Vorteil der
ersten Lösung: bei festen Werten einfacher zu lesen und zu schreiben,
Vorteil der zweiten Lösung: einfacher zu iterieren.
HTH,
Klaus
PS: ungetestet.
Karl Heinz Buchegger schrieb:> Der Hauptnachteil dürfte eher sein, dass man das PROGMEM, sprich das> Flash nicht zur Laufzeit beschreiben kann :-)
Wenn man ein Stück Assembler mit in's Programm aufnehmen will:
In der Assembler-Hilfe ist das gut beschrieben, wie man das macht. Geht
dann zwar nur 'seitenweise', könnte aber RAM sparen. Atmega8: Page Size:
32 words
Karl Heinz Buchegger schrieb:> struct MyValue> {> uint8_t Sign;> uint16_t Value;> };>> struct MyValue Values[200];
Hallo,
ok, das habe ich soweit verstanden, wie man die Daten ablegen kann.
Etwas schwer tue ich mir aber mit der Verwertung.
Lege ich dann eine globale Variable an ein einfaches int32_t s32foo;
Darin speichere ich mir dann den Wert des structs MyValue, z.B. so (für
Stelle0):
1
s32foo=Values[0].Sign<<31;// Sign = 0 vorzeichenlos, Sign = 1 vorzeichenbehaftet, höchstes Bit der 32-bit Variable schieben (=Vorzeichenbit);
2
s32foo|=Values[0].Value;// noch mit den Bits des Wertes verodern
Sven schrieb:> Karl Heinz Buchegger schrieb:>> struct MyValue>> {>> uint8_t Sign;>> uint16_t Value;>> };>>>> struct MyValue Values[200];>> Hallo,>> ok, das habe ich soweit verstanden, wie man die Daten ablegen kann.> Etwas schwer tue ich mir aber mit der Verwertung.
Der Knackpunkt liegt darin, dass du ohne Cast nicht auskommen wirst.
D.h. wenn du den uint16_t verwenden willst, musst du ihn ja erst mal
wieder zu einem korrekten int32_t 'aufblasen'. Dabei muss aber der
unsigned Wert zuerst auf 32 Bit hochgehoben werden und erst dann kann
man mit der Zusatzinfo aus Sign entscheiden, ob das jetzt ein positiver
Wert oder ein negativer Wert war.
Am besten überlässt man diese Details gleich mal einer Funktion :-) Dann
braucht man sich an der eigentlichen verewndenden Stelle nicht mehr
darum kümmern. Man ruft einfach die Funktion auf, übergibt ihr einen
struct MyValue Wert und kriegt den fix&fertigen int32_t zurück. Ich
verwende dazu die Konvention, dass ein Sign Wert von 0 positive Zahlen
kennzeichnet, während alles andere in Sign einen negativen Wert
darstellt. Theoretisch sollten da eigentlich nur die Werte 0 und 1
vorkommen - aber man weiß ja nie, sicher ist sicher. Da nehme ich
Anleihen bei C, welches ja auch die Konvention hat: 0 ist logisch FALSE,
alles andere ist TRUE
1
int32_ttoInt32(structMyValue*wert)
2
{
3
int32_tresult=wert->Value;
4
if(wert->Sign!=0)
5
result=-result;
6
returnresult;
7
}
Damit kann ich zb mit den einzelnen Array-Elementen jetzt leicht
Operationen durchführen. zb
1
for(i=0;i<200;i++)
2
{
3
int32_tj=toInt32(Values[i]);
4
5
...
6
sprintf(buffer,"%ld",toInt32(Values[i]));
7
8
...
9
10
k=toInt32(Values[i])/toInt32(Values[0]);
11
12
...
Wann immer ich den echten 32-Bit Wert benötige, benutze ich die
Funktion, die mir den aus so einem Struktur-Objekt erzeugt. Ob ich den
dann in einem int32_t speichere, ob ich damit rechne, ob ich ihn
ausgebe, ... ist dann meine Sache, wie ich den Wert benutzen will.
Selbiges in die umgekehrte Richtung. Die Umwandlung eines int32_t in ein
struct MyValue Objekt, ... das macht mir gleich mal wieder eine
Funktion. Ist der Wert negativ, dann mach ich ihn positiv, womit er (mit
einer Ausnahme!) in den uint16_t passt und das Vorzeichen vermerke ich
mir getrennt
1
voidtoMyValue(structMyValue*value,int32_twert)
2
{
3
if(wert<0)
4
{
5
value->Value=(uint16_t)-wert;
6
value->Sign=1;
7
}
8
else
9
{
10
value->Value=(uint16_t)wert;
11
value->Sign=0;
12
}
13
}
> Ist das so ok?
Widersteh der Versuchung da mit Bitoperationen um dich zu schmeissen.
Lass das den Compiler machen. Du schreibst erst mal dein Programm so,
wie es am einfachsten zu verstehen ist. Erst dann, wenn eine Analyse
ergeben hat, dass das zu langsam ist, dann überlegt man, wie man es
beschleunigen könnte. Aber da dieses 200-er Array ja wohl hauptsächlich
nur dazu dient, eine Datenmenge zwischenzuspeichern und damit keine
großartig vielen Operationen gemacht werden, ist das (geschätzt) alles
erst mal nicht zeitkritisch.
Erst macht man es richtig - dann macht man es schnell.
Performance kann man noch verbessern, wenn man statt dem Shift um einen
dynamischen Wert eine Tabelle für die Bitmasken nutzt, aber das sei dem
interessierten Leser überlassen ... ;-)
Danke, das war sehr hilfreich und verständlich.
Auf das erste Lesen verstehe ich den Code, muss ihn mir aber mal noch
Schritt für Schritt nachvollziehen, um das zu verinnerlichen.
Merci nochmals.
Fabian O. schrieb:> int32_t get_value(uint8_t n)> {> if (Values[n].Sign) {> return -((int32_t) Values[n].Value);> } else {> return Values[n].Value;> }> }>> void set_value(uint8_t n, int32_t val)> {> if (val < 0) {> Values[n].Sign = 1;> Values[n].Value = -val;> } else {> Values[n].Sign = 0;> Values[n].Value = val;> }> }
Ich muss hier nochmal nachlegen, da ich ein Problem habe.
Ist der Übergabewert val für
1
voidset_value(uint8_tn,int32_tval)
nur 16-bit groß, z.B. -10000, dann erkennt die Funktion nicht, dass der
Wert negativ ist, d.h. es wird der else-Zweig verwendet.
Ändere ich die Funktion in
1
voidset_value(uint8_tn,int16_tval)
funktioniert es. Ist aber nicht Sinn der Sache, da val je nachdem 32-bit
oder 16-bit groß sein kann, d.h. ich brauche in jedem Fall 32-bit.
Stellt sich da der Compiler doof an oder wie kann ich das lösen?
Danke.
Zeig mal, wie Du die Funktion aufrufst, möglichst den ganzen Code.
Könnte z.B. sein, dass Du die -10000 in einer uint16_t- statt
int16_t-Variable liegen hast.
Bronco schrieb:> Johann L. schrieb:>> int32_t compose (uint16_t val, uint8_t sign)>> {>> if (sign)>> return (int16_t) val;>> else>> return val;>> }>> Hä?> Mit>
1
>return(int16_t)val;
2
>
> könnte er doch nicht einen gewünschten Wertebereich von -65535 bis> +65535 abdecken, oder steh ich gerad auf dem Schlauch?
Nö, aber ich stand drauf :-)
Bitte reduzieren Sie die Anzahl der Zitatzeilen. Bitte reduzieren Sie
die Anzahl der Zitatzeilen.
Die Idee, die unteren zwei Bytes nicht zu ändern, hat aber schon was.
Mit
1
return(int32_t)((uint32_t)val|0xFFFF0000);
oder
1
return(int32_t)((uint32_t)val-65536);
sollte bei 2er-Komplement das Richtige rauskommen. Allerdings ist es
streng genommen Type Punning, weil der Cast von unsigned nach signed
"implementation defined" ist, wenn ich das richtig in Erinnerung habe.
Oder gibt es eine standardkonforme Methode?
Außerdem muss der Compiler das kürzer als die Negation umsetzen, damit
es was bringt ... Sofern man das nicht extrem häufig braucht, würde ich
lieber die verständlichere und standardkonforme Version nehmen.
So endlich von der Arbeit zu Hause.
Ich habe das Programm mal auf das wesentliche gekürzt, compiliert und
das ausgegeben auf der UART, was nicht funktioniert.
Anbei das Programm und die UART-Mitschnitt.
Wie man sieht, wird bei der UART-Ausgabe bei Vorzeichen 0 gesetzt, d.h.
er erkennt nicht, dass der Wert negativ ist, obwohl er es ist, siehe
Ausgabe davor "Berechneter Wert".
Die Werte sind nur zu Demo, es können auch Werte theoretisch vorkommen,
die größer als ein int16_t sind (ist hier nun nicht der Fall).
eine andere möglichkeit den Speicher zu sparen, wäre, sich zu überlegen,
ob der Benutzer denn tatsächlich eine 1-bit Auflösung der
Konfigurationsparameter braucht, oder ob es nicht reicht, wenn er nur in
2er, 5er oder 10er Schritten oder einer logarithmischen Skala Werte
eingeben kann.
In diesem Fall sucht man sich eine passende Bijektivität in den zur
Speicherung zur Verfügung stehenden Wertebereich und speichert nur
diesen Wert.
Danke Fabian, damit geht es. Habe die Zeile geändert und nun läuft's.
Verstehen tu ich es aber noch nicht ganz.
Gut, der ausgelesene Wert ist ein int16_t. Aber ein int16_t passt doch
in ein int32_t rein, wieso muss ich hier nochmals explizit casten?
Sven schrieb:> Gut, der ausgelesene Wert ist ein int16_t.
Eben nicht, es ist ein uint16_t. Wenn Du den in einen int32_t packst,
entsteht kein negatives Vorzeichen. Deshalb der explizite Cast von einem
uint16_t in einen int16_t und dann erst in int32_t.
Solche Probleme lassen sich übrigens gut vermeiden, wenn man mit
passenden (lokalen) Variablentypen arbeitet, die auch nicht volatile
sein müssen. Und für Zeilenumbrüche besser "\r\n" nehmen:
Fabian O. schrieb:> Und für Zeilenumbrüche besser "\r\n" nehmen:
Aber nicht so:
> uart_puts_P("\r\n");
Ein Zeilenumbruch in C ist ein \n.
Da hier keine OS-Unterstützung vorhanden ist, gibt man den \r zentral
und an einer einzigen Stelle aus, etwa innerhalb der innersten
uart_put, welche die Zeichen ausgibt.
Ob wirklich ein \r auszugeben ist oder nicht, hängt vom Filesystem des
Host-OS a, hier also davon, ob zu einem Linux- oder MS-Win Terminal
übertragen / angezeigt wird.
Und das will man nicht 100 mal im Cose stehen haben sondern an einer
zentralen Stelle, etwa
Fabian O. schrieb:> Eben nicht, es ist ein uint16_t.
Hmm, wieso das? TabellePROGMEM ist doch vom Typ int16_t?
Diese Struktur werde ich mir angewöhnen, dass man alles schön der Reihe
nach von Typ nach Typ macht, das ist sicherlich deutlich weniger
fehleranfällig. Danke.
Na so ein Quatsch.
Dann muss ich ja je nachdem ob meine Gegenstelle ein unixoid, macoid
oder windowsoid ist, ja eigene Firmwares und Geräteversionen bauen.
Damit sollte imho die Gegenstelle klar kommen.
Die meisten Terminal-Programme die ich kenne, lassen sich einstellen,
bei was sie einen Umbruch machen und was sie bei einem Enter
rauschicken.
Sven schrieb:> Hmm, wieso das? TabellePROGMEM ist doch vom Typ int16_t?
Ah, jetzt versteh ich Dein Problem. Die Sache ist, dass die Funktion
pgm_read_word() nicht weiß, welchen Typ die Daten im Flash haben. Sie
liest einfach von der Adresse, die Du ihr übergibst, 16 Bit und gibt sie
als uint16_t zurück. Wenn Du die 16 Bit vorzeichenbehaftet
interpretieren willst, musst Du sie explizit in ein int16_t speichern
bzw. casten.
Ob Du TabellePROGMEM als int16_t oder uint16_t angelegt hast, spielt
also so gesehen keine Rolle. Genau wegen solcher Dinge ist diese
PROGMEM-Geschichte eine ziemliche Krücke ...
In neueren avr-gcc-Versionen (ab 4.7) gibt es zu PROGMEM die Alternative
__flash. Dann braucht man keine extra Funktionen mehr, um aus dem Flash
zu lesen. Beim aktuellen Atmel Studio ist leider Version 4.6 dabei,
damit geht es noch nicht.
Ok, jetzt verstehe ich auch.
Weitergedacht wäre es folglich ein Problem, wenn man gemischte Werte im
Flash ablegt (signed und unsigned) willkürlich verteilt im Vektor (also
Vorzeichen der jeweiligen Stelle unbekannt bzw. ohne Algorithmus). Diese
könnte ich dann doch niemals Vorzeichenrichtig holen mit der Funktion
pgm_read_wort, oder?
Das ist jetzt bei mir zwar nicht der Fall, möchte aber nur
weiterdenken...
Hallo,
ich hab ein bisschen mitgelesen und mich über viele wertvolle Beiträge
gefreut. Eine Frage wurde aber noch nicht beantwortet, und auf diese
Antwort wär ich schon irgendwie neugierig, weil sie vielleicht deutliche
einfachere Lösungen ermöglicht:
Karl Heinz Buchegger schrieb:> Was sind das für Werte? Welche Bedeutung haben sie?
Weitere Fragen von mir dazu:
Wird wirklich ein Wertebereich von -65536..+65535 benötigt, oder reicht
zum Beispiel -16384..49151 ?
Ist wirklich eine Genauigkeit von +/-1 notwendig, oder reicht es, wenn
der fragliche Zahlenwert vor dem Speichern auf 2er-Schritte gerundet
wird?
Die Werte stellen eine Tabelle dar, aus der sich je nach Bedarf ein
Timer-Wert geholt wird bzw. der Wert in der Tabelle mit dem aktuellen
Timer-Wert verrechnet wird und dann ein Compare Match vorlädt.
Positiv werden die Werte in der Regel nur bis ca. 5000. Negativ benötige
ich aber bis -60000, also 16-bit Breite könnte gehen, wenn es darum
geht.
Eine Genauigkeit für den Compare Matach von +/-2 ist auch vertretbar.
Sven schrieb:> Positiv werden die Werte in der Regel nur bis ca. 5000. Negativ benötige> ich aber bis -60000, also 16-bit Breite könnte gehen, wenn es darum> geht.
Das passt doch prima. Einfach nen Offset von 60000 drauf und fertig.
Und 200 Timer???
Sven schrieb:> Weitergedacht wäre es folglich ein Problem, wenn man gemischte Werte im> Flash ablegt (signed und unsigned) willkürlich verteilt im Vektor (also> Vorzeichen der jeweiligen Stelle unbekannt bzw. ohne Algorithmus). Diese> könnte ich dann doch niemals Vorzeichenrichtig holen mit der Funktion> pgm_read_wort, oder?
In so einem Fall bietet es sich an, die Daten in einer Struktur
abzulegen und mit memcpy_P() zu holen.
Beispiel:
Sven schrieb:> Die Werte stellen eine Tabelle dar, aus der sich je nach Bedarf ein> Timer-Wert geholt wird bzw. der Wert in der Tabelle mit dem aktuellen> Timer-Wert verrechnet wird und dann ein Compare Match vorlädt.>> Positiv werden die Werte in der Regel nur bis ca. 5000. Negativ benötige> ich aber bis -60000, also 16-bit Breite könnte gehen, wenn es darum> geht.>> Eine Genauigkeit für den Compare Matach von +/-2 ist auch vertretbar.
Das hättest Du wirklich einfacher haben können ... Entweder wie gesagt
mit einem festen Offset oder die halbierten Werte als int16_t speichern.
Sven schrieb:> Die Werte stellen eine Tabelle dar, aus der sich je nach Bedarf ein> Timer-Wert geholt wird bzw. der Wert in der Tabelle mit dem aktuellen> Timer-Wert verrechnet wird und dann ein Compare Match vorlädt.>> Positiv werden die Werte in der Regel nur bis ca. 5000. Negativ benötige> ich aber bis -60000, also 16-bit Breite könnte gehen, wenn es darum> geht.>> Eine Genauigkeit für den Compare Matach von +/-2 ist auch vertretbar.
Puh - gut, das ich nochmal nachgehakt habe. :-)
Dann ist die Lösung wirklich einfach: Du verwendest uint16_t in der
Tabelle. Vor dem Schreiben in den Speicher addierst du 60000 zum Wert,
nach dem Lesen aus dem Speicher schiebst du den Wert in eine
32-Bit-Variable (signed) und subtrahierst 60000 – genau wie Johann
vorschlägt.
Eine andere, ebenso einfache Lösung wäre es, die Zahlen generell als
halben Wert als int16_t zu verarbeiten und zu speichern (signed).
Trotzdem – falls mal jemand ein ähnliches Problem hat, das sich nicht so
einfach lösen lässt wie bei Sven, finde ich die vielen Vorschläge in
diesem Thread sehr interessant. Eure Arbeit war also bestimmt nicht "für
die Katz"! :-)