Maple BUS в ореховой скорлупе или Периферия SEGA Dreamcast, как сделать
И сразу к делу!
Протокол Maple BUS симметричный, то есть имея одну хорошую реализацию например HOST’а эту же реализацию можно использовать и как DEVICE. Проще, — можно читать джойстик, а можно им прикинуться.
Описание протокола (аппаратная часть).
Интерфейс Maple BUS двух-проводный. SDCKA/SDCKB, каждая из линий на определенных этапах выполняет роль как «передающая данные» и так и «защелкивающая данные».
Общение по шине Maple BUS осуществляется пакетами. Каждый пакет данных состоит из заголовочного паттерна, данных, контрольной суммы и завершающего паттерна. Максимальная длина пакета данных 1024 байта.
Всего паттернов 5 видов:
START — указывает на начало передачи данных (4-ре клока SDCKB в то время пока SDCKA в низком уровне).

Паттерн START всегда должен заканчиваться паттерном END (2-ва клока SDCKA пока SDCKB в низком уровне):

Occupancy паттерн — указывает на старт режима прослушивания шины (8-мь клоков SDCKB пока SDCKA в низком уровне). Переход линии HI->LO SDCKA после получения этого паттерна указывает на начало режима, LO->HI указывает на завершение. Этот режим используется для взаимодействия со световым пистолетом (Light GUN — Func. FT7):

RESET — аппаратный перезапуск устройства (14-ть клоков SDCKB пока SDCKA в низком уровне, только для DEVICE).

Теперь рассмотрим как по шине передаются данные.
Биты данных передаются фазами. В четной фазе линия данных — SDCKB, а клок — SDCKA, в нечетной наоборот (этот фрагмент тоже назовем паттерном 🙂 ).

Величина таймаута на ответ от устройства после запроса хоста 1мс:

Помним, что например к джойстику можно подключать VMU, вибропак, микрофон…
Устройства подключаемые непосредственно к Maple BUS называются Device, а устройства подключаемые к Device называются Expansion Device, общение между Device и Expansion Device осуществляется средствами протокола LM-Bus. Expansion устройств можно подключить до 5-ти, хотя я не видел ни одного устройства в котором это было реализовано, а в чипах (например 315-6211-AB) «выведено наружу» только под 2-ва (хотя в программной части протокола под идентификацию EXP-DEV выделено пять бит, но тут честно говоря нужно уточнить, VMU например содержит память и LCD дисплей, это уже два Exp. устройства).

LM-BUS это что то типа суррогата Maple BUS, то есть шина на которую DEVICE напрямую переключает шину Maple BUS согласно тому какой Exp. DEVICE выбран HOST’ом.
LM-BUS тема отдельного разговора, отвлекаться не буду, перейдем к программной реализации протокола.
Программная часть протокола.
Как я уже писал выше данные передаются пакетами, рассмотрим пакет поближе:

-
COMMAND — команда, может принимать значения от 0x01 до 0xFE (см. возможные значения в коде ниже «maplebus.h»).
//HOST
#define DeviceRequest 0x01
#define AllStatusRequest 0x02
#define DeviceReset 0x03
#define DeviceKill 0x04
#define GetCondition 0x09
#define GetMediaInfo 0x0A
#define BlockRead 0x0B
#define BlockWrite 0x0C
#define GetLastError 0x0D
#define SetCondition 0x0E
#define FT4Control 0x0F
#define ARControl 0x10
#define TransmitAgain 0xFC
//Device
#define DeviceStatus 0x05
#define DeviceAllStatus 0x06
#define DeviceReply 0x07
#define DataTransfer 0x08
#define ARError 0xF9
#define LCDError 0xFA
#define FileError 0xFB
#define TransmitAgain 0xFC
#define CommandUnknown 0xFD
#define FunctionTypeUnknown 0xFE
-
DEST. AP — адрес назначения пакета (для какого устройства пакет).
-
ORIG. AP — от кого пакет.
Для AP справедлива следующая таблица:

