Datum:
Hallo zusammen Ich habe eine c-Anwendung für einen Mikrocontroller, die ich möglichst unabhängig von der Hardware erstellen möchte. (Z.B. auf Mikrocontroller und auf PC lauffähig). U.a. soll auf Anwendungsebene eine Timer-ISR aufgerufen werden. In der Anwendungsebene sollen aber keine Mikrocontrollerspezifischen Anweisungen stehen. Ich habe nun die Hardware von der Anwendung getrennt. 1.) Was hält ihr von der nachfolgenden Lösung? 2.) Ist bei dieser Lösung mit Performanceeinbussen zu rechnen, z.B. weil die Initialsierung der ISR in einem anderen Modul wie die ISR selbst stattfindet? Fehlt evtl. noch ein "inline":)? 3.) Wie würdet ihr es machen? // Hardwareebene
static inline void TMR0ISREnter(void)
{
#ifdef WINARM
ISR_ENTRY();
#endif
}
static inline void TMR0ISRExit(void)
{
VICVectAddr = 0;
#ifdef WINARM
ISR_EXIT();
#endif
}
void InitTimer0ISR(uint32_t Handler)
{
VICVectAddr1 = (uint32_t)Handler; // set interrupt vector in 0
// weitere Initialisanweisungen für Time
}
|
und hier der Code der Applikation
static void Timer0ISR(void) ISR_ATTRIB_C
{
TMR0ISREnter
// do something
TMR0ISRExit
}
void InitTimer1ISR(void)
{
InitTimer0ISR((uint32)Timer1ISR);
}
|
Danke für eure Meinung und Tipps Geri
Datum:
Abgesehen davon, dass mir nicht klar ist, warum du hier ...
static inline void TMR0ISRExit(void) { VICVectAddr = 0; #ifdef WINARM ISR_EXIT(); #endif } |
... den 'Weiterreichungspointer' löschen willst: Ja klar. Kann man so machen. Und ein universelles Betriebssystem wird das auch so machen. Die Frage ist halt immer: Wieviel kostet mir diese Universalität (in Einheiten der Rechenzeit), kann ich das verschmerzen und bin ich gewillt diesen Penalty zu bezahlen. Denn da fehlt ja noch was: Die eigentliche ISR, die über den Funktionspointer die Funktion aufruft. Aber bitte, bitte: Funktionspointer nicht als uint32_t anlegen. Mach einen ordentlichen Datentyp dafür (mit einem typedef) und leg den entsprechend an. Sinnigerweise wird man dann auch noch Möglichkeiten ersinnen, wie sich mehrer Funktionen auf Anwendercode-Ebene in einen Interrupt einhängen können, d.h. da wird es ein Array geben oder eine Liste von Pointer zu Handlern, etc. etc. Und so führt eines zum anderen und im Endeffekt kommt da einiges an Code zusammen.
Datum:
Hallo Karlheinz Danke für deine Kommentare! Mit VICVectAddr = 0; führt ma ein Interrupt Aknowledge durch. Bei nested Interrupts relevant Timer0ISR ist die ISR und dort würde ich den Code hineinschreiben. Ich würde hier also keine Funktionspointer aufrufen. Es gibt nur einen Funkionspointer, und den muss man am Anfang kennen und dem Interruptsystem mitteilen.
static void Timer0ISR(void) ISR_ATTRIB_C
{
TMR0ISREnter
// do something
TMR0ISRExit
}
|
Ich würde der Initialisierungsroutine also einen Zeiger auf Timer0ISR geben. Das macht man ja auch, wenn man den Timer sonst initialsiert.
// Im Hardwarefile codiert
void InitTimer0ISR(uint32_t Handler)
{
VICVectCntl1 = VIC_ENABLE | VIC_TIMER0; // use it for Timer 0 Interrupt:
VICVectAddr1 = (uint32_t)Handler; // set interrupt vector in 0
VICIntEnable = VIC_BIT(VIC_TIMER0); // Enable Timer0 Interrupt
T0TCR = 0; // Reset timer 1
T0PR = 0; //Set the timer 1 prescale counter
T0MR0 = 60000; // Set timer 1 match register
T0MCR = 3; // Generate interrupt and reset counter on match
T0TCR = 2; // Reset Timer 0 and disableit}
}
// in der Anwendung aufgerufen
void InitTimer1ISR(void)
{
InitTimer0ISR((uint32)Timer1ISR);
}
|
Ich würde evtl. lediglich währende der Ausführung mal einen anderen Handler initialisieren. Der Unterschied im Code ist meiner Ansicht nach nur, dass die ISR in einer anderen c-Datei implementiert ist. Kommt es damit auch zu Geschwindigkeitseinbussen? Mit einem ordentlichen Datentyp hast du aber jedenfalls recht:)! Beste Grüsse Geri
Datum:
> Es gibt nur einen Funkionspointer, und den muss man am Anfang kennen > und dem Interruptsystem mitteilen. > > static void Timer0ISR(void) ISR_ATTRIB_C > { > TMR0ISREnter > > // do something > > TMR0ISRExit > } ??? Dann hast du ja wieder die Details, wie das auf einem spezifischen µC konkret zu schreiben ist, im Anwendercode. Das hab ich dann missverstanden, wie das gemeint war. Vergiss die Sache mit dem Funktionspointer. OK, ein bischen besser ist es, weil du dir über die Makros eine gewisse Flexibilität einbaust. Nur darf halt derjenige, der den Mechanismus dann letztendlich benutzt, nicht auf die Makros vergessen. Sonst kann es natürlich passieren, dass das auf System A funktioniert (weil die Makros zu keinem Code expandieren) und auf einem System B aber nicht (weil dort die Makros relevanten Code einbringen).
Datum:
Hallo Karlheinz ????, Genauso habe ich es gemeint, wie du zuletzt verstanden hast:) Den Code nutze ich nur selber. Die Makros würden sich ja danach nur änderen, wenn man eine andere Hardware oder Compiler untersützen möchte. Ich erwarte mir halt einen einfachen Umstieg auf einen anderen Controller. Wenn man das Ganze hobbymässig betreibt, dann steigt man gerne wieder mal auf einen anderen Controller um so ist es dann halt doch einfacher, wenn der Grossteil von Code noch läuft. Einen besonders grossen Vorteil erwarte ich mir aber v.a bei Modultests, weil diese auf einem System wie Windows besser automatisierbar sind. Wenn das Programmgerüst so wie gezeigt aufgebaut ist, ergeben sich dadurch überhaupt Performanceeinbussen? Mit compilerdirektiven könnte man im Code auch arbeiten, wenn man aber schliesslich für mehrer Compiler code schreibt, dann wird der code mit der Zeit nicht gerade Gibt es noch bessere Möglichkeiten? Was könnte man noch evtl. noch verbessern? Beste Grüsse Geri
Datum:
Dinge wie ISR_ENTRY() müssen direkt in der ISR ausgeführt werden. Sie in Funktionen auszulagern ist auch dann riskant, wenn sie als "inline" deklariert werden, denn der Compiler wird sich dennoch seine Gedanken dazu machen und wird sie je nach Flags evtl. nicht inlinen. Was dann in kräftigst die Hose geht. Ich sehe auch keinen Sinn in dieser Auslagerung. Die ISR selbst ist notwendigerweise nicht portabel, weil abhängig vom Controller. Was wird durch diese Pseudo-Abstraktion gewonnen? Wenn es beispielsweise darum geht, die ähnliche UART aller LPC1000/2000 gemeinsam handhaben zu können, dann kann man aus den notwendigerweise controllerabhängig verschiedenen Handlern heraus den abstrakteren Handler eines universelleren UART-HAL aufrufen.
Datum:
Ich finde die Idee mit dem Funktionspointer gut. Das Platformunabhängige Programm gibt dem HW-abhängigen Treiber beim Initialisieren den Funktinspointer bekannt. Wenn nun ein Timer-Event fällig wird, macht der Treiber zuerst ISREnter, ruft dann den Funktionspointer auf, und am schluss ISRExit. Das Hauptprogramm muss keine Makros kennen.
Datum:
Hallo Zusammen
Danke für eure Kommentare!
@Andreas:
Die Befürchtung, dass der Compiler hier unberechenbare Dinge macht, der
kam mir eben auch:)
>>Was wird durch diese Pseudo-Abstraktion gewonnen?
1.) Der Inhalt meiner ISR von der Ausführungszeit kurz, der Code ist
aber recht lang und absolut unabhängig von der verwendeten Hardware. Ein
meiner Wünsche wäre eben, dass der Code, sobald controllerunabhängig in
der Anwendungsebene codiert wird. Bei den Uarts hast du wahrscheinlich
recht, ein Timer kann halt sehr universell eingesetzt werden - er ist
wahrscheinlich auch ein Sonderfall - aber eben wichtig
2.) Mit defines zu arbeiten macht den Code mit der Zeit halt etwas
unübersichtlich. Kleine Makros dünken mich modularer. Wenn ich z.B. 5
verschieden Compiler verwende man muss ich an den speziellen stellen
immer wieder ifdef....
3.) Wie ich oben geschrieben habe, möchte ich einen möglichst einfach
portieren können.
4.) Das Testen von Programmmodulen könnte auf einen PC verlagert werden.
=> Erleichterung beim Debugging.
Wenn der Compiler aber wirklich so unberechenbar sind, dann werde ich,
wenn nicht anderweitig möglich wohl mit defines arbeiten. Das hat bis
jetzt sehr gut geklappt. Man sucht halt immmer wieder Möglichkeiten zur
Verbesserung / Generalisierung.
@StinkyWinky
Das würde auch funktionieren, hat aber Performanceeinbussen zur Folge,
da du dann ja in der ISR eine Prozedur aufrufst. Man könnte sogar eine
verlinkte Liste von Handlern aufbauen.
Ich bin kein absoluter Crack in c. Vielleicht gibt es aber auch die
Möglichkeit, einen allgemeinnen Codeblock in einem Programmmodul zu
definieren, der dann in die ISR beim Compilieren mit Sicherheit
eigebunden wird.
Beste Grüsse
Geri
Datum:
Geri schrieb: > 1.) Der Inhalt meiner ISR von der Ausführungszeit kurz, der Code ist > aber recht lang und absolut unabhängig von der verwendeten Hardware. Ein > meiner Wünsche wäre eben, dass der Code, sobald controllerunabhängig in > der Anwendungsebene codiert wird. Und was spricht dann gegen eine hardwareabhängige Funktion static void Timer0ISR(void) ISR_ATTRIB_C { ISR_ENTRY(); handler(); VICVectAddr = 0; ISR_EXIT(); } und eine nicht davon abhängige Funktion handler() ?
Datum:
Warum das alles nicht in C++, mit virtuellen Funktioen? Oliver
Datum:
@Andreas: Dieser Ansatz gefiele mir auch sehr gut - eigentlich sogar noch besser, allerdings muss man ja hier wieder eine Prozedur (Handler) aufrufen. Das kostet Ressourcen, gerade wenn der Timer mit hoher Frequenz getaktet wird. Oder weiss der Compiler hier besser Bescheid, wenn der Handler inline definert ist? @Oliver Ich habe fast ausschliesslich c-Compiler in Verwendung. Wie würde das aber bitte konkret aussehen? Beste Grüsse Geri
Datum:
Geri schrieb: > Oder weiss der Compiler hier besser Bescheid, wenn der Handler inline > definert ist? Den Handler darfst du problemlos inlinen. Ob dar Unterschied wirklich gross ist wäre aber noch abzuwarten.
Datum:
Geri schrieb: > Ich habe fast ausschliesslich c-Compiler in Verwendung. Wie würde das > aber bitte konkret aussehen? Hardwareunabhängige Basisklasse, von der die diversen hardwareabhängigen Implementierungen abgeleitet werden. Ich mache das gerne in einer Variation davon für Schnittstellentreiber UART/I2C/etc. Eine normale Basisklasse mit dem Löwenanteil vom Code für diese Sorte Schnittstelle des Controllers, darunter dem Interrupt-Handler, und ein davon abgeleitetes Template für die Schnittstelleninstanzen mit den von der Hardware angesprochenen Handlern, die nur den Handler der Basisklasse aufrufen. Ein netter Nebeneffekt dieser Technik ist es, dass irgendwelche Pufferspeicher im Template mit pro Instanz wählbarer Grösse direkt anlegt werden können, ohne dynamische Speicherverwaltung verwenden zu müssen - die versuche ich bei Controllern so weit wie möglich zu vermeiden.
Datum:
Vielen Dank für deine Erläuterungen Das klingt danns sehr gut. Genau so etwas ähnliches habe ich auf PC-Ebene für Schnittstellen (USB, RS232 und Ethernet) gemacht, allerdings in Delphi und ohne ISR. >Ich mache das gerne in einer Variation davon für Schnittstellentreiber >UART/I2C/etc. Eine normale Basisklasse mit dem Löwenanteil vom Code für >diese Sorte Schnittstelle des Controllers bis hier her ist mir das noch klar:) >, darunter dem Interrupt-Handler, du meinst wahrscheinlich davon abgeleitet - oder, wäre dann auch klar:)? >und ein davon abgeleitetes Template für die Schnittstelleninstanzen mit den von der Hardware angesprochenen Handlern, die nur den Handler der Basisklasse aufrufen. Hm, wieso dann bitte noch ein Template? Wenn ich festestelle, dass ich z.B. die eine von der Basisklasse (TCommunicationBase) abeleitete Klasse TComRS232 intanziere soll, dann habe ich doch vollständigen Zugriff auf die Klasse. Gleichzeitig bei der Erzeugung wird ein Zeiger auf die Handlermethode übergeben...? Hast du davon hier bitte vielleicht ein Stück Beispielcode oder eine Literarutstelle? >>Ein netter Nebeneffekt dieser Technik ist es, dass irgendwelche >>Pufferspeicher im Template mit pro Instanz wählbarer Grösse direkt >>anlegt werden können, ohne dynamische Speicherverwaltung verwenden zu >>müssen - die versuche ich bei Controllern so weit wie möglich zu >>vermeiden. Dieser Teil wird mir dann wahrscheinlich klarer, wenn ich zuerst obigen gecheckt habe:) Vielen Dank nochmals für deine Mühe! Geri
Datum:
Geri schrieb: >>, darunter dem Interrupt-Handler, > du meinst wahrscheinlich davon abgeleitet - oder, wäre dann auch klar:)? Im Template ist der von der Hardware angesprochene Handler. Wie das genau funktioniert hängt vom Controller ab. Bei Controllern mit Vektoren in Registern (LPC2000) kann man eine statische Memberfunktion direkt verwenden, während es bei Controllern mit Bindung über den Namen (Cortex M3) etwas komplizierter wird. In diesem Hardware-Handler steht dann aber kaum mehr drin als ein Aufruf einer Funktion aus der Basisklasse. > Hm, wieso dann bitte noch ein Template? Auf diese Art lassen sich die Transferpuffer dort als Arrays einbauen, mit einer Grösse die per Template-Parameter bestimmt wird. Das ersetzt die von mir in solchem Umfeld wenig geliebte dynamische Speicherallokation - ich habe es gern, wenn die RAM-Belegung abgesehen vom Stack bereits mit dem Linken offenbar wird. Sowas wie (vereinfachtes Schema, kein realer Code): template<int RdBufSz, int WrBufSz> class UART : UART_Base { ... uint8_t rdbuf[RdBufSz], wrbuf[WrBufSz]; } Statt dynamischer Allokation werden im Konstruktur der abgeleiteten Klasse den Pointern in der Basisklasse die Adressen dieser Arrays zugewiesen.
Datum:
Ok, Danke! Die Überlegungen mit den Puffer kann ich nun nachvollziehen. Den Handler in Verbindung mit dem Template zu bringen, muss ich mir noch ein wenig durchdenken:) Vielen Dank vorab nochmals für deine Hilfe! Geri