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
1
static inline void TMR0ISREnter(void)
2
{
3
#ifdef WINARM
4
ISR_ENTRY();
5
#endif
6
}
7
8
static inline void TMR0ISRExit(void)
9
{
10
VICVectAddr = 0;
11
#ifdef WINARM
12
ISR_EXIT();
13
#endif
14
}
15
16
17
void InitTimer0ISR(uint32_t Handler)
18
{
19
VICVectAddr1 = (uint32_t)Handler; // set interrupt vector in 0
Abgesehen davon, dass mir nicht klar ist, warum du hier ...
1
staticinlinevoidTMR0ISRExit(void)
2
{
3
VICVectAddr=0;
4
#ifdef WINARM
5
ISR_EXIT();
6
#endif
7
}
... 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.
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.
1
static void Timer0ISR(void) ISR_ATTRIB_C
2
{
3
TMR0ISREnter
4
5
// do something
6
7
TMR0ISRExit
8
}
Ich würde der Initialisierungsroutine also einen Zeiger auf Timer0ISR
geben. Das macht man ja auch, wenn man den Timer sonst initialsiert.
1
// Im Hardwarefile codiert
2
void InitTimer0ISR(uint32_t Handler)
3
{
4
VICVectCntl1 = VIC_ENABLE | VIC_TIMER0; // use it for Timer 0 Interrupt:
5
VICVectAddr1 = (uint32_t)Handler; // set interrupt vector in 0
T0MCR = 3; // Generate interrupt and reset counter on match
11
T0TCR = 2; // Reset Timer 0 and disableit}
12
}
13
14
15
// in der Anwendung aufgerufen
16
void InitTimer1ISR(void)
17
{
18
InitTimer0ISR((uint32)Timer1ISR);
19
}
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
> 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).
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
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.
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.
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
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() ?
@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
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.
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.
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
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.
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