PO[1:0] — Номер порта (A — 00, B — 01, C — 10, D — 11).
D/E — (1 — Device, 0 — Expansion Device или PORT).
LM[4:0] — (1 — Exp. DEVICE подключено, 0 — Слот Exp. пуст).
-
DATA SIZE — размер данных в пакете в 32-х битных чанках.
-
DATA — Состав пакета.
-
CRC — побайтный XOR всех данных включая COMMAND, AP, DATA SIZE, DATA.
«Общение» между HOST и DEVICE начинается с запроса DeviceRequest, в нем хост указывает какой порт он опрашивает, устройство, первый раз после включения или сброса «увидев» номер порта присваивает его себе (A/B/C/D).
Отвечать на данный запрос любое устройство обязано статусом (DeviceStatus answer):

Device ID — содержит функциональные возможности периферии (Device ID содержит блок FT, состав включенных битов в этом блоке определяет функции которые поддерживает устройство и FD — параметры поддерживаемых функций).

/*Device functions*/
#define CONTROLLER MAKE_DWORD(0x00000001) //FT0 : Controller Function
#define STORAGE MAKE_DWORD(0x00000002) //FT1 : Storage Function
#define LCD MAKE_DWORD(0x00000004) //FT2 : B/W LCD Function
#define TIMER MAKE_DWORD(0x00000008) //FT3 : Timer Function
#define AUDIO_INPUT MAKE_DWORD(0x00000010) //FT4 : Audio input device Function
#define AR_GUN MAKE_DWORD(0x00000020) //FT5 : AR-Gun Function
#define KEYBOARD MAKE_DWORD(0x00000040) //FT6 : Keyboard
#define GUN MAKE_DWORD((unsigned int)0x00000080) //FT7 : Light-Gun Function
#define VIBRATION MAKE_DWORD((unsigned int)0x00000100) //FT8 : Vibration Function
#define MOUSE MAKE_DWORD((unsigned int)0x00000200) //FT9 : Pointing Function
#define EXMEDIA MAKE_DWORD((unsigned int)0x00000400) //FT10 : Exchange Media Function
#define CAMERA MAKE_DWORD((unsigned int)0x00000800) //FT11 : Camera Device Functio
Destination code — указывает на целевой регион использования устройства.
Product name — название устройства (например {‘D’,’r’,’e’,’a’,’m’,’c’,’a’,’s’,’t’,’ ‘,’C’,’o’,’n’,’t’,’r’,’o’,’l’,’l’,’e’,’r’, ‘ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘} — 30 байт).
License — кому принадлежит лицензия ( {‘P’,’r’,’o’,’d’,’u’,’c’,’e’,’d’,’ ‘,’B’,’y’,’ ‘,’o’,’r’,’ ‘,’U’,’n’,’d’,’e’,’r’,’ ‘,’L’,’i’,’c’,’e’,’n’,’s’,’e’,’ ‘,’F’,’r’,’o’,’m’,’ ‘,’S’,’E’,’G’,’A’,’ ‘,’E’,’N’,’T’,’E’,’R’,’P’,’R’,’I’,’S’,’E’,’S’,’,’,’L’,’T’,’D’,’.’,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,} -60 байт ).
Min./Max. current — соотв. минимальное и максимальное потребление устройства (1мА = 10 единиц, 43мА => 0x1AE).
Далее в пакете может идти «свободный статус устройства» (на изображении не указано, так как этот кусочек не обязателен), для джойстика он выглядит так: 40 байт «Version 1.000,1998/05/11,315-6125-AB Analog Module: The 4th Edition. 05/08».
То, какие команды применимы к устройству нам показывает блок Device ID.
К FT0, CONTROLLER, применима команда GetCondition — получить состояние кнопок/триггеров и аналоговых стиков геймпада. То в каких битах расположены какие значения указано всё в том же блоке Device ID. В частном случае, для геймпада Device ID будет выглядеть так:

В ответ на запрос GetCondition, джойстик обязан отправить отчет о состоянии кнопок, выглядит он следующим образом:

Ra/La/Da/Ua — Право/Лево/Вниз/Вверх (цифровой «крестик»).
Start/A/B/X/Y — соотв кнопки.
A1, A2 — аналоговые курки
A3 и A4 — положение «стика».
Вот собственно и всё что нужно знать для реализации контроллера.
Реализация (аппаратная часть)
В общем то можно взять линии SDCKA и SDCKB и прикрутить на прерывания микроконтроллера и чисто программно реализовать, однако если МК медленный, то успевать не будет, и это не самое главное, во первых «плотность данных» для разных устройств разная и если например джойстик работать будет на одной программной реализации, то не факт что будет работать вибропак или карта памяти, во вторых в программной реализации определение ошибок внутри фрейма как и скажем команда «аппаратный сброс» не реализуемы, поэтому правильнее будет сделать аппаратный кусочек приемника, а отправлять и микроконтроллером можно.
Возьмем CPLD попроще (EPM3032) и реализуем xMAPLE:

