Hallo,
mache gerade meine ersten Experimente mit C++ auf einem STM32f429
Discoveryboard.
Jetzt stehe ich vor folgendem Problem.
Ich habe eine Klasse in der es eine Funktion
1
floatdoEnvelope();
gibt.
Diese funktion möchte ich in einet Timer ISR aufrufen.
Diese soll den Rückgabewert über den DAC ausgeben.
Jetzt zu meiner Frage wie rufe ich die Funktion auf?
Habe es wie folgt versucht
1
volatileCEnvelopeGeneratorEG1;//global in main.cpp
2
3
4
voidTIM2_IRQHandler(void){
5
6
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
7
if(ISR_COUNTER==0)
8
{
9
10
TOOGLE_LED_GREEN;
11
12
floatEnv=EG1.doEnvelope();
13
//Ausgabe env auf Dac
14
.....
15
16
}
beim kompilieren bekomme ich folgende Fehlermeldung:
Invalid arguments '
Candidates are:
float doEnvelope()
Was mache ich hier falsch?
Vielen Dank im voraus.
Greetz
Jim M. schrieb:> Kann es sein das da einfach der Header vergessen wurde?
Dann müsste der Compiler schon die definition von EG1 anmeckern.
>> Außerdem ist das ja jetzt zingend C++, damit muss man folgendes machen:
Komm auf den Startup code an, der die Vectortable definiert.
Wahrscheinluch hast Du aber Recht und er müsste der ISR C-Binding geben.
Das geht aber viel einfacher:
1
extern"C"voidTIM2_IRQHandler()
2
{
3
...
4
}
Das Problem ist aber, dass EG1 `voltaile` definiert. `volatile` verhält
sich analog zu `const` und dem entsprechend muss doEnvelope() `volatile`
qualifiziert werden:
Wobei es keine wirklich sinnvolle Definition gibt, was eine volatile
qualified member function macht. Ich würde einfach dokumentieren, dass
die Klasse so implementiert ist, dass member function von einer ISR aus
aufgerufen werden können und dann ggf. die notwendigen member Variablen
volatile qualifizieren.
mfg Torsten
Torsten R. schrieb:> Wobei es keine wirklich sinnvolle Definition gibt, was eine volatile> qualified member function macht.
Man kann ja gemäß der CV-Qualifizierung überladen. Damit kann man die
Anwendung einer Operation auf volatile / non-volatile Objekte
unterscheiden. Und wenn man dann ein Exemplar dieser Klasse als
non-volatile hat, kann der Compiler ggf. besser optimieren, was er im
Fall der volatile-qualif. Elementfunktion ja nicht darf.
Wilhelm M. schrieb:> Man kann ja gemäß der CV-Qualifizierung überladen. Damit kann man die> Anwendung einer Operation auf volatile / non-volatile Objekte> unterscheiden.
Da würde mir jetzt kein Beispiel einfallen, wo das sinnvoll wäre.
> Und wenn man dann ein Exemplar dieser Klasse als> non-volatile hat, kann der Compiler ggf. besser optimieren, was er im> Fall der volatile-qualif. Elementfunktion ja nicht darf.
Dazu müsste die Klasse die selbe Operation dann aber immer zwei mal
implementieren. Und wenn ich die Operation zwei mal habe, kann ich durch
das Weglassen des volatile qualifiers aus Versehen das falsche Set an
Operationen erwischen. -> Würde ich nicht machen.
Kommt aber auch darauf an, wie wahrscheinlich es ist, das eine Klasse,
die in der Kommunkation mit einer ISR eingesetzt wird auch in einem
anderen Kontext sinnvoll ist.
Der volatile qualifier an der Variablen sorgt dafür dass bei der Klasse
alle member functions auch volatile qualifiziert werden müssten und dass
dadurch dann implizit auch alle member variables volatile qualifiziert
sind.
Wenn ich jetzt z.B. einen Ring-Puffer habe, um mit einer ISR zu
kommunizieren, dann müssen die Zeiger in den Puffer volatile sein, der
Puffer selber aber nicht.
mfg Torsten
Hallo,
vielen Dank für die Antworten. Ich denke mein Problem liegt in der
Instanzinierung der globalen Klasse. HIer stellt sich für mich die Frage
wo und wie ich dies erledige. Mache ich das mit "new" in der main?
Benötige hier später ein Array aus CEnvelopeGenerator.
Fragen über Fragen.
Kann mir hier bitte jemand witerhelfen?
Vielen Dank
Greetz
Warum um alles in der Welt wolltest Du new verwenden? Was ist den Deine
"Problem"?
Wenn Du Hilfe brauchst, ist es ganz ratsam, so viele Hinweise auf Dein
Problem, wie möglich zu geben. Mit der von mir vorgeschlagenen Änderung,
sollte Dein Ursprüngliches Problem behoben sein.
> Fragen über Fragen.
Was hindert Dich den daran, konkrete Fragen zu stellen?
EG1.startEG();//ERROR request for member 'startEG' in 'EG1', which is of non-class type 'CEnvelopeGenerator()'
66
return;
67
}
68
69
70
voidTIM2_IRQHandler(void){
71
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
72
if(ISR_COUNTER==0)
73
{
74
TOOGLE_LED_GREEN;
75
UB_DAC_SetDAC2((uint16_t)(EG1.doEnvelope()*4095.0));//ERROR request for member 'doEnvelope' in 'EG1', which is of non-class type 'CEnvelopeGenerator()'
76
}
77
}
78
79
80
intmain(void)
81
{
82
SystemInit();// Quarz Einstellungen aktivieren
83
init();
84
85
UB_DAC_Init(SINGLE_DAC2);
86
initTimerIRQ();
87
88
LED_GREEN_ON;
89
90
while(1){
91
;
92
}
93
}
94
95
96
97
98
99
///"CEnvelopeGenerator.h"
100
101
102
#define EG_DEFAULT_STATE_TIME 1000.0f
103
104
enummodeStrings{
105
analog,digital
106
};
107
108
enumenvState{
109
off,
110
attack,
111
decay,
112
sustain,
113
release,
114
shutdown
115
};
116
117
classCEnvelopeGenerator{
118
public:
119
boolm_bResetToZero;//RTZ mode flag
120
boolm_bLegatoMode;//legato mode flag
121
boolm_bOutputEG;//true if this EG is connected to the output amplifier of the patch
122
intm_uEGMode;//mode flag
123
124
CEnvelopeGenerator();
125
126
voidstartEG();
127
voidstopEG();
128
floatdoEnvelope();
129
130
131
protected:
132
floatm_fSampleRate;//the samplerate
133
floatm_fEnvelopeOutput;//the current Envelope Output
134
floatm_fAttackCoeff;//coeff b for attack
135
floatm_fAttackOffset;//attack offset x0
136
floatm_fAttackTCO;//Attack Time constant overshoot
137
floatm_fDecayCoeff;//coeff b for decay
138
floatm_fDecayOffset;//decay offset x0
139
floatm_fDecayTCO;//decay Time constant overshoot
140
floatm_fReleaseCoeff;//coeff b for release
141
floatm_fReleaseOffset;//release offset x0
142
floatm_fReleaseTCO;//release Time constant overshoot
143
144
floatm_fAttackTime_mSec;//attack time in milliseconds
145
floatm_fDecayTime_mSec;//decay time in milliseconds
146
floatm_fReleaseTime_mSec;//release time in milliseconds
147
floatm_fShutdownTime_mSec;//shutdown time in milliseconds
148
floatm_fSustainLevel;//sustain level [0...+1]
149
floatm_fIncShutdown;//increment value for shutdown mode
150
intm_uState;//state variable
151
};
Die Interrupt Routinen sowie die Auwertung der Midi In Signale, die
Ausgabe auf dem DAC funktionieren.
Was ich erreichen möchte.
Erstellen eines, später mehreren globalen CEnvelopeGenerator Objekten.
In den beiden ISRs müssen die Memberfunktion NoteOn(), NoteOff()
doProcess() aufgerufen werden können.
Ist diesed Vorgehen falsch? Wenn ja was wäre hier die Alternative?
Fehlt hier vielleicht nur der Konstruktoraufruf? Das Objekt muss ja
irgendwo erzeugt werden.
New oder ander dynamischen Sachen sind hier wohl falsch!!
Vielen Dank für eure Hilfe.
Greetz
Oliver F. schrieb:> Was ich erreichen möchte.>> Erstellen eines, später mehreren globalen CEnvelopeGenerator Objekten.>> In den beiden ISRs müssen die Memberfunktion NoteOn(), NoteOff()> doProcess() aufgerufen werden können.>> Ist diesed Vorgehen falsch? Wenn ja was wäre hier die Alternative?>> Fehlt hier vielleicht nur der Konstruktoraufruf? Das Objekt muss ja> irgendwo erzeugt werden.>> New oder ander dynamischen Sachen sind hier wohl falsch!!>> Vielen Dank für eure Hilfe.>> Greetz
Prinzipiell ist das Aufrufen von Member Funktionen aus einer ISR
überhaupt kein Problem? Folgendes compiliert mit GCC 6.3.1 problemlos:
1
structSomeClass
2
{
3
voidfoo()
4
{
5
trace_printf("foo\n");
6
}
7
};
8
9
SomeClassc1;
10
SomeClassc2;
11
12
voidSysTick_Handler()
13
{
14
HAL_IncTick();
15
HAL_SYSTICK_IRQHandler();
16
c1.foo();
17
c2.foo();
18
}
"Falsch" ist dieses Vorgehen perse nicht. Ich persönlich bin kein großer
Freund dieser Variante und lasse meine Interrupts lieber von einer
eigenen Klasse managen. Das kann man aber, sobald man ein tieferes
Verständnis von C++ hat immer noch implementieren...
Der Konstruktur wird übrigens sowohl in deinem, als auch in meinem
Beispiel aufgerufen. Das lässt sich sehr einfach überprüfen, indem du
innerhalb des Konstrukturs einen Breakpoint setzt. Beim Debuggen wirst
du dann (vermutlich) merken, dass der Aufruf statt findet, noch bevor du
in main() landest.
1
classCEnvelopeGenerator
2
{
3
4
CEnvelopeGenerator()
5
{
6
asmvolatile("nop");// hier Break
7
}
8
9
}
/edit
Hungarian Notation ist übrigens furchtbar. ;)
Vincent H. schrieb:>> Der Konstruktur wird übrigens sowohl in deinem, als auch in meinem> Beispiel aufgerufen. Das lässt sich sehr einfach überprüfen, indem du> innerhalb des Konstrukturs einen Breakpoint setzt. Beim Debuggen wirst> du dann (vermutlich) merken, *dass der Aufruf statt findet, noch bevor du> in main()* landest.>
Richtig, deswegen rate ich bei der Arbeit mit statisch angelegten
Objekten immer sehr zur Vorsicht, denn zu dem Zeitpunkt ist noch nichts
richtig initialisiert... wenn der Konstruktor Dinge macht, die ein
initialisiertes System voraussetzen, sind Probleme vorprogrammiert. Das
können sehr subtle Dinge sein, die schwierig herauszufinden sind (z.B.
Hardwareinitialisierungen, die später durch Aufruf von SystemInit()
übergebügelt werden, oder Aufrufe zu malloc() bei noch nicht
funktionierendem sbrk() oder Umbiegen der Speicherverwaltung zum
späteren Zeitpunkt); ausserdem hängt es auch bom Laufzeitsystem ab, wo
genau die Objekte hingelegt werden (was manchmal mit dem gewollten
Speicherlayout clashen kann).
Ich mache es lieber so:
SomeClass *c1;
SomeClass *c2;
void SysTick_Handler()
{
HAL_IncTick();
HAL_SYSTICK_IRQHandler();
c1->foo();
c2->foo();
}
und irgendwo zu einem angemessenen Zeitpunkt (also bevor der
SysTick_Handler zum ersten Mal aufgerufen wird aber nachdem das System
hinreichend initialisiert wurde):
c1 = new SomeClass();
c2 = new SomeClass();
Oliver F. schrieb:> arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -mfloat-abi=hard> -mfpu=fpv4-sp-d16> -T"C:\Users\oliverf\workspace\HelloDSP\LinkerScript.ld"> -Wl,-Map=output.map -Wl,--gc-sections -fno-exceptions -fno-rtti -lm -o> "HelloDSP.elf" @"objects.list"> src/main.o: In function `noteOff(unsigned char, unsigned char, unsigned> char)':> C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:160: undefined> reference to `CEnvelopeGenerator::stopEG()'> src/main.o: In function `noteOn(unsigned char, unsigned char, unsigned> char)':> C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:168: undefined> reference to `CEnvelopeGenerator::startEG()'> src/main.o: In function `TIM2_IRQHandler':> C:\Users\oliverf\workspace\HelloDSP\Debug/../src/main.cpp:307: undefined> reference to `CEnvelopeGenerator::doEnvelope()'> collect2.exe: error: ld returned 1 exit status
Sieht immer noch nach "Header Problem" aus. Schieb die Deklaration der
Interrupts mal in den Header, wo sie auch hingehört.
1
// HPP
2
classCEnvelopeGenerator
3
{
4
// impl
5
};
6
7
#ifdef __cplusplus
8
extern"C"
9
{
10
#endif
11
12
voidTIM2_IRQHandler();
13
voidUSART1_IRQHandler();
14
15
#ifdef __cplusplus
16
}
17
#endif
/edit
Ein Header Guard wär auch nicht schlecht. Nachdem du uns die
Implementierungen der Klasse nicht gezeigt hast geh ich davon aus, dass
"CEnvelopeGenerator.h" öfter als 1x inkludiert wird...
Hatte die Klasse jetzt mal direkt in die Main gepackt, ging dann
fehlerfrei zu kompilieren und der Aufruf der startEG() Methode hat
geklappt.
Die Timer ISR läuft auch (mit Oszi geprüft).
Wie kann ich denn prüfen ob die Objectdatei hinzugebunden wird bzw. wo
stelle ich das ein?
Greetz
Hallo,
erstmal Vielen Dank an alle das Problem ist gelöst!
In der Cpp Datei der Klasse CEnvelopeGenerator
waren einige Funktione mit inline gekennzeichnet.
nachdem ich diese Entfernt hatte lies sich das Programm fehlerfrei
kompilieren.
Greetz
Oliver F. schrieb:> In der Cpp Datei der Klasse CEnvelopeGenerator>> waren einige Funktione mit inline gekennzeichnet.
Die hätten in die .h-Datei gehört.
Ruediger A. schrieb:> Ich mache es lieber so:>> SomeClass *c1;> SomeClass *c2;> ...> c1 = new SomeClass();> c2 = new SomeClass();
Juhu, mit Speicherleck und unnötiger Verwendung eines Heaps. Die
Interrupts einfach erst in main() einschalten würde das von Dir
konstruierte Problem nachhaltig und effektiv lösen.
Torsten R. schrieb:> Ruediger A. schrieb:>> Ich mache es lieber so:>>>> SomeClass *c1;>> SomeClass *c2;>> ...>> c1 = new SomeClass();>> c2 = new SomeClass();>> Juhu, mit Speicherleck und unnötiger Verwendung eines Heaps. Die> Interrupts einfach erst in main() einschalten würde das von Dir> konstruierte Problem nachhaltig und effektiv lösen.
Hä? Wieso "unnötiger Verwendung eines Heaps"? Meinst Du, dass ein
statisch alloziiertes Objekt keinen Speicher braucht? Für den Chip ist
es relativ egal; der Speicher ist eh weg. Der Unterschied ist, dass in
meiner Implementation die Kontrolle darüber, WO das Objekt liegt, beim
Entwickler liegt, bei der statischen Alloziierung beim Laufzeitsystem
(über das man mglw. keine Kontrolle hat).
Wieso "Speicherleck?" Ein statisches Objekt wird niemals dealloziiert,
das hält seinen Speicher bis zum Reset, also ist es für diesen Zweck
auch egal, ob das new() durch ein delete() gematcht wird.
Ausserdem: "Mein" Problem ist nicht "konstruiert," sondern durch 20
Jahre Erfahrung untermauert. Und wenn Du mein Posting vollständig
gelesen hättest, wüsstest Du auch, dass IRQs nur eine mögliche
Fehlerquelle sind. Wie würdest Du z.B. das Thema handlen, dass der
Konstruktor eines Objektes meint, einen UART mit 19200 Baud
initialisieren zu können? Wenn der Konstruktor VOR der
Systeminitialisierung aufgerufen wird, wird sehr wahrscheinlich die
spätere Initialiserung den UART auf Defaultwerte umsetzen. Den Fehler
darfste lange suchen, viel Spass.
Es geht hier einfach darum, dass der statische Konstruktor unter Anderen
Bedingungen aufgerufen wird als der dynamische, d.h. das System wird
sich hier Anders verhalten, und das muss man halt wissen.
Hallo Ruediger,
Ruediger A. schrieb:> Hä? Wieso "unnötiger Verwendung eines Heaps"?
"Unnötig" im Sinn von: Braucht es nicht zur Lösung des Problems und hat
im Zweifelsfall mehr Nebenwirkungen als erwünscht.
> Meinst Du, dass ein> statisch alloziiertes Objekt keinen Speicher braucht? Für den Chip ist> es relativ egal; der Speicher ist eh weg.
Der Speicherbedarf für statische Objekte wird zur Linkzeit ermittelt.
Wenn der nicht ausreicht, bekommen wir eine Fehlermeldung, bevor der
Code jemals die Hardware gesehen hat. Bei Deiner Variante, fehlt noch
irgend eine Form der Prüfung zur Laufzeit (das ist Code, der
geschrieben, gelesen und gewartet werden muss). Und sein es nur, um mal
zu gucken, ob der ggf. auftretende Hardfault ausreicht, die Ursache
recht einfach auf "out of memory" zurück zu führen.
Ein Heap ist ein nicht trivialer Satz an Funktionen, die einen gegebenen
Speicher dynamisch verwalten. Dieser Satz an Funktionen kostet
Program-Speicher und für die Verwaltung des heaps braucht es auch
zusätzlches RAM.
Im Beispiel würde man die Größe des heaps auch nicht einfach nur auf 2 *
sizeof(SomeClass) dimensionieren, sondern noch etwas Reserve drauf
geben, um nicht bei jeder Änderung an den Linker-Einstellungen herum
drehen zu müssen. (und je nach Implementierung des heaps müsste der
Speicherbereich sogar deutlich größer sein).
Also, ja ich meine ein statisch alloziiertes Objekt braucht weniger
Arbeits-Speicher. Und wenn die Anwendung vorher keinen heap verwendet
hat, würde ich für das von Dir skizzierte Problem auch keinen heap
einsetzen.
Wenn die Anwendung eh einen heap verwendet, dann können solche
statischen Objekte auf dem heap auch noch zu einer zusätzlichen
Fragmentierung des heaps führen.
> Der Unterschied ist, dass in> meiner Implementation die Kontrolle darüber, WO das Objekt liegt, beim> Entwickler liegt, bei der statischen Alloziierung beim Laufzeitsystem> (über das man mglw. keine Kontrolle hat).
Es ist eigentlich genau anders herum. Mit new überläßt Du es dem heap,
zur Laufzeit zu bestimmen, wo genau das Objekt liegt (und damit dem
Laufzeitsystem). Die Adresse von statischen Objekten legt der Linker
bereits zur Link-Zeit fest. Letzteres hat auch noch den enormen Vorteil,
dass Compiler / Linker darauf optimieren können.
Was Du bestimmst, ist der Zeitpunkt der Initialisierung (das kann man
aber auch anders machen).
> Wieso "Speicherleck?" Ein statisches Objekt wird niemals dealloziiert,> das hält seinen Speicher bis zum Reset, also ist es für diesen Zweck> auch egal, ob das new() durch ein delete() gematcht wird.
Ja, da hast Du Recht. Aber: wenn ich den Code lese, dann ist da formal
ein Speicherleck. Das dies kein Fehler ist, muss ich mir erst erarbeiten
(std::unique_ptr, könnte helfen).
> Ausserdem: "Mein" Problem ist nicht "konstruiert," sondern durch 20> Jahre Erfahrung untermauert. Und wenn Du mein Posting vollständig> gelesen hättest, wüsstest Du auch, dass IRQs nur eine mögliche> Fehlerquelle sind.
Ich habe deinen Post vollständig gelesen und ja, ich gebe Dir Recht,
dass Bugs in der Initialisierungsreihenfolge alles andere als trivial
sind. Ich finde die Nachteile Deiner Lösung für das gegebene Problem
überwiegen aber bei weitem.
> Wie würdest Du z.B. das Thema handlen, dass der> Konstruktor eines Objektes meint, einen UART mit 19200 Baud> initialisieren zu können? Wenn der Konstruktor VOR der> Systeminitialisierung aufgerufen wird, wird sehr wahrscheinlich die> spätere Initialiserung den UART auf Defaultwerte umsetzen. Den Fehler> darfste lange suchen, viel Spass.
Dann lass die UART ein member des Objekts sein. Dann ist die
Initalisierungsreihenfolge definiert und zusätzlich wird verhindert,
dass die UART zuerst default initialisiert wird um sie anschließend
wieder um zu konfigurieren (das zweite Problem ignoriert Deine Lösung).
Generell würde ich es immer vermeiden, Probleme, die bereits zur
Compile/Link-Zeit erkennbar sind, auf die Laufzeit zu verschieben. Die
Prüfungen zur Laufzeit müssen zumindest in einem Debug-Build
implementiert werden und kosten unnötig Code und Zeit und eliminieren
Optimierungspotential.
mfg Torsten
Torsten R. schrieb:>>> Wie würdest Du z.B. das Thema handlen, dass der>> Konstruktor eines Objektes meint, einen UART mit 19200 Baud>> initialisieren zu können? Wenn der Konstruktor VOR der>> Systeminitialisierung aufgerufen wird, wird sehr wahrscheinlich die>> spätere Initialiserung den UART auf Defaultwerte umsetzen. Den Fehler>> darfste lange suchen, viel Spass.>> Dann lass die UART ein member des Objekts sein. Dann ist die> Initalisierungsreihenfolge definiert und zusätzlich wird verhindert,> dass die UART zuerst default initialisiert wird um sie anschließend> wieder um zu konfigurieren (das zweite Problem ignoriert Deine Lösung).>
Versteh ich ehrlich gesagt nicht wie Du das meinst. Ist aber auch nur
ein Nebenkriegsschauplatz.
> Generell würde ich es immer vermeiden, Probleme, die bereits zur> Compile/Link-Zeit erkennbar sind, auf die Laufzeit zu verschieben. Die> Prüfungen zur Laufzeit müssen zumindest in einem Debug-Build> implementiert werden und kosten unnötig Code und Zeit und eliminieren> Optimierungspotential.>
Gut, dann haben wir relativ schnell (also bevor der Ton unangemessen
geworden ist ;-)) unsere verschiedenen Positionen als
Philosophieunterschiede identfiziert. Schön!
Ich kann deine Argumentation nachvollziehen, das ist so ein bisschen wie
die MISRA Idee: Was immer sich statisch bestimmen lässt, kann dynamisch
keine Fehler mehr erzeugen. Da ist was dran, und dagegen kann man
erstmal auch wenig sagen.
Allerdings argumentiere ich in diesem Fall, dass das Verhalten nicht
intuitiv ist, weil ein Entwickler davon ausgeht, dass sich sein
Konstruktor immer gleich verhält. Wir sind aber sicherlich einer
Meinung, dass sich ein Konstruktor notwendigerweise verschieden
verhalten muss, wenn er in zwei verschiedenen Systemkontexten (System
initialisiert gegenüber nicht initalisiert) aufgerufen wird. Oder noch
interessanter: Abhängig davon, was er genau macht, kann sein
Verhalten in beiden Kontexten identisch sein oder nicht.
Das muss ein Entwickler wissen, um nicht in eine dieser Fallen zu
tappen, die wochenlange Fehlersuche nach sich ziehen können.
Also es ist ein bisschen wie Pest vs. Cholera. Jede/r Mitlesende darf
sich daraus sein(e) eigene Art zu sterben auswählen... ;-)
> mfg Torsten
zurück!