Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@ add_library(tinysd_sdhci INTERFACE)
target_sources(tinysd_sdhci INTERFACE src/host/sdhci.c)
target_include_directories(tinysd_sdhci INTERFACE include)

# SPI host driver target
add_library(tinysd_spi INTERFACE)
target_sources(tinysd_spi INTERFACE src/host/spi.c)
target_include_directories(tinysd_spi INTERFACE include)
target_link_libraries(tinysd_spi INTERFACE tinysd_frame)

# Frame and CRC helpers target, for software hosts
add_library(tinysd_frame INTERFACE)
target_sources(tinysd_frame INTERFACE src/frame.c src/crc.c)
target_include_directories(tinysd_frame INTERFACE include)

# The whole library
add_library(tinysd INTERFACE)
target_link_libraries(tinysd INTERFACE tinysd_card tinysd_sdio tinysd_sdhci tinysd_frame)
target_link_libraries(
tinysd
INTERFACE tinysd_card tinysd_sdio tinysd_sdhci tinysd_spi tinysd_frame
)

# Build tests by default only when tinysd is the top-level project
option(TINYSD_BUILD_TESTS "Build tinysd tests" ${PROJECT_IS_TOP_LEVEL})
Expand Down
68 changes: 58 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Dependency-free C library for SD cards and SDIO on embedded and bare-metal syste
- SD memory card driver (SDSC, SDHC and SDXC) with high speed support
- SDIO card driver with byte and block transfers, card interrupts, and high speed support
- SDHCI host driver for SD Host Controller versions 1 to 3
- SPI host driver for SD memory cards on any SPI peripheral
- Simple interface for adding your own SD hosts
- CRC and framing helpers for software hosts (bit-banged, PIO, etc)
- Polling API, no threads, callbacks or interrupts
Expand Down Expand Up @@ -51,7 +52,7 @@ void tsd_delay_us(uint32_t microseconds)

### Initializing a Host

A host drives the SD bus, and the card drivers talk to it through `struct tsd_host`.
A host drives the SD bus, and the card drivers talk to it through `struct tsd_host`. TinySD has built-in hosts for SDHCI controllers and SPI buses, or you can write your own.

For example, if using the built-in SDHCI host controller:

Expand All @@ -66,11 +67,11 @@ int ret = tsd_sdhci_init(&sdhci, config);
struct tsd_host *host = TSD_HOST(&sdhci);
```

For other hosts, see [Writing Your Own Host](#writing-your-own-host).
See [Hosts](#hosts) for how to set up each built-in host, and how to write your own.

### Using SD Memory Cards

Bring the card up at the bus width and highest clock you want. It runs slower if the card cannot keep up.
Bring the card up on 1 or 4 data lines, at the highest clock you want in kHz, such as 50000 for 50 MHz. A card without high speed runs at 25 MHz instead. An SPI host has one data line, so pass 1.

```c
static struct tsd_card card;
Expand All @@ -87,9 +88,9 @@ ret = tsd_card_write(&card, buffer, 0, 1);
ret = tsd_card_sync(&card);
```

### Bring Your Own Filesystem
#### Bring Your Own Filesystem

TinySD reads and writes raw blocks, so it is usually paired with a filesystem such as [FatFs](https://elm-chan.org/fsw/ff/), [littlefs](https://github.com/littlefs-project/littlefs), etc.
TinySD reads and writes raw blocks, so it is usually paired with a filesystem such as ChaN's [FatFs](https://elm-chan.org/fsw/ff/), [littlefs](https://github.com/littlefs-project/littlefs), etc.

For example, when using FatFs:

Expand All @@ -103,7 +104,6 @@ For example, when using FatFs:
| `GET_SECTOR_SIZE` in `disk_ioctl` | `TSD_CARD_BLOCK_SIZE` |
| `GET_SECTOR_COUNT` in `disk_ioctl` | `card.blocks` |


### Using SDIO Devices

Bring the card up, then enable the function your device uses and set its block size.
Expand All @@ -127,13 +127,56 @@ ret = tsd_sdio_transfer(&sdio, 1, 0x100, false, buffer, sizeof(buffer), 64, NULL

After init, the ids from the card's CIS are in `sdio.cis`. To read them without bringing the card up, call `tsd_sdio_identify`, which fills in a `struct tsd_sdio_cis` and leaves the card for another driver.

## Running from RAM
## Hosts

To speed up transfers, or to keep them running while flash is unavailable, place `card_io.c`, `sdio_io.c`, `frame.c` and `crc.c` in RAM from your linker script, along with their read-only data. Everything they call must be in RAM too, including your host's operations, `tsd_delay_us` and compiler helpers such as `memcpy`.
### SDHCI

The SDHCI host drives an SD Host Controller, versions 1 to 3, through its registers.

```c
static struct tsd_sdhci sdhci;

struct tsd_sdhci_config config = {
// Register base address, as the CPU sees it
.base = SDHCI_BASE,

// Map buffers for DMA, or leave these NULL to move data through the FIFO
.dma_map = my_dma_map,
.dma_unmap = my_dma_unmap,
};
int ret = tsd_sdhci_init(&sdhci, config);
```

### SPI

The SPI host runs a memory card in SPI mode, through four functions you provide. SDIO is not supported on SPI hosts.

## Writing Your Own Host
```c
static struct tsd_spi spi;

struct tsd_spi_config config = {
// Send bytes and ignore what comes back
.write = my_spi_write,

// Receive bytes while sending 0xff
.read = my_spi_read,

// Drive chip select low to select the card, or high to release it
.select = my_spi_select,

// Run the clock at the highest rate at or below a limit in kHz
.set_clock = my_spi_set_clock,
};

