Modularisierung in C (spezialfall)

Gast #3388872
Lesenswert?

Hallo.
Folgendes geht mir nicht aus dem Kopf.
Ich bin versucht so modular wie möglich zu Programmieren.

Folgender Fall lässt sich sicher besser lösen, aber ich hab im Moment 
keine Ahnung wie..

Ich habe einen Interleavten ADC-Buffer, also immer abwechselnd den einen 
Wert und dann den anderen Wert.
Sagen wir mal Spannung und Strom.

Umschreiben ist zunächst mal nicht möglich da der Speicher des MCUs das 
nicht zulässt.

Also habe ich zwei simple Funktionen:
1
int16_t lese_spannung(uint32_t* Buffer,int index)
2
{
3
    return (Buffer[index]>> 16)&0x0000FFFF;
4
}
5

6
int16_t lese_strom(uint32_t* Buffer,int index)
7
{
8
    return (Buffer[index]&0x0000FFFF);
9
}

jetzt muss ich aber jede weitere Funktion zweimal definieren, daher hab 
ich
zb
DFT_strom
DFT_spannung
RMS_strom
RMS_spannung
...

irgendwie nicht optimal.
Besser wärs ich übergeb einer Funktion einfach immer nur einen Buffer.
Ich müsste also mein 32bit array so übergeben dass die Funktion es als 
16bit array ansieht, jedoch mit 32bit Schrittweite...
Und wenn es sich um eine allgemeine Funktion handeln soll (zb DFT) muss 
natürlich ein 16bit array als Übergabe verlangt werden.
Ist das möglich? Kann ich ein Array irgendwie so virtualisieren dass 
fürs auslesen eine Funktion zuständig ist?
Defakto soll also bei:
stromwert = array_strom[i]
eine Funktion ausgeführt werden.

Grüsse.
M.
#3388900
Lesenswert?

Die Frage ist:
warum hast du in erster Linie schon mal einen uint32_t Buffer, wenn du 
doch eigentlich 2 Stück 16 Bit Buffer benötigen würdest?

Aha!
Denn dann vereinfachen sich deine ganze Zugriffsfunktionen darauf, dass 
da nicht Spannung und/oder Strom gelesen wird, sondern der i-te Index 
aus einem der beiden Buffer.

D.h. die Information, ob das jetzt eine Spannung oder der Strom ist, 
steckt nicht in den Funktionen, sondern darin, welchen Buffer ich in 
weiterer Folge zur Rechnerei benutze.
Gast #3388905
Lesenswert?

dotm schrieb:
> jetzt muss ich aber jede weitere Funktion zweimal definieren, daher hab
> ich
> zb
> DFT_strom
> DFT_spannung
> RMS_strom
> RMS_spannung
> ...

Warum? Greift doch nur auf einen jeweils anderen Buffer zu, oder? Also 
gleiche Funktion, anderer Übergabeparameter...

Und wie wäre es, die Funktion allgemeiner zu halten?

Code erhebt nicht den Anspruch, zu funktionieren, sollte aber 
verdeutlichen was ich meine...
1
#define SPANNUNG 1
2
#define STROM 0
3

4
int16_t lese(uint32_t* Buffer, int index, uint8_t step)
5
{
6
    return (Buffer[index]>>(step*16)&0x0000FFFF);
7
}
8

9
MyStrom = lese(&LeseBuffer, index, STROM);
10
MySpannung = lese(&LeseBuffer, index, SPANNUNG);
Gast #3388908
Lesenswert?

z.B. so
Leseroutine arbeitet mit 16 Bit-Feld: (uint16_t* Buffer,int index)
dann musst du beim Aufruf casten: xy= lese_spannung((uint16_t*)Buffer32, 
idx, STROM)
Für die Unterscheidung Strom/Spannung könntest du weiteren 
(boolean-)Parameter in Lesefunktion verwenden (s.o.).
Zugriff:
Spannung: Buffer[2*index+1]
Strom: Buffer[2*index]
Gast #3388909
Lesenswert?

