将操纵杆从Dendy连接到Raspberry Pi

有一次,看完足够多的“每个人都在玩”时,我也想玩我的Raspberry pi。是的,不仅仅是玩游戏,而是使用真实设备玩游戏。为什么在地铁上花了150卢布从丹迪买了一个操纵杆(好吧,不是从丹迪买来的,而是从Simbas Junior买来的)。感兴趣的人可以单击下面的按钮。在文章末尾,将有一个到证明的链接。



我们的中国朋友以座右铭收集它-没有品质,但您坚持。立即将本机电缆焊接到横截面为2平方微米的导体,并替换为某个工业接口转换器的电缆,该电缆在下一次调试后得到,只有5根线。

在更改代码之前,您必须弄清楚游戏手柄本身的工作方式。游戏手柄具有移位寄存器。游戏手柄有5条线-2条-电源,3条信息-闩锁(频闪),时钟(脉冲)和数据。将逻辑单元提供给锁存器时,将保存移位寄存器的输入状态,并在输出-数据处立即提供“ A”按钮的状态,并且当输出-时钟线上的逻辑电平-数据发生变化时,会出现与其他七个按钮的状态相对应的电压电平以顺序形式。被按下的按钮对应于-0,而不是被按下-1。此外,对于游戏来说,所有事情都是完全相反的,因此有必要进行反转。下图显示了游戏手柄的示意图。

图片

其次是选择仿真器。该选择落在旧的fceu版本0.98.12上,因为它具有出色的模块化并非常准确地模拟了控制台体系结构,并且是用C编写的。其次是选择了一个用于GPIO的库,我从Mike McCauley 那里选择了bcm2835,它也是用C编写的,并且具有良好的性能。

由于我是编程新手,因此我不得不求助于同一位“每个人都在玩”的名人,并要求对代码部分发表评论。然后将您的鼻子刺入那些负责将按钮状态传输到游戏的功能。他们用通俗易懂的语言向我解释了什么以及如何进行。因此,input.c文件负责模拟输入,并且此处将进行主要更改。有几个函数负责模拟游戏手柄-FCEU_UpdateInput,ReadGP和DECLFW(4016),实际上,还有更多功能是主要的。除了input.c,我还必须对file.c和fceu.c进行更改。在第一种情况下,file.c文件中有错误,但是这个问题是google,这个文件有一个补丁,在fceu.c文件中,我在int函数FCEUI_Initialize(void)中添加了bcm2835库的初始化:

bcm2835_init();

预添加其头文件

#include	<bcm2835.h>

现在是input.c,我还添加了bcm2835库头文件(类似于fceu.c)和<unistd.h>库头文件以使用usleep。接下来,我宣布了将涉及的GPIO端口:

    #define LATCH RPI_V2_GPIO_P1_11
    #define CLK RPI_V2_GPIO_P1_13
    #define DATA RPI_V2_GPIO_P1_15

在void InitializeInput(void)函数中,我添加了一个代码,其中注册了每个GPIO端口的操作模式,并立即将负责锁存(选通)的端口和时钟重置为0。

        bcm2835_gpio_fsel(LATCH, BCM2835_GPIO_FSEL_OUTP);
	bcm2835_gpio_fsel(CLK, BCM2835_GPIO_FSEL_OUTP);
	bcm2835_gpio_fsel(DATA, BCM2835_GPIO_FSEL_INPT);
	bcm2835_gpio_set_pud(DATA, BCM2835_GPIO_PUD_UP);
	bcm2835_gpio_write(CLK, LOW);
	bcm2835_gpio_write(LATCH, LOW);

现在进入功能:

DECLFW(4016)-负责模拟锁存(选通)信号。如前所述,要读取按钮的状态,您需要一段时间应用到Latch-1。有一个Laststrobe变量,其中写入了写入该寄存器的最后一个值。如果Laststrobe为0,则分别写入逻辑1,并且还将将被称为Latch的GPIO引脚馈入1,并在1μs后将其复位为0。并且如果Laststrobe等于1,则将忽略此代码段。

static DECLFW(B4016)
{
	if (FCExp)
	if (FCExp->Write)
	FCExp->Write(V & 7);
	if (JPorts[0]->Write)
	JPorts[0]->Write(V & 1);
	if(JPorts[1]->Write)
        JPorts[1]->Write(V&1);

        if((LastStrobe&1) && (!(V&1)))
        {
	 /* This strobe code is just for convenience.  If it were
	    with the code in input / *.c, it would more accurately represent
	    what's really going on.  But who wants accuracy? ;)
	    Seriously, though, this shouldn't be a problem.
	 */
	 if(JPorts[0]->Strobe)
	  JPorts[0]->Strobe(0);
	 	if(JPorts[1]->Strobe)
		JPorts[1]->Strobe(1);
	 if(FCExp)
	  if(FCExp->Strobe)
	   FCExp->Strobe();
	 }
	if (LastStrobe==0)
		{
		bcm2835_gpio_write(LATCH, HIGH);
		usleep(1);
		bcm2835_gpio_write(LATCH, LOW);
		}
	LastStrobe=V&0x1;
}

好吧,现在操纵杆本身的轮询为void FCEU_UpdateInput(void)-在此功能中,将从配置模拟器或通过输入某些键(例如游戏手柄,三脚垫,光枪等)启动时选择的输入驱动程序读取数据。 。,所有这些都可以连接到控制台。它可以生成游戏手柄按钮[joy [0] ... joy [3]]状态的字节,数量为2到4,因为您可以启用Pribluda的仿真来连接另外2个游戏手柄。这是发生主要更改的地方。由于我不需要使用4个游戏手柄并从其他驱动程序接收数据的功能,因此我抛出了所有代码并输入了我的代码:

    joy[0] = 0;
    joy[1] = 0;
    for (i = 0; i <= 7; i++)
	{
		joy[0] ^= bcm2835_gpio_lev(DATA) << i;
		joy[0] ^= (1 << i);
		joy[1] ^= bcm2835_gpio_lev(DATA) << i;
		joy[1] ^= (1 << i);
		bcm2835_gpio_write(CLK, HIGH);
		usleep(1);
		bcm2835_gpio_write(CLK, LOW);
		usleep(1);
	}

此外,我立即分别形成了第一和第二操纵杆的两个字节。由于许多游戏同时从两个端口读取按钮的状态,因此对于它们来说,没有优先端口的概念。但是有些游戏存在这种概念-例如,所有Mario,Kirby,Terminator 2等。也就是说,它们仅从第一个端口(在Mario中,第一个玩家,从第二个中,仅从第二个玩家),即从寄存器4016读取按钮的状态。在调用此函数时将值分配为零也很重要,否则先前的值将被保存在其中。新的将已经叠加在它们上面。原则上,可以将第二个操纵杆的字节保留为零,但是我可以一起玩Mario。

ReadGP-已经从字节joy [0] ... joy [3]中提取位,并且ret变量将当前按钮的状态返回给游戏,按钮号由变量joy_readbit [w]设置,其中w是操纵杆的端口号,第一或第二。但是在此功能中,我没有做任何更改。保持原样。

为了成功进行编译,请在src目录中的Makefile(执行Configure命令后形成)中,将-lbcm2835 -lm -lrt添加到写入库依赖项的位置。行:

LIBS =

通常,一切正常。如果我突然决定购买第二个游戏杆,在同一辆战车上一起玩,我就离开了基础。

链接到证明
我们使用的数据来自该网站
“特别感谢这里的男人谁帮助理解代码模拟器

Source: https://habr.com/ru/post/zh-CN398285/


All Articles