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


PeriodicalsAbzac2009 No. 32 → Программирование SD карт на Спектруме

Программирование 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) является от­ветом карты и, если не равен нулю (команда будет выполнена), то содержит биты ошибки. В драйвере код ошибки чаще всего игнорируется. По специ­фикации карта выдает несколько разновидностей ответов различающихся длиной, в драйвере чаще всего используется только первый байт ответа.


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

https://zxpress.ru/images/periodicals/articles/original/205.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. Остальные биты от­вета в драйвере не используются.

https://zxpress.ru/images/periodicals/articles/original/206.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