Karl Heinz schrieb:
> Die Frage ist:
> warum hast du in erster Linie schon mal einen uint32_t Buffer, wenn du
> doch eigentlich 2 Stück 16 Bit Buffer benötigen würdest?

ganz einfach:
Der Mikrocontroller liest seine Werte per DMA in den Speicher.
Bei n synchronen (x-bit) Kanälen ist die Ausgabe nun mal interleaved und 
zwar in n*x bit grossen Datenblöcken.
Daran ist leider nichts zu ändern.
Gast #3388930
Lesenswert?

Random ... schrieb:
> Wenn dein Datenbuffer immer gleich ist, könntest du den umcasten:

Ok, also sagen wir mal ich verwende eine einheitliche Bibliothek für 
z.B. DFT oder ähnliches.
Diese erwartet ein 16bit array als eingabe
1
double dft (uint16_t* array, int bin)

drinnen geschieht wahrscheinlich noch eine Grössenüberprüfung
1
length = sizeof(Buffer)

geht das dann?
1
dft201 = dft(data[].current,201)
#3388949
Lesenswert?

Karl Heinz schrieb:
> dotm schrieb:
>
>> geht das dann?
> Nein.
>
> In einem Array müssen alle Array-Elemente hintereinander im Speicher
> liegen.
> Das tun sie bei dir aber nicht.
> Dir bleibt in so einem Fall nichts anderes übrig, als das Interleaving
> aufzulösen und diesen Zustand herzustellen.

d.h. nicht ganz.
Es gibt noch die Möglichkeit, der Funktion die zusätzliche Information 
mitzugeben.