SDCKA/SDCKB — вход линий Maple BUS.
GCLK — внешний CLK 16-48MHz.
INHTxD — сигнал блокировки работы приемника, 1 — игнорировать события на шине, 0 — нормальное функционирование.
RxD — идет прием пакета.
nSTRCV— начат прием пакета (Rising Edge).
nDLatch — Негативный импульс для «защелки данных» (сигнализирует о том что на линии данных Q[7..0] присутствует следующий полученный байт).
Q[7..0] — шина данных.
EOP — получен паттерн END (конец приема пакета).

FERR — обнаружена ошибка при приеме пакета.

nRST — подключается напрямую к микроконтроллеру — если получен RESET паттерн, — 0.

И общий вид:

Пишем это на верилог’е (3 файлика, надеюсь догадаетесь как это соединять):
SMAPLE.v
module SMAPLE(
input GCLK, //MCU Generated 16MHz clock input
input INHTxD, //Inhibit Input Data (User Can disable XMAPLE Detect Signals While MCU Transmit DATA)
input SDCKAi, //Data/Clock A Line
input SDCKBi, //Data/Clock B Line
output RxD, //Receive on progress (While receive is 1)
output [7:0]Q // Output data bus (MCU can read valid data on this
//port in time 200uS after data latch Negative Pulse received)
, output nSTRCV, //Receive start, negative pulse - Output
output OCPYi, //Occupancy packet received - Output
output nRST, //Reset packet received - Output
output FERR, //Frame error - Output
output EOPi, //End Of Packed received - Output
output nDLatch //New Data latched on BUS (Negative Pulse)
);
/*Control Register*/
reg rRxD = 0;
assign RxD = rRxD;
reg rFERR = 0;
assign FERR = rFERR;
wire nWE;
assign nDLatch = (EOPi & nWE);
wire iFERR;
/* Align Data Packet */
reg rENA = 1'b0;
reg rENB = 1'b0;
always @(posedge GCLK or negedge nRST) begin
if(!nRST) begin
rENA <= 1'b0;
rENB <= 1'b0;
end else begin
rENA <= SDCKAi;
rENB <= SDCKBi;
end
end
always @(posedge GCLK or negedge nRST) begin
if(!nRST) begin
rFERR <= 0;
rRxD <= 0;
end else begin
if(!EOPi)// && !INHTxD)
rRxD <=0 ;
else begin
if(!iFERR) rFERR <= 1;
if(!nSTRCV) begin
rFERR <= 0;
rRxD <= ~INHTxD;
end
end
end
end
line_monitor line_monitor
(
.GCLK(GCLK), //Global Clock - Input
.SDCKA(SDCKAi|INHTxD), //CLOCK/DATA Line A disabled by data transmit - Input
.SDCKB(SDCKBi|INHTxD), //CLOCK/DATA Line B disabled by data transmit - Input
.RxDr(RxD), //Data Receive in progress - Input
.RxD(nSTRCV), //Receive start, negative pulse - Output
.OCPY(OCPYi), //Occupancy packet received - Output
.RESET(nRST), //Reset packet received - Output
.FERR(iFERR), //Frame error - Output
.EOP(EOPi), //End Of Packed received - Output
.ENA(rENA), //CLOCK For Line B
.ENB(rENB) //CLOCK For Line A
);
/*Receive Maple Frame*/
maple_receive maple_receive
(
.SDCKA(SDCKAi), //CLOCK/DATA Line A disabled by data transmit - Input
.SDCKB(SDCKBi), //CLOCK/DATA Line B disabled by data transmit - Input
.ENA(rENA), //CLOCK For Line B
.ENB(rENB), //CLOCK For Line A
.RCV(RxD), //Receive in progress, 1 - receive - Input
.Dout(Q[7:0]), //Received data byte - Output
.nWE(nWE), //Write Latch - Output
.RxDi(nSTRCV), //Receive start, negative pulse - Input
.INHTxD(INHTxD) //Inhibit Input Data (User Can disable XMAPLE Detect Signals While MCU Transmit DATA)
);
endmodule
line_monitor.v
module line_monitor
(
input GCLK,
input SDCKA,
input SDCKB,
input RxDr, //Data Receive in progress - Input
output RxD,
output OCPY,
output RESET,
output FERR,
output EOP,
input ENA,
input ENB
);
reg [3:0] countA = 0;
reg [2:0] countB = 0;
reg [3:0] pcount = 0;
reg rEOP = 1'b1;
assign EOP = rEOP;
assign RxD = (pcount == 4'h4) ? 1'b0 : 1'b1;
assign OCPY = (pcount == 4'h8) ? 1'b0 : 1'b1;
assign RESET = (pcount == 4'hE)? 1'b0 : 1'b1; //Output reset signal does not need to check for FERR
assign FERR = (!((RxD & OCPY & RESET) && pcount[3:1])) | (!RxDr & !rEOP);
//assign EOP = (eopcount == 3'h2) ? 1'b0 : 1'b1;
always @(posedge SDCKA) pcount <= countA;
always @(posedge SDCKB) rEOP <= !(countB == 3'h2);
//Patterns
//PATTERN Counter Managing
always @(posedge ENA or negedge ENB) begin
if (ENA) begin
countA <= 0;
end
else begin
countA <= countA + 4'h1;
end
end
//EOP Counter Managing
always @(posedge ENB or negedge ENA) begin
if (ENB) begin
countB <= 0;
end
else begin
countB <= countB + 3'h1;
end
end
//synopsys translate_off
//synopsys translate_on
endmodule
maple_receive.v
module maple_receive
(
input SDCKA, //CLOCK/DATA Line A
input SDCKB, //CLOCK/DATA Line B
input ENA, //CLOCK
input ENB, //CLOCK
input RCV, //Receive in progress, 1 - valid
output [7:0]Dout, //received data output
output nWE,
input RxDi,
input INHTxD //Inhibit Input Data (User Can disable XMAPLE Detect Signals While MCU Transmit DATA)
);
reg [3:0] dataA = 4'h0;
reg [3:0] dataB = 4'h0;
reg [1:0]countB = 2'b00;
reg rLastBitCounted = 1'b1;
//B LINE
//Dout[1] = SDCKA Means Major version 1.
//Dout[0] = SDCKB Means Minor version .0
//And version result = 1.0
assign Dout[1] = !INHTxD ? dataB[0] : SDCKA;
assign Dout[3] = dataB[1];
assign Dout[5] = dataB[2];
assign Dout[7] = dataB[3];
//A LINE
assign Dout[0] = !INHTxD ? dataA[0] : SDCKB;
assign Dout[2] = dataA[1];
assign Dout[4] = dataA[2];
assign Dout[6] = dataA[3];
assign nWE = (dtaLock);
always @(negedge ENA)begin
dataB[3:1] <= dataB[2:0];
dataB[0] <= SDCKB;
if(RCV) begin
countB <= countB + 2'b1;
end else begin
countB <= 2'b11;
end
end
always @(negedge ENB)begin
dataA[3:1] <= dataA[2:0];
dataA[0] <= SDCKA;
rLastBitCounted <= !countB[0] | !countB[1];
end
wire dtaLock = rLastBitCounted;
endmodule
Чтобы не "развлекаться с проводочками" накидал Eval Board.
Общий вид по блокам:

Внешний вид:

И посадочное место под Eval...

И соединяем всё это вместе:

Реализация устройства.
Железки есть, схемы есть, переходим к реализации.
Для начала заделаем небольшой код чтобы чтобы геймпад Dreamcast прикидывался геймпадом XBOX360 (поскольку я заботливо "выкусил хэндшейк" с XBOX360, данная реализация на приставке работать не будет только на ПК).
И, опять таки чтобы не на проводках, делаем плату коннектора для джойстика из двух половинок:
Верхняя часть (GERBER), нижняя часть (GERBER).

Разумеется чтобы получить хороший контакт с разъемом геймпада, нужно припаять "усы".
Для этой цели можно к примеру разобрать разъем SD вот как-то так:


Прикинем как должен работать алгоритм... не буду останавливаться на том как работает USB HID, опишу общую схему опроса устройств на шине MAPLE.

И собственно архив с исходниками.
Компилируем определив константы:
-
USE_STDPERIPH_DRIVER - использовать стандартную библиотеку периферии от ST.
-
STM32F10X_MD - чип Medium Density.
-
MAPLE_HOST - библиотека MAPLE работает в режиме HOST.
-
USB_HID - собрать целевое HID устройство.
Прошиваем, подключаемся:

… и видим вот такую картину (не забываем что необходимо поставить Microsoft Xbox 360 Accessories, а ещё помним что геймпад у нас работает в режиме XInput... кому лень разбираться, можно скачать уже откомпилированную прошивку):

А теперь развернем xMAPLE в обратную сторону и...
Подключим мышь от ПК к DREAMCAST.
Мышь, - FT9 : Pointing Function.
Что нам нужно, DeviceID и состав команды GetCondition, чтоб собирать пакет с данными.
Mouse DeviceID:

Стандартная мышь Dreamcast содержит 3 кнопки: A,B,W, дельты смещения по осям X/Y: AC1,AC2 (ball) и смещение "колеса": AC3 (wheel).
AC1,AC2,AC3 - десяти-битные величины плюс флаг переполнения.
Вот так выглядит пакет данных:

AOV2, AOV1, AOV0 - флаги переполнения для AC3, AC2, AC1 соответственно.
Для удобства накидаем схемку адаптера PS/2 для нашей борды:

...разводим, получаем gerber'ы...
И с завода нам приезжает вот это:

Ну и чтобы совсем удобно, накидаем вот такую схему, если брать провод от оригинального пада, то просто подключаемся к разъему и УРА.

"Рисуем" gerber'ы и получаем вот такой переходник:

Собираем весь этот "огород" вместе:

Компилируем прошивку (ниже архив с исходниками) не забывая объявить константы препроцессора:
-
USE_STDPERIPH_DRIVER - использовать стандартную библиотеку периферии от ST.
-
STM32F10X_MD - чип Medium Density.
-
MAPLE_DEVICE - библиотека MAPLE работает в режиме DEVICE.
-
EN_MOUSE - собрать целевое HID устройство.
-
MOUSE_CALLBACK - обработать функцию чтения мыши в процессе ожидания запроса от HOST.
-
EXTI9_5_CALLBACK - передавать в код пользователя системные прерывания EXTI5-EXTI9 библиотеки MAPLE_BUS.
(исходники, скомпилированный HEX).
К слову, если вместо EN_MOUSE в данных исходниках определить константу EN_CONTROLLER, то мы получим довольно забавную штуку, переходник превращающий PS/2 мышь в контроллер DREAMCAST, собственно специально сделал, потому как мышью в меню DREAMCAST управлять нельзя. Поэтому чтобы наглядно увидеть работоспособность исходников и оборудования в целом не запуская скажем "HALF LIFE для проверки" можно прошить откомпилированный код с константой EN_CONTROLLER и управлять внутри меню мышкой PS/2.
Прошиваем, подключаемся к DREAMCAST и оно работает!!!

Вот собственно и всё что хотел поведать. Однако я не рассказал о (надеюсь ещё расскажу 🙂 ):
-
Как работать с VibroPAK.
-
Как реализовать Memory Unit (хотя на борде расширения PS/2 SPI EEPROM память можно установить и работать с ней).
-
И у меня остались комплекты печатных плат и трём желающим "попробовать свои силы" могу отправить комплекты печатных плат за стоимость почты.
Удачного дня! Отличного настроения и взаимопонимания!!!
Для Heroes of the Storm анонсировали нового играбельного персонажа Ксал’атат
Студия GamesVoice опубликовала первый дневник локализации русской озвучки для Cyberpunk 2077 Phantom Liberty
На съемках «Народа против Гориллы Гродда» заметили намеки на появление Короля Соломона
Premium Collectibles Studio открыла предзаказ на статую ксеноморфа из фильма Чужой высотой 124 сантиметра
Серии Crash Bandicoot исполнилось 30 лет, но Xbox практически никак не отметила юбилей
Градостроительный симулятор XIRI Oasis Cities посвящен выживанию города будущего
Разработчики Moss закрыли студию после релиза игры на всех платформах
Авторы Fable пообещали сделать боевую систему глубокой вместо копирования старых RPG