Перейти к содержимому

#24. SPI: SD Memory Cards

В этой части рассмотрим, как связать стандартную карту памяти SD с платой STM32 с помощью SPI. Хотя связь с картой памяти SD представляет собой простое расширение ранее представленной работы, управление картой и интерпретация требуют значительного дополнительного программного обеспечения. К счастью, большая часть необходимого программного обеспечения доступна в широко используемом модуле FatFs. Для использования этого модуля с нашим драйвером SPI требуется лишь небольшое количество «портирования».

Процессоры STM32 на плате имеют относительно ограниченную память — флэш-память 64 Кбайт и оперативная память 20 Кбайт — что ограничивает возможность хранения больших объемов данных как в виде входных данных, так и выходных данных встроенной программы. Например, в игровом приложении может быть желательно получить доступ к звуковым и графическим файлам или в приложении для регистрации данных, чтобы хранить расширенные объемы данных. Кроме того, для доступа к содержимому флэш-памяти STM32 требуется специальный интерфейс и программное обеспечение. В таких приложениях желательно обеспечить внешнее хранилище, к которому STM32 может обращаться во время работы, а пользователь / программист может легко получить доступ в другое время. Карты флэш-памяти (в частности, карты SD) обеспечивают экономически эффективное решение, к которому может легко получить доступ как процессор, так и пользователь. На практике эти карты имеют файловые системы (обычно FAT) и могут быть вставлены в общедоступные адаптеры для доступа к настольному компьютеру. Кроме того, физический интерфейс имеет режим SPI, который доступен с помощью кода, описанных в предыдущих постах.

Физически карты памяти SD состоят из массива флэш-памяти и управляющего процессора, который обменивается данными с хостом по шине SD (параллельная шина) или шине SPI. Связь основана на транзакциях — хост отправляет командное сообщение на SD-карту и получает ответ. Доступ к флэш-памяти SD-карты осуществляется посредством чтения и записи блоков фиксированного размера, которые также реализуются с помощью командного протокола.

Данные на SD-карте организованы в виде файловой системы — карты объемом менее 2 ГБ обычно форматируются как файловые системы FAT16. В файловой системе FAT первые несколько блоков хранения используются для хранения данных о файловой системе — например, таблиц размещения — в то время как остальные блоки используются для хранения содержимого файлов и каталогов.

SD Card Software Stack

Хотя ранее мы рассматривали драйвер шины SPI, который может взаимодействовать с SD-картой, нам не хватает нескольких ключевых программных компонентов. Рассмотрим рисунок выше, который иллюстрирует необходимый программный стек. Приложение, которое хочет получить доступ к данным, хранящимся на SD-карте, использует команды на уровне файлов, такие как открытие, чтение и запись, для доступа к определенным файлам в файловой системе SD-карты. Эти команды предоставляются драйвером файловой системы FAT. Файловая система FAT выдает команды на уровне чтения и записи блоков, не зная, как эти команды реализованы. Отдельный драйвер SD реализует эти команды. Наконец, драйвер SD использует интерфейс SPI для связи с SD-картой.

К счастью, нет необходимости писать все это программное обеспечение. Мы опишем использование общей файловой системы FatFs. Этот пакет с открытым исходным кодом содержит большинство необходимых компонентов, в том числе «универсальный» драйвер SD, который относительно легко модифицировать для использования драйвера SPI, представленного.
Чтобы понять, как приложение взаимодействует с FatFs, рассмотрим пример, полученный из примера распределения FatFs, показанного в листинге ниже. В этом примере фрагмента предполагается, что он обменивается данными с SD-картой, отформатированной в файловой системе FAT, которая содержит файл в корневом каталоге с именем MESSAGE.TXT. Программа читает этот файл и создает другой файл с именем HELLO.TXT. Обратите внимание на использование относительно стандартных команд файловой системы.

f_mount(0, &Fatfs);/* Register volume work area */
xprintf("\nOpen an existing file (message.txt).\n");
rc = f_open(&Fil, "MESSAGE.TXT", FA_READ);
if (!rc) {
xprintf("\nType the file content.\n");
for (;;) {
/* Read a chunk of file */
rc = f_read(&Fil, Buff, sizeof Buff, &br);
if (rc || !br) break;/* Error or end of file */
for (i = 0; i < br; i++)/* Type the data */
myputchar(Buff[i]);
}
if (rc) die(rc);
xprintf("\nClose the file.\n");
rc = f_close(&Fil);
if (rc) die(rc);
}
xprintf("\nCreate a new file (hello.txt).\n");
rc = f_open(&Fil, "HELLO.TXT", FA_WRITE | FA_CREATE_ALWAYS);
if (rc) die(rc);
xprintf("\nWrite a text data. (Hello world!)\n");
rc = f_write(&Fil, "Hello world!\r\n", 14, &bw);
if (rc) die(rc);
xprintf("%u bytes written.\n", bw);
FatFs Distribution Organization

Интерфейс между драйвером файловой системы FAT и драйвером SD определен в модуле diskio.h. Мы изменили дистрибутив по умолчанию, чтобы включить значимые имена параметров. Инициализированный диск можно читать, записывать и контролировать (ioctl). Команды чтения / записи ограничены уровнем блоков (размер блоков зависит от SD-карты). Функция ioctl обеспечивает средство для определения «геометрии» SD-карты (например, количества и размера блоков) и может предоставлять дополнительные функциональные возможности для обеспечения управления мощностью. Низкоуровневая реализация, описанная в дальнейшем, поддерживает только функции ioctl для определения «геометрии» диска и для принудительного завершения отложенных записей.

