2017-07-29 2 views
1

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

+1

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

+0

Und was bedeutet 'UCSR0B;' im Code? Ist es eine Variable? –

+1

Die Vorverarbeitung erfolgt vor dem Parsen von 'Uart' als Vorlage und führt nur eine einfache Textersetzung durch. – VTT

Antwort

2

Der folgende Code würde wie gewünscht arbeiten. Da Makros zuerst ausgewertet werden, müssen Sie Ihre Vorlagenspezialisierung für jedes Register manuell erstellen.

// Simulate register names (to test on any compiler)... 
static bool UCSR0B; 
static bool UCSR1B; 

static char UDR0; 
static char UDR1; 

Das gleiche Makro wie Original-Beitrag noch

nützlich sein kann
#define uart_is_enabled(i)  (UCSR ## i ## B) 
#define uart_putchar(i, c)  UDR ## i = c 

Dann erklären wir, eine Vorlage aus. Hier verwende ich eine leere Klasse. Ihre Standardimplementierung ist möglicherweise anders.

An diesem Punkt verwende ich ein Makro, um die Spezialisierung für alle Register zu definieren.

// Macro that define a template specialization for a specific UART 
#define UART(index) \ 
template <> \ 
class Uart<index> \ 
{ \ 
public: \ 
    static bool is_enabled() { return uart_is_enabled(index); } \ 
    static void putchar(uint8_t c) { uart_putchar(index, c); } \ 
} 

// Define Uart<0>, Uart<1>... classes 
UART(0); 
UART(1); 

Schließlich ist dies ein Testprogramm, das zeigt, dass es

// Test program 
void test() 
{ 
    Uart<0>::is_enabled(); 
    Uart<1>::is_enabled(); 
    // Uart<2>::is_enabled(); // Would not compile 

    Uart<0>::putchar('a'); 
    Uart<1>::putchar('b'); 
    // Uart<2>::putchar('c'); // Would not compile 
} 
+0

Vielen Dank! Daran hatte ich natürlich nicht gedacht! Das ist eine nette Art, Spezialisierung wie halbautomatisch zu machen. Ich mag es. –

Verwandte Themen