Getting started with SD card Arduino Library

I appreciate that this is not a new topic - I have read other posts.

I am trying to build a battery powered TTY logger so I want to use ATtiny1604 device (rather than eg an Uno)

I have a micro SD card reader. This is advertised as having signal level shifters.

I am powering the device and SD card reader from a 5v supply directly from a USB to power jack cable. (I have had issues with device restart then trying to power everything from the serial card I was using as a UPDI programmer). I added at 220uF across the 5v supply near the card reader just in case it was a power surge issue. (My device has a very local 100nF connected across its supply).

I have an 8M card (I appreciate it must be FAT not extFAT formatted).

I hooked it all up using the appropriate ATtiny1604 pins and copied in the ‘demo’ code - some of it seems to work but there are some things happening I do not understand.

To help with this post, I split up the functionality into three blocks of code. I called these checkCard(), listFilesOnCard() and testWriteReadToCard(). I also replaced all the example TTY debug serial output with string literals.

Test case 1

If I run checkCard() all is fine. If I then run checkCard() followed by listFilesOnCard() all seems fine.

Test case 2

If I run testWriteReadToCard() on its own, it initiates the card but fails on the SD.open() command. But interestingly, it still managed to create a 0 byte length file on the SD card with the correct name. (I can open this on a PC and edit the file - hence you see TEST.TXT with 27 bytes in it.)

If I run the checkCard() followed by testWriteReadToCard(), I get the same fail at the SD.open() command.

Test case 3

If I run checkCard() followed by listFilesOnCard() and then testWriteReadToCard(), the device restarts as soon as the SD.open() command is run, before the if (myFile){} statement.

I was worried about RAM, hence the string literal use. Could there be an interrupt that is not being handled? The example code of listFilesOnCard() did not include a root.close() so I added one just in case that recursive stuff was still using some resource.

Can you help with Test case 2? I don’t need Test case 3 to work but I’d be interested to understand the code crash mechanism.

I tried deleting the zero byte file DATA.TXT but that made no difference.

Could it be a permissions issue on the SD card? - I don’t think so, I just used a WIN11 PC to set Everyone permissions to Full Control.

I have included the program with the code split into the functions described, my platformio.ini and the three test case serial TTY output.

I am running PlatformIO on UBUNTU.

// cSpell:includeRegExp CStyleComment
// ATtiny1604 SD Card

// ATtiny1604 / ARDUINO
//		           _____
//	    VDD      1|*    |14  	GND
//	SS  PA4  0   2|     |13  10 PA3 SCK
//  	PA5  1   3|     |12  9  PA2 MISO
//		PA6  2   4|     |11  8  PA1 MOSI
//	 	PA7  3   5|     |10  11 PA0 
// 	RXD PB3  4   6|     |9   7  PB0 
// 	TXD PB2  5   7|_____|8   6  PB1 
//

// Pin Multiplex details 
// Pin Name  Other     ADC0  AC0   USART0  SPIO  TWI0  TCA0  TCB0  CCL
//
// 1  VDD
// 2  PA4             AIN4        XDIR*   SS          WO4         LUT0-OUT
// 3  PA5             AIN5  OUT                       WO5   WO
// 4  PA6             AIN6  AINN0
// 5  PA7             AIN7  AINP0                                 LUT1-OUT
// 6  PB3                         RxD                 WO0*
// 7  PB2   EVOUT1                TxD                 WO2
// 8  PB1             AIN10       XCK           SDA   WO1
// 9  PB0             AIN11       XDIR          SCL   WO0
// 10 PA0 RESET/UPDI  AIN0                                        LUT0-IN0
// 11 PA1             AIN1        TxD*    MOSI                    LUT0-IN1
// 12 PA2 EVOUT0      AIN2        RxD*    MISO                    LUT0-IN2
// 13 PA3 EXTCLK      AIN3        XCK*    SCK         WO3
// 14 GND
//  * denotes alternate pin

/* 	Pin numbering scheme
	This core uses a simple scheme for assigning the Arduino pin numbers:
	Pins are numbered starting from the the I/O pin closest to Vcc as pin 0
	and proceeding counterclockwise, skipping the (mostly) non-usable UPDI pin.
	The UPDI pin is then assigned to the last pin number (as noted above, it is possible to read the UPDI pin
	(both analog and digital reads work) even if it is not set as GPIO).
	We recommend this as a last resort: the UPDI pin always has its pullup enabled when not set as a GPIO pin,
	and a signal which looks too much like the UPDI enable sequence will cause undesired operation.
*/

// put external references here:
#include <Arduino.h>
#include <SPI.h>
#include <SD.h>

// put pre-compile stuff here:
#define BAUD 115200

// put global variables here:
// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;
const int chipSelect = 0; // AKA PA4
File myFile;


// put function declarations here:
void system_init();
void checkCard();
void listFilesOnCard();
void testWriteReadToCard();


void setup() {
  system_init();
  delay(2000);

  // put your setup code here, to run once:
  Serial.begin(BAUD);
  while (!Serial) {}
  Serial.println(F("\r\n22 Nov 2025"));

  checkCard();
  listFilesOnCard();
  testWriteReadToCard();
} 


void loop() {
  // put your main code here, to run repeatedly:
  static int counter = 0;
  Serial.print(counter++);
  Serial.print(F(" "));
  delay(1000);
  delay(1000);
  delay(1000);
  delay(1000);
}

// put function definitions here:

void checkCard(){
  Serial.print(F("\nInitializing SD card..."));

  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  if (!card.init(SPI_HALF_SPEED, chipSelect)) {
    Serial.println(F("fail!"));
    while (1);
  } else {
    Serial.println(F("OK"));
  }

  // print the type of card
  Serial.println();
  Serial.print(F("Card type:         "));
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:
      Serial.println(F("SD1"));
      break;
    case SD_CARD_TYPE_SD2:
      Serial.println(F("SD2"));
      break;
    case SD_CARD_TYPE_SDHC:
      Serial.println(F("SDHC"));
      break;
    default:
      Serial.println(F("Unknown"));
  }

  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  if (!volume.init(card)) {
    Serial.println(F("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card"));
    while (1);// a blocking error message
  }

  Serial.print(F("Clusters:          "));
  Serial.println(volume.clusterCount());
  Serial.print(F("Blocks x Cluster:  "));
  Serial.println(volume.blocksPerCluster());

  Serial.print(F("Total Blocks:      "));
  Serial.println(volume.blocksPerCluster() * volume.clusterCount());
  Serial.println();

  // print the type and size of the first FAT-type volume
  uint32_t volumesize;
  Serial.print(F("Volume type is:    FAT"));
  Serial.println(volume.fatType(), DEC);

  volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
  volumesize *= volume.clusterCount();       // we'll have a lot of clusters
  volumesize /= 2;                           // SD card blocks are always 512 bytes (2 blocks are 1KB)
  Serial.print(F("Volume size (Kb):  "));
  Serial.println(volumesize);
  Serial.print(F("Volume size (Mb):  "));
  volumesize /= 1024;
  Serial.println(volumesize);
  Serial.print(F("Volume size (Gb):  "));
  Serial.println((float)volumesize / 1024.0);
}
void listFilesOnCard(){
  
  Serial.println(F("\nFiles found on the card (name, date and size in bytes): "));
  root.openRoot(volume);

  // list all files in the card with date and size
  root.ls(LS_R | LS_DATE | LS_SIZE);

  root.close();   
}

void testWriteReadToCard(){
  Serial.print(F("\r\nInitializing SD card..."));

  if (!SD.begin(chipSelect)) {
    Serial.println(F("fail!"));
    while (1);  // a blocking error message
  }
  Serial.println(F("OK"));

  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print(F("Writing to test.txt..."));
    myFile.println(F("testing 1, 2, 3."));
    // close the file:
    myFile.close();
    Serial.println(F("done."));
  } 
  else {
    // if the file didn't open, print an error:
    Serial.println(F("error opening test.txt"));
    while (1);  // a blocking error message
  }

  // re-open the file for reading:
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println(F("test.txt:"));
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      Serial.write(myFile.read());
    }
    // close the file:
    myFile.close();
  } 
  else {
   // if the file didn't open, print an error:
    Serial.println(F("error opening test.txt"));
    while (1);  // a blocking error message
  }
}

void mcu_init(void)							/* MCU initialization */
{
	/* On AVR devices all peripherals are enable from power on reset, this
	 * disables allow peripherals to save power. Driver shall enable
	 * peripheral if used */

	/* Set all pins to low power mode */
	for (uint8_t i = 0; i < 8; i++) {								// PA0 to PA7
		*((uint8_t *)&PORTA + 0x10 + i) |= (1 << PORT_PULLUPEN_bp);
	}

	for (uint8_t i = 0; i < 4; i++) {								// PB0 to PB3
		*((uint8_t *)&PORTB + 0x10 + i) |= (1 << PORT_PULLUPEN_bp);
	}
}

