neuen Datentyp (10-bit) zur Speicheroptimierung erstellen

#5515808
Lesenswert?

Hallo Community,

ich arbeite gerade an einem Prgramm mit dem ich Melodien über einen uC 
(ATmega8a)abspielen möchte. Das Programm funktioniert einwandfrei und 
die Melodien Werden in zwei 8-bit Array mit einmal dem Ton (Zahlenwerte 
0 - 38) und im anderen der Tondauer (Zahlenwerte 0 - 15).

Auf diese Art und weise verschwende ich jedoch 6 bit von insgesagt 16 
bit, da ich ja nur 6 + 4 bit brauche. Ich könnte also 37,5% Speicher 
einsparen und bei bisher 650 verwendeten Byte ist das nicht zu 
vernachlässigen.

Gibt es eine einfache oder gute Möglichkeit den kompletten Speicher 
auszunutzen?
#5515843
Lesenswert?

Tobias K. schrieb:
> Gibt es eine einfache oder gute Möglichkeit den kompletten Speicher
> auszunutzen?

Bei der Tondauer ist es ja einfach. Da du dafür genau 4 Bit brauchst, 
kannst du einfach zwei Stück in ein Byte schreiben, dann hast du schon 4 
von deinen 6 Bit gespart.
Ob es sich jetzt lohnt, die restlichen 2 Bit auch noch einzusparen, 
musst du dir überlegen. Vielleicht kannst du dir ja alternativ noch ein 
nettes Zusatzfeature einfallen lassen, für das du die Bits nutzen kannst 
(z.B. vibrato).
#5515863
Lesenswert?

Rolf M. schrieb:
> Bei der Tondauer ist es ja einfach. Da du dafür genau 4 Bit brauchst,
> kannst du einfach zwei Stück in ein Byte schreiben, dann hast du schon 4
> von deinen 6 Bit gespart.

Danke für den Tipp, die Idee hatte ich auch schon und werde ich 
vermutlic hauch umsetzten. Die restlichen 2 Bit wären da nicht so 
einfach unter zu bringen. Gibt es C ne möglichkeit einen 6 bit Datentyp 
zu definieren, der mir einfach nacheinander en Speicher auffüllt, weil 
mit bitschieberein wird das nicht allzu toll
#5515870
Lesenswert?

Wenn man Ton und Dauer zusammenfasst, kommt man auf 624 unterschiedliche 
Noten. Im wirklichen Leben verwendet eine Melodie aber nur einen kleinen 
Teil davon. Du könntest max. 255 unterschiedliche Noten erlauben und die 
Melodie in einem normalen Byte Array speichern.

Zum Abspielen gehst du einen Umweg über eine Lookup Table, die dir 
Tonhöhe und Tondauer für eine bestimmte Note liefert. Diese Tabelle muss 
nur so viele Einträge haben, wie die Melodie unterschiedliche Noten hat. 
"Alle meine Entchen" für Xylophon kommt mit weniger als 16 aus ;) Da 
könnte man auch noch 2 Noten pro Melodie-Byte speichern.

Der GCC kennt __attribute__((packed)) für ein typedef struct. Damit kann 
man im Prinzip beliebig kurze Datentypen erzeugen. Allerdings wird der 
Code für die Zugriffe auf gepackte Daten u.U. ziemlich groß und langsam.
Persönliche Seite #5515889
Lesenswert?

Tobias K. schrieb:
> Gibt es C ne möglichkeit einen 6 bit Datentyp zu definieren, der mir
> einfach nacheinander en Speicher auffüllt, weil mit bitschieberein wird
> das nicht allzu toll

Nein, so etwas gibt es nicht, und um Bitschieberei kämst Du so oder so 
nicht herum. Der Compiler würde sie, wenn er Datentypen unterstützen 
würde, die kleiner sind als ein Byte, zwar vor Dir verbergen, aber 
geshiftet werden muss so oder so.

Auch Bitfelder helfen Dir nicht bei Deinem Problem.

Entweder Speicher sparen und wüste Shift-Orgien, oder Speicher 
"verschwenden" mit deutlich weniger Programmieraufwand.

Du könntest natürlich auch überlegen, ob Deine Datenstruktur so, wie sie 
ist, sinnvoll ist. Musst Du für jeden angeschlagenen Ton jedesmal 
einzeln dessen Lautstärke festlegen, oder würde es nicht vielmehr 
genügen, nur bei Lautstärkenänderungen die Lautstärke festzulegen?

Dann könntest Du mit einem Byte pro Operation - Ton anschlagen bzw. 
Lautstärke ändern - auskommen.