Low Level Driver Interface:

/*---------------------------------------*/
/* Prototypes for disk control functions */
int assign_drives (int, int);
DSTATUS disk_initialize (BYTE drv);
DSTATUS disk_status (BYTE drv);
DRESULT disk_read (BYTE drv, BYTE* buff, DWORD sector , BYTE count);
#if _READONLY == 0
DRESULT disk_write (BYTE drv, const BYTE* buff, DWORD sector , BYTE
,→count);
#endif
DRESULT disk_ioctl (BYTE drv, BYTE ctl, void* buff);

Как описано ранее, драйвер SD реализует пять функций для
поддержки FatFs и использует драйвер SPI для связи с SDCard. Протокол связи SDCard основан на транзакциях. Каждая транзакция начинается с однобайтового кода команды, за которым могут следовать параметры, а затем передача данных (чтение или запись). Далее мы рассмотрим несколько примеров, иллюстрирующих основные понятия. К счастью, нет необходимости создавать этот модуль с нуля. Существуют примеров проектов на: http://elm-chan.org/fsw/ff/ffsample.zip — мы используем общий пример. Кроме того, есть более сложный проект для STM32 (http://www.siwawi.arubi.unikl.de/avr_projects/arm_projects/arm_memcards/index.html#stm32_memcard). Пример кода организован так, как показано ниже.

FatFs Sample Code Organization
SD Transactions

Три основных типа транзакций, которые нас интересуют, показаны на рисунке выше. Каждая транзакция начинается с команды, выданной хостом (в данном случае STM32), за которой следует ответ с SD-карты. Для операций чистого управления (например, запрос состояния или конфигурации карты) ответ карты SD завершает транзакции. Для транзакций чтения или записи за ответом следует передача блока данных от (запись) или к хосту (чтение). Существуют и другие случаи, включая многоблочные транзакции данных и условия ошибок, которые не показаны. Должно быть понятно, что эти типы транзакций достаточны для реализации функциональности, требуемой FatFS.

Мы не будем углубляться в формат информации, передаваемой во время транзакций, за исключением перечисления нескольких команд, определенных в спецификациях SD-карты, и указания на то, что все поля транзакции могут быть дополнительно защищены кодами CRC. Подмножество команд SD-карты показано в таблице. Обратите внимание, что есть команды для сброса и инициализации карты, чтения / записи параметров (например, длины блока) и чтения / записи блоков данных. Эти команды поддерживаются несколькими форматами ответов, длина которых варьируется от 1 до 5.

Some SD Card Commands

Наша реализация драйвера SD — это простой порт generic / mmbc.c. Существует только небольшое количество подпрограмм, которые необходимо изменить, чтобы использовать этот модуль. Они представлены в листингах ниже. Кроме того, в примере кода используется функция ожидания (DLY_US), которая считает микросекунды, а наша задержка — миллисекунды. Необходимо внести соответствующие изменения во всем коде. В большинстве периодов задержки нет, ничего принципиального, но любые изменения должны пытаться сделать аналогичную общую задержку. Наконец, мы изменили подпрограмму disk_initialize, чтобы установить скорость SPI на медленную во время инициализации и высокую после успешной инициализации.

FatFs — система с широкими возможностями настройки. Конфигурация контролируется через ffconf.h.

Чтобы использовать xprintf, нужно включить в свой код в
main:

#include "xprintf.h"
void myputchar(unsigned char c)
{
uart_putc(c, USART1);
}
unsigned char mygetchar()
{
return uart_getc(USART1);
}
int main(void)
{
...
xfunc_in = mygetchar;
xfunc_out = myputchar;
...
}

Подпрограмма SD Driver:

#include <stm32f10x.h>
#include <stm32f10x_spi.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_gpio.h>
#include "spi.h"
#define GPIO_Pin_CS GPIO_Pin_6
#define GPIO_CS GPIOC
#define RCC_APB2Periph_GPIO_CS RCC_APB2Periph_GPIOC
#define SD_SPI SPI2
enum spiSpeed Speed = SPI_SLOW;
void Delay(uint32_t);
/* ... */
/*----------------------------*/
/* Transmit bytes to the card */
/*----------------------------*/
static
void xmit_mmc (
const BYTE* buff, /* Data to be sent */
UINT bc /* Number of bytes to send */
)
{
spiReadWrite(SD_SPI , 0, buff, bc, Speed);
}
/*-----------------------------*/
/* Receive bytes from the card */
/*-----------------------------*/
static
void rcvr_mmc (
BYTE *buff, /* Pointer to read buffer */
UINT bc /* Number of bytes to receive */
)
{
spiReadWrite(SD_SPI , buff, 0, bc, Speed);
}
/*-----------------------*/
/* Deselect the card */
/*-----------------------*/
static
void deselect (void)
{
BYTE d;
GPIO_SetBits(GPIO_CS , GPIO_Pin_CS);
rcvr_mmc(&d, 1); /* Dummy clock (force DO hi-z for multiple
,→slave SPI) */
}
/*-------------------*/
/* Select the card */
/*-------------------*/
static
int select (void) /* 1:OK, 0:Timeout */
{
BYTE d;
GPIO_ResetBits(GPIO_CS , GPIO_Pin_CS);
rcvr_mmc(&d, 1); /* Dummy clock (force DO enabled) */
if (wait_ready()) return 1; /* OK */
deselect();
return 0; /* Failed */
}
INIT_PORT()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_CS , ENABLE);
/* Configure I/O for Flash Chip select */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_CS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_CS , &GPIO_InitStructure);
deselect();
}

Добавить комментарий