SprinterSB
21.09.2005, 17:54
Das Problem mit den Ports ist recht nervig. Vor allem dann, wenn man mal einen Port umdefiniert und dann alle Quellen durchsehen muss.
Ich habe mir mit Macros geholfen und das funktioniert ganz gut, wenn man optimiert (ab -O ).
Als erstes definiere ich Makros, die eine Konstante 0, 1, ... etc auf einen Port abbilden. Diese Makros sind Target-unabhängig.
avr-port-macros.h
#ifndef _AVR_PORT_MACROS_H_
#define _AVR_PORT_MACROS_H_
#ifndef PORT_ID
#define PORT_ID(n) \
(n ## _ID << 3)
#endif /* PORT_ID */
#ifdef USE_SERPA
extern unsigned char serpa[];
extern unsigned char serpa_read[];
extern const unsigned char SERPA_NUM;
//#define SERPA_NUM 4
//unsigned char serpa[SERPA_NUM];
#define IS_SERPA_PORT(pinid) \
((SERPA_ID <= (pinid) >> 3) /* && (SERPA_ID+SERPA_NUM > (pinid) >> 3)*/)
#define N_TO_SERPA(pinid) \
serpa[((pinid) >> 3)-SERPA_ID]
#define N_TO_SERPAIN(pinid) \
serpa_read[0]
#define IF_SERPA_OP_ELSE(pinid,op) \
if (IS_SERPA_PORT (pinid))\
N_TO_SERPA(pinid) op ID_TO_BIT(pinid); \
else
#else /* USE_SERPA */
//#define SERPA_NUM 0
#define IF_SERPA_OP_ELSE(pinid,op)
#define N_TO_SERPA(pinid) 0
#define N_TO_SERPAIN(pinid) 0
#endif /* USE_SERPA */
#define IS_HARD_PORT(pinid) \
((pinid >> 3) < SERPAIN_ID)
/** Set a PORT bit to 1 */
#define SET(pinid) \
do { \
if ((pinid) >> 3 == SERPAIN_ID); else \
IF_SERPA_OP_ELSE(pinid, |=) \
N_TO_PORT (pinid) |= ID_TO_BIT(pinid); \
} while (0)
/** Set a PORT bit to 0 */
#define CLR(pinid) \
do { \
if ((pinid) >> 3 == SERPAIN_ID); else \
IF_SERPA_OP_ELSE(pinid, &= ~) \
N_TO_PORT (pinid) &= ~ID_TO_BIT(pinid); \
} while (0)
/** Toggle a PORT bit.
* Note: The generated code will NOT be atomic!
*/
#define TOGGLE(pinid) \
do { \
if ((pinid) >> 3 == SERPAIN_ID); else \
IF_SERPA_OP_ELSE(pinid, ^= ) \
N_TO_PORT (pinid) ^= ID_TO_BIT(pinid); \
} while (0)
/** Set DDR to 1 */
#define MAKE_OUT(pinid) \
do { \
if (IS_HARD_PORT(pinid)) \
N_TO_DDR (pinid) |= ID_TO_BIT(pinid); \
} while (0)
/** Set DDR to 0 */
#define MAKE_IN(pinid) \
do { \
if (IS_HARD_PORT(pinid)) \
N_TO_DDR (pinid) &= ~ID_TO_BIT(pinid); \
} while (0)
/** Is PIN == 1 ? */
#define IS_SET(pinid) \
(IS_HARD_PORT(pinid) \
? (N_TO_PIN (pinid) & ID_TO_BIT(pinid)) \
: (unsigned char) ((unsigned char) N_TO_SERPAIN(pinid) & (unsigned char) ID_TO_BIT(pinid)))
/** Is PIN == 0 ? */
#define IS_CLEAR(pinid) \
(!IS_SET(pinid))
/** Is PORT == 1 ? */
#define IS_PORT(pinid) (N_TO_PORT (pinid) & ID_TO_BIT(pinid))
/*
* IDs for the various PORTs of our AVRs.
* If you want to make this work for targets with
* even more ports (like ATMega128 etc) just add these
* ports in the following macro definitions.
* Using #error would require making the macros target dependent.
*/
enum {
PORTA_ID,
PORTB_ID,
PORTC_ID,
PORTD_ID,
SERPAIN_ID,
SERPA_ID
};
#define pDDRX(pinid) ({asm volatile (".bad_ddr_id_" # pinid);(unsigned char*) 0;})
#define pPORTX(pinid) ({asm volatile (".bad_port_id_" # pinid);(unsigned char*) 0;})
#define pPINX(pinid) ({asm volatile (".bad_pin_id_" # pinid);(unsigned char*) 0;})
/*
* #define addresses of PORTs or an assembler error if
* a non-existing PORT is refered.
* I found no way to tell this error by the (pre)compiler.
* Note that #error does not work as expected here.
*/
#if defined(PORTA)
#define pPORTA &PORTA
#else
#define pPORTA ({asm volatile (".target_has_no_porta");(unsigned char*) 0;})
#endif
#if defined(PORTB)
#define pPORTB &PORTB
#else
#define pPORTB ({asm volatile (".target_has_no_portb");(unsigned char*) 0;})
#endif
#if defined(PORTC)
#define pPORTC &PORTC
#else
#define pPORTC ({asm volatile (".target_has_no_portc");(unsigned char*) 0;})
#endif
#if defined(PORTD)
#define pPORTD &PORTD
#else
#define pPORTD ({asm volatile (".target_has_no_portd");(unsigned char*) 0;})
#endif
/*
* #define addresses of DDRs or an assembler error if
* a non-existing DDR is refered.
*/
#if defined(DDRA)
#define pDDRA &DDRA
#else
#define pDDRA ({asm volatile (".target_has_no_ddra");(unsigned char*) 0;})
#endif
#if defined(DDRB)
#define pDDRB &DDRB
#else
#define pDDRB ({asm volatile (".target_has_no_ddrb");(unsigned char*) 0;})
#endif
#if defined(DDRC)
#define pDDRC &DDRC
#else
#define pDDRC ({asm volatile (".target_has_no_ddrc");(unsigned char*) 0;})
#endif
#if defined(DDRD)
#define pDDRD &DDRD
#else
#define pDDRD ({asm volatile (".target_has_no_ddrd");(unsigned char*) 0;})
#endif
/*
* #define addresses of PINs or an assembler error if
* a non-existing PIN is refered.
*/
#if defined(PINA)
#define pPINA &PINA
#else
#define pPINA ({asm volatile (".target_has_no_pina");(unsigned char*) 0;})
#endif
#if defined(PINB)
#define pPINB &PINB
#else
#define pPINB ({asm volatile (".target_has_no_pinb");(unsigned char*) 0;})
#endif
#if defined(PINC)
#define pPINC &PINC
#else
#define pPINC ({asm volatile (".target_has_no_pinc");(unsigned char*) 0;})
#endif
#if defined(PIND)
#define pPIND &PIND
#else
#define pPIND ({asm volatile (".target_has_no_pind");(unsigned char*) 0;})
#endif
#define N_TO_pPORT(pinid) \
( \
(PORTA_ID == (pinid) >> 3) ? pPORTA : \
(PORTB_ID == (pinid) >> 3) ? pPORTB : \
(PORTC_ID == (pinid) >> 3) ? pPORTC : \
(PORTD_ID == (pinid) >> 3) ? pPORTD : \
pPORTX(pinid) \
)
#define N_TO_pDDR(pinid) \
( \
(PORTA_ID == (pinid) >> 3) ? pDDRA : \
(PORTB_ID == (pinid) >> 3) ? pDDRB : \
(PORTC_ID == (pinid) >> 3) ? pDDRC : \
(PORTD_ID == (pinid) >> 3) ? pDDRD : \
pDDRX(pinid) \
)
#define N_TO_pPIN(pinid) \
( \
(PORTA_ID == (pinid) >> 3) ? pPINA : \
(PORTB_ID == (pinid) >> 3) ? pPINB : \
(PORTC_ID == (pinid) >> 3) ? pPINC : \
(PORTD_ID == (pinid) >> 3) ? pPIND : \
pPINX(pinid) \
)
#define N_TO_PORT(pinid) (*(N_TO_pPORT(pinid)))
#define N_TO_DDR(pinid) (*(N_TO_pDDR(pinid)))
#define N_TO_PIN(pinid) (*(N_TO_pPIN(pinid)))
#define CHECK_PORT_MACRO(port) \
do { \
if (&PORT ## port != N_TO_pPORT (PIN_ID (port,0))) asm volatile (".error_in_N_TO_pPORT_" #port); \
if (&DDR ## port != N_TO_pDDR (PIN_ID (port,0))) asm volatile (".error_in_N_TO_pDDR_" #port); \
if (&PIN ## port != N_TO_pPIN (PIN_ID (port,0))) asm volatile (".error_in_N_TO_pPIN_" #port); \
} while (0)
#endif /* _AVR_PORT_MACROS_H_ */
Im zweiten File werden je nach verwendetem Target die vorhandenen Ports auf Konstanten abgebildet:
avr-port-enum.h
#ifndef _AVR_PORT_ENUM_H_
#define _AVR_PORT_ENUM_H_
/************************************************** *********/
#ifndef ID_TO_BIT
#define ID_TO_BIT(pinid) \
(1<<((pinid)&7))
#endif /* ID_TO_BIT */
#define PIN_ID(c,n) \
((PORT ## c ## _ID << 3) | (n))
#define ENUM_PIN(letter,pin) \
PORT ## letter ## _ ## pin = PIN_ID(letter,pin)
#define ENUM_PORT(p) \
ENUM_PIN (p,0), \
ENUM_PIN (p,1), \
ENUM_PIN (p,2), \
ENUM_PIN (p,3), \
ENUM_PIN (p,4), \
ENUM_PIN (p,5), \
ENUM_PIN (p,6), \
ENUM_PIN (p,7)
/*
* Enumerate all ports of respective MCU,
* giving us the desired values like PORTD_1 or PORTB_4, e.g.
*/
#if defined(__AVR_AT90S2313__)
enum {
ENUM_PORT (B),
ENUM_PIN (D,0),
ENUM_PIN (D,1),
ENUM_PIN (D,2),
ENUM_PIN (D,3),
ENUM_PIN (D,4),
ENUM_PIN (D,5),
ENUM_PIN (D,6),
ENUM_PIN_LAST
};
#elif defined(__AVR_ATmega8__)
enum {
ENUM_PORT (B),
ENUM_PIN (C,0),
ENUM_PIN (C,1),
ENUM_PIN (C,2),
ENUM_PIN (C,3),
ENUM_PIN (C,4),
ENUM_PIN (C,5),
ENUM_PIN (C,6),
ENUM_PORT (D),
ENUM_PIN_LAST
};
#elif defined(__AVR_AT90S2333__) || defined(__AVR_AT90S4433__)
enum {
ENUM_PIN (B,0),
ENUM_PIN (B,1),
ENUM_PIN (B,2),
ENUM_PIN (B,3),
ENUM_PIN (B,4),
ENUM_PIN (B,5),
ENUM_PIN (C,0),
ENUM_PIN (C,1),
ENUM_PIN (C,2),
ENUM_PIN (C,3),
ENUM_PIN (C,4),
ENUM_PIN (C,5),
ENUM_PORT (D),
ENUM_PIN_LAST
};
#elif defined(__AVR_AT90S4414__) || defined(__AVR_AT90S8515__) || \
defined(__AVR_AT90S4434__) || defined(__AVR_AT90S8535__) || \
defined(__AVR_ATmega163__)
enum {
ENUM_PORT (A),
ENUM_PORT (B),
ENUM_PORT (C),
ENUM_PORT (D),
ENUM_PIN_LAST
};
#else
#error Please define available ports for MCU
#endif
#if 0
/* Code snippet from avr-gcc source */
static const struct mcu_type_s avr_mcu_types[] = {
/* Classic, <= 8K. */
{ "avr2", 2, NULL },
{ "at90s2313", 2, "__AVR_AT90S2313__" },
{ "at90s2323", 2, "__AVR_AT90S2323__" },
{ "at90s2333", 2, "__AVR_AT90S2333__" },
{ "at90s2343", 2, "__AVR_AT90S2343__" },
{ "attiny22", 2, "__AVR_ATtiny22__" },
{ "attiny26", 2, "__AVR_ATtiny26__" },
{ "at90s4414", 2, "__AVR_AT90S4414__" },
{ "at90s4433", 2, "__AVR_AT90S4433__" },
{ "at90s4434", 2, "__AVR_AT90S4434__" },
{ "at90s8515", 2, "__AVR_AT90S8515__" },
{ "at90c8534", 2, "__AVR_AT90C8534__" },
{ "at90s8535", 2, "__AVR_AT90S8535__" },
{ "at86rf401", 2, "__AVR_AT86RF401__" },
/* Classic, > 8K. */
{ "avr3", 3, NULL },
{ "atmega103", 3, "__AVR_ATmega103__" },
{ "atmega603", 3, "__AVR_ATmega603__" },
{ "at43usb320", 3, "__AVR_AT43USB320__" },
{ "at43usb355", 3, "__AVR_AT43USB355__" },
{ "at76c711", 3, "__AVR_AT76C711__" },
/* Enhanced, <= 8K. */
{ "avr4", 4, NULL },
{ "atmega8", 4, "__AVR_ATmega8__" },
{ "atmega8515", 4, "__AVR_ATmega8515__" },
{ "atmega8535", 4, "__AVR_ATmega8535__" },
/* Enhanced, > 8K. */
{ "avr5", 5, NULL },
{ "atmega16", 5, "__AVR_ATmega16__" },
{ "atmega161", 5, "__AVR_ATmega161__" },
{ "atmega162", 5, "__AVR_ATmega162__" },
{ "atmega163", 5, "__AVR_ATmega163__" },
{ "atmega169", 5, "__AVR_ATmega169__" },
{ "atmega32", 5, "__AVR_ATmega32__" },
{ "atmega323", 5, "__AVR_ATmega323__" },
{ "atmega64", 5, "__AVR_ATmega64__" },
{ "atmega128", 5, "__AVR_ATmega128__" },
{ "at94k", 5, "__AVR_AT94K__" },
/* Assembler only. */
{ "avr1", 1, NULL },
{ "at90s1200", 1, "__AVR_AT90S1200__" },
{ "attiny11", 1, "__AVR_ATtiny11__" },
{ "attiny12", 1, "__AVR_ATtiny12__" },
{ "attiny15", 1, "__AVR_ATtiny15__" },
{ "attiny28", 1, "__AVR_ATtiny28__" },
{ NULL, 0, NULL }
};
#endif /* 0 */
#endif /* _AVR_PORT_ENUM_H_ */
Das sieht alles recht kompliziert aus, aber das kann man wie eine Black Box benutzen und muss es nicht verstehen. Diese beiden Dateien verwende ich in allen Projekten. Sie liegen in einem Verzeichnis ausserhalb der Projekte und den Pfad gebe ich gcc mit dem -I Schalter mit. Etwa:
avr-gcc ... -Ic:/avr-projekte/include ...
Die Dateien in jedes Projekt zu kopieren empiehlt sich nicht, weil man dadurch 1000 Kopien der selben Dateien hat.
Verwendung:
In jedem Projekt hab ich eine Datei ports.h, die etwa so aussehen könnte
#ifndef _PORTS_H_
#define _PORTS_H_
#include <avr/io.h>
// Make sure that gcc can find avr-port-xxx.h
// Use the -I option to tell gcc where to find these files.
// avr-gcc ... -Ic:/foo ... (windows)
// avr-gcc ... -I/usr/local/foo ...(linux)
#include <avr-port-macros.h>
#include <avr-port-enum.h>
enum
{
PORT_LED = PORTD_2,
PORT_SER = PORTD_4,
PORT_SCK = PORTD_5,
PORT_RCK = PORTD_6,
PORT_TAST = PORTB_3,
#ifdef USE_DCF
PORT_DCF = PORTD_0,
#endif /* USE_DCF */
// sentinel
PORT_NIL
};
#endif /* _PORTS_H_ */
Hier ein Beispiel zur Verwendung:
#include <avr/io.h>
#include "ports.h"
void main() // compiled with -ffreestanding
{
// PORT_LED ist Output
MAKE_OUT (PORT_LED);
// PORT_LED aus
CLR (PORT_LED);
// PORT_TAST Input mit Pullup
MAKE_IN (PORT_TAST);
SET (PORT_TAST);
if (!IS_SET (PORT_TAST))
CLR (PORT_LED);
if (IS_CLEAR (PORT_TAST))
TOGGLE (PORT_LED);
if (!IS_PORT (PORT_LED))
SET (PORT_LED);
}
Definierte Makros
SET(id)
Setzt PORTx auf 1.
Beispiel: SET (PORTD_0);
CLR(id)
Setzt PORTx auf 0.
Beispiel: CLR (PORTD_0);
TOGGLE(id)
Wechselt den Zustand von PORTx.
Beispiel: TOGGLE (PORTD_0);
Anmerkung: Hier werden mehrere asm-Instruktionen generiert, der erzeugte Code ist also nicht atomar.
MAKE_OUT(id)
Setzt DDRx auf 1.
Beispiel: MAKE_OUT (PORTD_0);
MAKE_IN(id)
Setzt DDRx auf 0.
Beispiel: MAKE_IN (PORTD_0);
IS_SET(id)
Testet PINx auf 1.
Beispiel: if (IS_SET (PORTD_0)) {...}
Beispiel: if (!IS_SET (PORTD_0)) {...}
IS_CLEAR(id)
Testet PINx auf 0.
Beispiel: if (IS_CLEAR (PORTD_0)) {...}
Beispiel: if (!IS_CLEAR (PORTD_0)) {...}
IS_PORT(id)
Testet PORTx auf 1.
Beispiel: if (IS_PORT (PORTD_0)) {...}
Anmerkung: wird man normalerweise nicht brauchen
Wie man sieht wird mittels PORTD_0 sowohl das passende DDR, PORT und PIN angesprochen! Schreibt man nicht PORTD_0 hin sondern
#define PORT_LED PORTD_0
oder
enum
{
...
PORT_LED = PORTD_0,
...
};
dann muss man nur an 1 Stelle im Code anpacken, um alle Zugriffe auf einen anderen Port zu lenken!
Durch die komplexen Makros wird erst mal recht viel C-Code erzeugt. Da alles Konstanten sind und die Ausdrücke zur Compile-Zeit ausgewertet werden können, kollabieren fast alle zu einer einzigen asm-Instruktion, so daß in *.s alles recht gut aussieht:
Assembler-Ausgabe des Beispiels
; GNU C version 3.4.1 (avr)
; compiled by GNU C version 3.3.1 (cygming special).
; GGC heuristics: --param ggc-min-expand=38 --param ggc-min-heapsize=16318
; options passed: -fpreprocessed -mmcu=atmega8 -auxbase -O1 -Wall
; -Winline -ffreestanding -fno-keep-inline-functions -fverbose-asm
; options enabled: -feliminate-unused-debug-types -fdefer-pop
; -fomit-frame-pointer -fthread-jumps -fpeephole -ffunction-cse
; -fkeep-static-consts -freg-struct-return -fgcse-lm -fgcse-sm -fgcse-las
; -floop-optimize -fif-conversion -fif-conversion2 -fsched-interblock
; -fsched-spec -fsched-stalled-insns -fsched-stalled-insns-dep
; -fbranch-count-reg -fcprop-registers -fcommon -fverbose-asm
; -fargument-alias -fmerge-constants -fzero-initialized-in-bss -fident
; -fguess-branch-probability -fmath-errno -ftrapping-math
; -minit-stack=__stack -mmcu=atmega8
.text
.global main
.type main, @function
main:
/* prologue: frame size=0 */
ldi r28,lo8(__stack - 0)
ldi r29,hi8(__stack - 0)
out __SP_H__,r29
out __SP_L__,r28
/* prologue end (size=4) */
sbi 49-0x20,2 ; , ; 15 *sbi [length = 1]
cbi 50-0x20,2 ; , ; 31 *cbi [length = 1]
cbi 55-0x20,3 ; , ; 42 *cbi [length = 1]
sbi 56-0x20,3 ; , ; 58 *sbi [length = 1]
sbis 54-0x20,3 ; , ; 68 *sbix_branch [length = 2]
cbi 50-0x20,2 ; , ; 82 *cbi [length = 1]
.L12:
sbic 54-0x20,3 ; , ; 93 *sbix_branch [length = 2]
rjmp .L16 ;
in r24,50-0x20 ; tmp74, ; 105 *movqi/4 [length = 1]
ldi r25,lo8(4) ; tmp75, ; 106 *movqi/2 [length = 1]
eor r24,r25 ; tmp73, tmp75 ; 107 xorqi3 [length = 1]
out 50-0x20,r24 ; , tmp73 ; 108 *movqi/3 [length = 1]
.L16:
sbis 50-0x20,2 ; , ; 119 *sbix_branch [length = 2]
sbi 50-0x20,2 ; , ; 133 *sbi [length = 1]
.L1:
/* epilogue: frame size=0 */
rjmp exit
/* epilogue end (size=1) */
Vielleicht findet jemand so ein Paket auch praktisch?
Würd mich über Rückmeldungen freuen.
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.