Author Topic: Super Sweet Bit Banging Macros  (Read 1182 times)

ChemE

  • Sr. Member
  • ****
  • Posts: 419
  • Country: us
Super Sweet Bit Banging Macros
« on: August 03, 2017, 02:59:18 AM »
I've been meaning to do this for a long long time and finally got around to it tonight.  As some of you know, I'm quite fond of low-level bit banging commands using macros like this:

Code: [Select]
DDBD = B01101010;

This produces very small and fast code but it can be difficult to modify later.  However, pinMode() and digitalWrite() are too bloated and slow for me to even contemplate using so they are not an option either for anything except the most quick and dirty test code.  But I like their format since it is easy to see what is going on and I find digital pin numbers easier to work than PB5 for instance.  Well feast your eyes on these little beauties!  They are as easy to look at as the Arduino functions they replace but they only add 2 bytes of program size and they each execute in a single clock cycle since all this looking up is done at compile time rather than run time.  The only efficiency I give up is you can't change more than one pin per clock cycle.  Hope this helps others, I plan to fold this back into most of my code.  If anyone spots a way to improve this please let me know.

Code: [Select]
// Macros to define the ports and bitmasks for the pins on the 328p succinctly
#if defined(__AVR_ATmega328P__)
  #define     DirRegFromPin(pin)      pin<8 ? 0x0A : pin<14 ? 0x04 : 0x07 // Register which controls input or output
  #define     DataRegFromPin(pin)     pin<8 ? 0x0B : pin<14 ? 0x05 : 0x08 // Register which controls high or low
  #define     BitmaskFromPin(pin)     pin<8 ? 1<<pin : pin<14 ? 1<<(pin-8) : 1<<(pin-14)
#endif

// Bit-banging macros - each adds 2 bytes of sketch size and takes 1 clock cycle to execute
#define     MakeOutput(pin)         _SFR_IO8(DirRegFromPin(pin))  |=  BitmaskFromPin(pin)
#define     MakeInput(pin)          _SFR_IO8(DirRegFromPin(pin))  &=  ~(BitmaskFromPin(pin))
#define     PullHigh(pin)           _SFR_IO8(DataRegFromPin(pin)) |=  BitmaskFromPin(pin)
#define     PullLow(pin)            _SFR_IO8(DataRegFromPin(pin)) &=  ~(BitmaskFromPin(pin))

Just in case it isn't obvious, they are used like so:

Code: [Select]
MakeOutput(9);
PullHigh(9);    // LED is on now
PullLow(9);    // LED is off now
MakeInput(9);

or even better like so:

Code: [Select]
#define LED 9

MakeOutput(LED);
PullHigh(LED);    // LED is on now
PullLow(LED);    // LED is off now
MakeInput(LED);
« Last Edit: August 03, 2017, 03:05:10 AM by ChemE »

Lukapple

  • Full Member
  • ***
  • Posts: 202
Re: Super Sweet Bit Banging Macros
« Reply #1 on: August 03, 2017, 08:23:43 AM »
Great, thanks for sharing those beautiful macros with us! I'll definitely use them  ;D

ChemE

  • Sr. Member
  • ****
  • Posts: 419
  • Country: us
Re: Super Sweet Bit Banging Macros
« Reply #2 on: August 25, 2017, 08:00:13 AM »
I added a read as well.  I'm not able to edit the initial post anymore so here is the latest:

Code: [Select]
#if defined(__AVR_ATmega328P__) /* Macros to define the ports and bitmasks for the pins on the 328p succinctly */
  #define     InpRegFromPin(pin)      pin<8 ? 0x09 : pin<14 ? 0x03 : 0x06 /* Input registers for the ports */
  #define     DirRegFromPin(pin)      pin<8 ? 0x0A : pin<14 ? 0x04 : 0x07 /* Data direction registers for the ports */
  #define     DataRegFromPin(pin)     pin<8 ? 0x0B : pin<14 ? 0x05 : 0x08 /* Output registers for the ports */
  #define     BitmaskFromPin(pin)     pin<8 ? 1<<pin : pin<14 ? 1<<(pin-8) : 1<<(pin-14)
#endif

/* Bit-banging macros - each adds 2 bytes of sketch size and takes 1 clock cycle to execute */
#define    MakeOutput(pin)    _SFR_IO8(DirRegFromPin(pin))  |=  BitmaskFromPin(pin)           /* Much faster and smaller version of pinMode(Pin, OUTPUT) */
#define    MakeInput(pin)     _SFR_IO8(DirRegFromPin(pin))  &=  ~(BitmaskFromPin(pin))        /* Much faster and smaller version of pinMode(Pin, INPUT) */
#define    PullHigh(pin)      _SFR_IO8(DataRegFromPin(pin)) |=  BitmaskFromPin(pin)           /* Much faster and smaller version of digitalWrite(Pin, HIGH) */
#define    PullLow(pin)       _SFR_IO8(DataRegFromPin(pin)) &=  ~(BitmaskFromPin(pin))        /* Much faster and smaller version of digitalWrite(Pin, LOW) */
#define    ReadPin(pin)       _SFR_IO8(InpRegFromPin(pin))  & (BitmaskFromPin(pin)) ? 1 : 0   /* One line if else statement using the format [test ? true return : false return] */

Just to drive the point of smaller sketch sizes home here is a comparison of blinks.

Arduino 1.8.3 Blink - 928 bytes of hex and 9 bytes of SRAM
Code: [Select]
/* the setup function runs once when you press reset or power the board */
void setup() {
  /* initialize digital pin LED_BUILTIN as an output. */
  pinMode(LED_BUILTIN, OUTPUT);
}

/* the loop function runs over and over again forever */
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   /* turn the LED on (HIGH is the voltage level) */
  delay(1000);                       /* wait for a second */
  digitalWrite(LED_BUILTIN, LOW);    /* turn the LED off by making the voltage LOW */
  delay(1000);                       /* wait for a second */
}

My Blink - 176 bytes of hex and 0 bytes of SRAM
Code: [Select]
int main(void) {  /* =========== the setup function runs once when you press reset or power the board =========== */
  MakeOutput(9);

  for(;;) {   /* =========== the loop function runs over and over again forever =========== */
    PullHigh(9);
    _delay_ms(1000);
    PullLow(9);
    _delay_ms(1000);
  }           /* =========== the loop function runs over and over again forever =========== */
} /* End main */
« Last Edit: August 25, 2017, 09:18:29 AM by ChemE »