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(§ors_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(§ors_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