Forum: Mikrocontroller und Digitale Elektronik Modularisierung in C (spezialfall)


von dotm (Gast)


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.

von Karl H. (kbuchegg)


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.

: Bearbeitet durch User
von Michael (Gast)


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);

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Wenn dein Datenbuffer immer gleich ist, könntest du den umcasten:
1
typedef {
2
  uint16_t  voltage;
3
  uint16_t  current;
4
} data_t;
5
6
int16_t ReadData(uint32_t *pBuf, int i, int cur)
7
{
8
  data_t *data = (data_t *) pBuf;
9
  
10
  if(cur) return (data[i].voltage);
11
  else    return (data[i].current);
12
}

von Humptydumpty (Gast)


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]

von dotm (Gast)


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.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Obs :-)
1
typedef {
2
  uint16_t  voltage;
3
  uint16_t  current;
4
} data_t;
5
6
int16_t ReadData(uint32_t *pBuf, int i, int cur)
7
{
8
  data_t *data = (data_t *) pBuf;
9
  
10
  if(cur) return (data[i].current);
11
  else    return (data[i].voltage);
12
}

von dotm (Gast)


Lesenswert?

Michael schrieb:
> Code erhebt nicht den Anspruch, zu funktionieren, sollte aber
> verdeutlichen was ich meine...

Ja. so geht das natürlich.
Das ansprechen des Arrays geschieht bei dir natürlich immer noch mit 
lese() anstelle array[].
Etwas besser ist es schon.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

**argh** warum kann man hier nicht bearbeiten. Da fehlt natürlich noch 
ein "struct" :-)

von dotm (Gast)


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)

von dotm (Gast)


Lesenswert?

dotm schrieb:
> drinnen geschieht wahrscheinlich noch eine Grössenüberprüfung
> length = sizeof(Buffer)

sizeof(array) mein ich natürlich

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

dotm schrieb:
> dotm schrieb:
>> drinnen geschieht wahrscheinlich noch eine Grössenüberprüfung
>> length = sizeof(Buffer)
>
> sizeof(array) mein ich natürlich

Ist egal.
Denn die Funktion kann die Größe sowieso nicht aus eigener Kraft 
feststellen, sondern ist darauf angewiesen, dass du korrekte Werte 
übergibst.

von Karl H. (kbuchegg)


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.

: Bearbeitet durch User
von dotm (Gast)


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...

von dotm (Gast)


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.

von Chris (Gast)


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.

von Karl H. (kbuchegg)


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.

: Bearbeitet durch User
von dotm (Gast)


Lesenswert?

Ok, ich denk ich habs.
danke an alle!

von Karl H. (kbuchegg)


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.

von dotm (Gast)


Lesenswert?

Karl Heinz schrieb:
> natürlich so auf C-Ebene vereinfachen

Genau. Habe mir die Funktionen angesehen, alle beinhalten in etwa diese 
Schleifen.

Somit ist zwar für interleavte Arrays jede signalverarbeitende Funktion 
umzuschreiben, jedoch ändert sich die Geschwindigkeit nicht allzusehr.

von Chris (Gast)


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.

von dotm (Gast)


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...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.