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.