void pin_init(void){ 						/* PIN initialization */
  PORTA_DIRSET = (1<<PIN4_bp);	// define output pins
}

void system_init(){							/* system initialization */
	mcu_init();
	pin_init();
}

Platformio.ini

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:ATtiny1604]
platform = atmelmegaavr
board = ATtiny1604
framework = arduino

upload_speed = 115200
; upload_speed = 230400
upload_port = /dev/ttyUSB0
; upload_port = /dev/ttyUSB1

; monitor_port = /dev/ttyUSB0
monitor_port = /dev/ttyUSB1
; monitor_speed = 57600
; monitor_speed = 9600
monitor_speed = 115200
monitor_filters = time

upload_flags =
    --tool
    uart
    --device
    ATtiny1604
    --uart
    $UPLOAD_PORT
    --clk
    $UPLOAD_SPEED
upload_command = pymcuprog write --erase $UPLOAD_FLAGS --filename $SOURCE

Test Case 1 Output - SD card seems OK

--- Terminal on /dev/ttyUSB1 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
14:02:50.234 > 
14:02:50.234 > 22 Nov 2025
14:02:50.234 > 
14:02:50.234 > Initializing SD card...OK
14:02:50.670 > 
14:02:50.670 > Card type:         SDHC
14:02:50.670 > Clusters:          244192
14:02:50.670 > Blocks x Cluster:  64
14:02:50.670 > Total Blocks:      15628288
14:02:50.670 > 
14:02:50.670 > Volume type is:    FAT32
14:02:50.670 > Volume size (Kb):  7814144
14:02:50.670 > Volume size (Mb):  7631
14:02:50.670 > Volume size (Gb):  7.45
14:02:50.670 > 
14:02:50.670 > Files found on the card (name, date and size in bytes): 
14:02:50.670 > DATA.TXT      2000-01-01 01:00:00 0
14:02:50.670 > TEST.TXT      2025-11-23 11:46:38 27
14:02:50.670 > 0 1 2 

Test Case 2 - fails to open file for write

--- Terminal on /dev/ttyUSB1 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
14:04:58.665 > �
14:05:02.680 > 22 Nov 2025
14:05:02.680 > 
14:05:02.680 > Initializing SD card...OK
14:05:03.096 > error opening test.txt

Test Case 3 - code crashes

--- Terminal on /dev/ttyUSB1 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
14:05:54.183 > 
14:05:54.183 > 22 Nov 2025
14:05:54.183 > 
14:05:54.183 > Initializing SD card...OK
14:05:54.183 > 
14:05:54.183 > Card type:         SDHC
14:05:54.183 > Clusters:          244192
14:05:54.183 > Blocks x Cluster:  64
14:05:54.183 > Total Blocks:      15628288
14:05:54.183 > 
14:05:54.183 > Volume type is:    FAT32
14:05:54.183 > Volume size (Kb):  7814144
14:05:54.183 > Volume size (Mb):  7631
14:05:54.183 > Volume size (Gb):  7.45
14:05:54.183 > 
14:05:54.183 > Files found on the card (name, date and size in bytes): 
14:05:54.183 > DATA.TXT      2000-01-01 01:00:00 0
14:05:54.183 > TEST.TXT      2025-11-23 11:46:38 27
14:05:54.183 > 
14:05:54.183 > Initializing SD card...OK
14:05:56.199 > 
14:05:56.199 > 22 Nov 2025
14:05:56.199 > 
14:05:56.199 > Initializing SD card...OK
14:05:56.252 > 
14:05:56.252 > Card type:         SDHC
14:05:56.252 > Clusters:          244192
14:05:56.252 > Blocks x Cluster:  64
14:05:56.252 > Total Blocks:      15628288
14:05:56.252 > 
14:05:56.252 > Volume type is:    FAT32
14:05:56.252 > Volume size (Kb):  7814144
14:05:56.252 > Volume size (Mb):  7631
14:05:56.252 > Volume size (Gb):  7.45
14:05:56.252 > 
14:05:56.252 > Files found on the card (name, date and size in bytes): 
14:05:56.252 > DATA.TXT      2000-01-01 01:00:00 0
14:05:56.252 > TEST.TXT      2025-11-23 11:46:38 27
14:05:56.252 > 
14:05:56.252 > Initializing SD card...OK
14:05:58.267 > 
14:05:58.267 > 22 Nov 2025

Finally, here’s the compile output

Processing ATtiny1604 (platform: atmelmegaavr; board: ATtiny1604; framework: arduino)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelmegaavr/ATtiny1604.html
PLATFORM: Atmel megaAVR (1.9.0) > ATtiny1604
HARDWARE: ATTINY1604 16MHz, 1KB RAM, 16KB Flash
PACKAGES: 
 - framework-arduino-megaavr-megatinycore @ 2.6.7 
 - tool-avrdude @ 1.70100.0 (7.1.0) 
 - toolchain-atmelavr @ 3.70300.220127 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 15 compatible libraries
Scanning dependencies...
Dependency Graph
|-- SD @ 1.2.4
|-- SPI @ 1.1.2
Building in release mode
Checking size .pio/build/ATtiny1604/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [========  ]  82.1% (used 841 bytes from 1024 bytes)
Flash: [========= ]  89.2% (used 14622 bytes from 16384 bytes)
Configuring upload protocol...
AVAILABLE: jtag2updi
CURRENT: upload_protocol = jtag2updi
Looking for upload port...
Using manually specified: /dev/ttyUSB0
Uploading .pio/build/ATtiny1604/firmware.hex
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9425
Erasing device before writing from hex file...
Writing from hex file...
Writing flash...
Done.

I appreciate you are not supposed to reply to your own posts but I couldn’t find the edit button.

I think my problem is memory or lack thereof.

An ATtiny1604 has 1K SRAM and 16K Flash - so too has an Arduino Nano.

I tried to load just the ARDUINO example SD Card sketch on an Artduono Nano (Atmega168PA).

Digression.. Initially I tried using a new project within PlatformIO citing my device as an ATMEGA 168 but this did not seem to load the library versions required. With my ATtiny1604 build it used

Dependency Graph
|-- SD @ 1.2.4
|-- SPI @ 1.1.2

but with the new project based on the ATMEGA 168

[env:nanoatmega168]
platform = atmelavr
; platform = atmelmegaavr
board = nanoatmega168
framework = arduino

Platformio selected an old SPI reference and the SD.h reference bombed.

Dependency Graph
|-- SPI @ 1.0
Building in release mode
Compiling .pio/build/nanoatmega168/src/main.cpp.o
Archiving .pio/build/nanoatmega168/libcce/libSPI.a
Indexing .pio/build/nanoatmega168/libcce/libSPI.a
src/main.cpp:29:10: fatal error: SD.h: No such file or directory

Digression over

Anyway, I switched to Arduino IDE just for a quick test with the original Arduino example sketch and that wouldn’t even compile unless I switched out the Serial strings for string literals.

It also miserably failed to run.

Both my ATtiny1604 and Nano have about the same memory so I conclude that each device is just not big enough for the job.

I have a few ATtint3226 devices - these have twice the FLASH and SRAM. I’ll have a go with one of these.

I got it working but here’s what I found out.

I failed to get an ATtiny3226 to work with the SD library. This seems a common problem in some of the threads I read.

I ended up using an Arduino ProMini - this has a ATMEGA328 processor - just like an Arduino Uno.

This has 32K Flash and 2K SRAM.

I had to force PlatfirmIO to use a working version of SD.h by adding it as a library under the PIO Home | Libraries menu - this loaded the line below into my platformio.ini

lib_deps = arduino-libraries/SD@^1.3.0

Even so the small test sketches from Arduino examples took a bunch of RAM and Flash - see the compile output.

Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/pro16MHzatmega328.html
PLATFORM: Atmel AVR (5.1.0) > Arduino Pro or Pro Mini ATmega328 (5V, 16 MHz)
HARDWARE: ATMEGA328P 16MHz, 2KB RAM, 30KB Flash
DEBUG: Current (avr-stub) External (avr-stub, simavr)
PACKAGES: 
 - framework-arduino-avr @ 5.2.0 
 - toolchain-atmelavr @ 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 6 compatible libraries
Scanning dependencies...
Dependency Graph
|-- SD @ 1.3.0
|-- SPI @ 1.0
Building in release mode
Checking size .pio/build/pro16MHzatmega328/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [=====     ]  46.3% (used 949 bytes from 2048 bytes)
Flash: [=====     ]  47.0% (used 14434 bytes from 30720 bytes)

I still need a RTC and still want it to be battery powered so I think I’ll use a 3.3v ProMini at 8MHz and an SD card reader that works directly to 3.3v (mine had a level shifter in the way which I read hogs the SPI bus).