nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

SD-карточка заработала!

What it looks like:


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 ГБ в продаже-то и не найдёшь!

Как ещё можно было бы эту программу "причесать", не выкидывая нафиг всю обработку ошибок - даже не знаю пока.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

    Мишка такой человек — ему обязательно надо, чтоб от всего была польза. Когда у него бывают лишние деньги, он идёт в магазин и покупает какую-нибудь…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments