Fast digital I/O, software I2C, and software SPI libraries

I have posted updated fast digital I/O, software I2C, and software SPI libraries as DigitalIO20130221.zip Google Code Archive - Long-term storage for Google Code Project Hosting..

The DigitalPin.h file contains the fastest pin I/O class and functions. These functions require a constant pin number.

These function execute in two cycle with constant arguments. On an Uno, two cycles is 0.125 usec. This is about 25 times faster than the standard Arduino digitalWrite().

The PinIO class has run time pin numbers and is four to five times faster than the standard digitalRead() and digitalWrite() functions. For pin 13 on an Uno, write() executes in about 0.8 usec and toggle() is faster. The standard Arduino digitalWrite() function takes about 4 usec.

This class saves overhead by not disabling PWM mode on each read/write call and by storing the port register addresses and pin bit mask in private variables.

The template class SoftSPI uses fast pin I/O to implement a software SPI bus at about 2 MHz.

The two classes FastI2cMaster and SoftI2cMaster implement software master mode I2C.

FastI2cMaster is a template class and runs at about 400 kHz.

SoftI2cMaster uses run time pin numbers and runs at about 100 kHz.

Here is an example using the highest speed digital pin class.

// Read pin 12 and write value to pin 13.
#include <DigitalIO.h>

DigitalPin<12> pin12(INPUT);
DigitalPin<13> pin13(OUTPUT);

void setup() {}

void loop() {
  pin13 = pin12;
}

Thanks for sharing!

FYI (pin1 and pin2 connected together, @16MHz):

#include <DigitalIO.h>
DigitalPin<1> pin1(INPUT);
DigitalPin<2> pin2(OUTPUT);
void setup() {}
void loop() {
  pin2 = !pin1;
}
#include <DigitalIO.h>
DigitalPin<1> pin1(INPUT);
DigitalPin<2> pin2(OUTPUT);
void setup() {}
void loop() {
  pin2 = 1;
  pin2 = 0;
  pin2 = 1;
  pin2 = 0;
}

You only get 2 cycle write with a constant value.

I don't know what this test means:

#include <DigitalIO.h>
DigitalPin<1> pin1(INPUT);
DigitalPin<2> pin2(OUTPUT);
void setup() {}
void loop() {
  pin2 = !pin1;
}

Try this:

#include <DigitalIO.h>
DigitalPin<1> pin1(INPUT);
DigitalPin<2> pin2(OUTPUT);
void setup() {}
void loop() {
  pin2 = !pin1;
  pin2 = !pin1;
}

I get a pulse that is about 600 ns wide, maybe 10 cycles. About what I would expect from a read, complement the result and a write with a variable argument.

A write with a variable argument compiles as a test to select bit set or bit clear and then jumps to executes the 2 cycle bit operation.

The code for this

  pin13 = !pin12;

is:

  0:   83 b1           in      r24, 0x03       ; 3
  2:   82 95           swap    r24
  4:   8f 70           andi    r24, 0x0F       ; 15
  6:   80 fd           sbrc    r24, 0
  8:   00 c0           rjmp    .+0             ; 0xa <_Z4testv+0xa>
  a:   2d 9a           sbi     0x05, 5 ; 5
  c:   00 c0           rjmp    .+0             ; 0xe <_Z4testv+0xe>
  e:   2d 98           cbi     0x05, 5 ; 5

This compiles better, about a 7 cycle wide pulse.

#include <DigitalPin.h>
DigitalPin<12> pin12(0);
DigitalPin<13> pin13(1);
void test() {
  pin13 = pin12 ? 0 : 1;
  pin13 = pin12 ? 0 : 1;
}
#include <DigitalIO.h>
DigitalPin<1> pin1(INPUT);
DigitalPin<2> pin2(OUTPUT);
void setup() {}
void loop() {
  pin2 = !pin1;
  pin2 = !pin1;
}

#include <DigitalIO.h>
DigitalPin<1> pin1(INPUT);
DigitalPin<2> pin2(OUTPUT);
void setup() {}
void loop() {
  pin2 = pin1 ? 0 : 1;
  pin2 = pin1 ? 0 : 1;
}

avr-gcc 4.7.0 compiles this with a 437.5 ns pulse.

// Read pin 12 and write value to pin 13.
#include <DigitalIO.h>
DigitalPin<12> pin12(INPUT);
DigitalPin<13> pin13(OUTPUT);

void setup() {
}
void loop() {
  pin13 = !pin12;
  pin13 = !pin12;    
}

Well it's a lot better than digitalWrite(13, !digitalRead(12)) which takes about 7,500 ns.

