Start work on SD card driver
Working: - card initialization - card size & type detection - read block TODO: - write block - Use interrupts and DMA for read/write block
This commit is contained in:
21
software/src/standalone-mp3/CMakeLists.txt
Normal file
21
software/src/standalone-mp3/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# initialize pico-sdk from submodule
|
||||
# note: this must happen before project()
|
||||
include(../../lib/micropython/lib/pico-sdk/pico_sdk_init.cmake)
|
||||
|
||||
project(standalone_mp3)
|
||||
|
||||
# initialize the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
add_executable(standalone_mp3
|
||||
main.c
|
||||
sd.c
|
||||
sd_spi.c
|
||||
)
|
||||
|
||||
target_link_libraries(standalone_mp3 PRIVATE pico_stdlib hardware_spi)
|
||||
|
||||
pico_add_extra_outputs(standalone_mp3)
|
||||
pico_enable_stdio_uart(standalone_mp3 1)
|
||||
27
software/src/standalone-mp3/main.c
Normal file
27
software/src/standalone-mp3/main.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "sd.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/spi.h>
|
||||
#include <pico/stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
stdio_init_all();
|
||||
|
||||
if (!sd_init()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t buf[512];
|
||||
if (sd_readblock(0, buf)) {
|
||||
for(int row = 0;row < 512/16;++row) {
|
||||
printf("%03x: ", row*16);
|
||||
for (int col = 0; col < 16;++col)
|
||||
printf("%02hhx%c", buf[row*16+col], (col==15)?'\n':' ');
|
||||
}
|
||||
}
|
||||
|
||||
printf("Done.\n");
|
||||
}
|
||||
237
software/src/standalone-mp3/sd.c
Normal file
237
software/src/standalone-mp3/sd.c
Normal file
@@ -0,0 +1,237 @@
|
||||
#include "sd.h"
|
||||
#include "sd_util.h"
|
||||
#include "sd_spi.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SD_DEBUG
|
||||
|
||||
#define SD_R1_ILLEGAL_COMMAND (1 << 2)
|
||||
|
||||
struct sd_context {
|
||||
size_t blocks;
|
||||
bool initialized;
|
||||
bool old_card;
|
||||
bool sdhc_sdxc;
|
||||
};
|
||||
|
||||
static struct sd_context sd_context;
|
||||
|
||||
static bool sd_acmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t res[static resplen])
|
||||
{
|
||||
if (!sd_cmd(55, 0, 1, res))
|
||||
return false;
|
||||
if ((res[0] & 0x7e) != 0x00)
|
||||
return false;
|
||||
|
||||
return sd_cmd(cmd, arg, resplen, res);
|
||||
}
|
||||
|
||||
static bool sd_early_init(void)
|
||||
{
|
||||
uint8_t buf;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if (sd_cmd(0, 0, 1, &buf)) {
|
||||
#ifdef SD_DEBUG
|
||||
printf("CMD0 resp %02hhx\n", buf);
|
||||
#endif
|
||||
if (buf == 0x01) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#ifdef SD_DEBUG
|
||||
printf("CMD0 timeout, try again...\n");
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sd_check_interface_condition(void)
|
||||
{
|
||||
uint8_t buf[5];
|
||||
if (sd_cmd(8, 0x000001AA, 5, buf)) {
|
||||
if ((buf[3] & 0xf) != 0x1 || buf[4] != 0xAA) {
|
||||
printf("sd_init: check interface condition failed\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (buf[0] & SD_R1_ILLEGAL_COMMAND) {
|
||||
printf("sd_init: check interface condition returned illegal command - old card?\n");
|
||||
} else {
|
||||
printf("sd_init: check interface condition failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sd_send_op_cond(void)
|
||||
{
|
||||
uint8_t buf[1];
|
||||
bool use_acmd = true;
|
||||
for (int timeout = 0; timeout < 500; ++timeout) {
|
||||
bool result = false;
|
||||
if (use_acmd)
|
||||
result = sd_acmd(41, 0x40000000, 1, buf);
|
||||
else
|
||||
result = sd_cmd(1, 0x40000000, 1, buf);
|
||||
if (!result) {
|
||||
if (use_acmd && buf[0] & SD_R1_ILLEGAL_COMMAND) {
|
||||
printf("sd_init: card does not understand ACMD41, try CMD1...\n");
|
||||
continue;
|
||||
} else if (buf[0] != 0x01) {
|
||||
printf("sd_init: send_op_cond failed\n");
|
||||
return false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (buf[0] == 0x00) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
printf("sd_init: send_op_cond: timeout waiting for !idle\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sd_read_ocr(uint32_t *const ocr)
|
||||
{
|
||||
uint8_t buf[5];
|
||||
if (!sd_cmd(58, 0, 5, buf))
|
||||
return false;
|
||||
*ocr = buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | buf[4];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sd_dump_cid(void)
|
||||
{
|
||||
uint8_t buf[16];
|
||||
if (sd_cmd_read(10, 0, 16, buf)) {
|
||||
const uint8_t crc = sd_crc7(15, buf);
|
||||
if (buf[15] >> 1 != crc) {
|
||||
printf("CRC mismatch: Got %02hhx, expected %02hhx\n", buf[15] >> 1, crc);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t mid = buf[0];
|
||||
char oid[2], pnm[5];
|
||||
memcpy(oid, buf + 1, 2);
|
||||
memcpy(pnm, buf + 3, 5);
|
||||
uint8_t prv = buf[8];
|
||||
uint32_t psn = buf[9] << 24 | buf[10] << 16 | buf[11] << 8 | buf[12];
|
||||
int mdt_year = 2000 + ((buf[13] & 0xf) << 4 | (buf[14] & 0xf0) >> 4);
|
||||
int mdt_month = buf[14] & 0x0f;
|
||||
printf("CID: mid: %02hhx, oid: %.2s, pnm: %.5s, prv: %02hhx, psn: %08x, mdt_year: %d, mdt_month: %d\n", mid,
|
||||
oid, pnm, prv, psn, mdt_year, mdt_month);
|
||||
}
|
||||
}
|
||||
|
||||
static bool sd_read_csd(void)
|
||||
{
|
||||
uint8_t buf[16];
|
||||
if (sd_cmd_read(9, 0, 16, buf)) {
|
||||
const uint8_t crc = sd_crc7(15, buf);
|
||||
if (buf[15] >> 1 != crc) {
|
||||
printf("CRC mismatch: Got %02hhx, expected %02hhx\n", buf[15] >> 1, crc);
|
||||
return false;
|
||||
}
|
||||
const unsigned csd_ver = buf[0] >> 6;
|
||||
unsigned blocksize = 0;
|
||||
unsigned blocks = 0;
|
||||
unsigned version = 0;
|
||||
switch (csd_ver) {
|
||||
case 0: {
|
||||
if (sd_context.sdhc_sdxc) {
|
||||
printf("sd_init: Got CSD v1.0 but card is SDHC/SDXC?\n");
|
||||
return false;
|
||||
}
|
||||
const unsigned read_bl_len = buf[5] & 0xf;
|
||||
if (read_bl_len < 9 || read_bl_len > 11) {
|
||||
printf("Invalid read_bl_len in CSD 1.0\n");
|
||||
return false;
|
||||
}
|
||||
blocksize = 1 << (buf[5] & 0xf);
|
||||
const unsigned c_size_mult = (buf[9] & 0x1) << 2 | (buf[10] & 0xc0) >> 6;
|
||||
const unsigned c_size = (buf[6] & 0x3) << 10 | (buf[7] << 2) | (buf[8] & 0xc0) >> 6;
|
||||
blocks = (c_size + 1) * (1 << (c_size_mult + 2));
|
||||
version = 1;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
blocksize = 512;
|
||||
const unsigned c_size = (buf[7] & 0x3f) << 16 | buf[8] << 8 | buf[9];
|
||||
blocks = (c_size + 1) * 1024;
|
||||
version = 2;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
printf("sd_init: Got CSD v3.0, but SDUC does not support SPI.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sd_context.blocks = blocks;
|
||||
#ifdef SD_DEBUG
|
||||
printf("CSD version %u.0, blocksize %u, blocks %u, capacity %llu MiB\n", version, blocksize, blocks,
|
||||
((uint64_t)blocksize * blocks) / (1024 * 1024));
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_init(void)
|
||||
{
|
||||
sd_spi_init();
|
||||
if (!sd_early_init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sd_check_interface_condition()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ocr;
|
||||
if (!sd_read_ocr(&ocr)) {
|
||||
printf("sd_init: read OCR failed\n");
|
||||
return false;
|
||||
}
|
||||
if ((ocr & 0x00380000) != 0x00380000) {
|
||||
printf("sd_init: unsupported card voltage range\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sd_send_op_cond())
|
||||
return false;
|
||||
|
||||
sd_spi_set_bitrate(SD_BITRATE);
|
||||
|
||||
if (!sd_read_ocr(&ocr)) {
|
||||
printf("sd_init: read OCR failed\n");
|
||||
return false;
|
||||
}
|
||||
if (!(ocr & (1 << 31))) {
|
||||
printf("sd_init: card not powered up but !idle?\n");
|
||||
return false;
|
||||
}
|
||||
sd_context.sdhc_sdxc = (ocr & (1 << 30));
|
||||
|
||||
if (!sd_read_csd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SD_DEBUG
|
||||
sd_dump_cid();
|
||||
#endif
|
||||
|
||||
sd_context.initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_readblock(size_t sector_num, uint8_t buffer[static 512])
|
||||
{
|
||||
if (!sd_context.initialized || sector_num >= sd_context.blocks)
|
||||
return false;
|
||||
|
||||
return sd_cmd_read(17, sector_num, 512, buffer);
|
||||
}
|
||||
9
software/src/standalone-mp3/sd.h
Normal file
9
software/src/standalone-mp3/sd.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool sd_init(void);
|
||||
|
||||
bool sd_readblock(size_t sector_num, uint8_t buffer[static 512]);
|
||||
101
software/src/standalone-mp3/sd_spi.c
Normal file
101
software/src/standalone-mp3/sd_spi.c
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "sd_spi.h"
|
||||
#include "sd_util.h"
|
||||
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/spi.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void sd_spi_cmd_send(const uint8_t cmd, const uint32_t arg)
|
||||
{
|
||||
uint8_t buf[6] = {0x40 | cmd, arg >> 24, arg >> 16, arg >> 8, arg, 0};
|
||||
buf[5] = sd_crc7(5, buf) << 1 | 1;
|
||||
gpio_put(SD_CS, false);
|
||||
// Write command, argument and CRC
|
||||
spi_write_blocking(SD_SPI, buf, 6);
|
||||
}
|
||||
|
||||
bool sd_cmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t resp[static resplen])
|
||||
{
|
||||
sd_spi_cmd_send(cmd, arg);
|
||||
resp[0] = 0;
|
||||
// Read up to 8 garbage bytes (0xff), followed by R1 (MSB is zero)
|
||||
bool got_r1 = false;
|
||||
for (int timeout = 0; timeout < 8; ++timeout) {
|
||||
spi_read_blocking(SD_SPI, 0xff, resp, 1);
|
||||
if (!(resp[0] & 0x80)) {
|
||||
got_r1 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (got_r1 && (resp[0] & 0x7e) == 0) {
|
||||
// read rest of response if R1 does not indicate an error
|
||||
spi_read_blocking(SD_SPI, 0xff, resp + 1, resplen - 1);
|
||||
}
|
||||
gpio_put(SD_CS, true);
|
||||
const uint8_t buf = 0xff;
|
||||
// Ensure 8 SPI clock cycles after CS deasserted
|
||||
spi_write_blocking(SD_SPI, &buf, 1);
|
||||
|
||||
return got_r1 && (resp[0] & 0x7e) == 0;
|
||||
}
|
||||
|
||||
bool sd_cmd_read(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen])
|
||||
{
|
||||
uint8_t buf[2];
|
||||
sd_spi_cmd_send(cmd, arg);
|
||||
// Read up to 8 garbage bytes (0xff), followed by R1 (MSB is zero)
|
||||
bool got_r1 = false;
|
||||
for (int timeout = 0; timeout < 8; ++timeout) {
|
||||
spi_read_blocking(SD_SPI, 0xff, buf, 1);
|
||||
if (!(buf[0] & 0x80)) {
|
||||
got_r1 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!got_r1 || buf[0] != 0x00)
|
||||
goto abort;
|
||||
while (true) { // TODO: what is a sensible timeout here?
|
||||
spi_read_blocking(SD_SPI, 0xff, buf, 1);
|
||||
if (buf[0] != 0xff)
|
||||
break;
|
||||
}
|
||||
if (buf[0] != 0xfe) // unexpected read token
|
||||
goto abort;
|
||||
spi_read_blocking(SD_SPI, 0xff, data, datalen);
|
||||
spi_read_blocking(SD_SPI, 0xff, buf, 2);
|
||||
// we ignore the CRC16 for performance reasons
|
||||
gpio_put(SD_CS, true);
|
||||
spi_read_blocking(SD_SPI, 0xff, buf, 1);
|
||||
return true;
|
||||
|
||||
abort:
|
||||
gpio_put(SD_CS, true);
|
||||
spi_read_blocking(SD_SPI, 0xff, buf, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
void sd_spi_init(void)
|
||||
{
|
||||
gpio_set_function(SD_MISO, GPIO_FUNC_SPI);
|
||||
gpio_set_function(SD_SCK, GPIO_FUNC_SPI);
|
||||
gpio_set_function(SD_MOSI, GPIO_FUNC_SPI);
|
||||
gpio_init(SD_CS);
|
||||
gpio_set_dir(SD_CS, true);
|
||||
|
||||
sd_spi_set_bitrate(SD_INIT_BITRATE);
|
||||
spi_set_format(SD_SPI, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||
|
||||
gpio_put(SD_CS, true);
|
||||
uint8_t buf[16];
|
||||
memset(buf, 0xff, 16);
|
||||
// Ensure at least 74 SPI clock cycles without CS asserted
|
||||
spi_write_blocking(SD_SPI, buf, 10);
|
||||
|
||||
}
|
||||
|
||||
void sd_spi_set_bitrate(const int rate)
|
||||
{
|
||||
const unsigned actual_rate = spi_init(SD_SPI, rate);
|
||||
printf("SPI init: Requested bitrate %u, got %u\n", rate, actual_rate);
|
||||
}
|
||||
20
software/src/standalone-mp3/sd_spi.h
Normal file
20
software/src/standalone-mp3/sd_spi.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define SD_MISO 4
|
||||
#define SD_SCK 2
|
||||
#define SD_MOSI 3
|
||||
#define SD_CS 5
|
||||
|
||||
#define SD_SPI spi0
|
||||
|
||||
#define SD_INIT_BITRATE 400000
|
||||
#define SD_BITRATE 25000000
|
||||
|
||||
bool sd_cmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t resp[static resplen]);
|
||||
bool sd_cmd_read(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen]);
|
||||
|
||||
void sd_spi_init(void);
|
||||
void sd_spi_set_bitrate(const int rate);
|
||||
30
software/src/standalone-mp3/sd_util.h
Normal file
30
software/src/standalone-mp3/sd_util.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
inline static uint8_t sd_crc7(size_t len, const uint8_t data[const static len])
|
||||
{
|
||||
const uint8_t poly = 0b1001;
|
||||
uint8_t crc = 0;
|
||||
for (size_t pos = 0; pos < len; ++pos) {
|
||||
crc ^= data[pos];
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
crc = (crc << 1) ^ ((crc & 0x80) ? (poly << 1) : 0);
|
||||
}
|
||||
}
|
||||
return crc >> 1;
|
||||
}
|
||||
|
||||
/* inline static uint16_t sd_crc16(size_t len, const uint8_t data[const static len]) */
|
||||
/* { */
|
||||
/* const uint16_t poly = 0b1000000100001; */
|
||||
/* uint16_t crc = 0; */
|
||||
/* for (size_t pos = 0; pos < len; ++pos) { */
|
||||
/* crc ^= data[pos] << 8; */
|
||||
/* for (int bit = 0; bit < 8; ++bit) { */
|
||||
/* crc = (crc << 1) ^ ((crc & 0x8000) ? poly : 0); */
|
||||
/* } */
|
||||
/* } */
|
||||
/* return crc; */
|
||||
/* } */
|
||||
Reference in New Issue
Block a user