Generische Portzuweisungen

Schreibt man einen Treiber für eine Mikrocontrollerapplikation (z. B. für ein USB-Device oder LCD-Display), so will man diesen meist in späteren Projekten wiederverwenden wollen. Deshalb werden insbesondere Port- und Pinzuweisungen an einer zentralen Stelle definiert. Da hierfür bei AVR/Atmel-Prozessoren gleich drei Register (Richtung, Eingang, Ausgang bzw. DDR, PORT, PIN) nötig sind, muss man hierfür auch drei “später anzupassende” Zeilen vorsehen:

#define USB_OUT   PORTD
#define USB_IN    PIND
#define USB_DDR   DDRD

Dank reverse-Engineering eines anderen Problems bin ich heute auf folgende (meines Erachtens geniale) Codestelle gestoßen, wie dieses Problem möglichst generisch und kurz gelöst wird:

// 1. concatenation-macro:
#define CONCAT(a, b)            a ## b
// 2. generic macros for PORT, PIN and DDR (AVR/Atmel):
#define OUTPORT(name)           CONCAT(PORT, name)
#define INPORT(name)            CONCAT(PIN, name)
#define DDRPORT(name)           CONCAT(DDR, name)
// 3. spezific macros for USB-module:
#define USB_OUT          OUTPORT(USB_CFG_IOPORTNAME)
#define USB_IN           INPORT(USB_CFG_IOPORTNAME)
#define USB_DDR          DDRPORT(USB_CFG_IOPORTNAME)

Somit ist statt obigem Dreizeiler nur noch folgendes nötig:

#define USB_CFG_IOPORTNAME      B

Auf den ersten Blick sieht dies jetzt viel komplexer als obere Variante aus. Um aber in einem späteren Projekt einen anderen Port zu verwenden, ist nur eine statt drei Zeilen anzupassen – somit besteht auch nicht die Gefahr, eine dieser Zeilen zu “vergessen”.

Außerdem sind Block #1 und #2 wiederverwendbar, Beispiel:

#define LCD_PORTNAME  B
#define LCD_PORT      OUTPORT (LCD_PORTNAME)
#define LCD_DDR       DDRPORT (LCD_PORTNAME)