
What it feels like:

(одоление технологий из будущего с помощью шкур и каменных топоров, то бишь с 16-битного самопального процессора с 512 байтами оперативки и 256 байтами кода :))
В общем, ошибки поджидали повсюду: в QuatCore сбоил стек, но и после исправления этой ошибки ничего не получалось. Оказалось - на карту тупо не было подведено питание 3,3 вольта, она брала его со входов, но этого едва-едва хватало даже для инициализации.
После того, как питание наконец-то подал, инициализация стала идти "как по маслу", строго в соответствии со стандартом и с первого раза, и чтение тоже строго по стандарту, но почему-то ВСЕ НУЛИ.
Нашёл сразу проблему у чувака со StackExchange - то же, что у меня, всё вроде бы чётко и корректно, но кроме нулей ничего не читается. У него оказалось, что питание при чтении проседало, и от этого сбоить начинало. Я на всякий случай у себя перепаял - стал подавать на платку 5 вольт, чтобы уже её стабилизатор выдал 3,3. Но не поменялось ровным счётом ничего.
Проблема была в другом...
У меня на рабочем компе нет кардридера, и я в качестве такового использовал электронную книгу PocketBook. Когда в неё вставляешь карточку, а потом подключаешь по USB, определяется два съемных диска, один - внутренняя память самой книги, второй - карта памяти.
Именно оттуда я отформатировал карточку (быстрое форматирование), после чего создал текстовый документ HelloSD.txt:

А затем открыл программу HxD (Hex and Disk), которая в основном известна как Hex-редактор, но позволяет также открыть содержимое карточек памяти в "сыром виде". Я выбрал физический диск и увидел примерно такое:

Забавный факт: если вы пытаетесь включить компьютер с воткнутой флэшкой или картой памяти, и он пытается загрузиться с этой самой карты и выдаёт сообщение об ошибке, это сообщение делает не BIOS, а сама карточка. Соответвующий текст мы видим в конце нулевого сектора.
Потом я поискал слово "Привет" (по Ctrl+F) и нашёл его:

Сектор номер 30392 = 0x76B8. Если умножить на 0x200, получим 0xED7000 - а это и есть Offset, указанный слева, т.е номер байта от начала диска. В карточках памяти стандартной ёмкости (SC, Standart Capacity) при чтении нужно указать именно offset в байтах, но это карточки до 2 ГБ. Все, что выше - это HC (High Capacity) или XC (eXtended Capacity), а в них нужно указывать НОМЕР СЕКТОРА. Так что я указал в своей программке 0x76B8 и ожидал увидеть приветствие.
Но вместо этого увидел сплошные нули. Подумалось - а вдруг надо всё-таки подать CMD58 (Read OCR, Operating Conditions Register), и тогда только карточка поймёт, что мы в курсе, что она High Capacity, и будет вести себя соответственно? Сказано-сделано, в ответ на CMD58 мы действительно получаем 32-битное значение 0xC0FF8000, по которому следует, что карта High Capacity, она уже полностью готова к передачи данных и поддерживает любое рабочее напряжение от 2,7 до 3,6 вольта. Но результат никак не изменился - я по-прежнему получал сплошные нули.
Тогда я решил задать нулевой адрес - прочитаю хоть загрузочный сектор, в нём дофига всего лежит! И ноль уж точно ни с чем не спутаешь, адресуй хоть секторами, хоть байтами! Но и там меня ждали сплошные нули. Я, правда, не весь сектор читал, а только 21 символ, хотел собственно своё "приветствие" увидеть.
А сегодня я попробовал ту же самую карточку прочитать нормальным кард-ридером. И открыв физический диск всё в той же HxD, увидел совсем другую картину:

А как же поживает наш сектор 30392? Да вот чего-то не очень:

То есть, я И ДОЛЖЕН БЫЛ ПОЛУЧИТЬ НУЛИ. А где же "Привет"? Ищем и тут же находим:

Вместо смещения 0xED7000, имеем смещение 0x12D7000, т.е мы дальше от начала карточки на 0x400000 ≈ 4 МБ, чем раньше. А ну-ка, посмотрим теперь смещение 0x400000, или сектор 0x2000 = 8192:

Ба, да вот же наш загрузочный сектор!
Итак, электронная книга оказала нам медвежью услугу - сама разбила физический диск на логические, и сколько было логических (один) "представила наружу" через USB как физический!
Стоило только заменить номер сектора с 0x76B8 на 0x96B8, как я увидел столь долгожданную надпись:

Вот код программы, которая это учудила:
;учимся возиться с SD-карточкой через SPI. ;хотим получить полный лог инициализации, т.е какие команды давали какой ответ ;проще всё посылать по UART, чтобы была "простыня" ;ЖК в этот раз без надобности ;и числа с фикс. точкой не нужны, хватит целых, а может вообще HEX забабахать? %include "QuatCoreConsts.inc" %include "Win1251.inc" .rodata ORG 0 CMD0 dw 5,0x40,0x00,0x00,0x00,0x00,0x95 CMD8 dw 5,0x48,0x00,0x00,0x01,0xAA,0x87 CMD55 dw 5,0x77,0x00,0x00,0x00,0x00,0x65 ACMD41 dw 5,0x69,0x40,0x00,0x00,0x00,0x77 ECHO dw 0xFFAA,0x01,0x00,0x00 CMD58 dw 5,0x7A,0x00,0x00,0x00,0x00,0xFD CMD17ours dw 5,0x51,0x00,0x00,0x96,0xB8,0x29 ;CRC косой, но вроде бы не проверяется по умолчанию... Str0 dw 'Обращаемся к SD-карточке',13,0x800A CMD0str dw 'Send CMD0',13,0x800A CMD8str dw 'Send CMD8',13,0x800A Timeoutstr dw 'Timeout, restarting...',13,0x800A R1failstr dw 'R1 incorrect:',f' ' success dw 'Success',13,0x800A EchoFailStr dw 'CMD8 echo fail',13,0x800A OKCMD8 dw 'CMD8 success, v2.0 card',13,0x800A CMD55str dw 'Send CMD55',13,0x800A ACMD41str dw 'Send ACMD41',13,0x800A ReadDatastr dw 'Read data',13,0x800A CMD58str dw 'Send CMD58',13,0x800A .data Stack dw ?,?,?,?,? .rodata ORG -12 CMD58strAdr dw CMD58str ;-12 Str0Adr dw Str0 ;-11 CMD0strAdr dw CMD0str ;-10 CMD8strAdr dw CMD8str ;-9 TimeoutstrAdr dw Timeoutstr ;-8 R1failstrAdr dw R1failstr ;-7 EchoFailAdr dw EchoFailStr ;-6 OKCMD8Adr dw OKCMD8 ;-5 CMD55strAdr dw CMD55str ;-4 successAdr dw success ;-3 ACMD41strAdr dw ACMD41str ;-2 ReadDatastrAdr dw ReadDatastr ;-1 .code main proc SP Stack Inv 1 ;будет нам указывать состояние карточки, 1: idle, 0: режим передачи данных! ;надо дать ему 74 такта SCK, или 148 тактов наших. Один уже прошёл :) ;напишем что-нибудь по UART :) X Str0Adr CALL print ;экон 1 слово ;34 символа, по 10 бит, на 460800 бит/с даст 738 мкс, это 1475 тактов - сойдёт :) ;(точнее, 33 символа, т.к на последнем мы не дожидаемся, но тоже хватит!) ;давайте делать 3 попытки подключиться. j 3 InitSD: jLOOP @@Init @@endless: JMP @@endless ;ПОСЫЛАЕМ CMD0 @@Init: X CMD0strAdr ;экон 2 слова CALL print X CMD0 ;экон 3 слова CALL SDsend ;ответ получили, это должен быть 0x01, в случае таймаута нас уже вернули на InitSD. CALL checkR1 ;фух, первую команду осилили! X successAdr ;экон 4 слова CALL print ;ПОСЫЛАЕМ CMD8 X CMD8strAdr ;экон 5 слов CALL print X CMD8 ;экон 6 слов CALL SDsend ;должны получить R1=0x01, а затем "эхо" нашей команды ;если получаем R1 = 0x04 (illegal command), значит карта legacy. Пока мы её не рассматриваем... CALL checkR1 ;и по-хорошему ещё проверить "эхо", а для начала получить его. ;точнее, ответ R7 - он состоит из R1 и ещё 4 байт. Последний повторяет check pattern AA, которую мы послали ;(а вообще, можно посылать ЧТО УГОДНО) ;ноль вместо 0001 означает, что карта не поддерживает напряжения 2,7..3,6 вольта (и УЖЕ СГОРЕЛА) ;несовпадение check pattern - значит что-то глючит в нашей связи. k 3 X ECHO @@echo: C IN OUT -1 Acc C SUB [X+k] CALL IsZero JGE @@echoFail kLOOP @@echo ;если добрались до сюда, значит, эхо в порядке. X OKCMD8Adr ;экон 7 слов CALL print ;ПОСЫЛАЕМ CMD55 X ACMD41strAdr ;экон 8 слов CALL print @@DoCmd55: X CMD55 ;экон 9 слов CALL SDsend CALL checkR1 X ACMD41 ;экон 10 слов CALL SDsend ;а вот теперь проверка интереснее - если получаем 0x00, то всё хорошо. А если 0x01 - то повторно шлём CMD55 и т.д. ;но как всегда учтём - может не сразу прийти! CALL IsZero JGE @@DoCmd55 ;если добрались досюда, значит, карта проинициализировалась! X successAdr ;экон 11 слов CALL print Inv 0 ;указываем, что карта в режиме передачи данных ;но ещё бы узнать, она HC или где? ;ПОСЫЛАЕМ CMD58 X CMD58strAdr ;экон 12 слов CALL print X CMD58 ;экон 13 слов CALL SDsend ;сначала идёт R1 и по-моему он должен быть 0x01, как это ни странно! CALL checkR1 ;а теперь OCR, который просто выведем на UART. SPI у нас на 100 кГц, а UART на 460 кГц, т.к что успеваем кажись k 3 @@OCR: C IN OUT -1 SIO UART OUT C SIO SPI kLOOP @@OCR ;ладно, считаем, что карточка инициализирована, и в HC-режиме. ;осталось за малым - прочитать с неё строку. X ReadDatastrAdr ;экон 14 слов CALL print X CMD17ours ;экон 15 слов CALL SDsend CALL checkR1 ;теперь ещё должны прочитать токен FE @@DoRead: C IN OUT -1 Acc C SUB -2 ;ADD 2 не выставит флаг! CALL IsZero JGE @@DoRead ;а вот теперь уже данные должны пойти! i 21 @@ReadCyc: C IN OUT -1 SIO UART OUT C SIO SPI iLOOP @@ReadCyc JMP @@endless @@echoFail: X EchoFailAdr ;экон 16 слов CALL print JMP InitSD main endp ;посылает строку конкретно в UART. Длина строки не более 32 символов. ;X указывает на начало строки текста ;конец обозначается отрицательным числом (у нас НЕТ ФЛАГА НУЛЯ!!!) ;меняет значение регистра Acc и i. print proc ;[SP++] i SIO UART i 0 X [X+i] ;косвенная адресация @@start: OUT [X+i] Acc [X+i] i++ 0 SUB 0 JGE @@start ;i [--SP] JMP [--SP] print endp ;более универсальный вариант, здесь любые 16-битные "символы" допустимы. Поэтому в начале строки лежит количество символов ;считываем его и потом спокойненько отправляем! ;сейчас затираем регистры Y,i,C. Если они нужны, надо раскомментировать соотв. строки ;в конце получаем i от нуля до 7 ;ответный 1 символ лежит по Acc, а также в C. SDsend proc ;[SP++] Y ;[SP++] i SIO SPI Y SP SP X i [SP++] ;передавать само количество байт мы не должны! @@start: OUT [SP++] iLOOP @@start SP Y ;i [--SP] ;Y [--SP] ;теперь получаем ответ. Даём SD-карточке 8 байт на "подумать" i 7 OUT -1 ;0xFF по сути @@waitR1: C IN ;здесь мы, увы, не можем терять ни одного такта OUT -1 Acc C SUB 0 ;проверяем знак (если старший бит 1, значит так ничего и не приняли - "таймаут") JL @@proceed JMP [--SP] @@proceed: iLOOP @@waitR1 ;если дошли досюда, значит, ответа нормального не получили. Объявим таймаут и начнём по-новой... X TimeoutstrAdr ;экон 17 слов CALL print [--SP] Call(InitSD) SDsend endp ;после этой процедуры, JL означает "прыгнуть если РАВНО НУЛЮ", а JGE - "прыгнуть если НЕ РАВНО" IsZero proc ABS Acc SUB 1 JMP [--SP] IsZero endp ;в аккумуляторе и регистре C лежит однобайтовый ответ (НЕ 0xFF), хотим подтвердить, что это единичка. checkR1 proc SUB Inv CALL IsZero JGE @@R1fail JMP [--SP] @@R1fail: X R1failstrAdr ;экон 18 слов CALL print OUT C [--SP] Call(InitSD) checkR1 endp
Программа занимает 115 слов, или 230 байт, а оперативная память забита всевозможными "отладочными" сообщениями и посылками для SD-карточки. Сейчас забита очень неэкономично - на каждый байт уходит целое слово, а в случае с командами ещё и дополнительное число, "количество байт". На самом деле, у нас все команды здесь оказались 6-байтовыми, так что можно было подсократить. Ну да ладно, всё равно в ПЛИС каждый блок внутренней памяти - 512 байт, вот мы их почти что и забили!
Не сказать, что она шибко красивая - её можно было бы сократить на 15%, если чуть модернизировать QuatCorePC (program counter), чтобы вызывать ПРОЦЕДУРУ С ОДНИМ АРГУМЕНТОМ в одну строку.
Но в целом, она очень параноидально проходит всю процедуру инициализации. Не получил хоть какого-то ответа от карточки за 8 байт - ругаемся на таймаут и пытаемся по-новой, а после 3 попыток "сдаёмся". Ответ был не 0x01 (на этапе инициализации) или 0x00 (после неё) - ругаемся и пытаемся по-новой. Для CMD8 проверяем не только ответ R1, но и следующие за ним 4 байта - они должны повторять то, что мы отправили в качестве аргумента.
И мы даже получаем ответ, является ли карта HC или нет, что потом могло бы повлиять на общение с ней, хотя по-моему мы можем указать "размер сектора" 512 для SC, и тогда их работа будет эквивалентной, это я ещё не изучал, да скоро и не нужно станет - карт меньше 2 ГБ в продаже-то и не найдёшь!
Как ещё можно было бы эту программу "причесать", не выкидывая нафиг всю обработку ошибок - даже не знаю пока.