Информационный портал технической поддержки Центра проектирования интегральных микросхем |
Работа с картами памяти Secure Digital (SD) возможна через интерфейс SDIO и через SPI. В рамках данной статьи рассматривается работа с SD-картами по интерфейсу SDIO микроконтроллера К1901ВЦ1QI, а именно реализация файловой системы FatFs с помощью модуля от http://elm-chan.org/ (на момент написания статьи актуальная версия FatFs R0.15 от Nov 6, 2022). Портированный модуль тестировался на microSDHC-карте объемом 8Гб.
Рисунок 1. Назначение выводов SD и microSD в режимах SDIO и SPI
При работе с картой использовалось описание стандарта SD в переводе "Упрощенное описание стандарта физического уровня карт SD" и SD Card Association.
В проекте запускается тест драйвера "диска" от автора библиотеки файловой системы. Этот тест проверяет драйвер и сообщает, будет ли драйвер работать с файловой системой FatFs или нет.
Для успешного запуска тестирования требуется реализовать 5 функций (которые расположены в файле diskio.c модуля FatFs):
Пример работает следующим образом:
В спецификации SD по ссылке выше указано, какие сигналы и как необходимо подавать на карту при работе с ней. SDIO должен:
Логика работы с модулем SDIO реализована в файле sd_card_sdio_ll.c. Модуль реализует все функции, необходимые для обмена сигналами с SD-картой. Протокол обмена реализован в отдельном файле sd_card_protocol.c.
Поскольку внутри SD-карты имеется свой контроллер, работающий на своей частоте, частота по SDIO_CLK от микроконтроллера к SD-карте нужна только для обмена. Передача данных может осуществляться по одной линии (SDIO_DATA0) или по четырем (SDIO_DATA0-SDIO_DATA3, обеспечивает бо́льшую скорость) - в текущем примере используются все линии.
Для работы с картой предварительно необходимо подать на нее питание, это может быть осуществлено при физическом детектировании карты в слоте, или, как это реализовано на демонстрационной плате К1901ВЦ1QI, может быть подано всегда. Поскольку на демоплате питание подано всегда, каких-либо дополнительных действий не требуется и в функции включения питания SDIO_LL_PowerOn() только инициализируется регистр управления блоком MDR_SDIO->CR. Маски значений регистра CR вынесены отдельно для наглядности задаваемого режима работы (фрагмент кода 1):
#define SDIO_CR__PowerOn SDIO_CR_SDE | SDIO_CR_CLKOE | SDIO_CR_CRC_EN_DATA
#define SDIO_CR__SendCMD SDIO_CR_CRC_EN_CMD | SDIO_CR_SBITCMD | SDIO_CR_DIRCMD
#define SDIO_CR__ReadResp SDIO_CR_CRC_EN_CMD | SDIO_CR_SBITCMD
#define SDIO_CR__SendCycles SDIO_CR_DIRCMD
#define SDIO_CR__ReadBlock SDIO_CR_CRC_EN_CMD | SDIO_CR_SBITDAT
#define SDIO_CR__WriteBlock SDIO_CR_CRC_EN_CMD | SDIO_CR_SBITDAT | SDIO_CR_DIRDATA
#define SDIO_CR__ModeClear (SDIO_CR_CRC_EN_CMD | SDIO_CR_SBITCMD | SDIO_CR_DIRCMD | SDIO_CR_SBITDAT | SDIO_CR_DIRDATA)
void SDIO_LL_PowerOn(void)
{
MDR_SDIO->CR = SDIO_CR__PowerOn;
}
Фрагмент кода 1.
Биты регистра CR при включении питания:
После подачи питания SD-карту необходимо идентифицировать и инициализировать, обмен при этом осуществляется через линии SDIO_CLK и SDIO_CMD. Для посылки команд реализована функция SDIO_LL_SendCMD(cmd, arg), которая является оберткой функции SPL SDIO_SendCommand(cmd, arg) (фрагмент кода 2).
static inline void SDIO_CR_SetMode(uint32_t CR_Flags)
{
uint32_t regCR = MDR_SDIO->CR;
regCR &= ~(SDIO_CR__ModeClear);
MDR_SDIO->CR = regCR | CR_Flags;
}
SD_CARD_CMD_Result_TypeDef SDIO_LL_SendCMD(uint32_t cmd, uint32_t arg)
{
SDIO_CR_SetMode(SDIO_CR__SendCMD);
SDIO_SendCommand(cmd, arg);
// The transfer is finished
return SD_CARD_CMD_OK;
}
Фрагмент кода 2.
Пример диаграммы исполнения функции SDIO_LL_SendCMD(cmd, arg) представлен на рисунке 1:
Рисунок 2. Диаграмма исполнения функции SDIO_LL_SendCMD(cmd, arg)
Тактовые импульсы подаются только на время вывода команды. Их столько, сколько задано в (CMD_TRANSFER + 1), то есть для посылки команды к карте - (48 + 1) (по стандарту допускается отключить тактирование, не дожидаясь выхода карты из режима Busy. Текущая операция в карте SD при этом все равно закончится, но в данном случае необходимо подать на карту дополнительный тактовый перепад для снятия сигнала занятости по линии данных SDIO_DATA0).
Сигнал SDIO_CMD начинается со стартового бита, который всегда равен "0", и заканчивается стоповым битом, который всегда равен "1". Таким образом непрерывно идущие посылки разграничиваются переключением уровня сигнала SDIO_CMD "0"->"1" вне зависимости от данных.
Второй бит за стартовым определяет направление передачи по линии SDIO_CMD.Необходимо, чтобы любая передача по линиям SDIO_DATA0-SDIO_DATA3 и SDIO_CMD всегда заканчивалась 8-ю дополнительными тактами на линии CLK, только после дополнительных 8 тактов сигнал тактирования на CLK может быть остановлен. Аппаратно такой режим не реализуется, дополнительные 8 тактов CLK необходимо формировать программно. Для этого реализована отдельная функция SDIO_LL_SendClockCycles(N) (рисунок 3).
Рисунок 3. Формирование дополнительных 8 тактов CLK после отправки команды
Между посылкой команды и дополнительными 8-ю тактами есть промежуток, связанный с переинициализацией блока SDIO, что, однако, при тестировании не вызывает нарушений обмена с SD-картой. Также следует отметить, что за один вызов функции максимальное количество посылаемых периодов - 32 (поскольку в регистр CMDDR записывается только одно слово для отправки), поэтому для формирования большего числа тактов необходимо вызывать функцию несколько раз.
#define CHECK_CMD_STOPPED 0
#if CHECK_CMD_STOPPED
static inline void WaitCMDStopped(void)
{
do {
MDR_SDIO->CR &= ~(SDIO_CR_WORK2 | SDIO_CR_SBITCMD);
} while (MDR_SDIO->CR & SDIO_CR_WORK2);
}
#define WAIT_CMD_STOPPED WaitCMDStopped
#else
#define WAIT_CMD_STOPPED __nop
#endif
bool SDIO_LL_SendClockCycles(uint32_t clockCycles)
{
WAIT_CMD_STOPPED();
SDIO_CR_SetMode(SDIO_CR__SendCycles);
MDR_SDIO->CMDDR = 0x00FF;
MDR_SDIO->CMD_TRANSFER = clockCycles; // Number of cycles
MDR_SDIO->CMDCRC = 0x00000000;
MDR_SDIO->CR |= SDIO_CR_WORK2;
if (!WaitCMDCompleted(_SDIO_Timeouts.SD_SendCMD_Timout_ms))
return false;
MDR_SDIO->CR &= ~SDIO_CR_DIRCMD; // Command RX
return true;
}
Фрагмент кода 3.
Функция SDIO_LL_SendClockCycles() практически аналогична SDIO_LL_SendCMD(), различие заключается в управляющих битах регистра CR. В ней сброшены биты:
Код чтения ответа от карты такой же, как в спецификации (фрагмент кода 4).
SD_CARD_CMD_Result_TypeDef SDIO_LL_ReadResponse(SD_CARD_ResponseLength_TypeDef respLength,
uint32_t* response)
{
uint32_t tmpBuff[5];
// Read answer form SD card
SDIO_CR_SetMode(SDIO_CR__ReadResp);
MDR_SDIO->CMD_TRANSFER = (uint32_t)respLength;
MDR_SDIO->CMDCRC = 0x00000000;
MDR_SDIO->CR |= SDIO_CR_WORK2;
// Waiting for a response with a timeout
if (!WaitCMDCompleted(_SDIO_Timeouts.SD_ReadResp_Timout_ms)) {
// Force stopping a transaction
MDR_SDIO->CR &= ~(SDIO_CR_WORK2 | SDIO_CR_SBITCMD);
return SD_CARD_CMD_TimeoutSend;
}
// Reading received data from FIFO
tmpBuff[0] = MDR_SDIO->CMDDR; // Read the response words 0
tmpBuff[1] = MDR_SDIO->CMDDR; // Read the response words 1
response[0] = ((tmpBuff[0] << 8) & 0xFFFFFF00) | ((tmpBuff[1] >> 8) & 0x000000FF);
if (respLength == SD_CARD_Resp_136bit) {
tmpBuff[2] = MDR_SDIO->CMDDR; // Read the response words 2
tmpBuff[3] = MDR_SDIO->CMDDR; // Read the response words 3
tmpBuff[4] = MDR_SDIO->CMDDR; // Read the response words 4
response[1] = ((tmpBuff[1] << 8) & 0xFFFFFF00) | ((tmpBuff[2] >> 8) & 0x000000FF);
response[2] = ((tmpBuff[2] << 8) & 0xFFFFFF00) | ((tmpBuff[3] >> 8) & 0x000000FF);
response[3] = ((tmpBuff[3] << 8) & 0xFFFFFF00) | ((tmpBuff[4] >> 8) & 0x000000FF);
}
return SD_CARD_CMD_OK;
}
Фрагмент кода 4.
В регистре CR выставляются те же биты, что и для посылки команды, сброшен только бит:
В этом режиме блок SDIO бесконечно генерирует тактовые импульсы и, после появления на линии SDIO_CMD стартового бита, считывает данные в количестве бит, указанных в регистре CMD_TRANSFER. По окончании чтения линии бит SDIO_CR_WORK2 сбросится в 0, и полученные данные будут вычитаны из регистров блока. Операция чтения выполняется с таймаутом, поскольку если ответа нет, то бесконечное чтение необходимо прервать. Это указывает на то, что карта SD не приняла команду, то есть не послала ответ, либо самой карты нет в разъеме. По этой причине при срабатывании таймаута необходимо вручную сбросить биты SDIO_CR_WORK2 и SDIO_CR_SBITCMD, чтобы остановить транзакцию.
В спецификации все таймауты указаны в миллисекундах. Для их обработки в примере используется системный таймер SysTick, который входит в состав ядра. Таймер настраивается на генерацию прерывания каждую миллисекунду. В обработчике прерывания декрементируются две переменные, которые отсчитывают таймаут. Значение переменной таймаута задается в коде и затем там же происходит ожидание обнуления этой переменной. Если переменная обнулилась, значит сработал таймаут.
В модуле декрементируются две переменные таймаута. Одна используется внутренними функциями модуля sd_card_sdio_ll.c, а вторая используется функциями более высокого уровня, реализованными в модуле sd_card_protocol.c. Декремент переменных выделен в отдельную функцию SDIO_LL_DelayHandler(). Это сделано для того, чтобы было возможно использовать обработчик системного таймера в основной программе, тогда в обработчике прерываний системного таймера нужно отдельно вызывать функцию SDIO_LL_DelayHandler() для отсчета таймаутов. Если в основной программе обработчик прерывания системного таймера не нужен, то обработчик по умолчанию SysTick_Handler() уже реализован в sd_card_sdio_ll.c и активируется макроопределением USE_SYS_TIMER_HANDLER = 1, в файле sd_card_sdio_ll.h (фрагмент кода 5).
volatile uint32_t DelayInt_ms, DelayExt_ms;
void SDIO_LL_DelayHandler(void)
{
volatile uint32_t n;
n = DelayExt_ms;
if (n)
DelayExt_ms = --n;
n = DelayInt_ms;
if (n)
DelayInt_ms = --n;
}
#if USE_SYS_TIMER_HANDLER
void SysTick_Handler(void)
{
SDIO_LL_DelayHandler();
}
#endif
Фрагмент кода 5.
В текущем примере прерывание от системного таймера используется в main.c для мигания светодиодом LED_CYCLE, поэтому определение USE_SYS_TIMER_HANDLER = 0, а в SysTick_Handler() вызывается SDIO_LL_DelayHandler().
Работа с SD-картой подразумевает посылку команд, принятие ответов и обмен данными. Все это реализуется функциями SDIO, которые были рассмотрены выше. Согласно стандарту некоторые команды требуют предварительного переключения SD-карты в режим восприятия следующей команды как "Application Specific Command" (команды, специфичные для приложения). Такие команды начинаются с префикса "A", то есть это ACMDx, и их исполнение состоит из двух посылок. На примере ACMD41 это:
Для удобства реализована отдельная функция SDIO_LL_ExecACMD(), которая принимает на вход оба типа команд и, если посылаемая команда ACMD, предварительно посылает команду CMD55 (фрагмент кода 6).
#define CMD55 (55) // APP_CMD
bool SD_ExecACMD(uint32_t cmd, uint32_t arg, SD_CARD_ResponseLength_TypeDef respLength, uint32_t* response)
{
SD_ResponseR1 respCMD55;
SD_CARD_CMD_Result_TypeDef result;
if (cmd & 0x80) {
// Switch SD card to ACMD command response waiting
result = SD_ExecCMD(CMD55, CardRCA << 16, SD_CARD_Resp_48bit, &respCMD55.value);
if (result != SD_CARD_CMD_OK)
return false;
// Exit if SD card doesnt return APP_CMD wait status
if (!(respCMD55.bits.SD_R1_APP_CMD))
return false;
}
// Command "Application-Specific Command"
return (SD_ExecCMD(cmd, arg, respLength, response) == SD_CARD_CMD_OK);
}
Через функцию SD_ExecACMD() реализована вся логика работы с картой.
Работа с картой делится на два этапа:
Идентификация карты происходит после включения питания. Опрашиваются внутренние регистры карты, определяется ее тип, объем, рабочее напряжение, максимальная скорость работы и т.д. В каждый момент времени на шине SDIO может быть активна только одна карта, поэтому карта назначается активной и затем с ней происходит обмен данными.
Идентификация должна проходить на низкой скорости 100-400кГц и при широком диапазоне напряжений питания 2.7-3.6 В, что необходимо для обратной совместимости со старыми картами.
Последовательность команд идентификации карты описана в спецификации SD, где наглядно указаны переходы между состояниями в виде графов. Ниже рассматривается ветка инициализации, используемая в примере:
Настраивать напряжение питания нет необходимости, поскольку, согласно спецификации на SD-карты, по напряжению питания карты делятся на:
После идентификации и ответа карты о поддерживаемых скоростях микроконтроллер может выставить более высокую скорость общения с картой. Скорость определяется классом карты, для используемой в примере карты указан класс 10 - это значит, что карта поддерживает скорость не меньше 10МБ/сек. Максимальная же скорость обмена по стандарту 2.00 составляет 25МБ/сек, т.е. 50МГц (в режиме High Speed, введенным с версии 1.10 спецификации).
Работа с данными происходит блоками по 512 байт. Карты поддерживают одноблочные операции чтения/записи и многоблочные. При записи данных сразу в несколько блоков рекомендуется задать команду на предварительное стирание этих блоков, тогда запись данных произойдет быстрее.
Последовательность чтения данных:
Последовательность записи данных:
Последовательности чтения и записи заканчиваются подачей дополнительных 8-ми тактов на линии SDIO_CLK.
Как было указано ранее, в проекте используется библиотека файловой системы FatFs от elm-chan.org. Она качается с сайта и подключается к проекту, Download. В файле diskio.c заведены прототипы функций, которые необходимо реализовать для работы файловой системы.
Иерархия проекта:
Функция IO драйвера FatFs | Функция brdSD_Card.c |
---|---|
DSTATUS disk_initialize (BYTE pdrv) | SD_CARD_CardDetect() - идентификация карты |
DSTATUS disk_status (BYTE pdrv) | Возврат переменной статуса |
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count) | SD_CARD_CardRead() - чтение секторов |
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count) | SD_CARD_CardWrite() - запись секторов |
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff) | SD_CARD_IO_Ctrl() - вспомогательные операции IO |
На сайте http://elm-chan.org/ предлагается модуль для теста функций IO - "A function checker for low level disk I/O module is available here". Модуль вызывает ранее перечисленные функции IO и, если тест проходит успешно, то выдается заключение о том, что реализация драйвера IO выполнена правильно и файловая система работоспособна.
Этот тест сохранен в проекте в функции TestFileSystem(), файл test_funcIO.c. Он вызывается по нажатию кнопки Down на отладочной плате. Если тест проходит успешно, то загорается светодиод LED_OK (PB15). Если тест не прошел, то загорается светодиод LED_FAULT (PB14).
В процессе исполнения TestFileSystem() отладочная информация выводится через функцию printf(). Для того чтобы получать эти логи, настроен вывод printf() через ITM. Следует обратить внимание, что если отсутствует реализация функции printf(), то есть не подключен реализующий функцию драйвер, проект не будет запускаться (при запуске ядро не будет доходить до функции main, "застревая" во внутренней функции инициализации).
Сайт: | https://support.milandr.ru |
E-mail: | support@milandr.ru |
Телефон: | +7 495 221-13-55 |