The above measurement is 440ns with 5ns resolution, so it is 437.5ns in reality..

testSoftSPI:

void loop() {
 spi.send(0X55);
 delay(10);
}

DigitalPinShiftOut

@fat16lib: would it be possible to enhance the drivers for atmega32 (the same pinout as the 1284p mighty, see below)? Thnx.

// MIGHTY ATMEL ATMEGA32
//                       +---\/---+
//           (D 0) PB0  1|        |40  PA0 (AI 0 / D24)
//           (D 1) PB1  2|        |39  PA1 (AI 1 / D25)
//      INT2 (D 2) PB2  3|        |38  PA2 (AI 2 / D26)
//       PWM (D 3) PB3  4|        |37  PA3 (AI 3 / D27)
//        SS (D 4) PB4  5|        |36  PA4 (AI 4 / D28)
//      MOSI (D 5) PB5  6|        |35  PA5 (AI 5 / D29)
//      MISO (D 6) PB6  7|        |34  PA6 (AI 6 / D30)
//       SCK (D 7) PB7  8|        |33  PA7 (AI 7 / D31)
//                 RST  9|        |32  AREF
//                 VCC 10|        |31  GND 
//                 GND 11|        |30  AVCC
//               XTAL2 12|        |29  PC7 (D 23)
//               XTAL1 13|        |28  PC6 (D 22)
//      RxD (D 8)  PD0 14|        |27  PC5 (D 21) TDI
//      TxD (D 9)  PD1 15|        |26  PC4 (D 20) TDO
//     INT0 (D 10) PD2 16|        |25  PC3 (D 19) TMS
//     INT1 (D 11) PD3 17|        |24  PC2 (D 18) TCK
//      PWM (D 12) PD4 18|        |23  PC1 (D 17) SDA
//      PWM (D 13) PD5 19|        |22  PC0 (D 16) SCL
//          (D 14) PD6 20|        |21  PD7 (D 15) PWM
//                       +--------+
//

The pin map for 1284P that is in DigitalIO appears to be different than the map for 1284P mighty. It dates back many years to the 644 Sanguino. The problem is at D24 - D31.

I think I will change it to match 1284P mighty and add atmega32.

Try the following at about line 147 of DigitalPin.h and let me know if it works.

#elif defined(__AVR_ATmega644P__)\
|| defined(__AVR_ATmega644__)\
|| defined(__AVR_ATmega1284P__)\
|| defined(__AVR_ATmega32__)
// Mighty 1284P
static const pin_map_t pinMap[] = {
  {&DDRB, &PINB, &PORTB, 0},  // B0  0
  {&DDRB, &PINB, &PORTB, 1},  // B1  1
  {&DDRB, &PINB, &PORTB, 2},  // B2  2
  {&DDRB, &PINB, &PORTB, 3},  // B3  3
  {&DDRB, &PINB, &PORTB, 4},  // B4  4
  {&DDRB, &PINB, &PORTB, 5},  // B5  5
  {&DDRB, &PINB, &PORTB, 6},  // B6  6
  {&DDRB, &PINB, &PORTB, 7},  // B7  7
  {&DDRD, &PIND, &PORTD, 0},  // D0  8
  {&DDRD, &PIND, &PORTD, 1},  // D1  9
  {&DDRD, &PIND, &PORTD, 2},  // D2 10
  {&DDRD, &PIND, &PORTD, 3},  // D3 11
  {&DDRD, &PIND, &PORTD, 4},  // D4 12
  {&DDRD, &PIND, &PORTD, 5},  // D5 13
  {&DDRD, &PIND, &PORTD, 6},  // D6 14
  {&DDRD, &PIND, &PORTD, 7},  // D7 15
  {&DDRC, &PINC, &PORTC, 0},  // C0 16
  {&DDRC, &PINC, &PORTC, 1},  // C1 17
  {&DDRC, &PINC, &PORTC, 2},  // C2 18
  {&DDRC, &PINC, &PORTC, 3},  // C3 19
  {&DDRC, &PINC, &PORTC, 4},  // C4 20
  {&DDRC, &PINC, &PORTC, 5},  // C5 21
  {&DDRC, &PINC, &PORTC, 6},  // C6 22
  {&DDRC, &PINC, &PORTC, 7},  // C7 23
  {&DDRA, &PINA, &PORTA, 0},  // A0 24
  {&DDRA, &PINA, &PORTA, 1},  // A1 25
  {&DDRA, &PINA, &PORTA, 2},  // A2 26
  {&DDRA, &PINA, &PORTA, 3},  // A3 27
  {&DDRA, &PINA, &PORTA, 4},  // A4 28
  {&DDRA, &PINA, &PORTA, 5},  // A5 29
  {&DDRA, &PINA, &PORTA, 6},  // A6 30
  {&DDRA, &PINA, &PORTA, 7}   // A7 31
};

