- PVSM.RU - https://www.pvsm.ru -
Привет, Habr! Однажды на работе мне досталась задача оценить возможность реализации хранения данных на SD-карте при подключении ее к FPGA. В качестве интерфейса взаимодействия предполагалось использование SPI, так как он проще в реализации. Полученным опытом хотелось бы поделиться.

Так как место на печатной плате всегда ограничено, рассмотрение SD-карт будет выполняться на примере карт в форм-факторе MicroSD.
1. Чтение спецификации [1]
1.1 Общие сведения [2]
1.2 Инициализация [3]
1.3 Стирание информации [4]
1.4 Чтение информации [5]
1.4.1 Чтение одного блока данных [6]
1.4.2 Чтение множества блоков данных [7]
1.5 Запись информации [8]
1.5.1 Запись одного блока данных [9]
1.5.2 Запись множества блоков данных [10]
2. Реализация алгоритма в аппаратуре [11]
2.1 Компонент физического уровня [12]
2.2 Компонент командного уровня [13]
2.3 Компонент общения с внешним миром [14]
3. Проверка в железе [15]
4. Результаты [16]
Быстрое чтение спецификации сообщает нам следующие параметры SD-карт:
Отсюда сразу вытекает пункт о теоретической пиковой пропускной способности: 25МГц * 1 бит = 25 Мбит/с, что несколько маловато. Второй минус использования SD-карт, питание +3,3В. На печатной плате, на которую планировалось устанавливать SD-карту, такого напряжения нет.
Карты форм-фактора MicroSD по емкости можно разделить на 3 категории:
Общий вид карты приведен на рисунке ниже.

| Номер контакта | Имя | Тип | Описание |
|---|---|---|---|
| 1 | RSV | - | Не используется |
| 2 | CS | Вход | Выбор микросхемы |
| 3 | DI | Вход | Линия данных от Master устройства (MOSI) |
| 4 | Vdd | Питание | Напряжение питания |
| 5 | SCLK | Вход | Тактовый сигнал |
| 6 | Vss | Питание | Земля |
| 7 | DO | Выход | Линия данных в Master устройство (MISO) |
| 8 | RSV | - | Не используется |
Подключение осуществляется по схеме ниже:

Номинал резисторов должен быть 50 кОм
После включения питания, SD-карта находится в режиме работы SDIO. Для переключения в режим SPI необходимо выполнить инициализацию. Протокол работы с картой предполагает использование схемы контроля корректной передачи данных и команд в виде алгоритма CRC. При работе в режиме SPI проверка CRC выключена по умолчанию. Таким образом, первая команда, отправляемая в карту для переключения карты в режим SPI, должна содержать корректное значение CRC.
Команды, передаваемые по интерфейсу SPI, имеют размер 48 бит. Формат команд:
Биты 47 и 46 дают карте возможность однозначно отследить начало транзакции, так как шина MOSI в состоянии покоя равна единице.

На команды, используемые при работе с SD-картой, будут поступать ответы типа R1, R3, R7.

Ответ типа R1, размер 8 бит.
[17]
Ответ типа R3, размер 40 бит.
[18]
Ответ типа R7, размер 40 бит.
Все команды и ответы на них подробно расписаны в спецификации Physical Layer Simplified Specification [19]
Алгоритм инициализации:
После завершения инициализации с картой можно работать блоками по 512 байт с тактовой частотой в 25 МГц. Это полная версия алгоритма инициализации, которая охватывает все типы карт. В моем случае, при использовании карты на 16 Гбайт алгоритм инициализации состоял из шагов 1-6, 17-22, 16.
Карты форм-фактора Micro SD поддерживают команды стирания информации. После команды стирания информации, значение указанных адресов для стирания будет заполнено значением 0xFF или 0x00, в зависимости от карты.
Алгоритм стирания информации
Чтение информации с SD-карты по интерфейсу SPI возможно в двух вариантах.
При чтении одного блока данных, Master устройство формирует для SD-карты команду на чтение одного блока данных, ожидает ответа, что команда обработана и ожидает пакета с данными от карты. После получения пакета с данными от карты, транзакция чтения заканчивается.

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

Общий вид транзакции чтения множества блоков данных.

Структура Data Packet
Где:
В случае, если при чтении произошла ошибка, карта вместо Data Token возвращает Error Token. Размер Error Token – 1 байт. Биты 7..5 содержат значение нуля, биты 4..0 кодируют тип ошибки.
Алгоритм чтения множества блоков данных:
В алгоритме чтения есть небольшой нюанс. Сигнал выбора микросхемы (CS) должен быть установлен в состояние логического нуля перед формированием команды CMD18 и установлен в состояние логической единицы после получения ответа на команду CMD12.
Запись информации на SD-карту по интерфейсу SPI возможна в двух вариантах.
При записи одного блока данных, Master устройство формирует для SD-карты команду на запись одного блока данных, ожидает ответ от карты, что команда обработана, передает в карту пакет с данными для записи, ожидает от карты ответа, что данные записаны, завершает транзакцию.

Общий вид транзакции записи одного блока данных.
Как и с чтением, изначально была реализована запись одного блока данных. Результаты скорости получились неудовлетворительные.
При записи множества блоков данных, Master устройство формирует команду на запись множества блоков данных, ожидает ответ от карты, что команда обработана, передает в карту пакет с данными для записи, ожидает от карты ответа, что данные записаны. После получения ответа Master устройство передает в карту пакет со следующими данными для записи. Так будет продолжаться до тех пор, пока Master устройство не отправит Stop Data Token.

Общий вид транзакции записи множества блоков данных.
Структура Data Packet аналогична структуре Data Packet при чтении данных.
Формат:
Stop Tran Token, используемый для завершения команды записи, имеет размер 1 байт и равен значению 0xFD.
После того, как последний бит Data Packet был задвинут в карту, на следующий такт карта отвечает статусом записи данных – Data Response. Data Response имеет размер 1 байт, биты 7..5 могут быть любыми, бит 4 всегда равен нулю, бит 0 всегда равен единице, биты 3..1 кодируют статус записи данных. После того, как карта отдала Data Packet, карта притягивает линию MISO к нулю, индицируя занятость карты. После того, как на линии MISO будет уровень логической единицы, можно передавать в карту следующий пакет с данными.
Алгоритм записи множества блоков данных:
В алгоритме записи есть так же небольшой нюанс. Сигнал выбора микросхемы (CS) должен быть установлен в состояние логического нуля перед формированием команды CMD25 и установлен в состояние логической единицы после получения ответа на Stop Tran Token
В результате прочтения спецификации, получаем некоторые характеристики алгоритма, которые необходимо реализовать в аппаратуре.
Возможные режимы работы:
Возможные режимы тактового сигнала:
С моей точки зрения, алгоритм оптимально реализовать с помощью трех компонентов:
entity SDPhy is
generic ( gTCQ : time := 2 ns );
port ( -- Control bus
iPhyTxData : in std_logic_vector( 9 downto 0);
iPhyMode : in std_logic_vector( 4 downto 0);
iPhyTxWrite : in std_logic;
oPhyTxReady : out std_logic;
-- Out Data
oPhyRxData : out std_logic_vector( 7 downto 0);
oPhyRxWrite : out std_logic;
oPhyCmdEnd : out std_logic;
-- Spi
oSdCS : out std_logic;
oSdClk : out std_logic;
oSdMosi : out std_logic;
oSdMosiT : out std_logic;
iSdMiso : in std_logic;
-- system
sclk : in std_logic;
pclk : in std_logic;
rst : in std_logic );
end SDPhy;
Где:
В ПЛИС существуют специальные блоки для работы с тактовым сигналом (PLL, MMCM), однако, получить с их выхода тактовый сигнал меньше 5 МГц проблематично. В результате физический уровень работает на частоте 50 МГц. Вместе с каждыми данными в сигнале iPhyMode поступает бит, который указывает, на какой частоте эти данные должны быть переданы в SD-карту (или приняты от нее). В зависимости от бита скорости формируются сигналы разрешения тактового сигнала (Clock enable).
В компоненте физического уровня реализованы два автомата, для передачи данных в SD-карту и для приема данных от нее.
Код автомата для передачи данных: github [24].
Код автомата для приема данных: github [25].
entity SdCommand is
generic ( gTCQ : time := 2 ns );
port ( -- Command from host
oSdInitComp : out std_logic;
oSdInitFail : out std_logic;
iSdAddress : in std_logic_vector(31 downto 0);
iSdStartErase : in std_logic;
iSdStartRead : in std_logic;
iSdStartWrite : in std_logic;
oSdCmdFinish : out std_logic_vector( 1 downto 0);
oSdhcPresent : out std_logic;
-- Data
oSdReadData : out std_logic;
iSdDataR : in std_logic_vector(31 downto 0);
oSdWriteData : out std_logic;
oSdDataW : out std_logic_vector(32 downto 0);
-- Spi
oSdCS : out std_logic;
oSdClk : out std_logic;
oSdMosi : out std_logic;
oSdMosiT : out std_logic;
iSdMiso : in std_logic;
-- system
pclk : in std_logic;
sclk : in std_logic;
rst : in std_logic );
Где:
Остальные сигналы совпадают с сигналами физического уровня.
В компоненте реализованы 5 автоматов.
entity SdHost is
generic ( gTCQ : time := 2 ns );
port ( -- Sd Host command
iSdCommand : in std_logic_vector( 2 downto 0);
iSdAddress : in std_logic_vector(31 downto 0);
iSdStart : in std_logic;
oSdStatus : out std_logic_vector( 1 downto 0);
oSdInitFail : out std_logic;
-- Write data to card
iSdTxData : in std_logic_vector(31 downto 0);
iSdTxValid : in std_logic;
iSdTxLast : in std_logic;
oSdTxReady : out std_logic;
-- Read data from card
oSdRxData : out std_logic_vector(31 downto 0);
oSdRxValid : out std_logic;
oSdRxLast : out std_logic;
iSdRxReady : in std_logic;
-- Spi
oSdCS : out std_logic;
oSdClk : out std_logic;
oSdMosi : out std_logic;
oSdMosiT : out std_logic;
iSdMiso : in std_logic;
-- system
pclk : in std_logic;
sclk : in std_logic;
rst : in std_logic );
Где:
Остальные сигналы совпадают с сигналами физического уровня.
В компоненте реализован один автомат smSdControl (github [31]).
Алгоритм написан, в симуляторе проверен. Нужно проверить теперь в железе. Из того, что имелось в наличии, подошла плата Zybo от фирмы Digilent [32]
На ней имеются свободные выводы ПЛИС в банке с напряжением +3,3В, к которому можно легко подключить внешнее устройство. Да еще и тип используемой ПЛИС — Zynq-7000, значит там есть процессорное ядро. Можно написать тест на языке С, что несколько упросит задачу тестирования.
И так, подключаем реализованный алгоритм к процессорному ядру через порт GP (возможна работа по 4 байта, похоже на PIO). С прерываниями заморачиваться не будем, реализуем опрос по таймеру.
При работе на процессорном модуле алгоритм записи данных будет следующим:
Реализованный тест:
for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress ++)
{
if ((SectorAddress % 1024) == 0)
{
xil_printf("Data write to %d sector nr", SectorAddress);
}
/** Set address */
Xil_Out32(0x43c00008, SectorAddress);
/** Set command */
Xil_Out32(0x43c00004, 2);
/** Write data to PL */
for (int32_t i = 0; i < 1024; i++)
{
Xil_Out32(0x43c00014, cntrData);
cntrData++;
}
/** Start */
Xil_Out32(0x43c00000, 1);
/** Wait end of operation */
for (;;)
{
status = Xil_In32(0x43c0000c);
if (status == 0x01 || status == 0x03)
{
if (status == 0x03)
{
xil_printf("Error in write nr");
}
break;
}
else
{
cntrDuration++;
usleep(100);
}
}
/** Duration operation */
durationWrite += cntrDuration;
if (cntrDuration > MaxWrite )
{
MaxWrite = cntrDuration;
}
cntrDuration = 0x00;
/** Clear start */
Xil_Out32(0x43c00000, 0);
SectorAddress += 7;
}
На вопрос, почему в цикле используется внешняя граница 1024. В алгоритме задано количество блоков, равное 8. [34] Размер одного блока равен 512 байт. Общий размер 8 блоков данных составляет 8 * 512 байт = 4096 байт. Шина между процессорным модулем и программируемой логикой имеет размер 4 байта. Получается, чтобы переслать из процессорного модуля в программируемую логику 4096 байт по 4 байта необходимо выполнить 4096 / 4 = 1024 операции записи.
При работе на процессорном модуле алгоритм чтения данных будет следующим:
Реализованный тест:
for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress++)
{
if ((SectorAddress % 1024) == 0)
{
xil_printf("Data read from %d sector nr", SectorAddress);
}
/** Set address */
Xil_Out32(0x43c00008, SectorAddress);
/** Set command */
Xil_Out32(0x43c00004, 1);
/** Start */
Xil_Out32(0x43c00000, 1);
/** Wait end of operation */
for (;;)
{
status = Xil_In32(0x43c0000c);
if (status == 0x01 || status == 0x03)
{
if (status == 0x03)
{
xil_printf("Error in read nr");
}
break;
}
else
{
cntrDuration++;
usleep(100);
}
}
/** Duration operation */
durationRead += cntrDuration;
if (cntrDuration > MaxRead )
{
MaxRead = cntrDuration;
}
cntrDuration = 0x00;
/** Clear start */
Xil_Out32(0x43c00000, 0);
/** Read data from PL */
for (int32_t i = 0; i < 1024; i++)
{
DataR = Xil_In32(0x43c0001c);
if (DataR != cntrData)
{
xil_printf("Data corrupt! nr");
}
DataR = Xil_In32(0x43c00020);
cntrData++;
}
SectorAddress += 7;
}
При работе на процессорном модуле алгоритм стирания данных будет следующим:
Реализованный тест:
for (SectorAddress = 0; SectorAddress < 1048576; SectorAddress++)
{
if ((SectorAddress % 1024) == 0)
{
xil_printf("Data erase from %d sector nr", SectorAddress);
}
/** Set address */
Xil_Out32(0x43c00008, SectorAddress);
/** Set command */
Xil_Out32(0x43c00004, 4);
/** Start */
Xil_Out32(0x43c00000, 1);
/** Wait end of operation */
for (;;)
{
status = Xil_In32(0x43c0000c);
if (status == 0x01 || status == 0x03)
{
if (status == 0x03)
{
xil_printf("Error in write! nr");
}
break;
}
else
{
cntrDuration++;
usleep(100);
}
}
/** Duration operation */
durationErase += cntrDuration;
if (cntrDuration > MaxErase )
{
MaxErase = cntrDuration;
}
cntrDuration = 0x00;
/** Clear start */
Xil_Out32(0x43c00000, 0);
SectorAddress += 7;
}
Тест полностью на github. [35]
| Количество данных | Чтение | Запись | Стирание |
|---|---|---|---|
| 1 блок (512 байт) | 4.7 Мбит/с | 1.36 Мбит/с | 0.58 Мбит/с |
| 8 блоков (4096 байт) | 15.4 Мбит/с | 6.38 Мбит/с | 4.66 Мбит/с |
| 16 блоков (8192 байта) | 18.82 Мбит/с | 11.26 Мбит/с | 9.79 Мбит/с |
Использовалась карта на 16 Гбайт. При тестировании было записано 2 Гбайта данных, прочитано 2 Гбайта данных, стерто 2 Гбайта данных.
Выводы неутешительны. При использовании FPGA нет смысла использовать SD-карту в режиме SPI, кроме того случая, когда очень нужно хранить большие объемы данных без предъявления требований к скорости.
Автор: Павел
Источник [36]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/fpga/352241
Ссылки в тексте:
[1] 1. Чтение спецификации: #SpecRead
[2] 1.1 Общие сведения: #GeneralInformation
[3] 1.2 Инициализация: #Init
[4] 1.3 Стирание информации: #Erase
[5] 1.4 Чтение информации: #Read
[6] 1.4.1 Чтение одного блока данных: #ReadOne
[7] 1.4.2 Чтение множества блоков данных: #ReadMore
[8] 1.5 Запись информации: #Write
[9] 1.5.1 Запись одного блока данных: #WriteOne
[10] 1.5.2 Запись множества блоков данных: #WriteMore
[11] 2. Реализация алгоритма в аппаратуре: #Hardware
[12] 2.1 Компонент физического уровня: #HwPhy
[13] 2.2 Компонент командного уровня: #HwCmd
[14] 2.3 Компонент общения с внешним миром: #HwHost
[15] 3. Проверка в железе: #RealTest
[16] 4. Результаты: #Results
[17] Image: https://habrastorage.org/webt/mu/dg/yb/mudgybpndrbrqpsz5icwgkb1prk.png
[18] Image: https://habrastorage.org/webt/s-/vq/a8/s-vqa83la-g6k3bwgvvmmfoawms.png
[19] Physical Layer Simplified Specification: https://www.sdcard.org/downloads/pls/index.html
[20] Image: https://habrastorage.org/webt/vp/di/pw/vpdipwdy4umv4iahsea1g2snruu.png
[21] Image: https://habrastorage.org/webt/hm/qs/cq/hmqscqj62emd9yp30prqqasyjjm.png
[22] Image: https://habrastorage.org/webt/ht/8f/uf/ht8fufwcf9qq-bdrif-4chjd5cg.png
[23] Image: https://habrastorage.org/webt/_9/kt/ts/_9kttsrffln_gmvgw-l4ch0ekva.png
[24] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SDPhy.vhd#L223
[25] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SDPhy.vhd#L274
[26] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdCommand.vhd#L151
[27] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdCommand.vhd#L331
[28] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdCommand.vhd#L396
[29] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdCommand.vhd#L513
[30] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdCommand.vhd#L728
[31] github: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdHost.vhd#L173
[32] Zybo от фирмы Digilent: https://store.digilentinc.com/zybo-zynq-7000-arm-fpga-soc-trainer-board/
[33] Image: https://habrastorage.org/webt/oe/oy/nj/oeoynjbbxwco9wrihj-f3unb3cs.png
[34] В алгоритме задано количество блоков, равное 8.: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources_1/hdl/SdCommand.vhd#L133
[35] Тест полностью на github.: https://github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.sdk/helloworld.c#L54
[36] Источник: https://habr.com/ru/post/495400/?utm_source=habrahabr&utm_medium=rss&utm_campaign=495400
Нажмите здесь для печати.