Абзац
:: Поиск
:: Поддержка проекта
Webmoney:
  • Z610389805629
  • R427996570517
  • E023541002978
  • :: №32 (10.10.2009) Просмотров: 19794

    Автор: Вячеслав Савенков / savelij.

    Рубрика: В помощь разработчику.

    Номер: №32 (10.10.2009).



    Программирование SD карт на Спектруме

    О спецификациях и SD картах

    Карта памяти SD (Secure Digital) является од­ной из разновидностей флэш-памяти, такие же как и обычные USB-Flash накопители, но основной ее особенностью является ставка на защиту запи­санной информации.

    Разновидностей SD карт на сегодняшний день доступно ровно три вида. Различаются они как по физическому размеру (стандартные, мини и микро), так и по объему. Основным явля­ется стандартный размер, для карт размера Mini и Micro существуют переходники до стандартно­го размера.

    Различий по объему, согласно спецификации, всего два: до 2 Гб включительно карта называется стандартной и не несет на себе никаких дополни­тельных указаний, кроме объема (описано специ­фикацией версия 1.1). Карты начиная с 4 Гб обя­зательно должны нести на себе гордую надпись SDHC (SD High Capacity) и Class 2 (4 или 6), цифра указывает скорость передачи данных в мегабай­тах. Если на карте с объемом 4 Гб и более нет таких надписей, то карта просто неправильно маркиро­вана (описано спецификацией версии 2.0). Так же спецификацией версии 2.0 описаны карты физи­ческого размера мини и микро.

    Набор внутренних команд зависит от специ­фикации и режима работы. На сегодняшний день последняя версия спецификации имеет номер 2.0 и датирована 25 сентября 2006 годом и существу­ет на английском языке (возможно и на каких-то других языках), перевода на великий и могучий не обнаружено. Согласно этой спецификации объем карты до 2 Гигабайт включительно дает возмож­ность изменять программно размер сектора от 1 до 512 байт, по умолчанию размер сектора равен 512 байтам. Карты объемом от 4 до 32 Гигабайт (32 Гб - ограничение  спецификации версии 2.0) име­ют фиксированный размер сектора равный 512 байтам, который не может быть изменен. Карты физического размера мини и микро относятся к спецификации 2.0 (хотя могут иметь объем менее 4ГБ) и программируются как SDHC карты, размер сектора также не может быть изменен.

    Карта может работать в одном из двух режи­мов: непосредственно SD режим и SPI. Для нас наиболее интересен режим SPI, который и реали­зован в Z-контроллере. Реализация и работа  само­го интерфейса может быть описана автором кон­троллера - Жабиным Алексеем (KOE), я же опишу программирование SD карты в режиме SPI. Коли­чество команд в этом режиме значительно мень­ше, чем в режиме SD. На текущий момент написан и отлажен драйвер для работы с картами любого объема и размера (по спецификации 2.0).

    На момент написания статьи появилась но­вость о скором появлении карт SDXC (объемом до 2 Терабайт) и соответствующих спецификаций, по­сле чего будут внесены соответствующие измене­ния в драйвер. 


    О файловой системе

    FAT (File Allocation Table – таблица размещения файлов). Изначально она была создана для гибких дисков размером меньше чем 500K, но со време­нем развивалась для поддержки дисков всё боль­ших и больших объемов. Сейчас уже существуют три типа FAT: FAT12, FAT16 и FAT32. Основные раз­личия в типах FAT отражены в их названии - это размер (в битах) значений таблицы FAT. 12 бит в FAT12, 16 бит в FAT16, и 32 бит в FAT32.

    MBR (Master Boot Record – главная загрузочная запись). Это первый физический сектор на винче­стере или другом устройстве хранения информа­ции, разбиваемом на логические диски (разделы). MBR содержит таблицу разделов (partition table) и небольшой фрагмент исполняемого кода.

    SD карты выпускаются отформатированными в файловую систему ФАТ. Разрядность ФАТ за­висит от объема карты и может быть изменена переформатированием. После заводского фор­матирования на карте всегда существует MBR, содержащая только один раздел. Между секто­ром MBR и началом раздела всегда есть неис­пользуемые сектора, количество которых за­висит от производителя карточки. Карточки объемом до 16Мб включительно поставлялись с файловой системой ФАТ12, от 32Мб до 2Гб вклю­чительно с файловой системой ФАТ16, от 4ГБ и более ФАТ32.

    Для PC существует программа форматирова­ния карточек (восстановления заводского фор­мата) (http://www.sdcard.org/consumers/formatter/sdfv2000.exe, для скачивания надо принять лицен­зионное соглашение), или если сказать точнее для возвращения карте исходного заводского форма­тирования.  Эта программа всегда создает MBR и только один раздел.

    При работе с ФАТом под Windows ХР (далее XP) обнаружилась одна хитрая особенность или воз­можно ошибка. У ФАТ32 из-за большого количест­ва кластеров в дополнение к описателю раздела добавлен еще один сектор в котором помещает­ся информация о количестве свободных класте­ров и номере кластера, с которого надо начинать поиск свободных кластеров для записи. При раз­рядности 32 бита номер первого свободного кла­стера записывается как 16-битное число, то есть старшие 16 бит всегда, или почти всегда, равны нулю. При попытке исправить на правильное зна­чение и дальнейшей работе под ХР старшие 16 бит номера первого свободного кластера просто обнуляются. Чтобы правильно работать с ФАТом не только на Спектруме, но и под ХР надо учиты­вать данную особенность и производить поиск первого свободного кластера или с указанного номера или от начала ФАТ таблицы. Что, конечно, будет занимать длительное время, которое зави­сит от размера раздела и объема уже записанной информации.


    Порты Z-контроллера

    Со стороны Спектрума интерфейс SPI досту­пен как порт данных #57 и порт конфигурации #77. Оба порта доступны и по чтению и по запи­си. Порт данных для программирования абсолют­но прозрачен и служит для двухсторонней пере­дачи между контроллером и картой.

    Порт конфигурации по записи:

    бит 0 – установка этого бита подает питание на карту;

    бит 1 – установка этого бита снимает выбор карты и делает ее недоступной для программиро­вания, сброс соответственно выбирает карту и по­зволяет отдавать команды. Остальные биты не ис­пользуются.

    Порт конфигурации по чтению:

    бит 0 – сброшен когда карта памяти вставле­на в слот;

    бит 1 – сброшен если карта памяти не защище­на от записи, установлен если карта защищена от записи. Остальные биты не используются.


    Немного о драйвере

    При написании драйвера использовалось опи­сание инициализации от создателя Хард-тапер и исходники с сайта wwwArray для ра­боты с MMC картой. Драйвер писался из расче­та поддержки только SD карт и не поддержива­ет MMC карты по причине отсутствия как самих карт для проверки, так и спецификаций на них в свободном доступе.  При попытке использования MMC карт драйвер будет зависать - о причинах в тексте драйвера.

    Текст драйвера снят с рабочего исходника и является полностью рабочим. Со времени напи­сания статьи для журнала «NedoPC №5» прошло достаточно много времени. Выявлены и устра­нены некоторые неточности и ошибки. Улучше­на работа в турбо-режиме за счет дополнитель­ных задержек, предыдущая версия драйвера при частоте процессора 7МГц иногда бессистемно чи­тала с ошибками, что было выявлено в процес­се эксплуатации. Данный драйвер поддерживает карты любого объема и любого размера в преде­лах текущей спецификации версии 2.0, опреде­ление типа карты делается во время исполнения команды чтения/записи.


    Команды и ответы

    Передача команд/параметров на карту и от­ветов карты осуществляется от старшего бита до младшего. Размер любой команды для карты ра­вен 6 байтам (48 бит).

    Формат любой команды:


                  Стартовый  Направление  Индекс   Аргумент  CRC7  Стоповый 
                  бит        передачи     команды                  бит
    Номер бита    47         46           45-40    39-8       7-1  0
    Размер, бит   1          1            6        32         7    1 
    Значение      0          1            Х        Х          Х    1

    Команды использованные в драйвере:

    CMD0 (ответ R1) - после подачи питания на кар­ту и инициализации интерфейса переводит карту в режим SPI.

    CMD8 (ответ R7) - применяется для определе­ния какая версия спецификации поддерживает­ся картой. Для спецификации версии 1.1 выдает ошибку, команда появилась в спецификации 2.0.

    CMD12 (ответ R1b) - команда применяется для останова многоблочной операции чтения. У SD карт нет счетчика чтения, поэтому после чтения нужного количества секторов должна подаваться команда останова. Для запуска и останова много­блочной записи используются байты-маркеры.

    CMD16 (ответ R1) - установка размера сектора. Только для карт поддерживающих спецификацию 1.1. Для спецификации 2.0 игнорируется.

    CMD17 (ответ R1) - команда чтения одного сек­тора.

    CMD18 (ответ R1) - команда чтения нескольких секторов.

    CMD24 (ответ R1) - команда записи одного сек­тора.

    CMD25 (ответ R1) - команда записи нескольких секторов.

    CMD55 (ответ R1) - указывает карте, что сле­дующая команда специальная.

    CMD58 (ответ R3) - чтение OCR регистра карты. Применяется для определения стандартная или SDHC/мини/микро SD карта.

    CMD59 (ответ R1) - изменение режима (включить/выключить) подсчета контрольной суммы CRC.

    ACMD41 (ответ R1) - специальная команда. Применяется для запуска процесса инициализа­ции карты с учетом типа карты.

    В процессе чтения/записи секторов использу­ются специальные байты-маркеры.

    Маркер #FE - выдается картой при операци­ях чтения одного (CMD17) или нескольких (CMD18) секторов. После получения этого маркера мож­но производить чтение сектора. Выдается пе­ред каждым сектором. При записи одного секто­ра (CMD24) должен передаваться перед передачей сектора на карту.

    Маркер #FC - передает карте о начале запи­си сектора (только при многосекторной записи (CMD25)). После подачи этого маркера можно на­чинать передачу сектора. Маркер выдается пе­ред каждым сектором.

    Маркер #FD - передает карте команду об ос­тановке много секторной записи (для завершения команды CMD25).

    После подачи любой команды, первый считан­ный байт из SD карты (не равный #FF) является от­ветом карты и, если не равен нулю (команда будет выполнена), то содержит биты ошибки. В драйвере код ошибки чаще всего игнорируется. По специ­фикации карта выдает несколько разновидностей ответов различающихся длиной, в драйвере чаще всего используется только первый байт ответа.


    Ответы карты:

    http://abzac.retropc.ru/images/i32_sd1.png


    R1(R1b) - длина ответа 8 бит. Короткий ответ, так же входит в состав многобайтовых ответов.

    Коды ошибок по битам. Возможно, я где-то не так перевел, поэтому привожу и оригинальное описание битов ошибок:

    7 - всегда 0

    6 - parameter error (ошибочный параметр)

    5 - address error (неправильный адрес блока)

    4 - erase sequence error

    3 - com crc error (ошибка CRC команды (при от­ключенном CRC не должна появляться))

    2 - illegal command (неизвестная команда)

    1 - erase reset

    0 - in idle state (карта находится в режиме ини­циализации и недоступна)

    R7 - длина ответа 40 бит. Старшие 8 бит соот­ветствуют формату ответа R1. Остальные биты от­вета в драйвере не используются.

    http://abzac.retropc.ru/images/i32_sd2_tmb640.png

    Вызов драйвера производится через общую точку входа, код команды помещается сразу по­сле команды вызова. Всего команд 6, нумерация от 0 до 5.


    Перед вызовом драйвера регистры процессора должны содержать:

    HL - адрес чтения/записи.

    DE - младшие 16 бит номера сектора.

    BC - старшие 16 бит номера сектора.

    A - количество секторов, только для многосек­торных команд (команды 3 и 5).

    Команды используемые в драйвере:

    0 - включение питания и инициализация SD карты

    1 - выключение питания карты

    2 - чтение одного сектора

    3 - чтение нескольких секторов

    4 - запись одного сектора

    5 - запись нескольких секторов

    Ошибки, выдаваемые драйвером в регистре «A»:

    0 - инициализация прошла успешно

    1 - карта не найдена или не ответила

    2 - карта защищена от записи

    3 - попытка записи в сектор 0 карты


    ;Пример использования драйвера:
        CALL COM__SD    
        DB 0
    ;включение и инициализация карты памяти
    ;на выходе А - смотрим коды возвращаемых ошибок
        LD HL,АДРЕС ЗАГРУЗКИ
        LD BC,СТАРШИЕ 16 БИТ НОМЕРА СЕТОРА
        LD DE,МЛАДШИЕ 16 БИТ НОМЕРА СЕКТОРА
        LD A,КОЛИЧЕСТВО СЕКТОРОВ
        CALL COM__SD
        DB 3
    ;чтение заданного количества секторов по заданному адресу начиная с заданного сектора
    ;на выходе не забываем проверять код ошибки в А.
        CALL COM__SD
        DB 1 
    ;выключаем питание карты
    ;Драйвер SD карты
    ;LAST UPDATE 14.04.2009 savelij
    ;Входные параметры общие:
    ;HL-адрес загрузки в память
    ;BCDE-32-х битный номер сектора
    ;A-количество блоков (блок=512 байт) - только для многоблочной записи/чтения
    ;Ошибки выдаваемые на выходе:
    ;A=0 - инициализация прошла успешно
    ;A=1 - карта не найдена или не ответила
    ;A=2 - карта защищена от записи
    ;A=3 - попытка записи в сектор 0 карты
    P_DATA    EQU #57    ;порт данных
    P_CONF    EQU #77    ;порт конфигурации
    CMD_12    EQU #4C    ;STOP_TRANSMISSION
    CMD_17    EQU #51    ;READ_SINGLE_BLOCK
    CMD_18    EQU #52    ;READ_MULTIPLE_BLOCK
    CMD_24    EQU #58    ;WRITE_BLOCK
    CMD_25    EQU #59    ;WRITE_MULTIPLE_BLOCK
    CMD_55    EQU #77    ;APP_CMD
    CMD_58    EQU #7A    ;READ_OCR
    CMD_59    EQU #7B    ;CRC_ON_OFF
    ACMD_41    EQU #69    ;SD_SEND_OP_COND
    ;ОБЩАЯ ТОЧКА ВХОДА ДЛЯ РАБОТЫ С SD
    COM__SD    EXA
        EX (SP),HL
        LD A,(HL)
        INC HL
        EX (SP),HL
        ADD A,A
        PUSH HL
        LD HL,TABLSDZ
        ADD A,L
        LD L,A
        LD A,H
        ADC A,0
        LD H,A
        LD A,(HL)
        INC HL
        LD H,(HL)
        LD L,A
        EXA
        EX (SP),HL
        RET
            ;коды и описания функций
    TABLSDZ    DW SD_INIT    ;0-параметров не требует, на выходе A
            ;смотри коды ошибок, первые 2 значения
        DW SD__OFF    ;1-выключить питание карты
        DW RDSINGL    ;2-читать один сектор
        DW RDMULTI    ;3-читать «А» секторов
        DW WRSINGL    ;4-записать один сектор
        DW WRMULTI    ;5-записать «А» секторов
    SD_INIT    CALL CS_HIGH    ;включаем питание карты при снятом выборе
        LD BC,P_DATA
        LD DE,#20FF    ;бит выбора карты в «1»
        OUT (C),E    ;записываем в порт много единичек
        DEC D    ;количество единичек несколько больше
        JR NZ,$-3    ;чем надо
        XOR A    ;запускаем счетчик на 256
        EXA    ;для ожидания инициализации карты
    ZAW001    LD HL,CMD00    ;даем команду сброса
        CALL OUTCOM    ;этой командой карточка переводится в режим SPI
        CALL IN_OOUT    ;читаем ответ карты
        EXA
        DEC A
        JR Z,ZAW003    ;если карта 256 раз не ответила, то карты нет
        EXA
        DEC A
        JR NZ,ZAW001    ;ответ карты «1», перевод в SPI прошел успешно
        LD HL,CMD08    ;запрос на поддерживаемые напряжения
        CALL OUTCOM    ;команда поддерживается начиная со спецификации
        CALL IN_OOUT    ;версии 2.0 и только SDHC, мини и микро SD картами
        IN H,(C)    ;в A=код ответа карты
        NOP    ;считываем 4 байта длинного ответа
        IN H,(C)    ;но не используем
        NOP
        IN H,(C)
        NOP
        IN H,(C)
        LD HL,0    ;HL=аргумент для команды инициализации
        BIT 2,A    ;если бит 2 установлен, то карта стандартная
        JR NZ,ZAW006    ;стандартная карта выдаст «ошибка команды»
        LD H,#40    ;если ошибки не было, то карта SDHC, мини или микро SD
    ZAW006    LD A,CMD_55    ;запускаем процесс внутренней инициализации
        CALL OUT_COM    ;для карт MMC здесь должна быть другая команда
        CALL IN_OOUT    ;соответственно наличие в слоте MMC-карты
        LD A,ACMD_41    ;вызовет зависание драйвера, от применения
        OUT (C),A    ;общей команды запуска инициализации я отказался
        NOP    ;бит 6 установлен для инициализации SDHC карты
        OUT (C),H    ;для стандартной сброшен
        NOP
        OUT (C),L
        NOP
        OUT (C),L
        NOP
        OUT (C),L
        LD A,#FF
        OUT (C),A
        CALL IN_OOUT    ;ждем перевода карты в режим готовности
        AND A    ;время ожидания примерно 1 секунда
        JR NZ,ZAW006
    ZAW004    LD A,CMD_59    ;принудительно отключаем CRC16
        CALL OUT_COM
        CALL IN_OOUT
        AND A
        JR NZ,ZAW004
    ZAW005    LD HL,CMD16    ;принудительно задаем размер блока 512 байт
        CALL OUTCOM
        CALL IN_OOUT
        AND A
        JR NZ,ZAW005
    ;включение питания карты при снятом сигнале выбора карты
    CS_HIGH    PUSH AF
        LD A,3
        OUT (P_CONF),A    ;включаем питание, снимаем выбор карты
        XOR A
        OUT (P_DATA),A    ;обнуляем порт данных
        POP AF    ;обнуление порта можно не делать, просто последний
        RET    ;записанный бит всегда 1, а при сбросе через вывод
            ;данных карты напряжение попадает на вывод питания
            ;карты и светодиод на питании подсвечивается
    ;возврат при не ответе карты с кодом ошибки 1
    ZAW003    CALL SD__OFF
        INC A
        RET
    SD__OFF    XOR A
        OUT (P_CONF),A    ;выключение питания карты
        OUT (P_DATA),A    ;обнуление порта данных
        RET
    ;выбираем карту сигналом 0
    CS__LOW    PUSH AF
        LD A,1
        OUT (P_CONF),A
        POP AF
        RET
    ;запись в карту команды с неизменяемым параметром из памяти
    ;адрес команды в «HL»
    OUTCOM    CALL CS__LOW
        PUSH BC
        LD BC,#600+P_DATA
        OTIR    ;передаем 6 байт команды из памяти
        POP BC
        RET
    ;запись в карту команды с нулевыми аргументами
    ;А-код команды, аргумент команды равен 0
    OUT_COM    PUSH BC
        CALL CS__LOW
        LD BC,P_DATA
        OUT (C),A
        XOR A
        OUT (C),A
        NOP
        OUT (C),A
        NOP
        OUT (C),A
        NOP
        OUT (C),A
        DEC A
        OUT (C),A    ;пишем пустой CRC7 и стоповый бит
        POP BC
        RET
    ;запись команды чтения/записи с номером сектора в BCDE для карт стандартного размера
    ;при изменяемом размере сектора номер сектора нужно умножать на его размер, для карт 
    ;SDHC, мини и микро размер сектора не требует умножения
    SECM200    PUSH HL
        PUSH DE
        PUSH BC
        PUSH AF
        PUSH BC
        LD A,CMD_58
        LD BC,P_DATA
        CALL COM_OUT
        CALL IN_OOUT
        IN A,(C)
        NOP
        IN H,(C)
        NOP
        IN H,(C)
        NOP
        IN H,(C)
        BIT 6,A    ;проверяем 30 бит регистра OCR (6 бит в «А»)        
        POP HL    ;при установленном бите умножение номера сектора
        JR NZ,SECN200    ;не требуется
        EX DE,HL    ;при сброшенном бите соответственно
        ADD HL,HL    ;умножаем номер сектора на 512 (#200)
        EX DE,HL
        ADC HL,HL
        LD H,L
        LD L,D
        LD D,E
        LD E,0
    SECN200    POP AF    ;заготовленный номер сектора находится в «HLDE»
        OUT (C),A    ;пишем команду из «А» на SD карту
        NOP    ;записываем 4 байта аргумента
        OUT (C),H    ;пишем номер сектора от старшего
        NOP
        OUT (C),L
        NOP
        OUT (C),D
        NOP
        OUT (C),E    ;до младшего байта
        LD A,#FF
        OUT (C),A    ;пишем пустой CRC7 и стоповый бит
        POP BC
        POP DE
        POP HL
        RET
    ;чтение ответа карты до 32 раз, если ответ не #FF - немедленный выход
    IN_OOUT    PUSH DE
        LD DE,#20FF
    IN_WAIT    IN A,(P_DATA)
        CP E
        JR NZ,IN_EXIT
    IN_NEXT    DEC D
        JR NZ,IN_WAIT
    IN_EXIT    POP DE
        RET
    CMD00    DB #40,#00,#00,#00,#00,#95 ;GO_IDLE_STATE
        ;команда сброса и перевода карты в SPI режим после включения питания
    CMD08    DB #48,#00,#00,#01,#AA,#87 ;SEND_IF_COND
        ;запрос поддерживаемых напряжений
    CMD16    DB #50,#00,#00,#02,#00,#FF ;SET_BLOCKEN
        ;команда изменения размера блока
    ;читаем один сектор из карты в память, адрес чтения в «HL»
    RD_SECT    PUSH BC
        LD BC,P_DATA
        INIR
        NOP
        INIR
        NOP
        IN A,(C)
        NOP
        IN A,(C)
        POP BC
        RET
    ;записываем один сектор из памяти в карту, адрес записи в «HL»
    WR_SECT    PUSH BC
        LD BC,P_DATA
        OUT (C),A
        NOP
        OTIR
        NOP
        OTIR
        LD A,#FF
        OUT (C),A
        NOP
        OUT (C),A
        POP BC
        RET
    ;многосекторное чтение
    RDMULTI    EXA    ;прячем счетчик секторов
        LD A,CMD_18
        CALL SECM200    ;даем команду многосекторного чтения
        EXA
    RDMULT1    EXA
        CALL IN_OOUT
        CP #FE
        JR NZ,$-5    ;ждем маркер готовности #FE для начала чтения
        CALL RD_SECT    ;читаем сектор
        EXA
        DEC A
        JR NZ,RDMULT1    ;продолжаем пока не обнулится счетчик
        LD A,CMD_12    ;по окончании чтения даем команду карте «СТОП»
        CALL OUT_COM    ;команда мультичтения не имеет счетчика и
        CALL IN_OOUT    ;должна останавливаться здесь командой 12
        INC A
        JR NZ,$-4    ;ждем освобождения карты
        JP CS_HIGH    ;снимаем выбор с карты и выходим с кодом 0
    ;чтение одного блока
    RDSINGL    LD A,CMD_17    ;даем команду чтения одного сектора
        CALL SECM200
        CALL IN_OOUT
        CP #FE
        JR NZ,$-5    ;ждем маркер готовности #FE для начала чтения
        CALL RD_SECT    ;читаем сектор
        CALL IN_OOUT
        INC A
        JR NZ,$-4    ;ждем освобождения карты
        JP CS_HIGH    ;снимаем выбор карты и выходим с кодом 0
    ;запись одиночного блока
    WRSINGL    XOR A
        IN A,(P_CONF)    ;проверяем защиту от записи
        AND 2    ;если защита включена выходим с кодом 2
        RET NZ
        LD A,B    ;проверяем на попытку записи в нулевой
        OR C    ;сектор, сделано на всякий пожарный
        OR D
        OR E
        LD A,3    ;если сектор нулевой, выходим с кодом 3
        RET Z
        LD A,CMD_24    ;даем команду записи одного сектора
        CALL SECM200
        CALL IN_OOUT
        INC A
        JR NZ,$-4    ;ждем освобождения карты
        LD A,#FE    ;пишем стартовый маркер, сам блок и пустое CRC16
        CALL WR_SECT
        CALL IN_OOUT
        INC A
        JR NZ,$-4    ;ждем освобождение карты
        JP CS_HIGH    ;снимаем выбор карты и выходим с кодом 0
    ;многосекторная запись
    WRMULTI    EX AF,AF'    ;прячем счетчик секторов
        XOR A
        IN A,(P_CONF) ;    как и в случае с записью одного сектора
        AND 2    ;проверяем защиту от записи
        RET NZ
        LD A,B    ;и попытку записи в MBR
        OR C
        OR D
        OR E
        LD A,3    ;если сектор нулевой, выходим с кодом 3
        RET Z
        LD A,CMD_25 ;даем команду мультисекторной записи
        CALL SECM200
        CALL IN_OOUT
        INC A
        JR NZ,$-4 ;ждем освобождения карты
        EXA
    WRMULT1 EXA
        LD A,#FC ;пишем стартовый маркер, сам блок и пустое CRC16
        CALL WR_SECT
        CALL IN_OOUT
        INC A
        JR NZ,$-4 ;ждем освобождения карты
        EXA
        DEC A
        JR NZ,WRMULT1 ;продолжаем пока счетчик не обнулится
        LD C,P_DATA
        LD A,#FD
        OUT (C),A ;даем команду остановки записи
        CALL IN_OOUT
        INC A
        JR NZ,$-4 ;ждем освобождения карты
        JP CS_HIGH ;снимаем выбор карты и выходим с кодом 0



    © 2004-2013 Perspective group