Edit: Any other 40 pin parts for this map?

Let us call it "mighty32" (instead of atmega32, to indicate the pin layout). I would vote for mighty layout (there are sanguino, bobulino and avr-developers layouts as well). Most probably we will have (all DIP40 w/ the same pin layout):

mighty16
mighty32
mighty324
mighty64
mighty644
mighty1284

Tried with mighty32 and ScanI2cBus demo

// Set pin numbers for your configuration.
const uint8_t SDA_PIN = 17;
const uint8_t SCL_PIN = 16;

Device at address: 0XA2 responds to Read and Write.
Device at address: 0XEE responds to Read and Write.
Done

So it seems it works.
PS: Mighty defines D24-D31 for "analog" pins as well, how this is covered??

I am not sure what you are suggesting.

Should I have more than one pin layout for 40-pin DIP processors?

Sangino defines the analog pins and digital pins 24 - 31 in a different order than mighty.

If so how do I know which layout to use?

With ATmega32U4 I have Leonardo and Teensy and the CORE_TEENSY symbol lets me choose the pin layout.

Edit: I looked at the three variants and don't see a #define that helps for Standard, SANGUINO, Bobuino.

I am suggesting the mighty layout (you already did above) - for all DIP40 chips: 16/32/324/64/644/1284.
The 32U4 is not available in DIL40, afaik..

#elif defined(__AVR_ATmega1284P__)\
|| defined(__AVR_ATmega1284__)\
|| defined(__AVR_ATmega644P__)\
|| defined(__AVR_ATmega644__)\
|| defined(__AVR_ATmega64__)\
|| defined(__AVR_ATmega32__)\
|| defined(__AVR_ATmega324__)\
|| defined(__AVR_ATmega16__)
// Mighty Layout
..

It seems the toggling ie. pin31 does not work (nor 14,23,24..). This works:

#include <DigitalIO.h>
// Create object for pin XX in output mode and demo toggle().
DigitalPin<31> pin31(OUTPUT);
void setup() {}
void loop() {
  // toggle is a two byte instruction that executes
  // in two cycles or 125 ns on a 16 MHz CPU
  //pin31.toggle(); // <<does not work
  //delay(250);

  pin31 = 0;   // <<<this blinks
  delay(250);
  pin31 = 1;
  delay(250);
}

The same with fastDigitalToggle(PIN)..

The 32U4 is not available in DIL40, afaik..

I am not worried about 40 pin 32U4.

If I choose the "standard layout for these:

#elif defined(__AVR_ATmega1284P__)\
|| defined(__AVR_ATmega1284__)\
|| defined(__AVR_ATmega644P__)\
|| defined(__AVR_ATmega644__)\
|| defined(__AVR_ATmega64__)\
|| defined(__AVR_ATmega32__)\
|| defined(__AVR_ATmega324__)\
|| defined(__AVR_ATmega16__)

What about SANGUINO and Bobuino boards?

I will look at the data sheet for toggle. It may not work on this family. It could also be a compiler problem.

The compiler must compile this way:

    // will compile to sbi and PIN register will not be read.
      *pinMap[pin].pin |= 1 << pinMap[pin].bit;

What about SANGUINO and Bobuino boards?

The pin layout and core is defined in /variants/blabla and /cores/blabla (where the board.txt points to the actual core and variant) ..

So, the best solution would be, if all libs creators will use the settings coming from those folders, somehow.. :disappointed_relieved:

fat16lib:
I am not sure what you are suggesting.

Should I have more than one pin layout for 40-pin DIP processors?

Sangino defines the analog pins and digital pins 24 - 31 in a different order than mighty.

If so how do I know which layout to use?

With ATmega32U4 I have Leonardo and Teensy and the CORE_TEENSY symbol lets me choose the pin layout.

Edit: I looked at the three variants and don't see a #define that helps for Standard, SANGUINO, Bobuino.

Well that's why the user's hardware variants allow for unique pin mappings for different board pin out assignments even if the same avr chip is being used. For example the mighty 1284P and the Bobuino 1284P define different pins_arduino.h files to be used when their board type is selected even though they use the same avr chip. Can't your IO additions utilize the standard pin mapping that automatically gets selected when the board type is chosen?

Lefty