Was hier übrigens fehlt, ist die Zeitinformation; wie lange soll ein Ton 
erklingen? Auch das könntest Du in diesem Format unterbringen, wenn Du 
die oberen zwei Bits eines jeden Bytes für die Unterscheidung 
Ton/Lautstärke/Länge vorsiehst, hast Du jeweils sechs Bits (0..63) für 
den jeweiligen Wert zur Verfügung.

Und Du brauchst immer noch nur ein Byte pro Kommando.

Eine Melodie könnte dann so aussehen:

D4 L5 T1 T5 T7 L3 D2 T2 L5 D4 T3 T8 T16 L1 T1 L8 D1 T8 T9

(T für Ton, L für Lautstärke und D für Dauer)
Gast #5516043
Lesenswert?

Es ist kein Problem die Daten zu "packen", aber...
Hast Du so viel Rechenleistung über?

Da fallen bei jedem Datum etliche Zwischenbefehle an.
1. Eigene Adressberechnungen.
2. Schiebebefehle.
3. Maskierungssachen.

Bei Tönen geht es oft relativ zügig zu.
Gast #5516100
Lesenswert?

Rufus Τ. F. schrieb:
> Auch Bitfelder helfen Dir nicht bei Deinem Problem.
Man kann Bitfelder benutzen, um das Problem zu lösen.

Ziel ist es, diesen "10 Bit Datentypen" in ein Array ohne padding zu 
speichern. Es gibt genau 4 Möglichkeiten, wie die 10 Bit in 2 
aufeinander folgenden Bytes angeordnet sein können. Es werden also 4 
Bitfelder benötigt.
1
struct tb1 {
2
  unsigned int fB : 4;
3
  unsigned int sB : 6;
4
  unsigned int f1 : 6;
5
};
6
struct tb2 {
7
  unsigned int f1 : 2;
8
  unsigned int fB : 4;
9
  unsigned int sB : 6;
10
  unsigned int f2 : 4;
11
};
12
struct tb3 {
13
  unsigned int f1 : 4;
14
  unsigned int fB : 4;
15
  unsigned int sB : 6;
16
  unsigned int f2 : 2;
17
};
18
struct tb4 {
19
  unsigned int f1 : 6;
20
  unsigned int fB : 4;
21
  unsigned int sB : 6;
22
};
23
void set(int index, uint8_t* data, int8_t fourBit, int8_t sixBit)
24
{ 
25
  int const offset = index * 10 / 8;
26
  switch(index % 4) {
27
    case 0: {
28
      struct tb1* p = (struct tb1*)&data[offset];
29
      p->fB = fourBit;
30
      p->sB = sixBit;
31
      break;
32
    }
33
    case 1: {
34
      struct tb2* p = (struct tb2*)&data[offset];
35
      p->fB = fourBit;
36
      p->sB = sixBit;
37
      break;
38
    }
39
    case 2: {
40
      struct tb3* p = (struct tb3*)&data[offset];
41
      p->fB = fourBit;
42
      p->sB = sixBit;
43
      break;
44
    }
45
    case 3: {
46
      struct tb4* p = (struct tb4*)&data[offset];
47
      p->fB = fourBit;
48
      p->sB = sixBit;
49
      break;
50
    }
51
  }
52
}
53
void get(int index, uint8_t const* data, int8_t* fourBit, int8_t* sixBit)
54
{
55
  int const offset = index * 10 / 8;
56
  switch(index % 4) {
57
    case 0: {
58
      struct tb1* p = (struct tb1*)&data[offset];
59
      *fourBit = p->fB;
60
      *sixBit = p->sB;
61
      break;
62
    }
63
    case 1: {
64
      struct tb2* p = (struct tb2*)&data[offset];
65
      *fourBit = p->fB;
66
      *sixBit =p->sB;
67
      break;
68
    }
69
    case 2: {
70
      struct tb3* p = (struct tb3*)&data[offset];
71
      *fourBit = p->fB;
72
      *sixBit =p->sB;
73
      break;
74
    }
75
    case 3: {
76
      struct tb4* p = (struct tb4*)&data[offset];
77
      *fourBit = p->fB;
78
      *sixBit =p->sB;
79
      break;
80
    }
81
  }
82
}
Gast #5516685
Lesenswert?

Tobias K. schrieb:
> Auf diese Art und weise verschwende ich jedoch 6 bit von insgesagt 16
> bit

Dann speichere deine Melodie eben nicht als Array, sondern als 
Bitstream. Einfach nahtlos die jeweils 10 Bit aneinanderkleben und 
fertig. Das Auseinandernehmen geht genauso einfach - jedenfalls bei 
einem 32 Bitter. Kann dein µC wenigstens 16 Bit Operationen ohne 
Umstände?

Also etwa in dem Stil:
1
unsigned int Akku;
2
unsigned int tmp;
3
int bitcount;
4
unsigned int* P;
5

6
if (bitcount < 10)
7
{ tmp = *P++;
8
  Akku = Akku | (tmp<<bitcount);
9
  bitcount+= 10;
10
}
11
result = akku & 0x3FF;
12
bitcount -= 10;

W.S.
#5517177
Lesenswert?

W.S. schrieb:
>> Solange die Shiftcount konstant ist, und der Datentyp nicht zu breit
>
> .. was beides hier nicht zutrifft.
> Fazit: Der Chip ist zu mickrig für diesen Einsatz.

Es kommen nur 2 Feldlängen vor. Folglich kann man einen Bitstream 
implementieren, ohne dynamische Shifts verwenden zu müssen.

GCCs Optimierung der Shifts für AVR betrifft 16-Bit Daten. Das ist 
hierfür breit genug.

Ein besserer µC ist dafür freilich von Vorteil. Wenn der Dividieren 
kann, dann geht auch die erwähnte und sparsamste Radix-39 Codierung 
recht flott.
Gast #5517378
Lesenswert?

W.S. schrieb:
> A. K. schrieb:
>> Solange die Shiftcount konstant ist, und der Datentyp nicht zu breit
>
> .. was beides hier nicht zutrifft.
>
> Fazit: Der Chip ist zu mickrig für diesen Einsatz.

Oder das Programm ist nicht ausreichend für den Chip optimiert. Ob man 
jetzt gleich auf eine komplett andere Architektur umsteigen muss, nur 
weil die Trivial-Implementation von ein paar Bit-Schieberein sonst nicht 
effizient wäre?
Gast #5517893
Lesenswert?

Ich musste auch mal 4 x 10 bit AD-Werte etwas umpacken.
(In 5 Bytes a 8 Bit.)

Über C habe ich mir gar nicht erst den Kopf zerbrochen,
sondern gleich den Assembler angeworfen.

Herausgekommen ist eine lustige Schiebeorgie, die bemerkenswerterweise
beim Aufruf sowohl die Daten packen als auch entpacken kann.
Wohlgemerkt mit der selben Routine.
Wie nicht anders zu erwarten reichen genau 42 Zeilen Assembler dafür.


Das soll in C erstmal einer nachmachen...
Gast #5518212
Lesenswert?

> Du hättest die Routine ja netterweise dazulegen können.

Die war fuer einen Kleinen PIC und haette dem TO also so nuex genuetzt.

Der TO soll sich gefaelligst selber mit Assembler beschaeftigen.
Fuer Copy & Pasta spende ich hier nicht!
Gast #5518408
Lesenswert?

P.S.:

> ein Schwafler

Schwafler sind die, die ihre Postings mit:

> Hallo Community,

beginnen und selber nuex zu 100 % zustande bringen.
Wenn nur C im Kopf ist, kann auch nur C rauskommen.

Eine Datenwortbreite von 10 bit legt auf einem 8 bit Controller
ohnehin eine schnelle Assemblerroutine nahe.

Und wenn da bei ihm da nuex ist, muss er es halt lernen.
Nur der harte Weg ist der richtige.


Lächeln und Winken!
Gast #5518508
Lesenswert?

Sempfdazugeber schrieb:
> Ich musste auch mal 4 x 10 bit AD-Werte etwas umpacken.
> (In 5 Bytes a 8 Bit.)

Wie waren denn Ein- und Ausgabendaten kodiert? Z.B. die 4x 10bit als 4x 
16 Bit Ints und die 5 Byte? 4x8 Bit und dann ein Byte mit den restlichen 
4x2 Bit? Oder als Bit-Stream?

> Herausgekommen ist eine lustige Schiebeorgie, die bemerkenswerterweise
> beim Aufruf sowohl die Daten packen als auch entpacken kann.
> Wohlgemerkt mit der selben Routine.

Ohne Fallunterscheidung? Dann können die Eingabedaten wohl nicht als 
4x16Bit kodiert gewesen sein...

> Wie nicht anders zu erwarten reichen genau 42 Zeilen Assembler dafür.

In C werden es wohl deutlich unter 42 Zeilen sein. Wäre jetzt nur 
interessant, wie viel Zeilen Assemblercode der Compiler daraus macht.
Gast #5518512
Lesenswert?

> Was? Dass man so viele Zeilen dafür braucht?

Ja, das Loop Unrolling ist schuld daran!
Aber, es soll ja schnell gehn.
Aber es sind gut investierte 42 Worte im Flash.

> Rolf M.

Bist du der von der beirischen Trinkerrunde?
Gast #5518522
Lesenswert?

> die 4x 10bit als 4x 16 Bit Ints
[x]

> und die 5 Byte? 4x8 Bit und dann ein Byte mit den restlichen
> 4x2 Bit? Oder als Bit-Stream?
Wie speichert man wohl 5 Byte...

(Tipp: hintereinander...)

Es ging bei mir auch um Wandlerdaten von einem AD.
Und kein schnoedes Tongepiepse.


> In C werden es wohl deutlich unter 42 Zeilen sein. Wäre jetzt nur
> interessant, wie viel Zeilen Assemblercode der Compiler daraus macht.

Nicht schwafeln sondern machen!
Gast #5518527
Lesenswert?

>> sowohl die Daten packen als auch entpacken kann.
>> Wohlgemerkt mit der selben Routine.

> Ohne Fallunterscheidung? Dann können die Eingabedaten wohl nicht als
> 4x16Bit kodiert gewesen sein...

Unsigned ints waren es.
Das Resultat waren 5 unsigned chars.
Beim Auspacken natuerlich andersrum.

Auf eine C-Routine die das ebenfalls ohne Fallunterscheidung
schafft, waere ich ja mal gespannt :-).

Und ja: Es gibt keine Fallunterscheidung zwischen den
Operationen Packen und Entpacken in der Assemblerroutine.

Was in den 4 uints steht, landet in den 5 uchars und vice versa.
In einem Arbeitsgang.


> Moby, bist du es?
[ ]
Gast #5518541
Lesenswert?

A. K. schrieb:
> GCCs Optimierung der Shifts für AVR betrifft 16-Bit Daten. Das ist
> hierfür breit genug.
>
> Ein besserer µC ist dafür freilich von Vorteil. Wenn der Dividieren
> kann, dann geht auch die erwähnte und sparsamste Radix-39 Codierung
> recht flott.

OK, dann geht die Schieberei ja doch ausreichend gut. Aber vom 
Dividieren würde ich doch eher absehen.

W.S.
Gast #5518564
Lesenswert?

Sempfdazugeber schrieb:
> Ich musste auch mal 4 x 10 bit AD-Werte etwas umpacken.
> (In 5 Bytes a 8 Bit.)

jaja.

So, nachdem hier viel Senf geflossen ist, kommt zum Abschluß noch ne 
Portion Mayo:
1
; Protokoll 3: vierkanalig, 9 Bit
2
; Kanäle 0, 2, 3, 4, hier A,B,C,D genannt
3
; Byte-Block-Aufbau:
4
;    Seb_A    Seb_B    Seb_C    Seb_D    Seb_E    Seb_F
5
; 76543210 76543210 76543210 76543210 76543210 76543210   Bitnummern
6
; 0001000A 1AAAAAAA 1ABBBBBB 1BBBCCCC 1CCCCCDD 1DDDDDDD   Kanäle
7

8
prot_3:  MOVLW  10h
9
         MOVWF  Seb_A
10
         MOVF   Avalue0+1,W   ; A
11
         MOVWF  Seb_B
12
         BTFSC  Seb_B,7
13
         BSF    Seb_A,0
14
         BSF    Seb_B,7
15
         MOVF   Avalue2+1,W   ; B
16
         MOVWF  Seb_C
17
         MOVF   Avalue2,W
18
         MOVWF  Seb_D
19
         RLF    Avalue0,F     ; 9. Bit-->Carry
20
         RRF    Seb_C,F       ; als MSB in Seb_C
21
         RRF    Seb_D,F
22
         BSF    Carry
23
         RRF    Seb_C,F       ; Seb_C ist jetzt fertig
24
         RRF    Seb_D,F
25
         BSF    Carry
26
         RRF    Seb_D,F       ; B ist jetzt untergebracht
27
         MOVLW  B'11110000'
28
         ANDWF  Seb_D,F
29
         SWAPF  Avalue3+1,W
30
         ANDLW  B'00001111'
31
         IORWF  Seb_D,F       ; hi(C) ist jetzt untergebracht
32

33
         SWAPF  Avalue3+1,W
34
         ANDLW  B'11110000'
35
         MOVWF  Seb_E
36
         BTFSC  Avalue3,7
37
         BSF    Seb_E,3
38
         BSF    Carry
39
         RRF    Seb_E,F      ; C ist jetzt komplett untergebracht
40

41
         MOVF   Avalue4+1,W
42
         MOVWF  Seb_F
43
         RLF    Avalue4,W
44
         RLF    Seb_F,F
45
         SKIP   NC