zb könntest du ihr einen Startindex in ein uint16_t Array mitgeben und 
das Inkrement, welches den Abstand zwischen den relevanten Werten 
beschreibt.
1
double dft (uint16_t* array, size_t startIndex, size_t IndexInkrement, int bin)
2
{
3
  ...

für die Spannungen übergibst du dann
1
   dft( buffer, 0, 2, 201 );
für den Strom
1
   dft( buffer, 1, 2, 201 );

und innerhalb werden die Zugriffs-Indizes entsprechend für den Zugriff 
auf die Daten umgerechnet.
1
   for( i = 0; i < bin i++ )
2
     wert = array[ i * IndexInkrement + startIndex ];
3
     ....

aber abgesehen davon, sehe ich da nicht viele weitere Möglichkeiten.

Eine Möglichkeit dreht sich zb darum, die Funktion als Makro auszuführen 
und dann den Präprozessor dazu zu benutzen, um die beiden Versionen der 
Funktion zu erstellen. Bei kurzen Funktionen kann man das machen, wenns 
aber komplizierter wird, dann ist das ein pain in the ass.
Gast #3388950
Lesenswert?

Karl Heinz schrieb:
> Dir bleibt in so einem Fall nichts anderes übrig, als das Interleaving
> aufzulösen und diesen Zustand herzustellen.

geht das prinzipiell ohne weiteren speicheraufwand? was ich da 
rausgelesen hab:
http://stackoverflow.com/questions/7780279/de-interleave-an-array-in-place
scheint das ohne zwischenspeicher nicht zu funktionieren.

Schwierig scheint es mir auch abzuschätzen ob der Rechenaufwand für das 
umschreiben des Arrays sich dann durch das schnellere Ansprechen in der 
Funktion amortisiert...
Gast #3388952
Lesenswert?

Karl Heinz schrieb:
> zb könntest du ihr einen Startindex in ein uint16_t Array mitgeben und
> das Inkrement, welches den Abstand zwischen den relevanten Werten
> beschreibt.

Das scheint mir zumindest eine recht schnelle Lösung zu sein, da der 
Index ja sowieso erhöht werden muss.
Es fällt somit die Schiebeoperation und die Maske vollständig weg.
Gast #3388953
Lesenswert?

Ich würde es so lösen:

int16_t lese_spannung(uint32_t* Buffer,int index,int typ)
{
    return ((int16_t*)Buffer)[index<<1|!!typ];
}


Wenn man sicherstellen kann, daß typ nur 0 oder 1 ist, kann
man auch das "!!" weglassen.
#3388955
Lesenswert?

dotm schrieb:
> Karl Heinz schrieb:
>> Dir bleibt in so einem Fall nichts anderes übrig, als das Interleaving
>> aufzulösen und diesen Zustand herzustellen.
>
> geht das prinzipiell ohne weiteren speicheraufwand?

nein.

Nochmal.
Wenn du Funktion vorgegeben ist und ein Array will, dann will sie ein 
Array. Und Array bedeutet, dass die relevanten Informationen 
hintereinander im Speicher liegen. Dicht an dicht.

Entweder du stellst diesen Zustand her oder du pimpst die Funktion so, 
dass diese Vorbedingung nicht mehr erwartet wird.

> Schwierig scheint es mir auch abzuschätzen ob der Rechenaufwand für das
> umschreiben des Arrays sich dann durch das schnellere Ansprechen in der
> Funktion amortisiert...

Das wirst du raustimen müssen.
Das hängt von einigen Faktoren ab, die sich aus der konkreten 
Applikation ergeben.
#3388961
Lesenswert?

dotm schrieb:
> Karl Heinz schrieb:
>> zb könntest du ihr einen Startindex in ein uint16_t Array mitgeben und
>> das Inkrement, welches den Abstand zwischen den relevanten Werten
>> beschreibt.
>
> Das scheint mir zumindest eine recht schnelle Lösung zu sein, da der
> Index ja sowieso erhöht werden muss.
> Es fällt somit die Schiebeoperation und die Maske vollständig weg.

Dann lässt sich das hier
1
   for( i = 0; i < bin i++ )
2
     wert = array[ i * IndexInkrement + startIndex ];
3
     ....

natürlich so auf C-Ebene vereinfachen
1
   endBin = startIndex + bin*IndexInkrement;
2

3
   for( i = startIndex; i < endBin; i += IndexInkrement )
4
     wert = array[ i ];
5
     ....


der ursprüngliche Codevorschlag hat sich weniger auf for-Schleifen, 
sondern mehr auf den Zugriff konzentriert.
Gast #3389268
Lesenswert?

Wenn die Architektur einen Zugriff auf Wordsize mit Int Pointern erlaubt
(z.B. Arm, PC, ...) dann geht folgendes auch:

uint32_t * Spannung = buffer_adc;
uint32_t * Strom    = (unsigned int)Spannung+2;

int16_t _inline_ lese_buffer(uint32_t* Buffer,int index)
{
    return (int16_t)Buffer[index];
}

und dann lese_buffer(Spannung,idx)  sowie lese_buffer(Strom,idx) bzw

du könntest auch Spannung sowie Strom so definieren, und ersparst dir
die Funktion, aber Inline wird es eh so optimieren.

#define Spannung (int16_t)BufferAdc
#define Strom    (int16_t)((uint32_t*)((intptr_t)BufferAdc) + 2))

Dann kannst du Spannung[n] sowie Strom[n] verwenden, als wäre es ein
Array von int16_t ;  Ob jetzt Spannung oder Strom zuerst kommt, bzw
wie das aligmnment genau ist habe ich nicht genau gechekct, ev. muss man
dies umändern.
Gast #3389459
Lesenswert?

Chris schrieb:
> du könntest auch Spannung sowie Strom so definieren, und ersparst dir
> die Funktion, aber Inline wird es eh so optimieren.

gute Idee.
Ich glaube fürs erste werde ich das so machen, um CMSIS verwenden zu 
können.
Der richtige Ansatz wäre dennoch die Arrays im Speicher getrennt zu 
haben.
Eventuell pfeif ich auf DMA...

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