depends on the driver object you use to access it. There are a few older ones floating around that only work with <= 2GB cards (plain SD). The more usual ones should work with <= 32GB (SDHC). You can then make <= 2TB (SDXC) work by reformatting them to FAT32 (SDHC->SDXC transition didn't change the hardware protocol, just the factory formatting from FAT32 to exFAT)
I haven't seen any SDSC only drivers, but their protocol (Byte addressing) prevents them from working with > 2GB cards (Block addressing).
SDUC for over 2TB, up to 128TB, isn't supported. It adds prefixing complexity, like an ACMD, to the block addressing and I suspect will require more error handling.
EDIT: That said, it looks like a SDUC card will act as a 2 TB SDHC by default. The extension feature looks to be only activated if the driver requests it in the early ACMD41. The trick then is to format the card with first FAT32 partition contained entirely in the first 2 TB of the card.
EDIT2: Oh, and that would go for a SDSC driver too. A SDHC card will act as a 2GB SDSC card if the driver doesn't request for SDHC (HCS bit in ACMD41) extension.
Ohhh, it looks like SDUC cards won't do SPI mode at all. So rule of thumb is buy only cards marked as SDHC or SDXC.
PS: There's gonna be a lot of uControllers that'll trip up on this - Assuming SDUC cards start to be more common. All example driver code on the open web is exclusively SPI mode. I never found a single example of source code that operated in SD mode. My hunch is SD mode primarily exists as a licenced IP block with an associated API and binary blob to manage it.
Greater than 2 TB could be a long while off for being common though. It's not looking very easy to get there in such a small package. Price is more important for common.
Hehe, I just popped the question to Google's LLM: C driver example to operate a SD card
The opening paragraph response was as follows:
Operating an SD card in C, especially with a microcontroller and no OS, primarily involves using the SPI protocol for low-level communication. Most projects rely on established libraries like FatFS which handle the complex file system logic, requiring the user to only provide the basic hardware SPI functions.
Asking again but with the SD mode qualifier: C driver example to operate a SD card in SD mode
Operating an SD card in its native 1-bit or 4-bit
SD mode generally requires a specialized SD Host Controller peripheral on your microcontroller or system-on-chip, as bit-banging the required bidirectional command and data lines is complex. Off-the-shelf driver examples for bare-metal C are uncommon because most general-purpose microcontrollers use the simpler SPI mode for SD card communication.
For systems that do have a native SD Host Controller (like some Renesas RX or Espressif ESP32 MCUs), the drivers are typically provided as part of the manufacturer's SDK (Software Development Kit). These drivers manage the low-level command sequences and data transfer via the specific hardware registers.
And it provides an example using an API for such a controller.
On, man, absolute proof that LLMs are entirely limited to regurgitating only! After a few attempts to coax the LLM to answer with an actual example of bit-bashing in SD mode I did get one - prompt: C example to operate 4-bit SD mode as bit-bashed
Response:
Bit-banging the 4-bit SD mode is a complex task due to strict timing requirements, the need for a specific initialization sequence in 1-bit mode before switching to 4-bit, and the necessity of implementing a 16-bit CRC for data transfers. Unlike SPI mode, which can be bit-banged more simply, the full SD protocol requires careful timing and state management.
And the entirety of the code example:
#include <stdint.h>
// --- Hardware Abstraction Layer (HAL) Macro/Function Placeholders ---
// These must be defined based on your specific hardware platform.
#define SET_CLK_HIGH() // Set the CLK pin to a high state
#define SET_CLK_LOW() // Set the CLK pin to a low state
#define SET_CMD_HIGH() // Set the CMD pin to high, likely as input with pull-up
#define SET_CMD_LOW() // Set the CMD pin to low (output drive)
#define READ_CMD() // Read the state of the CMD pin
#define SET_DAT0_HIGH() // Set DAT0 high/input
#define SET_DAT0_LOW() // Set DAT0 low/output
#define READ_DAT0() // Read the state of DAT0
#define SET_DAT1_HIGH() // ... for DAT1
#define SET_DAT1_LOW()
#define READ_DAT1()
#define SET_DAT2_HIGH() // ... for DAT2
#define SET_DAT2_LOW()
#define READ_DAT2()
#define SET_DAT3_HIGH() // ... for DAT3
#define SET_DAT3_LOW()
#define READ_DAT3()
// A delay function is critical for timing
void SD_Delay_us(uint32_t us);
// ---------------------------------------------------------------------
// Function to send a command (CMD) over the CMD line in 1-bit mode
void send_command(uint8_t cmd, uint32_t arg, uint8_t crc) {
// Commands start with '01' followed by the 6-bit command index
uint8_t start_byte = 0x40 | (cmd & 0x3F);
// The command line (CMD) operates bidirectionally, but for commands it's output from host
// Set CMD as output and drive bits high/low
for (int i = 7; i >= 0; i--) {
if ((start_byte >> i) & 0x01) {
SET_CMD_HIGH();
} else {
SET_CMD_LOW();
}
SET_CLK_HIGH();
SD_Delay_us(1); // Adjust timing
SET_CLK_LOW();
SD_Delay_us(1);
}
// ... logic for sending 4 bytes of argument and 1 byte of CRC ...
}
// Function to read a response from the CMD line
uint8_t read_response() {
// ... logic to read bits from CMD line (as input with pull-up) ...
return 0; // Placeholder
}
// Function to switch to 4-bit mode after initialization in 1-bit mode
void switch_to_4bit_mode() {
// The host sends ACMD6 (with argument 2 for 4-bit bus width)
// This requires specific command sequence (CMD55 then CMD6)
// After the command, the card will acknowledge and switch
// In 4-bit mode, DAT0-DAT3 lines are used for parallel data transfer
// The host must set its corresponding GPIOs to input/output accordingly
// This is the core of bit-banging 4-bit data
}
// Function to read a 512-byte data block in 4-bit mode
void read_data_block_4bit(uint8_t* buffer) {
// Wait for the start token
// When reading, the card drives DAT0-DAT3
// The host clocks the bus (CLK) and reads the 4 data lines simultaneously
for (int byte_index = 0; byte_index < 512; byte_index++) {
uint8_t received_byte = 0;
for (int bit_index = 7; bit_index >= 0; bit_index -= 4) {
// Clock the bus
SET_CLK_HIGH();
SD_Delay_us(1);
// Read all four data lines simultaneously
uint8_t four_bits = 0;
if (READ_DAT0()) four_bits |= (1 << 0);
if (READ_DAT1()) four_bits |= (1 << 1);
if (READ_DAT2()) four_bits |= (1 << 2);
if (READ_DAT3()) four_bits |= (1 << 3);
// Store the bits into the byte (need to handle bit order as per spec)
// SD mode transfers data starting from the MSB, in byte-width format
// This is complex as it is 4-bit parallel
SET_CLK_LOW();
SD_Delay_us(1);
}
// ... store received_byte ...
}
// Read and verify the 16-bit CRC
}
// Main initialization and usage flow
int main() {
// 1. Initialize in 1-bit mode (power up sequence and initial commands like CMD0, CMD8, CMD55/ACMD41)
// 2. Send ACMD6 with argument 2 to set bus width to 4-bit
// 3. Configure host GPIOs (DAT0-DAT3) for 4-bit operation
// Example call to a read function (once initialization is complete)
uint8_t buffer[512];
// read_data_block_4bit(buffer);
return 0;
}
It's completely devoid of anything meaningful. A good demonstration of the lack of source material for it to have been trained on.
Comments
depends on the driver object you use to access it. There are a few older ones floating around that only work with <= 2GB cards (plain SD). The more usual ones should work with <= 32GB (SDHC). You can then make <= 2TB (SDXC) work by reformatting them to FAT32 (SDHC->SDXC transition didn't change the hardware protocol, just the factory formatting from FAT32 to exFAT)
I haven't seen any SDSC only drivers, but their protocol (Byte addressing) prevents them from working with > 2GB cards (Block addressing).
SDUC for over 2TB, up to 128TB, isn't supported. It adds prefixing complexity, like an ACMD, to the block addressing and I suspect will require more error handling.
EDIT: That said, it looks like a SDUC card will act as a 2 TB SDHC by default. The extension feature looks to be only activated if the driver requests it in the early ACMD41. The trick then is to format the card with first FAT32 partition contained entirely in the first 2 TB of the card.
EDIT2: Oh, and that would go for a SDSC driver too. A SDHC card will act as a 2GB SDSC card if the driver doesn't request for SDHC (HCS bit in ACMD41) extension.
Ohhh, it looks like SDUC cards won't do SPI mode at all. So rule of thumb is buy only cards marked as SDHC or SDXC.
PS: There's gonna be a lot of uControllers that'll trip up on this - Assuming SDUC cards start to be more common. All example driver code on the open web is exclusively SPI mode. I never found a single example of source code that operated in SD mode. My hunch is SD mode primarily exists as a licenced IP block with an associated API and binary blob to manage it.
Greater than 2 TB could be a long while off for being common though. It's not looking very easy to get there in such a small package. Price is more important for common.
Hehe, I just popped the question to Google's LLM: C driver example to operate a SD card
The opening paragraph response was as follows:
Asking again but with the SD mode qualifier: C driver example to operate a SD card in SD mode
And it provides an example using an API for such a controller.
On, man, absolute proof that LLMs are entirely limited to regurgitating only! After a few attempts to coax the LLM to answer with an actual example of bit-bashing in SD mode I did get one - prompt: C example to operate 4-bit SD mode as bit-bashed
Response:
And the entirety of the code example:
#include <stdint.h> // --- Hardware Abstraction Layer (HAL) Macro/Function Placeholders --- // These must be defined based on your specific hardware platform. #define SET_CLK_HIGH() // Set the CLK pin to a high state #define SET_CLK_LOW() // Set the CLK pin to a low state #define SET_CMD_HIGH() // Set the CMD pin to high, likely as input with pull-up #define SET_CMD_LOW() // Set the CMD pin to low (output drive) #define READ_CMD() // Read the state of the CMD pin #define SET_DAT0_HIGH() // Set DAT0 high/input #define SET_DAT0_LOW() // Set DAT0 low/output #define READ_DAT0() // Read the state of DAT0 #define SET_DAT1_HIGH() // ... for DAT1 #define SET_DAT1_LOW() #define READ_DAT1() #define SET_DAT2_HIGH() // ... for DAT2 #define SET_DAT2_LOW() #define READ_DAT2() #define SET_DAT3_HIGH() // ... for DAT3 #define SET_DAT3_LOW() #define READ_DAT3() // A delay function is critical for timing void SD_Delay_us(uint32_t us); // --------------------------------------------------------------------- // Function to send a command (CMD) over the CMD line in 1-bit mode void send_command(uint8_t cmd, uint32_t arg, uint8_t crc) { // Commands start with '01' followed by the 6-bit command index uint8_t start_byte = 0x40 | (cmd & 0x3F); // The command line (CMD) operates bidirectionally, but for commands it's output from host // Set CMD as output and drive bits high/low for (int i = 7; i >= 0; i--) { if ((start_byte >> i) & 0x01) { SET_CMD_HIGH(); } else { SET_CMD_LOW(); } SET_CLK_HIGH(); SD_Delay_us(1); // Adjust timing SET_CLK_LOW(); SD_Delay_us(1); } // ... logic for sending 4 bytes of argument and 1 byte of CRC ... } // Function to read a response from the CMD line uint8_t read_response() { // ... logic to read bits from CMD line (as input with pull-up) ... return 0; // Placeholder } // Function to switch to 4-bit mode after initialization in 1-bit mode void switch_to_4bit_mode() { // The host sends ACMD6 (with argument 2 for 4-bit bus width) // This requires specific command sequence (CMD55 then CMD6) // After the command, the card will acknowledge and switch // In 4-bit mode, DAT0-DAT3 lines are used for parallel data transfer // The host must set its corresponding GPIOs to input/output accordingly // This is the core of bit-banging 4-bit data } // Function to read a 512-byte data block in 4-bit mode void read_data_block_4bit(uint8_t* buffer) { // Wait for the start token // When reading, the card drives DAT0-DAT3 // The host clocks the bus (CLK) and reads the 4 data lines simultaneously for (int byte_index = 0; byte_index < 512; byte_index++) { uint8_t received_byte = 0; for (int bit_index = 7; bit_index >= 0; bit_index -= 4) { // Clock the bus SET_CLK_HIGH(); SD_Delay_us(1); // Read all four data lines simultaneously uint8_t four_bits = 0; if (READ_DAT0()) four_bits |= (1 << 0); if (READ_DAT1()) four_bits |= (1 << 1); if (READ_DAT2()) four_bits |= (1 << 2); if (READ_DAT3()) four_bits |= (1 << 3); // Store the bits into the byte (need to handle bit order as per spec) // SD mode transfers data starting from the MSB, in byte-width format // This is complex as it is 4-bit parallel SET_CLK_LOW(); SD_Delay_us(1); } // ... store received_byte ... } // Read and verify the 16-bit CRC } // Main initialization and usage flow int main() { // 1. Initialize in 1-bit mode (power up sequence and initial commands like CMD0, CMD8, CMD55/ACMD41) // 2. Send ACMD6 with argument 2 to set bus width to 4-bit // 3. Configure host GPIOs (DAT0-DAT3) for 4-bit operation // Example call to a read function (once initialization is complete) uint8_t buffer[512]; // read_data_block_4bit(buffer); return 0; }It's completely devoid of anything meaningful. A good demonstration of the lack of source material for it to have been trained on.