Maple BUS в ореховой скорлупе или Периферия SEGA Dreamcast, как сделать

от автора

И сразу к делу!

Протокол Maple BUS симметричный, то есть имея одну хорошую реализацию например HOST’а эту же реализацию можно использовать и как DEVICE. Проще, — можно читать джойстик, а можно им прикинуться.

Описание протокола (аппаратная часть).

Интерфейс Maple BUS двух-проводный. SDCKA/SDCKB, каждая из линий на определенных этапах выполняет роль как «передающая данные» и так и «защелкивающая данные».

Общение по шине Maple BUS осуществляется пакетами. Каждый пакет данных состоит из заголовочного паттерна, данных, контрольной суммы и завершающего паттерна. Максимальная длина пакета данных 1024 байта.

Всего паттернов 5 видов:

START — указывает на начало передачи данных (4-ре клока SDCKB в то время пока SDCKA в низком уровне).

Пакет всегда должен заканчиваться паттерном 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»).

maplebus commands
//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
/*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.

Общий вид по блокам:

Полная схема модуля.

Gerber фалы.

Внешний вид:

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

Gerber файлы.

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

Реализация устройства.

Железки есть, схемы есть, переходим к реализации.

Для начала заделаем небольшой код чтобы чтобы геймпад 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 память можно установить и работать с ней).

  • И у меня остались комплекты печатных плат и трём желающим «попробовать свои силы» могу отправить комплекты печатных плат за стоимость почты.

Удачного дня! Отличного настроения и взаимопонимания!!!

ссылка на оригинал статьи https://habr.com/ru/post/540236/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *