Доброго времени суток. Пытаюсь подключить Micro SD карточку на 256 МБ к STM32. Однако, оказалось это весьма не просто. Вообще, основная проблема в том, что карты бывают разных видов в зависимости от ёмкости - SDSC, SDHC, и т.д. К первому типу относятся карты до 2 ГБ (как раз мой случай), и все бы ничего, но большинство разработчиков забили на поддержку таких карт. Вроде есть проекты на другие МК, но портировать их в Cube IDE пока не удалось. Сложное это дело, карты памяти читать... Нашел проект, реализованный на Petit FatFS. Он вроде обещает работу с мелкими карточками, но на STM его нет, а вдумчивое чтение и перенос кода на Cube пока не принесло успехов. Может, есть у кого собранный в Cube рабочий проект чтения карт памяти SDSC для STM32F103C8T6?
Я реализовал свой собственный инит карты по этому чарту: А потом просто использовал FatFS от Чена, причём в моей версии пришлось исправлять ошибку преобразования адреса в сектор (и наоборот) потому что для XC должен использоваться uint64_t а Чен тогда использовал только uint32_t. Это было 10 лет назад, в современных версиях вроде как это уже пофикшено.
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
Вот где-то на этапе R1 resp все и заканчивается. Возвращает нули, хоть ты тресни Сегодня, в 4-й день моего "приключения на 15 минут" я, все же, решил посмотреть осциллографом сигналы, которые подает код, работающий с картами от 2 гигов. Описаний как инициализировать карту, множество. Вот например. Дословно читаем процесс перевода карты в режим SPI. - Подать единицу на CS. - Подать единицу на MOSI. - Подать 74 или более импульсов на линию CLK.
Однако, фактически CS выставляется в 0 в начальный момент времени, и всегда в единице все оставшееся время. То есть, инициализация SPI происходит при выставлении НУЛЯ на CS и ЕДИНИЦЫ во время операций чтения-записи. А в описании все наоборот... Да и по логике работы SPI CS должно быть в нуле во время обращения к устройству, а в SD картах, выходит, все строго наоборот
А ты скорость понижаешь? Инит карты происходит строго на частоте ниже 400кГц. А ещё, я подаю не 74CLK а 256 байт (т.е. 256*8 CLK). Некоторым картам мало всего лишь 74 такта. СпойлерВот мой код для 030 из этого проекта, для понимания сути, может на что-то натолкнёт:
Код:
// Константы команд SD карты #define CMD_LENGTH ((uint32_t) 6 ) // Размер команды #define CARD_DATA_TOKEN ((uint8_t) 0xFE ) // Токен начала данных #define CARD_CMD17 ((uint8_t) 0x51 ) // Команда чтения блока #define CARD_CMD24 ((uint8_t) 0x58 ) // Команда записи блока
// Посылаем команду и ждём окончания передачи void Card_SendCMD(const uint8_t *Buf, uint32_t Size ) { // Локальная переменная uint16_t Data; // Если есть данные while ( Size > 0 ) { // Первым делом формируем данные, с учётом пакинга для оптимизации Data = *(Buf); Buf++; Size--; if ( Size > 0 ) { // Забираем второй байт тоже Data += (*(Buf) * 0x100); Buf++; Size--; } // Данные сформированы, ожидаем готовности буфера передачи while ( (SPI1->SR & SPI_SR_TXE) == 0 ) {} // Передаём данные SPI1->DR = Data; } // Ожидаем опустошения очереди передачи while ( (SPI1->SR & SPI_SR_FTLVL) != 0 ) { Data = SPI1->DR; } // Ждём завершения последней транзакции while ( (SPI1->SR & SPI_SR_BSY) != 0 ) { Data = SPI1->DR; } // Опусташаем буфер приёма while ( (SPI1->SR & SPI_SR_FRLVL) != 0 ) { Data = SPI1->DR; } }
// Посылаем байт и ждём ответ uint8_t Card_SPI( uint8_t Data ) { // Локальная переменная uint8_t Res; // Ожидаем готовности передатчика while ( (SPI1->SR & SPI_SR_FTLVL) != 0 ) { } // Посылаем слово *((__IO uint8_t *)&SPI1->DR) = Data; // Ожидаем готовности приёмника while ( (SPI1->SR & SPI_SR_RXNE) == 0 ) { } // Вычитываем Res = *((__IO uint8_t *)&SPI1->DR); // Выход return Res; }
// Ожидаем ответ uint8_t Card_WaitResp( uint32_t *OCR, FunctionalState R7, uint8_t Count ) { // Локальная переменная uint8_t Res,Cnt; // Инит переменной *(OCR) = 0xFFFFFFFF; // Поиск первого байта ответа do { // Получаем данные Res = Card_SPI( 0xFF ); // И уменьшаем счётчик Count--; } while ( ((Res & 0xC0) != 0) && (Count > 0) ); // Проверяем, требуется ли догрузка ещё 4х байт? if ( R7 == ENABLE ) { // Догружаем ещё 4 байт (всего 40 бит вместе с Res) for (Cnt = 0; Cnt < 4;Cnt++) { // Готовим место под байт *(OCR) <<= 8; *(OCR) &= 0xFFFFFF00; // Получаем данные *(OCR) += Card_SPI( 0xFF ); } } // Ожидаем как минимум 1 байт с 0xFF while ( Card_SPI( 0xFF ) != 0xFF ) { } // Выход return Res; }
// Инициализация карты памяти TCardType Card_Init( void ) { // Локальные переменные TCardType Res; uint32_t Cnt,OCR; uint8_t Dat, Resp; // Отключаем карту CARD_OFF; Res = ctNone; // Настраиваем SPI на медленную скорость PCLK/128: 48/128 = 0,375МГц SPI1->CR1 &= ~SPI_CR1_SPE; SPI1->CR1 = SPI_CR1_MSTR | SPI_LOW_SPEED; SPI1->CR1 |= SPI_CR1_SPE; // Топчемся на месте HAL_Delay( 1 ); // Посылаем инит 256 байт for (Cnt = 0;Cnt < 256;Cnt++ ) { // Послыаем слово Card_SPI( 0xFF ); } // Начинаем инициализацию карты CARD_ON; // Ожидаем готовности карты do { // Посылаем 0xFF Dat = Card_SPI( 0xFF ); } while ( Dat != 0xFF ); // CMD0: GO_IDLE_STATE Card_SendCMD( &CARD_CMD0[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, DISABLE, 128 ); // Какой ответ получен? if ( Resp == 0x01 ) { // Карта вошла в IDLE_STATE, посылаем CMD8: SEND_IF_COND Card_SendCMD( &CARD_CMD8[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); // Если был дан адекватный респонс if ( Resp != 0x01 ) { // Это ветка SDv1/MMC do { // Посылаем ACMD41: APP_SEND_OP_COND Card_SendCMD( &CARD_ACMD41[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); } while ( Resp == 0x01 ); // Каков был ответ? if ( Resp == 0x00 ) { // Обнаружена карта SD v1 Res = ctSD1; } else { // Это ветка MMC, нам её некуда втыкать Res = ctUnknown; } } else { // Это ветка SDv2 if ( (OCR & 0x0001FF) == 0x0001AA ) { // Это карта SDv2 do { // Посылаем ACMD55: APP_CMD Card_SendCMD( &CARD_CMD55[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, DISABLE, 128 ); // Если ответ правильный if ( Resp == 0x01 ) { // Посылаем ACMD41: APP_SEND_OP_COND Card_SendCMD( &CARD_ACMD41[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); } } while ( Resp == 0x01 ); // Каков был ответ? if ( Resp == 0x00 ) { // Посылаем CMD58: READ_OCR Card_SendCMD( &CARD_CMD58[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); // Каков ответ? if ( Resp == 0x00 ) { // Анализируем OCR if ( (OCR & 0x40000000) == 0x00000000 ) { // Карта обычной ёмкости Res = ctSD2; } else { // Карта повышенной ёмкости Res = ctSD3; } } else { // Эта карта неисправна Res = ctUnknown; } } else { // Эта карта неисправна Res = ctUnknown; } } else { // Эта карта неисправна Res = ctUnknown; } } } else { // Карта ответила неправильно if ( Res != 0xFF ) { Res = ctUnknown; } } // Только для карт обычной ёмкости if ( (Res == ctSD1) || (Res == ctSD2) ) { // Устанавливаем размер блока 512 байт // CMD16: SET_BLOCKLEN Card_SendCMD( &CARD_CMD16[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, DISABLE, 128 ); // Каков ответ? if ( Resp != 0x00 ) { // Эта карта неисправна Res = ctUnknown; } } // Выключаем карту while ( (SPI1->SR & SPI_SR_BSY) != 0x0000 ) { } CARD_OFF; // Если карта инициализирована if ( (Res != ctNone) && (Res != ctUnknown) ) { // Настраиваем SPI на быструю скорость PCLK/2: 48/2 = 24МГц SPI1->CR1 &= ~SPI_CR1_SPE; SPI1->CR1 = SPI_CR1_MSTR; SPI1->CR1 |= SPI_CR1_SPE; } // Выходим return Res; }
// Чтение сектора карты памяти без DMA FunctionalState Card_Read( TCardType CardType, uint8_t *Buf, uint32_t *Loaded, uint32_t Addr ) { // Локальные переменные FunctionalState Res; uint8_t Cmd[ 6 ]; uint8_t Dat,Resp; uint32_t Cnt; // Инит Res = DISABLE; // Посмотрим, у нас в буфере уже загружено? if ( *(Loaded) != Addr ) { // Сохраняем новый номер сектора *(Loaded) = Addr; // Корректируем адрес для старых карт if ( (CardType == ctSD1) || (CardType == ctSD2) ) { // У старых карт адрес вместо LBA Addr *= 0x00000200; } // Работаем while ( 1 ) { // Если тип карты неправильный - выходим if ( CardType == ctNone ) { break; } if ( CardType == ctUnknown ) { break; } // Готовим команду на чтение сектора Cmd[ 0 ] = CARD_CMD17; Cmd[ 1 ] = Addr >> 24; Cmd[ 2 ] = Addr >> 16; Cmd[ 3 ] = Addr >> 8; Cmd[ 4 ] = Addr; Cmd[ 5 ] = 0xFF; // Включаем карту CARD_ON; // Ожидаем готовности карты do { // Посылаем 0xFF Dat = Card_SPI( 0xFF ); } while ( Dat != 0xFF ); // Посылаем команду чтения Card_SendCMD( &Cmd[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( (uint32_t *)&Cmd[ 0 ], DISABLE, 128 ); // Анализируем ответ на команду if ( Resp != 0x00 ) { break; } // Ожидаем токен данных Cnt = 2048; do { // Считываем данные Dat = Card_SPI( 0xFF ); // Считаем Cnt--; } while ( (Dat == 0xFF) && (Cnt > 0) ); // Таймаут? if ( Cnt == 0 ) { break; } // Ошибка в токене? if ( Dat != CARD_DATA_TOKEN ) { break; } // Начались данные, загружаем for (Cnt = 0;Cnt < 512;Cnt++) { // Считываем данные *(Buf) = Card_SPI( 0xFF ); Buf++; } // Дочитываем CRC Cmd[ 0 ] = Card_SPI( 0xFF ); Cmd[ 1 ] = Card_SPI( 0xFF ); // Без ошибок Res = ENABLE; // Выход break; } } else { // Без ошибок Res = ENABLE; } // Выключаем карту while ( (SPI1->SR & SPI_SR_BSY) != 0x0000 ) { } CARD_OFF; // Если была ошибка, обнулим номер if ( Res == DISABLE ) { *(Loaded) = 0xFFFFFFFF; } // Выход return Res; }
// Запись сектора карты памяти без DMA FunctionalState Card_Write( TCardType CardType, uint8_t *Buf, uint32_t *Loaded, uint32_t Addr ) { // Локальные переменные FunctionalState Res; uint8_t Cmd[ 6 ]; uint8_t Dat,Resp; uint32_t Cnt; // Инит Res = DISABLE; // Корректируем адрес для старых карт if ( (CardType == ctSD1) || (CardType == ctSD2) ) { // У старых карт адрес вместо LBA Addr *= 0x00000200; } // Работаем while ( 1 ) { // Если тип карты неправильный - выходим if ( CardType == ctNone ) { break; } if ( CardType == ctUnknown ) { break; } // Готовим команду на чтение сектора Cmd[ 0 ] = CARD_CMD24; Cmd[ 1 ] = Addr >> 24; Cmd[ 2 ] = Addr >> 16; Cmd[ 3 ] = Addr >> 8; Cmd[ 4 ] = Addr; Cmd[ 5 ] = 0xFF; // Включаем карту CARD_ON; // Ожидаем готовности карты do { // Посылаем 0xFF Dat = Card_SPI( 0xFF ); } while ( Dat != 0xFF ); // Посылаем команду чтения Card_SendCMD( &Cmd[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( (uint32_t *)&Cmd[ 0 ], DISABLE, 128 ); // Анализируем ответ на команду if ( Resp != 0x00 ) { break; } // Посылаем токен данных Card_SPI( CARD_DATA_TOKEN ); // Посылаем данные в цикле // Начались данные, загружаем for (Cnt = 0;Cnt < 512;Cnt++) { // Считываем данные Card_SPI( *(Buf) ); Buf++; } // Досылаем CRC Card_SPI( 0xFF ); Card_SPI( 0xFF ); // Без ошибок Res = ENABLE; // Выход break; } // Выключаем карту while ( (SPI1->SR & SPI_SR_BSY) != 0x0000 ) { } CARD_OFF; // Успешно? if ( Res == ENABLE ) { // Сохраняем новый номер сектора *(Loaded) = Addr; } else { // Обнуляем *(Loaded) = 0xFFFFFFFF; } // Выход return Res; }
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
Компания MEAN WELL пополнила ассортимент своей широкой линейки светодиодных драйверов новым семейством XLC для внутреннего освещения. Главное отличие – поддержка широкого спектра проводных и беспроводных технологий диммирования. Новинки представлены в MEANWELL.market моделями с мощностями 25 Вт, 40 Вт и 60 Вт. В линейке есть модели, работающие как в режиме стабилизации тока (СС), так и в режиме стабилизации напряжения (CV) значением 12, 24 и 48 В.
То есть, инициализация SPI происходит при выставлении НУЛЯ на CS и ЕДИНИЦЫ во время операций чтения-записи. А в описании все наоборот... Да и по логике работы SPI CS должно быть в нуле во время обращения к устройству, а в SD картах, выходит, все строго наоборот
Ничего там не наоборот. Начальные 74+ клока при CS=high - нужны очевидно для завершения возможных незавершённых сдвигов в машине состояний схемы карты. Если предыдущий цикл работы устройства оборвался во время передачи команды (МК неожиданно пересбросился). Вот чтобы вы-flush-ить все буфера и счётчики клоков карты и нужно, до начала работы с картой, при CS=high подать множество циклов. А штатная работа карты - естественно - при CS=low. На вышеприведённой ссылке это видно. Параграф "Cosideration on Multi-slave Configuration".
Нашел таки логический анализатор, и вот что получилось. Да, CS ведет как в описании. Наверное, осциллограф чудит при отображении информации. Сперва карты здорового человека и рабочий проект, который делает тестовые чтение-запись. Переходят в режим SPI без проблем. А вот карточки на 256 МБ. Уже и 256 единичных байт подал после запуска, и частоту задавил аж до 16 кбит (меньше уже нельзя, так как кварц 8 МГц). Карты здорового человека все еще определяются, но возвращают ошибку при попытке записи. В ответ лишь одиночные импульсы в рандомный момент времени. Похоже, нет у них режима SPI. Надо сказать, что карточки новодельные - продаются на алике упаковками по 5-10 штук. Возможно, поддержку SPI в них не завезли. В нативном режиме, судя по всему, работают нормально, так как прекрасно работают в компе... Такой же результат выдала двухгиговая карточка от Silicon Power
Сомнительно. Думаете - для таких дешёвых станут разрабатывать свой собственный уникальный контроллер? Лишь для того, чтобы выкусить SPI? Наверняка в них использован стандартный контроллер SD. А он ещё с былинных времён поддерживает SPI-режим.
А что за команда 0xA0 у вас в логе? А вообще, можете поделиться логами в бинарном формате а не картинками, чтобы изучить весь процесс?
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
А вообще, можете поделиться логами в бинарном формате а не картинками, чтобы изучить весь процесс?
Прикладываю файлы, созданные в PulseView. В бинарниках 4 канала, 1 MSmp/s. Реальная частота шины 140,625 kBit/s В "нормальной" флешке помимо инициализации код произвел чтение-запись
Ааа, это ты забыл указать декодеру /SS. Там нормально 0х40.
Вот нормальный лог:
Вот лог курильщика:
Зачем ты продолжаешь долбить после команды?
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
[uquote="Linuxoid91",url="/forum/viewtopic.php?p=4693129#p4693129"]Зачем ты продолжаешь долбить после команды?
Это чей-то скачанный с гитхаба пример. Уже не помню, какой конкретно - я их несколько скачал. Этот инициализирует карту, затем работает уже с FATFS - измеряет емкость, записывает и стирает файл, и т.д. Самое главное, что он выдает результат работы в UART, и сразу видно, что работа программы ведется. Единственное, что поменял в его работе - это увеличил количество стартовых байт с 10 до 255. Дальше код не менял
Добавлено after 35 minutes 1 second: В любом случае, курильщик холоден к попыткам связи его через SPI. Вообще, конечно, вряд ли китайцы специально вырезали протокол SPI, просто мне попалась версия с тем самым контроллером, где его нет. Однако, назрела необходимость заказать МК с интерфейсом SDIO, например, STM32F411. Смешно, но Bluepill ждали своего часа более 5 лет, ибо задачи для него достойной не находилось. Нужда пришла откуда не ждали - устройство должно стать мелкосерийным, а значит, старых запасов 8-битных МК не напасешься, а новые стоят неприлично много. Проект, к счастью, не коммерческий
Какой же он нормальный, если начинается с команды CMD40? Вы же сами выше приводили блок-схему алгоритма инициализации, из которой видно, что первой должна идти команда CMD0? А до неё ещё должны идти 74+ клоков при CS=high.
Какой же он нормальный, если начинается с команды CMD40? Вы же сами выше приводили блок-схему алгоритма инициализации, из которой видно, что первой должна идти команда CMD0? А до неё ещё должны идти 74+ клоков при CS=high.
Все команды имеют формат 01хххххх. То есть, сами команды 6-битные, к которым в качестве старших битов дописывается 01. Таким образом, CMD0=0100000 или 0х40
Все команды имеют формат 01хххххх. То есть, сами команды 6-битные, к которым в качестве старших битов дописывается 01. Таким образом, CMD0=0100000 или 0х40
Я лишь добавлю, что команда CMD40 существует. Её код это 0х68, потому что на интерфейсе ходит HEX а в названиях используется DEC.
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
Последний раз редактировалось HardWareMan Пт мар 14, 2025 10:41:04, всего редактировалось 1 раз.
Покуда придумываю, как бы мне заменить SPI на SDIO, нашел STM32F411 и мертвый DFPlayer в качестве слота Micro SD карты. И обнаружил, что на плеере не распаяны выводы 1,2 и 8 (фото в цвете). То есть, в режиме SDIO оборваны линии DAT1,2,3, а в режиме SPI - линия CS... Выходит, либо SD карточки работают по SPI безо всяких CS, либо по SDIO можно припаять всего один выход данных? Карты на 256 МБ в них прекрасно работают. Буду пробовать примерно через неделю, не раньше
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
_________________ Репозиторий STM32: https://cloud.mail.ru/public/2i19/Y4w8kKEiZ Актуальность репозитория: 16 мая 2025 года Если чего-то не хватает с сайта st.com - пишите, докачаю.
SDIO - это просто интерфейс. Не только SD-карты могут по нему подключаться. Есть микросхемы eMMC - они как раз используют 8 бит SDIO. Также встречал WiFi-чипы, подключающиеся по SDIO: TiWi-R2 (хотя в нём всего 4-битный SDIO). https://www.ezurio.com/part/450-0037
Нашел такую схему организации 1-битного SDIO посредством SPI. Однако, главный вопрос - а не сгорит? Ведь насколько мне известно, SPI не является интерфейсом с открытым коллектором, и в режиме простоя на нем единица... Смущает, правда, название RSPI... Из того, что я понял, его подключение ничем не отличается от SPI, и R в названии - не подтягивающий резистор... Но кажется, все будет работать, если запаять резистор последовательно с сигналом MOSI, а подтяжку к плюсу у D0 убрать... Правда, на STM так, похоже, никто не делал, либо я не нашел. Вообще, необходимость подтягивающих резисторов и двунаправленность больше напоминает интерфейс I2C.
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 17
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения