From 20d0295f842ef922c165a4b1894ef85a3a0a836d Mon Sep 17 00:00:00 2001
From: Erik Strand <erik.strand@cba.mit.edu>
Date: Wed, 15 May 2019 18:59:54 -0400
Subject: [PATCH] sd tests

---
 sd_tests/hardware_spi/hardware_spi.ino | 598 +++++++++++++++++++++++++
 sd_tests/hardware_spi/notes.txt        |  25 ++
 sd_tests/hardware_spi/tinySPI.cpp      |  45 ++
 sd_tests/hardware_spi/tinySPI.h        |  56 +++
 sd_tests/software_spi/Makefile         |  31 ++
 sd_tests/software_spi/read_sd.c        | 586 ++++++++++++++++++++++++
 sd_tests/software_spi/read_sd.c.hex    | 229 ++++++++++
 sd_tests/software_spi/read_sd.out      | Bin 0 -> 10748 bytes
 8 files changed, 1570 insertions(+)
 create mode 100644 sd_tests/hardware_spi/hardware_spi.ino
 create mode 100644 sd_tests/hardware_spi/notes.txt
 create mode 100644 sd_tests/hardware_spi/tinySPI.cpp
 create mode 100644 sd_tests/hardware_spi/tinySPI.h
 create mode 100644 sd_tests/software_spi/Makefile
 create mode 100644 sd_tests/software_spi/read_sd.c
 create mode 100644 sd_tests/software_spi/read_sd.c.hex
 create mode 100755 sd_tests/software_spi/read_sd.out

diff --git a/sd_tests/hardware_spi/hardware_spi.ino b/sd_tests/hardware_spi/hardware_spi.ino
new file mode 100644
index 0000000..5d0bde4
--- /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 0000000..6eb79ba
--- /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 0000000..b9dd4c0
--- /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 0000000..00aa171
--- /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 0000000..850b0d2
--- /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 0000000..4ec1dfb
--- /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 0000000..815da62
--- /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
GIT binary patch
literal 10748
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

literal 0
HcmV?d00001

-- 
GitLab