Ich versuche eine generische Klasse für den Mikrocontroller zu schreiben, den ich benutze. Es ist nicht selten, dass diese Tiere Register in Form <register prefix> <index> <suffix>
, verwenden wie UCSR0B
oder TCCR1A
Kann ich den "token pasting operator" mit 'const' Template Argumenten verwenden?
Also habe ich geschrieben Makros für ihre Argumente verketten ein neues Token zu bilden:
#define uart_is_enabled(i) (UCSR ## i ## B)
#define uart_putchar(i, c) UDR ## i = c
Hinweis: hier Ich benutze UART-Register, aber das ist nur ein Beispiel, ich versuche nicht, ein UART zu arbeiten, ich habe bereits Code dafür, den ich verbessern möchte.
EDIT: Für die Neugierigen, hier ist eine Definition eines Registers wie UCSR0B, wie pro Atmel Bibliothek:
#ifndef __SFR_OFFSET
# if __AVR_ARCH__ >= 100
# define __SFR_OFFSET 0x00
# else
# define __SFR_OFFSET 0x20
# endif
#endif
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define UCSR0B _SFR_IO8(0x25)
Nun möchte Ich mag Vorlagen Klassen versuchen, mit dem Makros zu arbeiten, ohne Rückgriff für jeden möglichen Index Spezialisierung:
template <const unsigned index>
class Uart
{
public:
static bool is_enabled() { return uart_is_enabled(index); }
static void putchar(uint8_t) { uart_putchar(index, c); }
};
natürlich mit uart<0>
oder uart<1>
oder jede unsigned int
für die Vorlage ein Argument, erhalte ich eine Fehlermeldung wie diese:
error: 'UCSRindexB' was not declared in this scope
Dies liegt daran, Makroargumente „bewertet“ (bitte gib mir eine pedantische Begnadigung einen falschen Begriffs für die Verwendung) vor Template-Argumente Werten kompiliert werden, wenn mein Verständnis korrekt ist. Gibt es eine Möglichkeit, das irgendwie zu machen?
EDIT: Obwohl Phil's answer 100% direkt meine Frage befasst, änderte ich meine Meinung und für einen anderen Ansatz entschieden. Ich sehe C/C++ eine Sprache, in der Sie ausführlich sein müssen, das heißt, es ist am besten, alle Fälle zu deklarieren, die Sie verwalten möchten. Es gibt Situationen, wie diese, schneidet Ecken wahrscheinlich schwerfälliger Code - das ist aber meiner Meinung nach natürlich, YMMV.
Als Konsequenz habe ich eine Form der Hardware-Abstraktion durch duck-typing implementiert: Ich habe so viele Hardware-Treiberklassen definiert, wie es viele verschiedene UARTs in Atmel-Prozessoren gibt - ich bin ziemlich glücklich, dass es nicht so viele UART-Typen gibt Atmel Mikrocontroller wie die Anzahl der Modelle.
Hier ist ein Beispiel mit einer LIN/UART-Treiberklasse:
// lin.h (excerpt)
// Auto-detect the one and only serial interface (set UART mode)
#ifdef LINDAT
/*
* UART0 driver — for microcontroller which UART module is shared
* with LIN, e.g. ATmega64M1
*/
struct uart0
{
static INLINE void power_on() { power_lin_enable(); }
static INLINE void power_off() { power_lin_disable(); }
static INLINE void enable() { uart_enable(); }
static INLINE void disable() { uart_disable(); }
...
static void reset();
};
#endif
Hier ist ein Beispiel für Mikrocontroller, die mit 1 oder mehrere UART Module definieren:
// uart.h (excerpt)
// Auto-detect first serial interface aka U[S]ART0
#ifdef UDR0
struct uart0
{
static INLINE void power_on() { power_usart0_enable(); }
static INLINE void power_off() { power_usart0_disable(); }
static INLINE void enable() { uart_enable(0); }
static INLINE void disable() { uart_disable(0); }
...
};
#endif
Hinweis Ich bin immer noch mit der C Makros, die ich oben in diesem Post eingeführt habe, um das Register-Handling zu verallgemeinern ... obwohl einige dazu neigen, ziemlich schwer lesbar zu werden, gebe ich zu.
Dann schrieb ich eine Template-Klasse (eigentlich eine Interface-Klasse), welches Argument die Treiberklasse ist, von der die Interface-Klasse erbt.
// Generic UART wrapper. Comes with a circular input buffer
template <class driver>
class tty : public driver
{
protected:
...
public:
static void putchar(char) { driver::putchar(c); }
static void power_off()
{
driver::disable();
driver::power_off();
}
...
};
-Controller-spezifische Header-Dateien werden nach erfassten Registernamen enthalten:
#if defined(UDR0) || defined(UDR1) || defined(UDR)
#include <drv/uart.h>
#endif
#ifdef LINDAT
#include <drv/lin.h>
#endif
Mit diesem Ansatz Antriebselementen, die die Architektur spezifisch sind, ich bin Kompilieren wird über die Schnittstelle Klasse ausgesetzt . Es erlaubt mir, für so viele Prozessoren relativ generischen Code zu schreiben, wie ich für eine einzelne Anwendung unterstützen:
typedef serio tty<uart0>; // same code for ATmega328p, ATmega64M1, ATtiny1634...
serio::putchar('a');
Ich kann auch sehr spezifischen Code schreiben, wenn ich meine Anwendung auf einen bestimmten Mikro-Controller widmen will . Ich muss nur Arch-spezifische Treibermitglieder verwenden. Für den generischen Ansatz müssen alle meine Treiberklassen übereinstimmend eine bestimmte Anzahl von gemeinsamen Elementen für dieses Konzept verfügbar machen, aber es muss nur einmal ausgeführt werden, d. H. Wenn Unterstützung für einen neuen Mikrocontroller hinzugefügt wird. Dies ist in der Tat eine Art von sich wiederholenden Aufgabe (vgl mein Punkt über „Weitschweifigkeit“), I-Typ-Code bedeuten, dass (fast) gleich aussieht und immer (rinse/wiederholen mit jedem Controller, den Sie Support für die) aber am Ende dieses ist die Flexibilität, die ich will.
Ich habe auch überprüft die Assembler-Code generiert, und ich kann das Optimierungsprogramm macht einen ziemlich guten Job, vor allem, wann und wo ich frage es Code Inline bestätigen.
Die kurze und einzige Antwort ist „nein“ wie erwartet funktioniert. Es ist einfach nicht möglich, den Wert von z.B. 'index' in einem Makro. –
Und was bedeutet 'UCSR0B;' im Code? Ist es eine Variable? –
Die Vorverarbeitung erfolgt vor dem Parsen von 'Uart' als Vorlage und führt nur eine einfache Textersetzung durch. – VTT