diff --git a/sd_tests/hardware_spi/hardware_spi.ino b/sd_tests/hardware_spi/hardware_spi.ino
new file mode 100644
index 0000000000000000000000000000000000000000..5d0bde4c84b55cfa1a5e53866288433e59f10b45
--- /dev/null
+++ b/sd_tests/hardware_spi/hardware_spi.ino
@@ -0,0 +1,598 @@
+//
+//
+// hello.uSD.44.read.c
+//
+// 19200 baud microSD hello-world
+//    assumes FAT32 SDHC/XC
+//
+// Neil Gershenfeld 11/26/18
+//
+// (c) Massachusetts Institute of Technology 2018
+// This work may be reproduced, modified, distributed,
+// performed, and displayed for any purpose. Copyright is
+// retained and must be preserved. The work is provided
+// as is; no warranty is provided, and users accept all
+// liability.
+//
+
+#include <stdint.h>
+#include <string.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+
+//#include "tinySPI.h"        // https://github.com/JChristensen/tinySPI
+
+#define output(directions,pin) (directions |= pin) // set port direction for output
+#define input(directions,pin) (directions &= (~pin)) // set port direction for input
+#define set(port,pin) (port |= pin) // set port pin
+#define clear(port,pin) (port &= (~pin)) // clear port pin
+#define pin_test(pins,pin) (pins & pin) // test for port pin
+#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
+#define bit_delay_time 49.5 // bit delay for 19200 with overhead
+//#define bit_delay_time 8.5 // bit delay for 115200 with overhead
+#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
+#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
+#define char_delay() _delay_ms(10) // char delay
+#define SPI_delay() _delay_us(2.5) // SPI delay
+
+#define serial_port PORTA
+#define serial_direction DDRA
+#define serial_pins PINA
+#define serial_pin_in (1 << PA0)
+#define serial_pin_out (1 << PA1)
+
+#define CS_port PORTA
+#define CS_direction DDRA
+#define CS_pin (1 << PA3)
+#define SCK_port PORTA
+#define SCK_direction DDRA
+#define SCK_pin (1 << PA4)
+#define MOSI_port PORTA
+#define MOSI_direction DDRA
+#define MOSI_pin (1 << PA6)
+#define MISO_port PORTA
+#define MISO_direction DDRA
+#define MISO_pin (1 << PA5)
+#define MISO_pins PINA
+
+#define DETECT_port PORTA
+#define DETECT_direction DDRA
+#define DETECT_pin (1 << PA7)
+
+#define led_port PORTB
+#define led_direction DDRB
+#define led_pin (1 << PB2)
+
+//
+// put_char
+//   send character in txchar
+//    assumes line driver (inverts bits)
+//    assume use of serial_port, serial_pin_out
+//
+void put_char(char txchar) {
+   //
+   // start bit
+   //
+   clear(serial_port,serial_pin_out);
+   bit_delay();
+   //
+   // unrolled loop to write data bits
+   //
+   if bit_test(txchar,0)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,1)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,2)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,3)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,4)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,5)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,6)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,7)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   //
+   // stop bit
+   //
+   set(serial_port,serial_pin_out);
+   bit_delay();
+   //
+   // char delay
+   //
+   bit_delay();
+   }
+
+//
+// put_string
+//    print a null-terminated string
+//    assumes use of serial_port, serial_pin_out
+//
+void put_string(char *str) {
+   static int index;
+   index = 0;
+   do {
+      put_char(str[index]);
+      ++index;
+      } while (str[index] != 0);
+   }
+
+//
+// put_flash_string
+//    print a null-terminated string from flash
+//    assumes use of serial_port, serial_pin_out
+//
+void put_flash_string(const char *string) {
+   static int index;
+   index = 0;
+   do {
+      put_char(pgm_read_byte(string+index));
+      ++index;
+      } while (pgm_read_byte(string+index) != 0);
+   }
+
+//
+// put_char_string
+//    print a string
+//    assumes use of serial_port, serial_pin_out
+//
+void put_char_string(uint8_t *string, uint16_t length) {
+   static int index;
+   for (index = 0; index < length; ++index) {
+      put_char(string[index]);
+      }
+   }
+
+//
+// put_hex_char
+//    put a char in hex
+void put_hex_char(uint8_t chr) {
+   static char hex[] = "0123456789ABCDEF";
+      put_char(hex[(chr >> 4) & 0x0F]);
+      put_char(hex[chr & 0x0F]);
+   }
+
+//
+// put_hex_string
+//    print a string in hex
+//    assumes use of serial_port, serial_pin_out
+//
+void put_hex_string(uint8_t *string, uint16_t length) {
+   static int index;
+   for (index = 0; index < length; ++index) {
+      put_hex_char(string[index]);
+      }
+   }
+
+//
+// SPI_write
+//    write an SPI character and return the response
+//
+unsigned char SPI_write(uint8_t chr) {
+    USIDR = chr;
+    USISR = _BV(USIOIF);
+    do {
+            USICR = _BV(USIWM0) | _BV(USICS1) |
+                    _BV(USICLK) | _BV(USITC);
+    } while ((USISR & _BV(USIOIF)) == 0);
+    return USIDR;
+}
+
+unsigned char SPI_write_software(uint8_t chr) {
+   static unsigned char bit,ret;
+   ret = 0;
+   //
+   // bit loop
+   //
+   for (bit = 0; bit < 8; ++bit) {
+      clear(SCK_port,SCK_pin);
+      if (chr & (1 << (7-bit)))
+         set(MOSI_port,MOSI_pin);
+      else
+         clear(MOSI_port,MOSI_pin);
+      SPI_delay();
+      set(SCK_port,SCK_pin);
+      SPI_delay();
+      if pin_test(MISO_pins,MISO_pin)
+         ret |= (1 << (7-bit));
+      }
+   //
+   // finish
+   //
+   return ret;
+   }
+
+//
+// SD_command
+//    write an SD command and return the response
+//
+void SD_command(uint8_t command, uint32_t argument, uint8_t CRC, uint8_t *result) {
+   clear(CS_port,CS_pin);
+   SPI_write(command);
+   SPI_write((argument >> 24) & 0xFF);
+   SPI_write((argument >> 16) & 0xFF);
+   SPI_write((argument >> 8) & 0xFF);
+   SPI_write(argument & 0xFF);
+   SPI_write(CRC);
+   result[0] = SPI_write(0xFF);
+   result[1] = SPI_write(0xFF);
+   result[2] = SPI_write(0xFF);
+   result[3] = SPI_write(0xFF);
+   result[4] = SPI_write(0xFF);
+   result[5] = SPI_write(0xFF);
+   result[6] = SPI_write(0xFF);
+   result[7] = SPI_write(0xFF);
+   set(CS_port,CS_pin);
+   }
+
+//
+// SD_read
+//    read size bytes at offset in sector into buffer
+//
+void SD_read(uint32_t sector,uint16_t offset,uint8_t *buffer,uint16_t size) {
+   static uint8_t chr;
+   static uint16_t index,count;
+   //
+   // start read
+   //
+   clear(CS_port,CS_pin);
+   //
+   // send CMD17
+   //
+   SPI_write(0x51);
+   SPI_write((sector >> 24) & 0xFF);
+   SPI_write((sector >> 16) & 0xFF);
+   SPI_write((sector >> 8) & 0xFF);
+   SPI_write(sector & 0xFF);
+   SPI_write(0);
+   //
+   // loop until 0xFE data token
+   //
+   while (1) {
+      chr = SPI_write(0xFF);
+      if (chr == 0xFE)
+         break;
+      }
+   //
+   // read up to offset
+   //
+   for (index = 0; index < offset; ++index) {
+      chr = SPI_write(0xFF);
+      }
+   //
+   // read from offset
+   //
+   count = 0;
+   for (index = offset; index < (offset+size); ++index) {
+      buffer[count] = SPI_write(0xFF);
+      count += 1;
+      }
+   //
+   // read up to sector
+   //
+   for (index = (offset+size); index < 512; ++index) {
+      chr = SPI_write(0xFF);
+      }
+   //
+   // read checksum
+   //
+   chr = SPI_write(0xFF);
+   chr = SPI_write(0xFF);
+   //
+   // finish read
+   //
+   chr = SPI_write(0xFF);
+   set(CS_port,CS_pin);
+}
+
+void setup() {
+   //
+   // main
+   //
+
+   //
+   // set clock divider to /1
+   //
+   CLKPR = (1 << CLKPCE);
+   CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
+
+   // Configure DETECT_pin as an input and led_pin as an output
+   DETECT_direction &= ~DETECT_pin;
+   led_direction |= led_pin;
+
+   //
+   // initialize output pins
+   //
+   set(serial_port,serial_pin_out);
+   output(serial_direction,serial_pin_out);
+   set(CS_port,CS_pin);
+   output(CS_direction,CS_pin);
+   clear(MOSI_port,MOSI_pin);
+   output(MOSI_direction,MOSI_pin);
+   set(SCK_port,SCK_pin);
+   output(SCK_direction,SCK_pin);
+   set(MISO_port,MISO_pin); //turn on pull-up
+   input(MISO_direction,MISO_pin);
+}
+
+void loop() {
+   static uint8_t count, sectors_per_cluster, FATs, attribute, result[8], buffer[50];
+   static uint16_t bytes_per_sector, reserved_sectors, offset, file_cluster_low, file_cluster_hi,
+      buffer_length, sector_count;
+   static uint32_t partition, FAT_sectors, fat_sector, root_cluster, root_sector, file_length,
+      file_cluster, file_sector, chars_read;
+
+   // Wait for card to be inserted
+   while (PINA & DETECT_pin) {
+       led_port |= led_pin;
+       _delay_ms(200);
+       led_port &= ~led_pin;
+       _delay_ms(200);
+   }
+   led_port |= led_pin;
+
+   //
+   // put card in SPI mode
+   //
+   set(MOSI_port, MOSI_pin);
+   set(CS_port, CS_pin);
+   for (count = 0; count < 80; ++count) {
+      set(SCK_port, SCK_pin);
+      SPI_delay();
+      clear(SCK_port, SCK_pin);
+      SPI_delay();
+   }
+   set(SCK_port, SCK_pin);
+
+   //SPI.begin();
+   //SPI.setDataMode(SPI_MODE1);
+
+   //
+   // CMD0: reset and enter idle state
+   //    should return 0x01
+   //
+   put_flash_string(PSTR("\r\nreset: 0x"));
+   SD_command(0x40, 0, 0x95, result);
+   put_hex_string(result,8);
+
+   //
+   // CMD8: send interface condition, set SDHC
+   //    should return 0x01000001AA
+   //
+   put_flash_string(PSTR("\r\nset interface: 0x"));
+   SD_command(0x48, 0x000001AA, 0x87, result);
+   put_hex_string(result,8);
+
+   //
+   // initialization loop
+   //
+   put_flash_string(PSTR("\r\ninitialization:"));
+   while (1) {
+      //
+      // CMD55: application command follows
+      //    should return 0x01
+      //
+      put_flash_string(PSTR("\r\n   application command: "));
+      SD_command(0x77, 0, 0, result);
+      put_hex_string(result, 8);
+
+      //
+      // ACMD41: initialize the card
+      //    should return 0x00 when ready
+      //
+      put_flash_string(PSTR("\r\n   initialize card: 0x"));
+      SD_command(0x69, 0x40000000, 0, result);
+      put_hex_string(result,8);
+
+      //
+      // check if done
+      //
+      if (result[1] == 0) {
+         put_string("\ncard initialized");
+         break;
+      }
+
+       if (PINA & DETECT_pin) {
+           put_string("\ncard removed");
+           return;
+       }
+   }
+
+   //
+   // read the first partition table
+   //
+   put_flash_string(PSTR("\r\nread first partition table:"));
+   SD_read(0,0x1BE,buffer,32);
+   put_flash_string(PSTR("\r\n   type: 0x")); // 0x0B or 0x0C for FAT32
+   put_hex_string(buffer+4,1);
+   put_flash_string(PSTR("\r\n   first sector: 0x"));
+   put_hex_string(buffer+8,4);
+   memcpy(&partition,buffer+8,4);
+
+   //
+   // read the first partition block
+   //
+   put_flash_string(PSTR("\r\nread first partition block:"));
+   SD_read(partition,0,buffer,50);
+   //
+   memcpy(&bytes_per_sector,buffer+11,2);
+   put_flash_string(PSTR("\r\n   bytes per sector: 0x"));
+   put_hex_char((bytes_per_sector >> 8) & 0xFF);
+   put_hex_char(bytes_per_sector);
+   //
+   memcpy(&sectors_per_cluster,buffer+13,1);
+   put_flash_string(PSTR("\r\n   sectors per cluster: 0x"));
+   put_hex_char(sectors_per_cluster);
+   //
+   memcpy(&reserved_sectors,buffer+14,2);
+   put_flash_string(PSTR("\r\n   reserved sectors: 0x"));
+   put_hex_char((reserved_sectors >> 8) & 0xFF);
+   put_hex_char(reserved_sectors & 0xFF);
+   //
+   fat_sector = partition+reserved_sectors;
+   //
+   memcpy(&FATs,buffer+16,1);
+   put_flash_string(PSTR("\r\n   FATs: 0x"));
+   put_hex_char(FATs & 0xFF);
+   //
+   memcpy(&FAT_sectors,buffer+36,4);
+   put_flash_string(PSTR("\r\n   FAT sectors: 0x"));
+   put_hex_char((FAT_sectors >> 24) & 0xFF);
+   put_hex_char((FAT_sectors >> 16) & 0xFF);
+   put_hex_char((FAT_sectors >> 8) & 0xFF);
+   put_hex_char(FAT_sectors  & 0xFF);
+   //
+   memcpy(&root_cluster,buffer+44,4);
+   put_flash_string(PSTR("\r\n   root cluster: 0x"));
+   put_hex_char((root_cluster >> 24) & 0xFF);
+   put_hex_char((root_cluster >> 16) & 0xFF);
+   put_hex_char((root_cluster >> 8) & 0xFF);
+   put_hex_char(root_cluster & 0xFF);
+
+   //
+   // read the root directory
+   //
+   root_sector = partition+reserved_sectors+(FATs*FAT_sectors);
+   put_flash_string(PSTR("\r\nread root directory:"));
+   offset = 0;
+   while (1) {
+      SD_read(root_sector,offset,buffer,32);
+      memcpy(&attribute,buffer+11,1);
+      put_flash_string(PSTR("\r\n   attribute: 0x"));
+      put_hex_char(attribute);
+      if (attribute == 0xF) {
+         //
+         // long file name
+         //
+         put_flash_string(PSTR("\r\n   long file name: 0x"));
+         put_hex_string(buffer+1,10);
+         put_hex_string(buffer+14,12);
+         put_hex_string(buffer+28,4);
+         put_flash_string(PSTR("\r\n                   "));
+         put_char_string(buffer+1,10);
+         put_char_string(buffer+14,12);
+         put_char_string(buffer+28,4);
+         }
+      else {
+         //
+         // short file name
+         //
+         put_flash_string(PSTR("\r\n   short file name: 0x"));
+         put_hex_string(buffer,11);
+         put_flash_string(PSTR("\r\n                    "));
+         put_char_string(buffer,11);
+         //
+         memcpy(&file_cluster_hi,buffer+20,2);
+         memcpy(&file_cluster_low,buffer+26,2);
+         file_cluster = (((uint32_t) file_cluster_hi) << 16)+file_cluster_low;
+         put_flash_string(PSTR("\r\n   file cluster: 0x"));
+         put_hex_char((file_cluster >> 24) & 0xFF);
+         put_hex_char((file_cluster >> 16) & 0xFF);
+         put_hex_char((file_cluster >> 8) & 0xFF);
+         put_hex_char(file_cluster & 0xFF);
+         //
+         memcpy(&file_length,buffer+28,4);
+         put_flash_string(PSTR("\r\n   file length: 0x"));
+         put_hex_char((file_length >> 24) & 0xFF);
+         put_hex_char((file_length >> 16) & 0xFF);
+         put_hex_char((file_length >> 8) & 0xFF);
+         put_hex_char(file_length & 0xFF);
+         //
+         // break if not unused
+         //
+         if (buffer[0] != 0xE5)
+            break;
+         }
+      offset += 32;
+      }
+
+   //
+   // read the file
+   //
+   put_flash_string(PSTR("\r\nread first file:\r\n\r\n"));
+   file_sector = root_sector+(file_cluster-2)*sectors_per_cluster;
+   buffer_length = 32;
+   offset = 0;
+   sector_count = 0;
+   chars_read = 0;
+   while (1) {
+      //
+      // check file length
+      //
+      if ((chars_read+buffer_length) > file_length) {
+         SD_read(file_sector,offset,buffer,file_length-chars_read);
+         put_char_string(buffer,file_length-chars_read);
+         break;
+         }
+      SD_read(file_sector,offset,buffer,buffer_length);
+      put_char_string(buffer,buffer_length);
+      chars_read += buffer_length;
+      offset += buffer_length;
+
+      //
+      // check sector length
+      //
+      if (offset == bytes_per_sector) {
+         //
+         // sector length reached, check cluster length
+         //
+         sector_count += 1;
+         if (sector_count == sectors_per_cluster) {
+            //
+            // cluster length reached, get new cluster from FAT
+            //
+            SD_read(fat_sector,4*file_cluster,buffer,4);
+            memcpy(&file_cluster,buffer,4);
+            //
+            // continue
+            //
+            file_sector = root_sector+(file_cluster-2)*sectors_per_cluster;
+            offset = 0;
+            sector_count = 0;
+            }
+         else {
+            //
+            // cluster length not reached, continue
+            //
+            file_sector += 1;
+            offset = 0;
+            }
+         }
+
+      //
+      // sector length not reached, continue
+      //
+      }
+   put_flash_string(PSTR("\r\n\r\nend of file\r\n"));
+   //SPI.end();
+   led_port &= ~led_pin;
+
+   while(1) {}
+}
diff --git a/sd_tests/hardware_spi/notes.txt b/sd_tests/hardware_spi/notes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6eb79ba20b61418cb231d319427a4b45a181ce76
--- /dev/null
+++ b/sd_tests/hardware_spi/notes.txt
@@ -0,0 +1,25 @@
+
+SPI_write just does 8 bits, msb first
+
+SD command args are command, argument, crc, and output buffer
+SD command steps:
+- set CS low
+- write command byte
+- write argument
+    - most significant byte first
+- write crc
+- write 8 255s, read 8 bytes from MISO
+- set CS high
+
+
+Reset
+- command 0x40
+- arg 0x0
+- crc 0x95
+So should see
+0x40
+0x00
+0x00
+0x00
+0x00
+0x95
diff --git a/sd_tests/hardware_spi/tinySPI.cpp b/sd_tests/hardware_spi/tinySPI.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b9dd4c05374f1139390d3e071500e7e7f987d9b1
--- /dev/null
+++ b/sd_tests/hardware_spi/tinySPI.cpp
@@ -0,0 +1,45 @@
+// Arduino tinySPI Library Copyright (C) 2018 by Jack Christensen and
+// licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
+//
+// Arduino hardware SPI master library for
+// ATtiny24/44/84, ATtiny25/45/85, ATtiny261/461/861, ATtiny2313/4313.
+//
+// https://github.com/JChristensen/tinySPI
+// Jack Christensen 24Oct2013
+
+#include "tinySPI.h"
+
+void tinySPI::begin()
+{
+    USICR &= ~(_BV(USISIE) | _BV(USIOIE) | _BV(USIWM1));
+    USICR |= _BV(USIWM0) | _BV(USICS1) | _BV(USICLK);
+    SPI_DDR_PORT |= _BV(USCK_DD_PIN);   // set the USCK pin as output
+    SPI_DDR_PORT |= _BV(DO_DD_PIN);     // set the DO pin as output
+    SPI_DDR_PORT &= ~_BV(DI_DD_PIN);    // set the DI pin as input
+}
+
+void tinySPI::setDataMode(uint8_t spiDataMode)
+{
+    if (spiDataMode == SPI_MODE1)
+        USICR |= _BV(USICS0);
+    else
+        USICR &= ~_BV(USICS0);
+}
+
+uint8_t tinySPI::transfer(uint8_t spiData)
+{
+    USIDR = spiData;
+    USISR = _BV(USIOIF);                // clear counter and counter overflow interrupt flag
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)   // ensure a consistent clock period
+    {
+        while ( !(USISR & _BV(USIOIF)) ) USICR |= _BV(USITC);
+    }
+    return USIDR;
+}
+
+void tinySPI::end()
+{
+    USICR &= ~(_BV(USIWM1) | _BV(USIWM0));
+}
+
+tinySPI SPI;
diff --git a/sd_tests/hardware_spi/tinySPI.h b/sd_tests/hardware_spi/tinySPI.h
new file mode 100644
index 0000000000000000000000000000000000000000..00aa171f2b171c6a68a601cb743a1ce89c20b7e4
--- /dev/null
+++ b/sd_tests/hardware_spi/tinySPI.h
@@ -0,0 +1,56 @@
+// Arduino tinySPI Library Copyright (C) 2018 by Jack Christensen and
+// licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
+//
+// Arduino hardware SPI master library for
+// ATtiny24/44/84, ATtiny25/45/85, ATtiny261/461/861, ATtiny2313/4313.
+//
+// https://github.com/JChristensen/tinySPI
+// Jack Christensen 24Oct2013
+
+#ifndef TINYSPI_H_INCLUDED
+#define TINYSPI_H_INCLUDED
+
+#include <util/atomic.h>
+
+// USI ports and pins
+// (Thanks to nopnop2002 for adding the definitions for ATtiny461/861 and 2313/4313.)
+#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
+  #define SPI_DDR_PORT DDRA
+  #define USCK_DD_PIN DDA4
+  #define DO_DD_PIN DDA5
+  #define DI_DD_PIN DDA6
+#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
+  #define SPI_DDR_PORT DDRB
+  #define USCK_DD_PIN DDB2
+  #define DO_DD_PIN DDB1
+  #define DI_DD_PIN DDB0
+#elif defined(__AVR_ATtiny261__) || defined(__AVR_ATtiny461__) || defined(__AVR_ATtiny861__)
+  #define SPI_DDR_PORT DDRB
+  #define USCK_DD_PIN DDB2
+  #define DO_DD_PIN DDB1
+  #define DI_DD_PIN DDB0
+#elif defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny4313__)
+  #define SPI_DDR_PORT DDRB
+  #define USCK_DD_PIN DDB7
+  #define DO_DD_PIN DDB6
+  #define DI_DD_PIN DDB5
+#else
+  #error "tinySPI does not support this microcontroller."
+#endif
+
+// SPI data modes
+#define SPI_MODE0 0x00
+#define SPI_MODE1 0x04
+
+class tinySPI
+{
+    public:
+        static void begin();
+        static void setDataMode(uint8_t spiDataMode);
+        static uint8_t transfer(uint8_t spiData);
+        static void end();
+};
+
+extern tinySPI SPI;
+
+#endif
diff --git a/sd_tests/software_spi/Makefile b/sd_tests/software_spi/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..850b0d2cd4f14fd762af0e6c7a2549837bbdc047
--- /dev/null
+++ b/sd_tests/software_spi/Makefile
@@ -0,0 +1,31 @@
+PROJECT=read_sd
+SOURCES=$(PROJECT).c
+MMCU=attiny84
+F_CPU = 20000000
+
+CFLAGS=-mmcu=$(MMCU) -Wall -Os -DF_CPU=$(F_CPU)
+
+$(PROJECT).hex: $(PROJECT).out
+	avr-objcopy -O ihex $(PROJECT).out $(PROJECT).c.hex;\
+	avr-size --mcu=$(MMCU) --format=avr $(PROJECT).out
+
+$(PROJECT).out: $(SOURCES)
+	avr-gcc $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)
+
+program-bsd: $(PROJECT).hex
+	avrdude -p t44 -c bsd -U flash:w:$(PROJECT).c.hex
+
+program-dasa: $(PROJECT).hex
+	avrdude -p t44 -P /dev/ttyUSB0 -c dasa -U flash:w:$(PROJECT).c.hex
+
+program-avrisp2: $(PROJECT).hex
+	avrdude -p t44 -P usb -c avrisp2 -U flash:w:$(PROJECT).c.hex
+
+program-usbtiny: $(PROJECT).hex
+	avrdude -p t84 -P usb -c usbtiny -U flash:w:$(PROJECT).c.hex
+
+program-dragon: $(PROJECT).hex
+	avrdude -p t44 -P usb -c dragon_isp -U flash:w:$(PROJECT).c.hex
+
+program-ice: $(PROJECT).hex
+	avrdude -p t44 -P usb -c atmelice_isp -U flash:w:$(PROJECT).c.hex
diff --git a/sd_tests/software_spi/read_sd.c b/sd_tests/software_spi/read_sd.c
new file mode 100644
index 0000000000000000000000000000000000000000..4ec1dfba9af82be7169962496be743e5d53c6137
--- /dev/null
+++ b/sd_tests/software_spi/read_sd.c
@@ -0,0 +1,586 @@
+//
+//
+// hello.uSD.44.read.c
+//
+// 19200 baud microSD hello-world
+//    assumes FAT32 SDHC/XC
+//
+// Neil Gershenfeld 11/26/18
+//
+// (c) Massachusetts Institute of Technology 2018
+// This work may be reproduced, modified, distributed,
+// performed, and displayed for any purpose. Copyright is
+// retained and must be preserved. The work is provided
+// as is; no warranty is provided, and users accept all
+// liability.
+//
+
+#include <stdint.h>
+#include <string.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+
+#define output(directions,pin) (directions |= pin) // set port direction for output
+#define input(directions,pin) (directions &= (~pin)) // set port direction for input
+#define set(port,pin) (port |= pin) // set port pin
+#define clear(port,pin) (port &= (~pin)) // clear port pin
+#define pin_test(pins,pin) (pins & pin) // test for port pin
+#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
+#define bit_delay_time 49.5 // bit delay for 19200 with overhead
+//#define bit_delay_time 8.5 // bit delay for 115200 with overhead
+#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
+#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
+#define char_delay() _delay_ms(10) // char delay
+#define SPI_delay() _delay_us(2.5) // SPI delay
+
+#define serial_port PORTA
+#define serial_direction DDRA
+#define serial_pins PINA
+#define serial_pin_in (1 << PA0)
+#define serial_pin_out (1 << PA1)
+
+#define CS_port PORTA
+#define CS_direction DDRA
+#define CS_pin (1 << PA3)
+#define SCK_port PORTA
+#define SCK_direction DDRA
+#define SCK_pin (1 << PA4)
+#define MOSI_port PORTA
+#define MOSI_direction DDRA
+#define MOSI_pin (1 << PA6)
+#define MISO_port PORTA
+#define MISO_direction DDRA
+#define MISO_pin (1 << PA5)
+#define MISO_pins PINA
+
+#define DETECT_port PORTA
+#define DETECT_direction DDRA
+#define DETECT_pin (1 << PA7)
+
+#define led_port PORTB
+#define led_direction DDRB
+#define led_pin (1 << PB2)
+
+//
+// put_char
+//   send character in txchar
+//    assumes line driver (inverts bits)
+//    assume use of serial_port, serial_pin_out
+//
+void put_char(char txchar) {
+   //
+   // start bit
+   //
+   clear(serial_port,serial_pin_out);
+   bit_delay();
+   //
+   // unrolled loop to write data bits
+   //
+   if bit_test(txchar,0)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,1)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,2)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,3)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,4)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,5)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,6)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   if bit_test(txchar,7)
+      set(serial_port,serial_pin_out);
+   else
+      clear(serial_port,serial_pin_out);
+   bit_delay();
+   //
+   // stop bit
+   //
+   set(serial_port,serial_pin_out);
+   bit_delay();
+   //
+   // char delay
+   //
+   bit_delay();
+   }
+
+//
+// put_string
+//    print a null-terminated string
+//    assumes use of serial_port, serial_pin_out
+//
+void put_string(char *str) {
+   static int index;
+   index = 0;
+   do {
+      put_char(str[index]);
+      ++index;
+      } while (str[index] != 0);
+   }
+
+//
+// put_flash_string
+//    print a null-terminated string from flash
+//    assumes use of serial_port, serial_pin_out
+//
+void put_flash_string(const char *string) {
+   static int index;
+   index = 0;
+   do {
+      put_char(pgm_read_byte(string+index));
+      ++index;
+      } while (pgm_read_byte(string+index) != 0);
+   }
+
+//
+// put_char_string
+//    print a string
+//    assumes use of serial_port, serial_pin_out
+//
+void put_char_string(uint8_t *string, uint16_t length) {
+   static int index;
+   for (index = 0; index < length; ++index) {
+      put_char(string[index]);
+      }
+   }
+
+//
+// put_hex_char
+//    put a char in hex
+void put_hex_char(uint8_t chr) {
+   static char hex[] = "0123456789ABCDEF";
+      put_char(hex[(chr >> 4) & 0x0F]);
+      put_char(hex[chr & 0x0F]);
+   }
+
+//
+// put_hex_string
+//    print a string in hex
+//    assumes use of serial_port, serial_pin_out
+//
+void put_hex_string(uint8_t *string, uint16_t length) {
+   static int index;
+   for (index = 0; index < length; ++index) {
+      put_hex_char(string[index]);
+      }
+   }
+
+//
+// SPI_write
+//    write an SPI character and return the response
+//
+unsigned char SPI_write(uint8_t chr) {
+   static unsigned char bit,ret;
+   ret = 0;
+   //
+   // bit loop
+   //
+   for (bit = 0; bit < 8; ++bit) {
+      clear(SCK_port,SCK_pin);
+      if (chr & (1 << (7-bit)))
+         set(MOSI_port,MOSI_pin);
+      else
+         clear(MOSI_port,MOSI_pin);
+      SPI_delay();
+      set(SCK_port,SCK_pin);
+      SPI_delay();
+      if pin_test(MISO_pins,MISO_pin)
+         ret |= (1 << (7-bit));
+      }
+   //
+   // finish
+   //
+   return ret;
+   }
+
+//
+// SD_command
+//    write an SD command and return the response
+//
+void SD_command(uint8_t command, uint32_t argument, uint8_t CRC, uint8_t *result) {
+   clear(CS_port,CS_pin);
+   SPI_write(command);
+   SPI_write((argument >> 24) & 0xFF);
+   SPI_write((argument >> 16) & 0xFF);
+   SPI_write((argument >> 8) & 0xFF);
+   SPI_write(argument & 0xFF);
+   SPI_write(CRC);
+   result[0] = SPI_write(0xFF);
+   result[1] = SPI_write(0xFF);
+   result[2] = SPI_write(0xFF);
+   result[3] = SPI_write(0xFF);
+   result[4] = SPI_write(0xFF);
+   result[5] = SPI_write(0xFF);
+   result[6] = SPI_write(0xFF);
+   result[7] = SPI_write(0xFF);
+   set(CS_port,CS_pin);
+   }
+
+//
+// SD_read
+//    read size bytes at offset in sector into buffer
+//
+void SD_read(uint32_t sector,uint16_t offset,uint8_t *buffer,uint16_t size) {
+   static uint8_t chr;
+   static uint16_t index,count;
+   //
+   // start read
+   //
+   clear(CS_port,CS_pin);
+   //
+   // send CMD17
+   //
+   SPI_write(0x51);
+   SPI_write((sector >> 24) & 0xFF);
+   SPI_write((sector >> 16) & 0xFF);
+   SPI_write((sector >> 8) & 0xFF);
+   SPI_write(sector & 0xFF);
+   SPI_write(0);
+   //
+   // loop until 0xFE data token
+   //
+   while (1) {
+      chr = SPI_write(0xFF);
+      if (chr == 0xFE)
+         break;
+      }
+   //
+   // read up to offset
+   //
+   for (index = 0; index < offset; ++index) {
+      chr = SPI_write(0xFF);
+      }
+   //
+   // read from offset
+   //
+   count = 0;
+   for (index = offset; index < (offset+size); ++index) {
+      buffer[count] = SPI_write(0xFF);
+      count += 1;
+      }
+   //
+   // read up to sector
+   //
+   for (index = (offset+size); index < 512; ++index) {
+      chr = SPI_write(0xFF);
+      }
+   //
+   // read checksum
+   //
+   chr = SPI_write(0xFF);
+   chr = SPI_write(0xFF);
+   //
+   // finish read
+   //
+   chr = SPI_write(0xFF);
+   set(CS_port,CS_pin);
+}
+
+void sd_loop() {
+   static uint8_t count, sectors_per_cluster, FATs, attribute, result[8], buffer[50];
+   static uint16_t bytes_per_sector, reserved_sectors, offset, file_cluster_low, file_cluster_hi,
+      buffer_length, sector_count;
+   static uint32_t partition, FAT_sectors, fat_sector, root_cluster, root_sector, file_length,
+      file_cluster, file_sector, chars_read;
+
+   // Wait for card to be inserted
+   while (PINA & DETECT_pin) {
+       led_port |= led_pin;
+       _delay_ms(200);
+       led_port &= ~led_pin;
+       _delay_ms(200);
+   }
+   led_port |= led_pin;
+
+   //
+   // put card in SPI mode
+   //
+   set(MOSI_port, MOSI_pin);
+   set(CS_port, CS_pin);
+   for (count = 0; count < 80; ++count) {
+      set(SCK_port,SCK_pin);
+      SPI_delay();
+      clear(SCK_port,SCK_pin);
+      SPI_delay();
+      }
+
+   //
+   // CMD0: reset and enter idle state
+   //    should return 0x01
+   //
+   put_flash_string(PSTR("\r\nreset: 0x"));
+   SD_command(0x40, 0, 0x95, result);
+   put_hex_string(result,8);
+
+   //
+   // CMD8: send interface condition, set SDHC
+   //    should return 0x01000001AA
+   //
+   put_flash_string(PSTR("\r\nset interface: 0x"));
+   SD_command(0x48, 0x000001AA, 0x87, result);
+   put_hex_string(result,8);
+
+   //
+   // initialization loop
+   //
+   put_flash_string(PSTR("\r\ninitialization:"));
+   while (1) {
+      //
+      // CMD55: application command follows
+      //    should return 0x01
+      //
+      put_flash_string(PSTR("\r\n   application command: "));
+      SD_command(0x77, 0, 0, result);
+      put_hex_string(result, 8);
+
+      //
+      // ACMD41: initialize the card
+      //    should return 0x00 when ready
+      //
+      put_flash_string(PSTR("\r\n   initialize card: 0x"));
+      SD_command(0x69, 0x40000000, 0, result);
+      put_hex_string(result,8);
+
+      //
+      // check if done
+      //
+      if (result[1] == 0) {
+         put_string("\ncard initialized");
+         break;
+      }
+
+       if (PINA & DETECT_pin) {
+           put_string("\ncard removed");
+           return;
+       }
+   }
+
+   //
+   // read the first partition table
+   //
+   put_flash_string(PSTR("\r\nread first partition table:"));
+   SD_read(0,0x1BE,buffer,32);
+   put_flash_string(PSTR("\r\n   type: 0x")); // 0x0B or 0x0C for FAT32
+   put_hex_string(buffer+4,1);
+   put_flash_string(PSTR("\r\n   first sector: 0x"));
+   put_hex_string(buffer+8,4);
+   memcpy(&partition,buffer+8,4);
+
+   //
+   // read the first partition block
+   //
+   put_flash_string(PSTR("\r\nread first partition block:"));
+   SD_read(partition,0,buffer,50);
+   //
+   memcpy(&bytes_per_sector,buffer+11,2);
+   put_flash_string(PSTR("\r\n   bytes per sector: 0x"));
+   put_hex_char((bytes_per_sector >> 8) & 0xFF);
+   put_hex_char(bytes_per_sector);
+   //
+   memcpy(&sectors_per_cluster,buffer+13,1);
+   put_flash_string(PSTR("\r\n   sectors per cluster: 0x"));
+   put_hex_char(sectors_per_cluster);
+   //
+   memcpy(&reserved_sectors,buffer+14,2);
+   put_flash_string(PSTR("\r\n   reserved sectors: 0x"));
+   put_hex_char((reserved_sectors >> 8) & 0xFF);
+   put_hex_char(reserved_sectors & 0xFF);
+   //
+   fat_sector = partition+reserved_sectors;
+   //
+   memcpy(&FATs,buffer+16,1);
+   put_flash_string(PSTR("\r\n   FATs: 0x"));
+   put_hex_char(FATs & 0xFF);
+   //
+   memcpy(&FAT_sectors,buffer+36,4);
+   put_flash_string(PSTR("\r\n   FAT sectors: 0x"));
+   put_hex_char((FAT_sectors >> 24) & 0xFF);
+   put_hex_char((FAT_sectors >> 16) & 0xFF);
+   put_hex_char((FAT_sectors >> 8) & 0xFF);
+   put_hex_char(FAT_sectors  & 0xFF);
+   //
+   memcpy(&root_cluster,buffer+44,4);
+   put_flash_string(PSTR("\r\n   root cluster: 0x"));
+   put_hex_char((root_cluster >> 24) & 0xFF);
+   put_hex_char((root_cluster >> 16) & 0xFF);
+   put_hex_char((root_cluster >> 8) & 0xFF);
+   put_hex_char(root_cluster & 0xFF);
+
+   //
+   // read the root directory
+   //
+   root_sector = partition+reserved_sectors+(FATs*FAT_sectors);
+   put_flash_string(PSTR("\r\nread root directory:"));
+   offset = 0;
+   while (1) {
+      SD_read(root_sector,offset,buffer,32);
+      memcpy(&attribute,buffer+11,1);
+      put_flash_string(PSTR("\r\n   attribute: 0x"));
+      put_hex_char(attribute);
+      if (attribute == 0xF) {
+         //
+         // long file name
+         //
+         put_flash_string(PSTR("\r\n   long file name: 0x"));
+         put_hex_string(buffer+1,10);
+         put_hex_string(buffer+14,12);
+         put_hex_string(buffer+28,4);
+         put_flash_string(PSTR("\r\n                   "));
+         put_char_string(buffer+1,10);
+         put_char_string(buffer+14,12);
+         put_char_string(buffer+28,4);
+         }
+      else {
+         //
+         // short file name
+         //
+         put_flash_string(PSTR("\r\n   short file name: 0x"));
+         put_hex_string(buffer,11);
+         put_flash_string(PSTR("\r\n                    "));
+         put_char_string(buffer,11);
+         //
+         memcpy(&file_cluster_hi,buffer+20,2);
+         memcpy(&file_cluster_low,buffer+26,2);
+         file_cluster = (((uint32_t) file_cluster_hi) << 16)+file_cluster_low;
+         put_flash_string(PSTR("\r\n   file cluster: 0x"));
+         put_hex_char((file_cluster >> 24) & 0xFF);
+         put_hex_char((file_cluster >> 16) & 0xFF);
+         put_hex_char((file_cluster >> 8) & 0xFF);
+         put_hex_char(file_cluster & 0xFF);
+         //
+         memcpy(&file_length,buffer+28,4);
+         put_flash_string(PSTR("\r\n   file length: 0x"));
+         put_hex_char((file_length >> 24) & 0xFF);
+         put_hex_char((file_length >> 16) & 0xFF);
+         put_hex_char((file_length >> 8) & 0xFF);
+         put_hex_char(file_length & 0xFF);
+         //
+         // break if not unused
+         //
+         if (buffer[0] != 0xE5)
+            break;
+         }
+      offset += 32;
+      }
+
+   //
+   // read the file
+   //
+   put_flash_string(PSTR("\r\nread first file:\r\n\r\n"));
+   file_sector = root_sector+(file_cluster-2)*sectors_per_cluster;
+   buffer_length = 32;
+   offset = 0;
+   sector_count = 0;
+   chars_read = 0;
+   while (1) {
+      //
+      // check file length
+      //
+      if ((chars_read+buffer_length) > file_length) {
+         SD_read(file_sector,offset,buffer,file_length-chars_read);
+         put_char_string(buffer,file_length-chars_read);
+         break;
+         }
+      SD_read(file_sector,offset,buffer,buffer_length);
+      put_char_string(buffer,buffer_length);
+      chars_read += buffer_length;
+      offset += buffer_length;
+
+      //
+      // check sector length
+      //
+      if (offset == bytes_per_sector) {
+         //
+         // sector length reached, check cluster length
+         //
+         sector_count += 1;
+         if (sector_count == sectors_per_cluster) {
+            //
+            // cluster length reached, get new cluster from FAT
+            //
+            SD_read(fat_sector,4*file_cluster,buffer,4);
+            memcpy(&file_cluster,buffer,4);
+            //
+            // continue
+            //
+            file_sector = root_sector+(file_cluster-2)*sectors_per_cluster;
+            offset = 0;
+            sector_count = 0;
+            }
+         else {
+            //
+            // cluster length not reached, continue
+            //
+            file_sector += 1;
+            offset = 0;
+            }
+         }
+
+      //
+      // sector length not reached, continue
+      //
+      }
+   put_flash_string(PSTR("\r\n\r\nend of file\r\n"));
+   led_port &= ~led_pin;
+}
+
+//
+// main
+//
+int main(void) {
+   //
+   // main
+   //
+
+   //
+   // set clock divider to /1
+   //
+   CLKPR = (1 << CLKPCE);
+   CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
+
+   // Configure DETECT_pin as an input and led_pin as an output
+   DETECT_direction &= ~DETECT_pin;
+   led_direction |= led_pin;
+
+   //
+   // initialize output pins
+   //
+   set(serial_port,serial_pin_out);
+   output(serial_direction,serial_pin_out);
+   set(CS_port,CS_pin);
+   output(CS_direction,CS_pin);
+   clear(MOSI_port,MOSI_pin);
+   output(MOSI_direction,MOSI_pin);
+   set(SCK_port,SCK_pin);
+   output(SCK_direction,SCK_pin);
+   set(MISO_port,MISO_pin); //turn on pull-up
+   input(MISO_direction,MISO_pin);
+
+   while (1) {
+       sd_loop();
+   }
+}
diff --git a/sd_tests/software_spi/read_sd.c.hex b/sd_tests/software_spi/read_sd.c.hex
new file mode 100644
index 0000000000000000000000000000000000000000..815da62be87ed0e366a951f7fc65b390f61abd1b
--- /dev/null
+++ b/sd_tests/software_spi/read_sd.c.hex
@@ -0,0 +1,229 @@
+:1000000026C140C13FC13EC13DC13CC13BC13AC117
+:1000100039C138C137C136C135C134C133C132C12C
+:1000200031C10D0A0D0A656E64206F662066696C29
+:10003000650D0A000D0A7265616420666972737449
+:100040002066696C653A0D0A0D0A000D0A20202011
+:1000500066696C65206C656E6774683A203078005C
+:100060000D0A20202066696C6520636C75737465C9
+:10007000723A203078000D0A2020202020202020F5
+:10008000202020202020202020202020000D0A20B9
+:10009000202073686F72742066696C65206E616DD4
+:1000A000653A203078000D0A2020202020202020D2
+:1000B0002020202020202020202020000D0A202089
+:1000C000206C6F6E672066696C65206E616D653AA5
+:1000D000203078000D0A20202061747472696275E6
+:1000E00074653A203078000D0A7265616420726F81
+:1000F0006F74206469726563746F72793A000D0AD7
+:10010000202020726F6F7420636C75737465723A6F
+:10011000203078000D0A202020464154207365636A
+:10012000746F72733A203078000D0A202020464107
+:1001300054733A203078000D0A20202072657365D0
+:100140007276656420736563746F72733A203078D9
+:10015000000D0A202020736563746F727320706530
+:100160007220636C75737465723A203078000D0AE2
+:100170002020206279746573207065722073656336
+:10018000746F723A203078000D0A726561642066DF
+:100190006972737420706172746974696F6E206221
+:1001A0006C6F636B3A000D0A2020206669727374CD
+:1001B00020736563746F723A203078000D0A202036
+:1001C00020747970653A203078000D0A7265616498
+:1001D00020666972737420706172746974696F6EDD
+:1001E000207461626C653A000D0A202020696E69F6
+:1001F0007469616C697A6520636172643A20307851
+:10020000000D0A2020206170706C69636174696F51
+:100210006E20636F6D6D616E643A20000D0A696E29
+:10022000697469616C697A6174696F6E3A000D0A6C
+:1002300073657420696E746572666163653A203017
+:1002400078000D0A72657365743A203078001124C5
+:100250001FBECFE5D2E0DEBFCDBF10E0A0E6B0E02C
+:10026000EAEFFDE002C005900D92A239B107D9F77F
+:1002700021E0A2E9B0E001C01D92AC30B207E1F785
+:1002800090D5B9C5BDCED998E7EFF0E03197F1F739
+:10029000000080FF02C0D99A01C0D998E7EFF0E0D2
+:1002A0003197F1F7000081FF02C0D99A01C0D998B7
+:1002B000E7EFF0E03197F1F7000082FF02C0D99A32
+:1002C00001C0D998E7EFF0E03197F1F7000083FF24
+:1002D00002C0D99A01C0D998E7EFF0E03197F1F761
+:1002E000000084FF02C0D99A01C0D998E7EFF0E07E
+:1002F0003197F1F7000085FF02C0D99A01C0D99863
+:10030000E7EFF0E03197F1F7000086FF02C0D99ADD
+:1003100001C0D998E7EFF0E03197F1F7000087FFCF
+:1003200002C0D99A01C0D99887EF90E00197F1F700
+:100330000000D99AE7EFF0E03197F1F7000087EF7E
+:1003400090E00197F1F700000895CF93DF93EC015F
+:1003500010920B0110920A01E0910A01F0910B0139
+:10036000EC0FFD1F80818FDF80910A0190910B01BE
+:10037000019690930B0180930A01FE01E80FF91F8B
+:1003800080818111E9CFDF91CF910895CF93DF93E1
+:10039000EC011092090110920801E0910801F0911E
+:1003A0000901EC0FFD1F84916EDFE0910801F091CF
+:1003B00009013196F0930901E0930801EC0FFD1F4C
+:1003C000E491E111EACFDF91CF9108950F931F934C
+:1003D000CF93DF938C01EB0110920701109206017D
+:1003E00080910601909107018C179D0778F4F80120
+:1003F000E80FF91F808147DF809106019091070186
+:1004000001969093070180930601EACFDF91CF9187
+:100410001F910F910895CF93C82FE82FE295EF70A9
+:10042000F0E0E05AFF4F80812EDFCF70EC2FF0E03C
+:10043000E05AFF4F8081CF9126CF0F931F93CF9328
+:10044000DF938C01EB011092050110920401809161
+:100450000401909105018C179D0778F4F801E80FCD
+:10046000F91F8081D8DF80910401909105010196E8
+:100470009093050180930401EACFDF91CF911F9102
+:100480000F9108951092030110920201282F30E07D
+:1004900067E070E0E1E0F0E080910201883088F5EB
+:1004A000DC9880910201AB01481B5109D90102C0BF
+:1004B000B595A7954A95E2F7A0FF02C0DE9A01C064
+:1004C000DE98B0E1BA95F1F700C0DC9A40E14A95B8
+:1004D000F1F700C090910201CD9B0FC0AB01491B09
+:1004E0005109DF0102C0AA0FBB1F4A95E2F7AD0117
+:1004F00080910301482B409303019F5F9093020179
+:10050000CBCF809103010895BF92CF92DF92EF92FB
+:10051000FF920F931F93CF93DF93E42ED52EC62E19
+:10052000B72EF22EE801DB98ADDF8B2DABDF8C2DE3
+:10053000A9DF8D2DA7DF8E2DA5DF8F2DA3DF8FEFF8
+:10054000A1DF88838FEF9EDF89838FEF9BDF8A8314
+:100550008FEF98DF8B838FEF95DF8C838FEF92DFA8
+:100560008D838FEF8FDF8E838FEF8CDF8F83DB9A0E
+:10057000DF91CF911F910F91FF90EF90DF90CF907F
+:10058000BF900895AF92BF92CF92DF92EF92FF9209
+:100590000F931F93CF93DF93D62EC72EB82EA92E7D
+:1005A0007A01E901DB9881E56DDF8A2D6BDF8B2D08
+:1005B00069DF8C2D67DF8D2D65DF80E063DF8FEFD6
+:1005C00061DF8E3FE1F710920101109200018091EE
+:1005D0000001909101018E159F0560F48FEF52DFAD
+:1005E0008091000190910101019690930101809307
+:1005F0000001EDCF1092FF001092FE00F092010179
+:10060000E09200010E0D1F1D8091000190910101EB
+:1006100080179107E8F4E090FE00F090FF00EC0EE8
+:10062000FD1E8FEF2FDFF70180838091FE009091F8
+:10063000FF0001969093FF008093FE0080910001DF
+:100640009091010101969093010180930001DCCF0C
+:10065000109301010093000180910001909101012C
+:100660008115924060F48FEF0DDF80910001909131
+:10067000010101969093010180930001EDCF8FEF6E
+:1006800001DF8FEFFFDE8FEFFDDEDB9ADF91CF9191
+:100690001F910F91FF90EF90DF90CF90BF90AF90A0
+:1006A00008954F925F926F927F928F929F92AF9236
+:1006B000BF92CF92DF92EF92FF920F931F93CF9B47
+:1006C00015C0C29A2FEF84E39CE0215080409040F7
+:1006D000E1F700C00000C2982FEF84E39CE02150B6
+:1006E00080409040E1F700C00000E9CFC29ADE9A56
+:1006F000DB9A1092FD008091FD00803580F4DC9A39
+:1007000020E12A95F1F700C0DC9880E18A95F1F7A5
+:1007100000C08091FD008F5F8093FD00ECCF82E4EC
+:1007200092E034DE05EF10E025E940E050E0BA0148
+:1007300080E4EADE68E070E0C8017FDE8EE292E0ED
+:1007400025DE27E84AEA51E060E070E088E4DCDE7C
+:1007500068E070E0C80171DE8CE192E017DE81E0B4
+:1007600092E014DE05EF10E020E040E050E0BA0136
+:1007700087E7CADE68E070E0C8015FDE88EE91E0DE
+:1007800005DE20E040E050E060E070E489E6BCDE99
+:1007900068E070E0C80151DE8091F600811104C16B
+:1007A00081E790E0D2DD8AEC91E0F0DD00E210E03C
+:1007B00023EC30E04EEB51E060E070E0CB01E2DE94
+:1007C0008CEB91E0E3DD61E070E087EC90E035DEFA
+:1007D00086EA91E0DBDD64E070E08BEC90E02DDEFA
+:1007E0008091CB009091CC00A091CD00B091CE0033
+:1007F0008093BF009093C000A093C100B093C2004B
+:1008000088E891E0C3DD6091BF007091C0008091E5
+:10081000C1009091C20002E310E023EC30E040E020
+:1008200050E0B0DE8091CE009091CF009093BE005A
+:100830008093BD008EE691E0A9DD8091BE00EBDDE6
+:100840008091BD00E8DD8091D0008093BC0081E5FF
+:1008500091E09CDD8091BC00DEDD8091D100909123
+:10086000D2009093BB008093BA0087E391E08EDDC5
+:100870008091BB00D0DD8091BA00CDDD2091BA001F
+:100880003091BB008091BF009091C000A091C10049
+:10089000B091C200820F931FA11DB11D8093B600BD
+:1008A0009093B700A093B800B093B9008091D300A3
+:1008B0008093B50089E291E069DD8091B500ABDD00
+:1008C0008091E7009091E800A091E900B091EA00E2
+:1008D0008093B1009093B200A093B300B093B400A2
+:1008E00084E191E053DD8091B40095DD8091B30007
+:1008F00092DD8091B2008FDD8091B1008CDD80911E
+:10090000EF009091F000A091F100B091F20080937F
+:10091000AD009093AE00A093AF00B093B0008EEF07
+:1009200090E034DD8091B00076DD8091AF0073DD22
+:100930008091AE0070DD8091AD006DDD2091BA0038
+:100940003091BB008091BF009091C000A091C10088
+:10095000B091C2006C017D01C20ED31EE11CF11CDE
+:100960006091B50070E080E090E02091B10030919E
+:10097000B2004091B3005091B40024D2DC01CB010D
+:100980008C0D9D1DAE1DBF1D8093A9009093AA00E4
+:10099000A093AB00B093AC0087EE90E0F7DC109230
+:1009A000A8001092A70078C0CF9BD9CE83E890E032
+:1009B0001F910F91FF90EF90DF90CF90BF90AF907D
+:1009C0009F908F907F906F905F904F90BECC8DE8FE
+:1009D00090E0DCDC6BE070E083EC90E02EDD86E7FD
+:1009E00090E0D4DC6BE070E083EC90E0EFDC809191
+:1009F000D7009091D800A0E0B0E0DC0199278827CB
+:100A00002091DD003091DE00820F931FA11DB11DEA
+:100A10008093A2009093A300A093A400B093A5009C
+:100A200080E690E0B3DC8091A500F5DC8091A40025
+:100A3000F2DC8091A300EFDC8091A200ECDC8091DD
+:100A4000DF009091E000A091E100B091E20080937E
+:100A50009E0090939F00A093A000B093A1008BE410
+:100A600090E094DC8091A100D6DC8091A000D3DCE2
+:100A700080919F00D0DC80919E00CDDC8091C300EE
+:100A8000853E09F04EC08091A7009091A800809605
+:100A90009093A8008093A7004091A7005091A800D0
+:100AA0006091A9007091AA008091AB009091AC0078
+:100AB00000E210E023EC30E065DD8091CE00809311
+:100AC000A60084ED90E062DC8091A600A4DC809119
+:100AD000A6008F3009F07BCF8CEB90E057DC6AE00A
+:100AE00070E084EC90E0A9DC6CE070E081ED90E0D7
+:100AF000A4DC64E070E08FED90E09FDC86EA90E09B
+:100B000045DC6AE070E084EC90E060DC6CE070E072
+:100B100081ED90E05BDC64E070E08FED90E056DC0E
+:100B2000B2CF84E390E032DC8091A2009091A300E8
+:100B3000A091A400B091A500BC01CD016250710943
+:100B4000810991092091BC0030E040E050E03AD1A9
+:100B50000091A9001091AA002091AB003091AC0047
+:100B6000DC01CB01800F911FA21FB31F80939A005D
+:100B700090939B00A0939C00B0939D0080E290E036
+:100B800090939900809398001092A8001092A7006B
+:100B9000109297001092960010929200109293007B
+:100BA0001092940010929500009198001091990075
+:100BB0008090920090909300A0909400B090950047
+:100BC000C0909E00D0909F00E090A000F090A10007
+:100BD00024013501400E511E611C711C4091A7007B
+:100BE0005091A80060919A0070919B0080919C00A8
+:100BF00090919D00C414D504E604F70430F58601F5
+:100C00000819190923EC30E0BDDC60919E00709159
+:100C10009F008091920090919300681B790B83EC68
+:100C200090E0D4DB82E290E0B1DBC2981F910F919B
+:100C3000FF90EF90DF90CF90BF90AF909F908F90FC
+:100C40007F906F905F904F90089523EC30E09ADC96
+:100C5000609198007091990083EC90E0B7DB20914F
+:100C600098003091990040919200509193006091CA
+:100C7000940070919500420F531F611D711D4093A8
+:100C8000920050939300609394007093950080912C
+:100C9000A7009091A800820F931F9093A8008093C3
+:100CA000A7002091BD003091BE008217930709F084
+:100CB0007BCF80919600909197000196909397003A
+:100CC000809396002091BC0030E08217930709F0D2
+:100CD00050C04091A2005091A300440F551F440FF3
+:100CE000551F6091B6007091B7008091B800909147
+:100CF000B90004E010E023EC30E044DC8091C30054
+:100D00009091C400A091C500B091C6008093A2004C
+:100D10009093A300A093A400B093A500BC01CD01C3
+:100D200062507109810991092091BC0030E040E0D6
+:100D300050E048D00091A9001091AA002091AB008A
+:100D40003091AC00DC01CB01800F911FA21FB31FBB
+:100D500080939A0090939B00A0939C00B0939D0079
+:100D60001092A8001092A70010929700109296007F
+:100D70001BCF80919A0090919B00A0919C00B09114
+:100D80009D000196A11DB11D80939A0090939B0038
+:100D9000A0939C00B0939D001092A8001092A70011
+:100DA00003CF80E886BD16BCD798BA9AD99AD19A53
+:100DB000DB9AD39ADE98D69ADC9AD49ADD9AD598A3
+:100DC00070DCFECFEE27FF27AA27BB2708C0A20FA3
+:100DD000B31FE41FF51F220F331F441F551F9695A5
+:100DE00087957795679598F37040A9F7009799F7DD
+:0A0DF000BD01CF010895F894FFCF74
+:100DFA003031323334353637383941424344454647
+:100E0A00000A6361726420696E697469616C697A47
+:100E1A006564000A636172642072656D6F76656449
+:020E2A000000C6
+:00000001FF
diff --git a/sd_tests/software_spi/read_sd.out b/sd_tests/software_spi/read_sd.out
new file mode 100755
index 0000000000000000000000000000000000000000..096853034f53a77fa154bf716d05f22e62598e80
Binary files /dev/null and b/sd_tests/software_spi/read_sd.out differ