Forum: Mikrocontroller und Digitale Elektronik Frage zu Struktur für IO-Port bei AVRs


von Jan (Gast)


Lesenswert?

Guten Abend,

ich bin gerade dabei, mit ein paar Standart-Funktionen und Strukturen 
für zukünftige 8 Bit AVR- Projekte anzulegen.
Habe mich jetzt mal durch die ganzen Header mit ettlichen defines des 
GCC gebissen und letztendlich eine Struktur entworfen, so wie ich es für 
richtig halte.

Nun hätte ich die Bitte, dass ihr mal kurz darüber schaut und mir ein 
Feedback gebt, ob ich die Header-Files richtig verstanden habe und die 
Struktur so passt.
Insbesondere bei den Makros setBit etc bin ich mir unsicher, da ich ja 
hier auf den Wert des Pointers zugreifen müsste?....

Vielen Dank schonmal.

Gruß Jan

Header:
1
#if defined (__AVR_ATtiny13A__)
2
  #define NUMPIN  5
3
#else
4
  #error "Wrong device selected, please adapt number of Pins."
5
#endif
6
7
#include <stdint.h>
8
9
10
#ifndef GPIO_H_
11
#define GPIO_H_
12
13
#define INPUT  0
14
#define OUTPUT  1
15
16
typedef struct
17
{
18
  uint8_t      *PR;        /* Address of Port-Register */
19
  uint8_t      *DR;        /* Address of Data-Direction-Register */
20
  uint8_t      Pin;        /* Pin-Number */
21
  uint8_t      Drctn[NUMPIN];    /* Array for Data-Direction of each Pin */
22
  uint8_t      PinCount;      /* Total number of Pins per Port */
23
}
24
GPIO;
25
26
#define setPin(PR,Pin)    ((*PR) |= (1<<(Pin)))
27
#define getPin(PR,Pin)    ((*PR) & (1<<(Pin)))
28
#define clearPin(PR,Pin)  ((*PR) &= ~(1<<(Pin)))
29
#define togglePin(PR,Pin)  ((*PR) ^= (1<<(Pin)))
30
31
#define setPort(PR)      ((*PR) = 0xFF)
32
#define getPort(PR)      ((*PR) & 0xFF)
33
#define clearPort(PR)    ((*PR) = 0x00)
34
#define togglePort(PR)    ((*PR) ^= 0xFF)
35
36
void GPIO_ini(GPIO *GPIO_Inst);

Und das Ini-File:
1
 #include "gpio.h"
2
3
void GPIO_ini(GPIO *GPIO_Inst)
4
{
5
  uint8_t i;
6
  
7
  for(i=0; i<=(GPIO_Inst->PinCount); i++)
8
     *(GPIO_Inst->DR) = (GPIO_Inst->Drctn[i]);
9
}

von Jan (Gast)


Lesenswert?

Edit:

I

Jan schrieb:
> for(i=0; i<=(GPIO_Inst->PinCount); i++)
>
>      *(GPIO_Inst->DR) = (GPIO_Inst->Drctn[i]);

Sollte eigentlich

*(GPIO_Inst->DR) |= (GPIO_Inst->Drctn[i]);

heißen

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jan schrieb:
> uint8_t      *PR;        /* Address of Port-Register */
>   uint8_t      *DR;        /* Address of Data-Direction-Register */

Die Zeiger müssen auf ein "volatile"-Objekt zeigen.

von Fabian O. (xfr)


Lesenswert?

Du weißt schon, dass der ATtiny13A nur 64 Byte RAM hat? Ein einziges 
GPIO-Struct belegt davon schon 11 Byte, ohne dass es irgendeine 
Information enthält, die im RAM sein muss ...

von Jan (Gast)


Lesenswert?

Ich zähle "nur" 10 Byte, wie kommst du auf 11?

Ja ist mir durchaus bewusst, aber die Anwendung für den Tiny ist auch 
nicht sehr Ressourcen-hungrig. Wird ein einfacher PWM-Dimmer.
Sicher kann man sowas auch ohne wilde Strukturen programmieren, mir ging 
es aber auch darum mal einen "Grundstock" für zukünftige Programme (die 
natürlich nicht auf dem Tiny laufen) zu legen. Dann bietet sich eine 
Modularisierung schon an.

@ Jörg: Danke für den Hinweis, hab ich im Eifer des Gefechts total 
übersehen.

von Ingo (Gast)


Lesenswert?

Wozu blähst du einen AVR so auf? Halte für ich etwas overkilled...


Ingo

von Jan (Gast)


Lesenswert?

Wie gesagt, meine Idee war die Software in verschiedene Schichten zu 
gliedern (GPIO gehört beispielsweise zur Hardware-Kapselung und muss 
controllerspezifisch angepasst werden). Das erleichtert die Portierung 
des Codes auf andere Zielsysteme.
Sicher wirkt das nun auf den ersten Blick total aufgebläht, aber ich 
verspreche mir davon Zeitersparnis bei größeren Projekten, da bei allen 
Schichten oberhalb der Hardware-Kapselung alle Schnittstellen gleich 
bleiben können.

von Wolfgang (Gast)


Lesenswert?

Jan schrieb:
> Wie gesagt, meine Idee war die Software in verschiedene Schichten zu
> gliedern (GPIO gehört beispielsweise zur Hardware-Kapselung und muss
> controllerspezifisch angepasst werden). Das erleichtert die Portierung
> des Codes auf andere Zielsysteme.

Das kannst du auf deinem Entwicklungsrechner so betrachten.

> Sicher wirkt das nun auf den ersten Blick total aufgebläht, aber ich
> verspreche mir davon Zeitersparnis bei größeren Projekten, da bei allen
> Schichten oberhalb der Hardware-Kapselung alle Schnittstellen gleich
> bleiben können.

Stimmt, auf einem kleinen µC kannst du das getrost als Hintergrundwissen 
betrachten, wenn deine Entwicklungsumgebung das nicht selbständig für 
das Zielsystem ausreichend optimiert.
Auf Controllern, bei denen man schon mit Funktionsaufrufen sparen muß, 
damit der Stack nicht überläuft, gelten andere Gesetze.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Um gut lesbaren und portablen Code zu erzeugen, ist es einfacher, die 
Pins als Bitvariablen zu definieren:
1
#include "sbit.h"
2
3
#define LED0            PORT_B0
4
#define LED0_oe         DDR_B0          // output enable
5
#define LED1            PORT_B2
6
#define LED1_oe         DDR_B2          // output enable
7
#define KEY0_in         PIN_B1
8
#define KEY0_pu         PORT_B1         // Pulllup on
9
#define KEY1_in         PIN_B4
10
#define KEY1_pu         PORT_B4         // Pulllup on
11
12
13
int main()
14
{
15
  LED0_oe = 1;          // output
16
  LED1_oe = 1;          // output
17
  KEY0_pu = 1;          // pullup on
18
  KEY1_pu = 1;          // pullup on
19
20
  for(;;){
21
    if( KEY0_in == 0 && KEY1_in == 1 ){
22
      LED0 = 1;
23
      LED1 = 0;
24
    }
25
    if( KEY1_in == 0 && KEY0_in == 1 ){
26
      LED0 = 0;
27
      LED1 = 1;
28
    }
29
  }
30
}

Das ist nicht nur gut lesbar, sondern erzeugt auch sehr effizienten Code 
(siehe lst-File).

von Fabian O. (xfr)


Lesenswert?

Oder man schreibt sich ein paar Headerdateien mit Inline-Funktionen. Die 
kann man einfach austauschen, wenn man das Programm auf eine andere 
Plattform portieren möchte. Wenn sich nur die Ports/Pins ändern, reicht 
es, die config-Datei zu ändern.

leds_config.h
1
#define LEDS_OUT PORTB
2
#define LEDS_DIR DDRB
3
4
#define LED_0 _BV(PINB0)
5
#define LED_1 _BV(PINB2)
6
#define LEDS_ALL LED_0 LED_1

keys_config.h
1
#define KEYS_IN     PINB
2
#define KEYS_PULLUP PORTB
3
4
#define KEY_0 _BV(PINB1)
5
#define KEY_1 _BV(PINB4)
6
#define KEYS_ALL KEY_0 KEY_1

keys.h
1
#include <avr/io.h>
2
#include "keys_config.h"
3
4
static inline void keys_init(void) {
5
  KEYS_PULLUP |= KEYS_ALL;
6
}
7
8
static inline bool keys_pressed(uint8_t key) {
9
  return KEYS_IN & key;
10
}

leds.h
1
#include <avr/io.h>
2
#inlude "leds_config.h"
3
4
static inline void leds_init(void) {
5
  LEDS_DIR |= LEDS_ALL;
6
}
7
8
static inline void leds_on(uint8_t led) {
9
  LED_OUT |= led;
10
}
11
12
static inline void leds_off(uint8_t led) {
13
  LED_OUT &= ~led;
14
}

main.c
1
#include "keys.h"
2
#include "leds.h"
3
4
int main()
5
{
6
  leds_init();
7
  keys_init();
8
9
  while(1) {
10
    if (key_pressed(KEY_0) && !key_pressed(KEY_1)) {
11
      leds_on(LED_0);
12
      leds_off(LED_1);
13
    }
14
    if (key_pressed(KEY_1) && !key_pressed(KEY_0)) {
15
      leds_on(LED_1);
16
      leds_off(LED_0);  
17
    }
18
  }
19
}

von Peter D. (peda)


Lesenswert?

@Fabian O.

das geht nur solange gut, solange Du alles an einem Port hast 
(ATtiny13). Bei größeren MCs mit mehreren Ports hast Du trauer.

von Fabian O. (xfr)


Lesenswert?

Peter Dannegger schrieb:
> Bei größeren MCs mit mehreren Ports hast Du trauer.

Dafür hast Du Trauer, wenn Du die GPIOs mit einem Funktionsaufruf statt 
per Speicherzugriff steuern musst ... ;)

Aber Du hast schon recht, wenn man mehrere Ports mit der gleichen 
Inline-Funktion bedienen will/muss, ist es nicht mehr so hübsch. Aber 
auch nicht unmöglich. Ich hatte genau diese Situation in einem Projekt, 
bei dem eine Status-LED in einer neuen Hardwareversion an einen anderen 
Port gewandert ist.

Die Datei sah dann so aus:
1
#define LEDS_SHIFT_A 2
2
#define LEDS_SHIFT_R 0
3
4
#define LED_GREEN  (PIN0_bm << LEDS_SHIFT_A) // PA0
5
#define LED_YELLOW (PIN0_bm << LEDS_SHIFT_R) // PR0
6
#define LED_RED    (PIN1_bm << LEDS_SHIFT_R) // PR1
7
8
#define LEDS_ALL_A  LED_GREEN
9
#define LEDS_ALL_R (LED_YELLOW | LED_RED)
10
#define LEDS_ALL   (LED_GREEN | LED_YELLOW | LED_RED)
11
12
static inline void leds_init(void)
13
{
14
  PORTA.DIRSET = LEDS_ALL_A >> LEDS_SHIFT_A;
15
  PORTA.OUTCLR = LEDS_ALL_A >> LEDS_SHIFT_A;
16
  PORTR.DIRSET = LEDS_ALL_R >> LEDS_SHIFT_R;
17
  PORTR.OUTCLR = LEDS_ALL_R >> LEDS_SHIFT_R;
18
}
19
20
static inline void leds_on(uint8_t mask)
21
{
22
  uint8_t bits;
23
  bits = (mask & LEDS_ALL_A) >> LEDS_SHIFT_A;
24
  if (bits) {
25
    PORTA.OUTSET = bits;
26
  }
27
  bits = (mask & LEDS_ALL_R) >> LEDS_SHIFT_R;
28
  if (bits) {
29
    PORTR.OUTSET = bits;
30
  }
31
}
32
33
static inline void leds_off(uint8_t mask)
34
{
35
  uint8_t bits;
36
  bits = (mask & LEDS_ALL_A) >> LEDS_SHIFT_A;
37
  if (bits) {
38
    PORTA.OUTCLR = bits;
39
  }
40
  bits = (mask & LEDS_ALL_R) >> LEDS_SHIFT_R;
41
  if (bits) {
42
    PORTR.OUTCLR = bits;
43
  }
44
}
45
46
static inline void leds_toggle(uint8_t mask)
47
{
48
  uint8_t bits;
49
  bits = (mask & LEDS_ALL_A) >> LEDS_SHIFT_A;
50
  if (bits) {
51
    PORTA.OUTTGL = bits;
52
  }
53
  bits = (mask & LEDS_ALL_R) >> LEDS_SHIFT_R;
54
  if (bits) {
55
    PORTR.OUTTGL = bits;
56
  }
57
}

Dank Inlining und Constant Folding bleibt von den Funktionen bei Aufruf 
mit konstantem Parameter nur noch das reine Ändern des Ports übrig. 
Weiterer Vorteil: Man kann ohne Overhead mehrere LEDs gleichzeitig 
ändern.

von Stefan (Gast)


Lesenswert?

Kennst du diese Makros?:
1
// Macro used to write to a single I/O pin
2
#define writeBit(port,bit,value) { if ((value)>0) (port) |= (1<<bit); else (port) &= ~(1<<bit); } 
3
4
// Macro used to read from a single I/O pin
5
#define readBit(port,bit) (((port) >> (bit)) & 1)
6
7
// how to use with pin PB3
8
writeBit(PORTB,3,0);
9
writeBit(PORTB,3,1);
10
11
uint8_t a=readBit(PORTB,3);
12
13
if (readBit(PORTB,3)) {
14
  ...
15
}

Die sind zwar nicht ganz so portabel, wie deine Strukturen, haben dafür 
aber den Vorteil, dass sie kein RAM und keinen zusätzlichen Code 
erzeugen (da der Compiler das sehr gut optimiert).

von Stefan (Gast)


Lesenswert?

Sorry, es muss natürlich readBit(PINB,3) heissen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.