// Start the host, which clocks the card awake at 400 kHz
int ret = tsd_spi_init(&spi, config);
```

For wiring, see ChaN's excellent [How to Use MMC/SDC](https://elm-chan.org/docs/mmc/mmc_e.html) guide to SD cards in SPI mode. It covers the pinout, the pull-ups and the power a card needs.

For systems without an SDHCI controller, you can write your own host, such as one that bit-bangs the bus or drives it with PIO. Hosts like these can build and check frames with `frame.h` and `crc.h`.
### Your Own Host

For systems without an SDHCI controller or an SPI peripheral, you can write your own host, such as one that bit-bangs the bus or drives it with PIO. Hosts like these can build and check frames with `frame.h` and `crc.h`.

Make a struct that embeds `struct tsd_host` as its first field, and point it at a table of your host's operations.

Expand All @@ -146,13 +189,18 @@ static const struct tsd_host_api my_api = {
.request = my_request,
.set_clock = my_set_clock,
.set_bus_width = my_set_bus_width,
.card_busy = my_card_busy,
};

static struct my_host my_host = {.base.api = &my_api};
```

Then pass `TSD_HOST(&my_host)` to the card drivers.

## Running from RAM

To speed up transfers, or to keep them running while flash is unavailable, place `card_io.c`, `sdio_io.c`, `frame.c` and `crc.c` in RAM from your linker script, along with their read-only data. Everything they call must be in RAM too, including your host's operations, `tsd_delay_us` and compiler helpers such as `memcpy`.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
1 change: 1 addition & 0 deletions include/tinysd.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
#include <tinysd/frame.h>
#include <tinysd/host.h>
#include <tinysd/host/sdhci.h>
#include <tinysd/host/spi.h>
#include <tinysd/sdio.h>
2 changes: 1 addition & 1 deletion include/tinysd/card.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct tsd_card {
*
* @param card the card
* @param host the host the card sits on, started by the caller
* @param width the bus width, 1 or 4
* @param width the bus width, 1 or 4, and 1 on an SPI host
* @param clock_khz the highest clock in kHz, held to 25000 for a card without high speed
* @return 0 or a negative tsd_error
*/
Expand Down
3 changes: 3 additions & 0 deletions include/tinysd/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
#define TSD_IF_COND_VOLTAGE_2_7_3_6V 1
#define TSD_IF_COND_CHECK_PATTERN 0xaa

/** Bytes in the CSD, which SPI mode reads as a data block. */
#define TSD_CSD_LEN 16

/** CSD structure, in response word 0, which holds CSD bits 127 to 96. */
#define TSD_CSD_STRUCTURE TSD_BITS(31, 30)

Expand Down
22 changes: 22 additions & 0 deletions include/tinysd/host.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ struct tsd_host_api {
*/
void (*set_bus_width)(struct tsd_host *host, uint8_t width);

/**
* Report whether the card holds its data line low while it programs or erases.
*
* @param host the host
* @return 1 while the card is busy, 0 once it is free, or a negative tsd_error
*/
int (*card_busy)(struct tsd_host *host);

/**
* Enable or disable the card interrupt. Optional, and NULL for a host without a card interrupt.
*
Expand All @@ -133,6 +141,9 @@ struct tsd_host_api {
* @return 0 or a negative tsd_error
*/
int (*continue_read)(struct tsd_host *host, void *buffer, uint32_t count);

/** The host runs the card in SPI mode. */
bool spi;
};

/**
Expand Down Expand Up @@ -199,6 +210,17 @@ static inline void tsd_host_set_bus_width(struct tsd_host *host, uint8_t width)
host->api->set_bus_width(host, width);
}

/**
* Report whether the card holds its data line low while it programs or erases.
*
* @param host the host
* @return 1 while the card is busy, 0 once it is free, or a negative tsd_error
*/
static inline int tsd_host_card_busy(struct tsd_host *host)
{
return host->api->card_busy(host);
}

/**
* Enable or disable the card interrupt, on a host that has one.
*
Expand Down
49 changes: 49 additions & 0 deletions include/tinysd/host/spi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>

#include <tinysd/errors.h>
#include <tinysd/host.h>

struct tsd_spi;

/**
* How the host reaches the card's SPI bus, passed to tsd_spi_init.
*/
struct tsd_spi_config {
/** Send bytes, ignoring what comes back. Return 0 or a negative tsd_error. */
int (*write)(struct tsd_spi *spi, const uint8_t *buffer, uint32_t len);

/** Receive bytes while sending 0xff. Return 0 or a negative tsd_error. */
int (*read)(struct tsd_spi *spi, uint8_t *buffer, uint32_t len);

/** Drive chip select low to select the card, or high to release it. */
void (*select)(struct tsd_spi *spi, bool selected);

/** Run the clock at the highest rate at or below a limit in kHz. Return 0 or a negative tsd_error. */
int (*set_clock)(struct tsd_spi *spi, uint32_t clock_khz);
};

/** Macro to cast a generic host instance to an SPI host instance. */
#define TSD_SPI(host) ((struct tsd_spi *)(host))

/**
* A card in SPI mode on an SPI bus.
*/
struct tsd_spi {
/** The host interface, which card drivers take. */
struct tsd_host host;

/** The bus's configuration. */
struct tsd_spi_config config;
};

/**
* Initialize an SPI host.
*
* @param spi the host
* @param config the bus's configuration
* @return 0 or a negative tsd_error
*/
int tsd_spi_init(struct tsd_spi *spi, struct tsd_spi_config config);
Loading
Loading