Открыть Электронные книги
Категории
Открыть Аудиокниги
Категории
Открыть Журналы
Категории
Открыть Документы
Категории
Все потоки Разработка Администрирование Дизайн Менеджмент Маркетинг Научпоп Войти Регистрация
Привет, Habr! Однажды на работе мне досталась задача оценить возможность реализации хранения Как выучить иностранный язык:
данных на SD-карте при подключении ее к FPGA. В качестве интерфейса взаимодействия
алгоритм
предполагалось использование SPI, так как он проще в реализации. Полученным опытом хотелось бы 4,7k 7
поделиться.
О спасении от удушья в своей
квартире или лонгрид о современной
вентиляции
1,4k 5
3. Проверка в железе
Электропочта
4. Результаты
1. Чтение спецификации
1.1 Общие сведения
Быстрое чтение спецификации сообщает нам следующие параметры SD-карт:
Отсюда сразу вытекает пункт о теоретической пиковой пропускной способности: 25МГц * 1 бит = 25
Мбит/с, что несколько маловато. Второй минус использования SD-карт, питание +3,3В. На печатной
плате, на которую планировалось устанавливать SD-карту, такого напряжения нет.
SDHC. Емкость карт больше 2 Гбайт и до 32 Гбайт включительно. Адрес внутри карты указывает на
блок размером 512 байт.
SDXC. Емкость карт больше 32 ГБайт и до 128 Тбайт включительно. Адрес внутри карты указывает
на блок размером 512 байт.
1 RSV - Не используется
8 RSV - Не используется
После включения питания, SD-карта находится в режиме работы SDIO. Для переключения в режим SPI
необходимо выполнить инициализацию. Протокол работы с картой предполагает использование схемы
контроля корректной передачи данных и команд в виде алгоритма CRC. При работе в режиме SPI
проверка CRC выключена по умолчанию. Таким образом, первая команда, отправляемая в карту для
переключения карты в режим SPI, должна содержать корректное значение CRC.
Биты 47 и 46 дают карте возможность однозначно отследить начало транзакции, так как шина MOSI в
состоянии покоя равна единице.
На команды, используемые при работе с SD-картой, будут поступать ответы типа R1, R3, R7.
Все команды и ответы на них подробно расписаны в спецификации Physical Layer Simplified Specification
1.2 Инициализация
Алгоритм инициализации:
2. Сформировать минимум 74 переключения тактового сигнала для карты. Линии CS и MOSI должны
быть в состоянии логической единицы.
4. Ожидать ответа на команду CMD0. Ответом является ответ типа R1. В случае, если ответ R1 не
пришел за 16 тактов частоты инициализации, переход к шагу 3. Если ответ R1 от карты поступил, но
отличается от значения 0x01 (карта не перешла в состояние инициализации), переход к шагу 3,
иначе переход к шагу 5.
6. Ожидать ответа на команду CMD8. Ответом являет ответ типа R7. В случае, если в ответе
содержится бит, что команда не поддерживается, переход к шагу 7, иначе, если тестовая
последовательность и напряжения питания карты совпадают с отправленными контроллером,
переход к шагу 17, иначе к шагу 13.
7. Отправка команды CMD55. Команда CMD55 сообщает карте, что следующая команда будет
нестандартная.
8. Ожидание ответа на команду CMD55. Ответом является ответ типа R1. Если ответ не равен 0x01
(произошла ошибка при выполнении команды), переход к шагу 7, иначе к шагу 9.
9. Отправка команды ACMD41. Команда ACMD41 сообщает карте, что поддерживает карты SDSC
(параметр 0x00000000) и запускает процесс инициализации.
10. Ожидание ответа на команду ACMD41. Ответом является ответ типа R1. В случае если ответе
содержится бит, что команда не поддерживается, переход к шагу 11. Иначе, если ответ равен 0x00
(инициализация завершена) переход к шагу 14, в противном случае переход к шагу 7.
12. Ожидание ответа на команду CMD1. Ответом являет ответ типа R1. В случае если в ответе
содержится бит, что команда не поддерживается, переход к шагу 13, иначе, если ответ равен 0x00
(инициализация завершена), переход к шагу 14, в противном случае переход к шагу 11.
13. Состояние ошибки. Инициализация завершилась неудачно, дальнейшая работа невозможна. Выход
из этого состояния не предусмотрен.
14. Отправка команды CMD16. Команда используется для настройки в карте режима работы с блоками,
размерами 512 байт.
15. Ожидание ответа на команду CMD16. Ответом является ответ типа R1. В случае, если ответ равен
0x00 (команда завершена удачно) переход к шагу 16, иначе к шагу 13.
16. Состояние удачного завершения инициализации. Можно начинать работать с данными на карте.
Выход из алгоритма.
17. Отправка команды CMD55. Команда CMD55 сообщает карте, что следующая команда будет
нестандартная.
18. Ожидание ответа на команду CMD55. Ответом является ответ типа R1. Если ответ не равен 0x01
(произошла ошибка при выполнении команды), переход к шагу 17, иначе к шагу 19.
19. Отправка команды ACMD41. Отправка команды ACMD41. Команда ACMD41 сообщает карте, что
поддерживает карты SDHC (параметр 0x40000000) и запускает процесс инициализации.
20. Ожидание ответа на команду ACMD41. Ответом является ответ типа R1. В случае если ответе
содержится бит, что команда не поддерживается, переход к шагу 13. Иначе, если ответ равен 0x00
(инициализация завершена) переход к шагу 21, в противном случае переход к шагу 17.
22. Ожидание ответа на команду CMD58. Ответом является ответ типа R3. В случае, если в ответе
установлен бит, что карта работает с адресами блоков по 512 байт, переход к шагу 16, иначе к шагу
14.
После завершения инициализации с картой можно работать блоками по 512 байт с тактовой частотой в
25 МГц. Это полная версия алгоритма инициализации, которая охватывает все типы карт. В моем случае,
при использовании карты на 16 Гбайт алгоритм инициализации состоял из шагов 1-6, 17-22, 16.
1. Отправка команды CMD32. Команда устанавливает адрес первого блока для стирания информации.
2. Ожидание ответа на команду CMD32. Ответом является ответ типа R1. В случае если ответ не равен
0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 4.
4. Отправка команды CMD33. Команда устанавливает адрес последнего блока для стирания
информации. В случае если стереть нужно только один блок, адрес должен быть равен адресу из
команды CMD32.
5. Ожидание ответа на команду CMD33. Ответом является ответ типа R1. В случае если ответ не равен
0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 6
7. Ожидание ответа на команду CMD38. Ответом является ответ типа R1b. Это расширенная версия
ответа, когда карта формирует ответ R1, а потом притягивает линию MISO к нулю, индицируя
занятость микросхемы. Необходимо дождаться появления на линии MISO значения единицы. В
случае если ответ не равен 0x00 (произошла какая-либо ошибка при выполнении команды),
переход к шагу 3, иначе к шагу 8
Первоначально был реализован именно такой вариант, но полученная скорость очень расстроила
(сравнения скоростей будут ниже).
Где:
В случае, если при чтении произошла ошибка, карта вместо Data Token возвращает Error Token. Размер
Error Token – 1 байт. Биты 7..5 содержат значение нуля, биты 4..0 кодируют тип ошибки.
1. Отправка команды CMD18. Команда сообщает карте, что будет выполняться чтение множества
блоков.
2. Ожидание ответа на команду CMD18. Ответом является ответ типа R1. В случае если ответ не равен
0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 4.
4. Ожидание токена от карты. В случае если от карты получен Error Token, переход к шагу 3, иначе к
шагу 5.
7. Проверка количества принятых блоков данных. Если принято необходимое количество блоков,
переход к шагу 8, иначе к шагу 4.
8. Отправка команды CMD12. Команда сообщает карте о необходимости остановки чтения. В случае,
если в этот момент выполняется передача Data Packet, она будет прервана.
9. Ожидание ответа на команду CMD12. Ответом является ответ типа R1b. Это расширенная версия
ответа R1, в котором карта после формирования ответа притягивает линию MISO к нулю,
индицируя занятость карты. Необходимо дождаться появления на линии MISO значения единицы,
что будет свидетельствовать об окончании команды. В случае если ответ не равен 0x00 (произошла
какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 10.
В алгоритме чтения есть небольшой нюанс. Сигнал выбора микросхемы (CS) должен быть установлен в
состояние логического нуля перед формированием команды CMD18 и установлен в состояние
логической единицы после получения ответа на команду CMD12.
Как и с чтением, изначально была реализована запись одного блока данных. Результаты скорости
получились неудовлетворительные.
Структура 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 будет уровень логической единицы, можно передавать в карту
следующий пакет с данными.
Отправка команды CMD25. Команда сообщает карте, что будет выполняться запись множества
блоков.
Ожидание ответа на команду CMD25. Ответом является ответ типа R1. В случае если ответ не равен
0x00 (произошла какая-либо ошибка при выполнении команды), переход к шагу 3, иначе к шагу 4.
Ожидание Data Response от карты. В случае если получен ответ с ошибкой, переход к шагу 3, иначе
к шагу 8.
Проверка количества записанных блоков. Если записано необходимое количество блоков, переход
к шагу 9, иначе к шагу 4.
Отправка Stop Tran Token. Это сообщает карте, что необходимо завершить транзакцию записи.
Ожидание ответа от карты. Карта на Stop Tran Token притягивает линию 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;
Где:
iPhyTxData содержит данные для уровня, а iPhyMode содержит режим, как эти данные
обрабатывать.
oPhyTxReady показывает, когда компонент готов к приему данных. Фактически представляет собой
выход FULL FIFO, используемого для синхронизации компонентов.
В ПЛИС существуют специальные блоки для работы с тактовым сигналом (PLL, MMCM), однако,
получить с их выхода тактовый сигнал меньше 5 МГц проблематично. В результате физический уровень
работает на частоте 50 МГц. Вместе с каждыми данными в сигнале iPhyMode поступает бит, который
указывает, на какой частоте эти данные должны быть переданы в SD-карту (или приняты от нее). В
зависимости от бита скорости формируются сигналы разрешения тактового сигнала (Clock enable).
В компоненте физического уровня реализованы два автомата, для передачи данных в SD-карту и для
приема данных от нее.
Состояние sBusy обеспечивает ожидание готовности SD-карты (карта отпускает линию MISO к
уровню единицы).
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 );
Где:
oSdCmdFinish статус завершения команды. Нулевой бит равен единице, команда завершена
успешно. Первый бит равен единице, команда завершена с ошибкой.
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 );
Где:
oSdStatus статус завершения команды. Нулевой бит равен единице — команда завершена. Первый
бит равен единице — команда завершена с ошибкой.
iSdTxValid. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с сигналом записи.
iSdTxLast. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с признаком последнего dw в
данных.
oSdTxReady. Интерфейс Axi-Stream для записи данных в SD-карту. Порт с признаком готовности к
приему данных.
oSdRxValid. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с сигналом записи.
oSdRxLast. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с признаком последнего dw
в данных.
iSdRxReady. Интерфейс Axi-Stream для чтения данных из SD-карты. Порт с признаком готовности к
приему данных.
sReadCmd. Проверка места в FIFO, что поместится пакет, который будет прочитан из SD-карты и
формирование сигнала запуска команды чтения.
sWriteCmd. Проверка, что в FIFO есть пакет для записи в SD-карту, и формирование сигнала запуска
команды записи.
3. Проверка в железе
Алгоритм написан, в симуляторе проверен. Нужно проверить теперь в железе. Из того, что имелось в
наличии, подошла плата Zybo от фирмы Digilent
На ней имеются свободные выводы ПЛИС в банке с напряжением +3,3В, к которому можно легко
подключить внешнее устройство. Да еще и тип используемой ПЛИС — Zynq-7000, значит там есть
процессорное ядро. Можно написать тест на языке С, что несколько упросит задачу тестирования.
Итак, подключаем реализованный алгоритм к процессорному ядру через порт GP (возможна работа по
4 байта, похоже на PIO). С прерываниями заморачиваться не будем, реализуем опрос по таймеру.
Реализованный тест:
/** Start */
Xil_Out32(0x43c00000, 1);
cntrDuration = 0x00;
SectorAddress += 7;
}
На вопрос, почему в цикле используется внешняя граница 1024. В алгоритме задано количество блоков,
равное 8. Размер одного блока равен 512 байт. Общий размер 8 блоков данных составляет 8 * 512 байт
= 4096 байт. Шина между процессорным модулем и программируемой логикой имеет размер 4 байта.
Получается, чтобы переслать из процессорного модуля в программируемую логику 4096 байт по 4
байта необходимо выполнить 4096 / 4 = 1024 операции записи.
Реализованный тест:
/** Start */
Xil_Out32(0x43c00000, 1);
cntrDuration = 0x00;
SectorAddress += 7;
}
Реализованный тест:
/** Start */
Xil_Out32(0x43c00000, 1);
cntrDuration = 0x00;
SectorAddress += 7;
}
4. Результаты
Использовалась карта на 16 Гбайт. При тестировании было записано 2 Гбайта данных, прочитано 2
Гбайта данных, стерто 2 Гбайта данных.
Выводы неутешительны. При использовании FPGA нет смысла использовать SD-карту в режиме SPI,
кроме того случая, когда очень нужно хранить большие объемы данных без предъявления требований
к скорости.
Оцените статью:
92,3% Понравилась 60
1,5% Не понравилась 1
56,5 0,0
Карма Рейтинг
Павел @ Fenja
Пользователь
Github
Мегапост
КУРСЫ
Android-разработчик с нуля
26 февраля 2021 • 7 месяцев • 70 740 ₽
• Нетология
iOS-разработчик с нуля
10 марта 2021 • 8 месяцев • 70 740 ₽
• Нетология
Пока таких планов нет. В случае необходимости реализации SDIO, я бы лучше обратил внимание на eMMC. Фермеры в США вынуждены
У eMMC есть возможность установки на плату с помощью припаивания, а не с помощью разъема. взламывать тракторы, чтобы просто
починить их
96,7k 430
HardWrMan 17 апреля 2020 в 15:16 +1
С eMMC понятно, это подходит (и уже давно используется) для набортного несъемного накопителя. Но Часы для обнаружения жестов на
часто нужна именно съемная карта. И для SD карт их родной SDIO, хотя бы на уровне физики, как это основе машинного обучения, ESP8266
сделано в старших семействах микроконтроллеров STM32F тоже интересен. Особенно, если его можно
и Arduino
будет подключить к чему-то другому. 3,9k 11
DAT0-DAT7: These are bidirectional data channels. The DAT signals operate in push-pull mode. Only the
card or the host is driving these signals at a time. By default, after power up or reset, only DAT0 is used
for data transfer. A wider data bus can be configured for data transfer, using either DAT0-DAT3 or
DAT0-DAT7, by the MultiMediaCard controller. ...
data: data can be transferred from the card to the host or vice versa. Data is transferred via the data
lines. The number of data lines used for the data transfer can be 1(DAT0), 4(DAT0-DAT3) or 8(DAT0-
DAT7)
ну и
For each data lines, data can be transferred at the rate of one bit (single data rate) or two bits (dual
data rate) per clock cycle.
Я уже плохо помню протокол, но, если не ошибаюсь, устройство всегда стартует в SDIO с однобитной
шиной. Сбрасываем устройство, получаем паспорт, переключаемся на максимальную из
поддерживаемых контроллером и устройством. И будут работать и древние MMC (1-битная шина) и
MMCPlus (1, 4 и 8), а также современные SD (1 и 4) и eMMC (1, 4 и 8).
На оперкорес было готовое ядро, которое у нас патрировали на AXI шину, с DMA, правда на с
транзакцией максимум на 8 килобайт.
AXI4, изначально делался Stream но потом сделали AXI4 в пользу простоты программирования.
Железо было на стандарт 3.3V по нему максимальный клок 50Mhz ну и 4битная шина.
Расчетная должна быть около 200 Mbit/s где так оно и получалось в пике, ну а так а вычетом
времени на посыл команды старта отсановки, поменьше выходила но точных цифр не приведу,
я это года два назад тестировал.
Тут дело не совсем в FPGA. Вроде как это замысел создателей стандарта SD-карточек: режим SPI — это
единственный открытый (бесплатный) вариант работы с SD-карточкой, а остальные все закрытые, т.е. требуют
либо покупки лицензий, либо покупки IP-корок, либо реверс-инжиниринга их проприетарного протокола. В
то же время многие производители микроконтроллеров включают подобные ядра в свои продукты, в
результате конечному пользователю не приходится заморачиваться.
Создали стандарта все равно немного жадные. В бесплатной документации нет временных диаграммы
работы по шине SPI, на которые удобно ориентироваться при разработке.
Картинка
Сейчас обнаружил, что вышла новая версия спецификации. Карты размером от 2 Тб до 128 Тб
включительно вынесли из группы SDXC и создали для них новую группу SDUC (Ultra Capacity SD Memory
Card). Карты SDUC режим SPI не поддерживают. Видимо чтобы не тащить протокол, которым мало кто
будет пользоваться.
не очень понял из кода: использовались команды чтения/записи по 1 сектору или по много подряд? во 2
случае достаточно задать начальный сектор и лить данные сколько надо.
спасибо. правильно ли я понимаю, что ваши измерения скоростей сделаны на кусках по 8 килобайт?
возможно, если вы возьмёте например 8 мегабайт, вы приблизитесь к 25 мегабитам (исходя из скорости
SPI-интерфейса), как минимум на чтении (т.к. скорость записи может ограничиваться неидеальностью
SD-карты как микро-пародии на SSD :))
Да, объем данных, читаемых подряд, был равен 8кБайт. С увеличением размера читаемых данных, я
думаю скорость на чтение вырастет. Проблема только в ограниченном объеме памяти в ПЛИС. Я
хотел попробовать работу с 32 блоками (16кБайт), но это потребовало бы модификацию командного
уровня. В результате мне стало жалко 4 BRAM на прием, 4 BRAM на передачу и своего времени
(Размер одного BRAM в ПЛИС Zynq равен 4кБайт). От идеи я отказался.
Скорей всего здесь это уже никому не интересно, но есть в спецификации такой кусоче:
It is an obvious requirement that the clock must be running for the SD Card to output data or response
tokens. After the last SD Card bus transaction, the host is required to provide eight (8) clock cycles for
the card to complete the operation before shutting down the clock. Following is a list of various SD
Card bus transactions:
− A command with no response—eight clocks after the host command end bit.
− A command with response—eight clocks after the card response end bit.
− A read data transaction—eight clocks after the end bit of the last data block.
− A write data transaction—eight clocks after the CRC status token.
То есть после исполнения команды и получения ответа, надо выдавать ещё 8 «пустых» клоков. Обычно это не
требуется, и многие так не делают. Но вчера мне как раз попалась карта, которая без этого не проходила
инициализацию.
Мне попадалась информация, что нужно сформировать 8 «пустых» переключений тактового сигнала
перед формированием команды и я это реализовал. Думал, что отражено в статье, оказывается нет. Я
расширил код команды, добавив в старший байт 0xFF.
github.com/Finnetrib/SdController/blob/cab2ef85c89d9ed7b696d84bf141bc6eb3f89a6b/SD_Controller.srcs/sources
САМОЕ ЧИТАЕМОЕ
© 2006 – 2021 «Habr» Настройка языка О сайте Служба поддержки Мобильная версия