46
         BSF    Seb_E,1
47
         BTFSC  Seb_F,7
48
         BSF    Seb_E,0
49
         BSF    Seb_F,7

Das war von mir, ist rund 10 Jahre alt und stammt aus meiner eigenen 
Firmware für den FA-NWT (Funkamateur-Wobbler). Und es sind dort 9 Bit 
pro Meßwert und 7 zu benutzende Bits in den zu sendenden Bytes.

Quasi Radio Eriwan.

Klar: In Hochsprache notiert sich sowas viel einfacher, aber was da als 
MC herauskommt, ist gerade in solchen Fällen gruseliger. So ist das 
eben, wenn man was auf nem 8 Bitter durchziehen muß.

W.S.
Moderator #5518972
Lesenswert?

Warum ist Geschwindigkeit hier überhaupt ein Thema?

Selbst die kürzesten Töne werden mindestens 10 ms lang sein, sonst würde
man sie gar nicht mehr als solche wahrnehmen. Nachdem der ATmega die
Periodendauer eines Tons in ein entsprechendes Timer-Register
geschrieben hat, hat er somit alle Zeit der Welt, um die Daten für den
nächsten Ton vorzubereiten.

Sempfdazugeber schrieb:
> Ich musste auch mal 4 x 10 bit AD-Werte etwas umpacken.
> (In 5 Bytes a 8 Bit.)
>
> Über C habe ich mir gar nicht erst den Kopf zerbrochen,
> sondern gleich den Assembler angeworfen.
>
> Herausgekommen ist eine lustige Schiebeorgie, die bemerkenswerterweise
> beim Aufruf sowohl die Daten packen als auch entpacken kann.
> Wohlgemerkt mit der selben Routine.
> Wie nicht anders zu erwarten reichen genau 42 Zeilen Assembler dafür.

Hast du den Code mit NOPs aufgefüllt, um auf die 42 zu kommen? ;-)

Da der TE keine 10-Bit-Zahlen braucht, sondern jeweils eine 4Bit- und
eine 6-Bit-Zahl, ist noch jede Menge zusätzlicher Bit-Operationen nötig,
um am Ende jeden der insgesamt 8 Zahlenwerte in einem eigenen Register
stehen zu haben.

Der Assemblercode im Anhang nimmt 5 Bytes mit den gepackten Daten für 4
Töne entgegen und liefert in 20 Taktzyklen die 4 Notenwerte und die 4
Dauern in 8 Bytes zurück. Das dabei verwendete Packschema ist im
Kommentar ganz am Anfang beschrieben. Ich habe den Code nicht getestet,
hoffentlich enthält er trotzdem keine Fehler.
Angehängte Dateien:
#5519296
Lesenswert?

Ich denke, das geht auch problemlos mit 8 Bit je Note. Anstelle einer 
Tondauer würde ich den Notenwert nehmen:

1/1, 3/4, 1/2, 3/8, 1/4, 3/16, 1/8, 1/16

sind 8 Werte, für die man 3 Bits benötigt.

Es bleiben 5 Bits für die Note (Tonhöhe) übrig. Das ergibt 32 Tonhöhen, 
was gegenüber den 38 geforderten zuwenig ist. Die meisten Melodien 
spielen sich zwischen 1-2 Oktaven ab, also könnte man 30 Tonhöhen 
verwenden und die Vorletzte als Pause und die Letzte für 8 Steuercodes 
nutzen:

- +1 Oktave
- -1 Oktave
- Beginn Wiederholung 1-fach
- Beginn Wiederholung 2-fach
- Beginn Wiederholung 3-fach
- Beginn Wiederholung 4-fach
- Ende Wiederholung
- Ende vom Lied

Ebenso könnte man noch eine weitere Note "abzwacken" und damit 8 
Laustärkestufen realisieren. Der AVR kann die Steuercodes schnell genug 
auswerten, so dass die Verzögerung eher im einstelligen 
Mikrosekunden-Bereich liegt und damit unhörbar ist.

Jörg
#5523906
Lesenswert?

W.S. schrieb:
> Ich vermute mal, daß das Ausführen dieser Unterscheidungen genauso viel
> oder mehr an Rechenzeit und Code kostet wie die variable Verschiebung.

Bei AVRs kostet ein ausgeführter Sprung nur einen Takt mehr als ein 
nicht ausgeführter, dynamische 16-Bit Shifts hingegen benötigen 5 Takte 
pro Bit.

Bei 16/32-Bittern mit Barrelshifter ist eine Fallunterscheidung jedoch 
ungünstig und mit tiefer Pipeline und Sprungvorhersage wirds erst recht 
kompliziert.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren