#ifndef __AVRPORT_H__
#define __AVRPORT_H__

#include <avr/io.h>
#include <stdint.h>

/****
 Use like:

1, define your output pins, some alternatives:

   #define C_DDR    0x05  // we want port pin 0 and 2 to be outputs, the others inputs

   #define C_DDR    (_BV(0) | _BV(2))

   #define BUZZER_bit (_BV(0))
   #define LOCK_bit (_BV(2)) // electric strike
   #define C_DDR  (BUZZER_bit | LOCK_bit)

2, define your inputs, which should have pullups
   unused pins should be set as inputs and have pullup to minimize power

   #define DOORTEST_bit (_BV(1))  // 0 when door is closed
   #define DOORBUTTON_bit (_BV(3))  // 0 when button is pressed
   #define C_PULLUP (~C_DDR) // set all inputs to have pullups

3, define pin tests and sets

 . to set an output pin to high, use

   #define BUZZER_ON  (c.write |= BUZZER_bit) // port_run() will then set the pin output
   #define LOCK_OPEN  (c.write |= LOCK_bit)

 . to set an output pin to low, just leave it as is, all outputs are set low in port_run() per default

 . to check if an input pin is high, use one of theese

   #define IS_DOOR_OPEN (c.read & DOORTEST_bit)
   #define IS_DOOR_OPEN PORT_IH(c, DOORTEST_BIT)

 . to check if an input pin is low, use one of theese

   #define IS_BUTTON_PRESSED (!(c.read & DOORBUTTON_bit))
   #define IS_BUTTON_PRESSED PORT_IL(c, DOORBUTTON_bit)

4, init the pins

   void init(void) {
     PORT_INIT( C, c, C_DDR, C_PULLUP);
     PORT_INIT( C, c, 0x05, ~0x05); // the same
     // the same:
     DDRC = 0x05;
     c.write = c.pullup = ~0x05;
     ...
   }

5, example main()

   void main(void) {
     init();
     while(
       // no "blocking" statements, this is a state machine and
       // port_run() is our event poller and output setter
       port_run(); // here all outputs are set to 0
       if (IS_BUTTON_PRESSED) LOCK_OPEN;
       if (IS_DOOR_OPEN)      BUZZER_ON;
     }
   }

 ****/

struct port_t {
  // we want the reads to be att a single point in time (as much is possible)
  // using PINB instead of b.read, could possible give us different values when
  // checking for a bit
  uint8_t read;

  // dito for outputs, different parts of a main loop can frealy set different
  // bits scheduled for output in one go
  uint8_t write;

  // we don't do test and set, we start from a clean slate every time
  // we save the pullup setting, so it can be reused in every loop run
  // i.e. the pullup value is what we init .write with at start of the loop
  uint8_t pullup;
};

#define PORT_INIT(X,x, x_ddr, x_pullup) (DDR ## X = x_ddr, x.write = x.pullup = x_pullup)

#define PORT_IH(x, bit)   (x.read & bit)
#define PORT_IL(x, bit) (!(x.read & bit))
#define PORT_O( x, bit) (x.write |= bit)

void port_run(void);

#ifdef PORTA
extern struct port_t a;
#endif
#ifdef PORTB
extern struct port_t b;
#endif
#ifdef PORTC
extern struct port_t c;
#endif
#ifdef PORTD
extern struct port_t d;
#endif
#ifdef PORTE
extern struct port_t e;
#endif
#ifdef PORTF
extern struct port_t f;
#endif
#ifdef PORTG
extern struct port_t g;
#endif
#ifdef PORTH
extern struct port_t h;
#endif
#ifdef PORTI
extern struct port_t i;
#endif
#ifdef PORTJ
extern struct port_t j;
#endif
#ifdef PORTK
extern struct port_t k;
#endif
#ifdef PORTL
extern struct port_t l;
#endif

#endif

