303 lines
7.0 KiB
C++
303 lines
7.0 KiB
C++
#pragma once
|
|
|
|
#include "../../io/GPIO.h"
|
|
#include "../../Debug.h"
|
|
|
|
#define TEENSY_SD_PIN_CS 46
|
|
#define TEENSY_SD_PIN_MOSI 45
|
|
#define TEENSY_SD_PIN_CLK 44
|
|
#define TEENSY_SD_PIN_MISO 43
|
|
|
|
|
|
// http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf
|
|
// https://www.convict.lu/pdf/ProdManualSDCardv1.9.pdf
|
|
// http://rjhcoding.com/avrc-sd-interface-3.php
|
|
template <typename SPI, int PIN_CS> class SDCard {
|
|
|
|
SPI& spi;
|
|
|
|
union R1 { // SanDisk Manual Page 5-13
|
|
uint8_t raw;
|
|
struct {
|
|
uint8_t inIdleState : 1;
|
|
uint8_t eraseReset : 1;
|
|
uint8_t illegalCommand : 1;
|
|
uint8_t crcError : 1;
|
|
uint8_t eraseSeqError : 1;
|
|
uint8_t addressError : 1;
|
|
uint8_t paramError : 1;
|
|
uint8_t zero : 1;
|
|
};
|
|
R1() {}
|
|
R1(uint8_t val) : raw(val) {}
|
|
};
|
|
|
|
union R3 { // response to OCR command, SanDisk Manual Page 5-14
|
|
uint8_t raw[5];
|
|
struct {
|
|
R1 state;
|
|
uint8_t busy : 1;
|
|
uint8_t reserved : 7;
|
|
uint32_t allowedVoltages: 20;
|
|
uint8_t checkPattern : 4;
|
|
};
|
|
};
|
|
|
|
union Rcmd8 { // response to cmd8
|
|
uint8_t raw[5];
|
|
struct {
|
|
R1 state;
|
|
uint8_t data[4];
|
|
};
|
|
};
|
|
|
|
|
|
|
|
static constexpr const char* NAME = "SDCard";
|
|
|
|
public:
|
|
|
|
SDCard(SPI& spi) : spi(spi) {
|
|
|
|
}
|
|
|
|
bool init() {
|
|
|
|
MyGPIO::setOutput(PIN_CS);
|
|
debugMod(NAME, "init()");
|
|
|
|
// RESET: MOSI = 1, CS = 1 (deselected!!), at least 74 Clocks
|
|
deselect();
|
|
for (uint8_t i = 0; i < 10; ++i) {spi.writeByte(0xFF);}
|
|
|
|
// switch to SPI mode
|
|
for (uint8_t i = 0; ; ++i) {
|
|
const R1 res = cmd0();
|
|
if (res.raw == 1) {break;}
|
|
if (i == 4) {debugMod(NAME, "init failed"); return false;}
|
|
delay(50);
|
|
}
|
|
|
|
|
|
// TODO?
|
|
// Version 2.0 Card
|
|
Rcmd8 r8 = cmd8();
|
|
|
|
if (r8.state.raw == 1) {
|
|
debugMod(NAME, "V2/V3 Card");
|
|
if (r8.data[2] == 0x01 && r8.data[3] == 0xAA) {
|
|
debugMod(NAME, "Pattern Correct");
|
|
} else {
|
|
debugMod(NAME, "Pattern Mismatch");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Version 1.0 Card
|
|
//R3 res = cmd58();
|
|
|
|
|
|
|
|
uint32_t hcs = 1<<30;
|
|
|
|
for (uint8_t i = 0; ; ++i) {
|
|
delay(100);
|
|
R1 r1 = acmd41(hcs);
|
|
if (i == 8) {debugMod(NAME, "init failed"); return false;}
|
|
if (r1.raw == 0) {break;} // finished
|
|
if (r1.raw == 1) {continue;} // card is still idle
|
|
if (r1.raw > 1) {debugMod(NAME, "init failed"); return false;}
|
|
}
|
|
|
|
debugMod(NAME, "init OK");
|
|
return true;
|
|
|
|
}
|
|
|
|
uint32_t read(uint32_t addr, uint32_t size, uint8_t* dst) {
|
|
|
|
uint32_t read = 0;
|
|
uint32_t readAddr = addr & (0xFFFFFFFF - 512); // 512 byte aligned staring address
|
|
|
|
if (readAddr != addr) { // non-aligned first read?
|
|
uint8_t buf[512];
|
|
uint16_t cOff = (addr-readAddr); // skip the unneeded bytes
|
|
uint16_t cSize = 512 - cOff;
|
|
readSingleBlock(readAddr, buf); // read a full block
|
|
memcpy(dst, buf+cOff, cSize); // copy only the required bytes
|
|
size -= cSize;
|
|
dst += cSize;
|
|
read += cSize;
|
|
readAddr += 512;
|
|
}
|
|
|
|
while(size >= 512) {
|
|
readSingleBlock(readAddr, dst);
|
|
readAddr += 512;
|
|
dst += 512;
|
|
read += 512;
|
|
size -= 512;
|
|
}
|
|
|
|
if (size > 0) {
|
|
uint8_t buf[512];
|
|
readSingleBlock(readAddr, buf);
|
|
memcpy(dst, buf, size);
|
|
read += size;
|
|
}
|
|
|
|
return read;
|
|
|
|
}
|
|
|
|
/** read a single block of 512 bytes */
|
|
bool readSingleBlock(uint32_t addr, uint8_t* dst) {
|
|
|
|
addr = addr / 512;
|
|
sendCMD(17, addr>>24, addr>>16, addr>>8, addr>>0, 0xFF);
|
|
R1 res; readResponse(&res.raw, 1);
|
|
debugMod2(NAME, "readSingleBlock(%08x): %02x", addr, res.raw);
|
|
|
|
// read command OK?
|
|
if (res.raw != 0) {endCMD(); return false;}
|
|
|
|
// wait for data to become available
|
|
for (uint16_t i = 0; ; ++i) {
|
|
uint8_t res = spi.readWriteByte(0xFF);
|
|
if (res == 0xFE) {break;} // available!
|
|
if (res != 0xFF) {debugMod(NAME, "invalid"); endCMD(); return false;} // invalid response
|
|
if (i > 1024) {debugMod(NAME, "timeout"); endCMD(); return false;} // timeout
|
|
}
|
|
|
|
// read data
|
|
for (uint16_t i = 0; i < 512; ++i) {
|
|
dst[i] = spi.readWriteByte(0xFF);
|
|
}
|
|
|
|
// done
|
|
endCMD();
|
|
return true;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
|
|
/** send a new command */
|
|
void sendCMD(uint8_t cmd, uint8_t arg0, uint8_t arg1, uint8_t arg2, uint8_t arg3, uint8_t crc) {
|
|
uint8_t buf[6];
|
|
buf[0] = (0b01 << 6) | (cmd&0b111111);
|
|
buf[1] = arg0;
|
|
buf[2] = arg1;
|
|
buf[3] = arg2;
|
|
buf[4] = arg3;
|
|
buf[5] = ((crc&0b1111111) << 1) | 1;
|
|
select();
|
|
spi.write(buf, 6);
|
|
}
|
|
|
|
/** end the current command */
|
|
void endCMD() {
|
|
deselect();
|
|
//spi.writeByte(0xFF); // SanDisk Manual Page 5-6 (required for card to finish current operation)
|
|
}
|
|
|
|
|
|
R1 cmd0() { // SanDisk Manual Page 5-1
|
|
sendCMD(0, 0x00, 0x00, 0x00, 0x00, 0x4A);
|
|
R1 res; readResponse(&res.raw, 1);
|
|
endCMD();
|
|
debugMod1(NAME, "cmd0: %02x", res.raw);
|
|
return res;
|
|
}
|
|
|
|
/** determine whether this is a V2 card?? */
|
|
Rcmd8 cmd8() {
|
|
sendCMD(8, 0x00, 0x00, 0x01, 0xAA, 0b1000011); // correct? Needs Checksum!!
|
|
//select(); uint8_t tmp[] = {0b01001000, val>>24, val>>16, val>>8, val>>0, 0b00001111}; spi.write(tmp, 6);
|
|
//R3 res; readResponse(&res.raw[0], 1); // read the R1 part
|
|
//if (res.raw[0] == 1) { // command supported
|
|
// readResponse(&res.raw[1], 4); // read the rest of the response
|
|
//}
|
|
Rcmd8 res; readResponse(res.raw, 5);
|
|
endCMD();
|
|
debugMod5(NAME, "cmd8: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]);
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
/** read OCR (Operating Conditions Register) */
|
|
R3 cmd58() {
|
|
//select(); uint8_t tmp[] = {0b01111010, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b01110101}; spi.write(tmp, 6);
|
|
sendCMD(58, 0x00, 0x00, 0x00, 0x00, 0xFF);//0b0111010);
|
|
R3 res; readResponse(res.raw, 5); // typical response: 0x01 0x00 0xff 0x80 0x00
|
|
endCMD();
|
|
debugMod5(NAME, "cmd58: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]);
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
/** "next command is app specific" */
|
|
R1 cmd55() {
|
|
sendCMD(55, 0x00, 0x00, 0x00, 0x00, 0xFF);
|
|
R1 res; readResponse(&res.raw, 1);
|
|
endCMD();
|
|
debugMod1(NAME, "cmd55: %02x", res.raw);
|
|
return res;
|
|
}
|
|
|
|
R1 acmd41(uint32_t val) {
|
|
cmd55();
|
|
sendCMD(41, val>>24, val>>16, val>>8, val>>0, 0xFF);
|
|
R1 res; readResponse(&res.raw, 1);
|
|
endCMD();
|
|
debugMod1(NAME, "acmd41: %02x", res.raw);
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void readResponse(uint8_t* dst, uint8_t len) {
|
|
|
|
// the SD card takes some time to answer, but must receive clock signals meanwhile!
|
|
// we detect the actual response as soon as the received byte starts has the highest bit zero.
|
|
// [while busy the SD card responds with 0xFF]
|
|
// NOTE: it is IMPORTANT to send 0xFF to the card while reading its responses!
|
|
|
|
// wait for the first byte to arrive and read it
|
|
for (uint8_t i = 0; i < 4; ++i) {
|
|
dst[0] = spi.readWriteByte(0xFF);
|
|
if ( (dst[0] & 0x80) == 0 ) {break;}
|
|
}
|
|
|
|
// read all subsequent bytes (if any)
|
|
for (uint8_t i = 1; i < len; ++i) {
|
|
dst[i] = spi.readWriteByte(0xFF);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** select the card (CS = low) */
|
|
void select() {
|
|
spi.writeByte(0xFF);
|
|
MyGPIO::clear(PIN_CS);
|
|
spi.writeByte(0xFF);
|
|
}
|
|
|
|
/** deselect the card (CS = high) */
|
|
void deselect() {
|
|
spi.writeByte(0xFF);
|
|
MyGPIO::set(PIN_CS);
|
|
spi.writeByte(0xFF);
|
|
}
|
|
|
|
};
|