Necromancy

Friday, 8th February 2008

After seeing Scet and Drilian's work on their respective emulator projects I decided I needed to do something with the stagnating Cogwheel source on my hard disk drive.

The only ROM I have tested where I can't find an explanation for a bug is the Game Gear Garfield: Caught in the Act game. Like many games, when left at the title screen it'll run a demo loop of the game in action. At one point Garfield would walk to the left of the screen, jump over a totem pole, shunt it to the right and use it as a way to jump out of a pit. However, in Cogwheel he would not jump far enough to the left, and not clearing the totem pole he'd just walk back to the right and not have anything to jump out of the pit on.

I remembered a post on MaxCoderz discussing a long-standing tradition of thinking that when a JP <condition>, <address> failed the instruction took a single clock cycle. You can see this misreported here, for example. This document, on the other hand, claims it always takes 10 clock cycles - and most importantly of all, the official user manual backs this up.

Garfield.png

So, Garfield can now get out of his pit. The user interface has changed (again) - I'm now using SlimDX to dump pixels to a Panel, which seems to be the least hassle distribution-wise and doesn't throw LoaderLockExceptions.

PlayStation Controllers

Monday, 4th February 2008

PlayStation controllers are relatively comfortable, and I have a number of them knocking about.

controllers.jpg
From top to bottom - PS2 IR remote control and receiver; Guitar Hero wireless Kramer and receiver; black PS2 DualShock 2 analogue joypad; a pair of grey standard PS1 digital joypads.

As I've learned in the past, a decent gamepad can help with certain games. Of course, what's much more fun than playing the games is trying to work out how these controllers work.

The byte-level protocol is very simple; the PlayStation pulls a select line low (used to grab the attention of the controller) then pulses the clock eight times, writing a bit at a time onto one line and reading another bit at a time from another. This means that the controller and PlayStation end up sending and receiving a byte simultaneously. Finally, the PlayStation checks to see if controller pulls the acknowledge line low to indicate that it received the data; if no acknowledgement is received it assumes that there is no controller on the port it is currently accessing.

All electrical connections are unidirectional, and so a controller can be easily connected to a standard PC's parallel port. There are a number of diagrams floating around the internet using similar pin connections, so I followed one of those.

adapter.jpg

I cut up a pound-shop parallel cable for the PC end and a controller extension cable for the PlayStation end. PlayStation controllers require power; a lot of diagrams I've seen refer to a 9V and 5V supply, some 7.6V and 3.3V. A voltmeter informs me that it's the latter option. Rather than try and draw power from the parallel port, I'm using a generic power supply set to 7.5V. To derive the 3.3V I'm using a 5V regulator followed by two 1A rectifier diodes in series - the diodes provide a voltage drop of 0.7V across each, resulting 3.6V.

I wrote an application in C# that attempted to swap bytes back and forth between the PC and the controller, and was getting good results. I was not, however, having any luck polling the acknowledgement line. It didn't appear to ever go low - my guess was that the program simply couldn't poll the parallel port rapidly enough. Not that this is a slur on C#, of course, but to access the parallel port in the first place I need to use an unmanaged library.

The solution was therefore to write an unmanaged library myself that would handle the PlayStation protocol side of things, which I could then wrap up and add nice functionality to via a C# managed library.

#include "Windows.h"

// inpout32.dll function declarations.
short Inp32(short portAddress);
void Out32(short portAddress, short data);

/// <summary>Gets the state of the data line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <returns>The state of the data line.</returns>
extern "C" __declspec(dllexport) bool GetData(short portAddress) {
	return (Inp32(portAddress + 1) & (1 << 6)) != 0;
}

/// <summary>Gets the state of the acknowledge line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <returns>The state of the data line.</returns>
extern "C" __declspec(dllexport) bool GetAcknowledge(short portAddress) {
	return (Inp32(portAddress + 1) & (1 << 5)) != 0;
}

/// <summary>Sets the state of the command line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="state">The state to set the command line to.</param>
extern "C" __declspec(dllexport) void SetCommand(short portAddress, bool state) {
	Out32(portAddress, (Inp32(portAddress) & ~0x01) | (state ? 0x01 : 0x00));
}

/// <summary>Sets the state of the select line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="state">The state to set the select line to.</param>
extern "C" __declspec(dllexport) void SetSelect(short portAddress, bool state) {
	Out32(portAddress, (Inp32(portAddress) & ~0x02) | (state ? 0x02 : 0x00));
}

/// <summary>Sets the state of the clock line.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="state">The state to set the clock line to.</param>
extern "C" __declspec(dllexport) void SetClock(short portAddress, bool state) {
	Out32(portAddress, (Inp32(portAddress) & ~0x04) | (state ? 0x04 : 0x00));
}

/// <summary>Begins a data transfer by pulling select low.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
extern "C" __declspec(dllexport) void BeginTransfer(short portAddress) {
	SetSelect(portAddress, false);
}

/// <summary>Ends a data transfer by releasing select high.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
extern "C" __declspec(dllexport) void EndTransfer(short portAddress) {
	SetSelect(portAddress, true);
}


/// <summary>Exchanges a byte between the PlayStation controller and the PC.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="data">The data to exchange.</param>
/// <returns>True if the transmission was acknowledged, false if it timed out.</returns>
extern "C" __declspec(dllexport) bool ExchangeByte(short portAddress, unsigned char* data) {

	DWORD TimeoutStart = GetTickCount();

	for (int i = 0; i < 8; ++i) {
		SetClock(portAddress, false);
		SetCommand(portAddress, (*data & (1 << i)) != 0);
		SetClock(portAddress, true);		
		if (GetData(portAddress)) {
			*data |= (1 << i);
		} else {
			*data &= ~(1 << i);
		}
	}

	while (GetAcknowledge(portAddress)) {
		if ((GetTickCount() - TimeoutStart) > 10) return false;
	}

	while (!GetAcknowledge(portAddress)) {
		if ((GetTickCount() - TimeoutStart) > 10) return false;
	}

	return true;

}

/// <summary>Exchanges a block of data between the PlayStation controller and the PC.</summary>
/// <param name="portAddress">Base address of the parallel port.</param>
/// <param name="command">The command byte to send.</param>
/// <param name="data">The data to exchange (input and output).</param>
/// <param name="numberOfElements">The size of data in bytes.</param>
/// <returns>The size of the received packet.</returns>
extern "C" __declspec(dllexport) int SendPacket(short portAddress, unsigned char* command, unsigned char* data, int numberOfElements) {

	// Start by sending 0x01.
	unsigned char ToExchange = 0x01;
	if (!ExchangeByte(portAddress, &ToExchange)) return 0;

	// Next send the command byte.
	// Controller will respond with packet size and mode.
	if (!ExchangeByte(portAddress, command)) return 0;
	
	// Check for end-of-header.
	ToExchange = 0x00;
	if (!ExchangeByte(portAddress, &ToExchange) || ToExchange != 0x5A) return 0;

	// Fix the "numberOfElements" to only try and fetch the number of bytes that are in the packet.
	numberOfElements = min(numberOfElements, (*command & 0xF) * 2);

	for (int i = 0; i < numberOfElements; ++i) {
		if (!ExchangeByte(portAddress, &data[i])) return i + 1;
	}

	return numberOfElements;
}

I'm not much of a C++ programmer, so I hope the above isn't too offensive.

Polling a standard digital joypad or a dual analogue is pretty straightforwards - send 0x42 to the device, and it'll return the status of each button as a bitfield 2 bytes in length. If the controller is in analogue mode, it'll then go on to return a further four bytes; one byte per axis, two axes per joystick.

Standard disclaimer applies; if you're going to hook anything up to your PC, I cannot be held responsible for any damages incurred. Be careful!

I'm still having some problems with data transfer. The controller doesn't always send back enough data (least reliable with a DualShock 2); this could be because I'm running its clock too fast. Introducing delays doesn't seem to help. This is most noticable in the demo program when a DualShock 2 is used in analogue mode; the analogue light flickers on and off.

I also haven't successfully managed to get the DualShock 2 to enter escape mode - this mode is used to access some of the more exotic commands, including commands to control the force feedback motors or to append extra data to the packet sent back when the controller is polled, such as the status of the analogue buttons.

Site update

Friday, 1st February 2008

I intended on making at least one post in January, but looked like I missed the opportunity by a day. I hope you all had pleasant breaks (and here I refer to breaks from my inane ramblings, not any religious festivals).

The lack of updates stem from having precious little to talk about; I've been busily trying to redevelop my website into a single, modular, extensible code base rather than having a large number of insular sub-webs.

Here's a whistle-stop tour of the various iterations of my website; whilst the underlying technology has improved in quality quite significantly it's evident that I couldn't design good looking websites for toffee.

01.jpg

Initially I just used the site to provide links to and information about the various calculator programs I was working on - hence its name, calc83plus. This was hosted for free, and so was just static HTML with each page having different content copied and pasted in.

02.jpg

I decided that this was a bit inefficient, so in the second version used javascript to draw a consistent header on each page. The content was much the same, I just changed the logo and colour scheme.

03.jpg

I eventually ended up with a real webhost, so changed the name and redesigned the site, adding my VB6 projects to it. I evidently decided against the javascript trick and was now using some ghastly frameset solution.

04.jpg

Hang on, this web host supports ASP scripting, and I know VB, so... why not make it server-side script driven? So I did. This version of the site allowed me to log in and post news articles directly with comments, and used server-side includes to insert the body of pages into a template.

05.jpg

I switched web hosts, and had a static placeholder page up for a fairly lengthy period until I decided to use my new PHP knowledge to build something that I could use to provide an image gallery for some of the VB and C projects I'd been working on. The host also supports ASP, but I'd decided that I never had anything interesting to say anyway so a news and comments system wouldn't be of much use. An inextensible system meant that the various new projects I developed ended up scattered all over the place in unrelated sub-webs and news posts resumed with my GDNet+ account.

06.jpg

Which leads us neatly to this; a design that is quite blatently "inspired" by GameDev.net's (and provides news updates by screen-scraping GDNet) but which can be used to host information about pictures, projects and a gallery in one centralised location and can be easily extended with new modules.

Scripting

Wednesday, 21st November 2007

Scripting with .NET is unbelievably easy. grin.gif

I wanted to add scripting support to Brass, and have added it using .NET's excellent powers of reflection and its System.CodeDom.Compiler namespace.

The first thing I need to do is find out which language the source script is written in. I use the extension to check for this.

string ScriptFile = ...; // Name of main script file to compile.

CodeDomProvider Provider = null;

// Get the extension (eg "cs")
string Extension = Path.GetExtension(ScriptFile).ToLowerInvariant();
if (Extension.Length > 0 && Extension[0] == '.') Extension = Extension.Substring(1);

// Hunt through all available compilers and dig out one with a matching extension.
foreach (CompilerInfo Info in CodeDomProvider.GetAllCompilerInfo()) {
	if (Info.IsCodeDomProviderTypeValid) {
		CodeDomProvider TestProvider = Info.CreateProvider();
		if (TestProvider.FileExtension.ToLowerInvariant() == Extension) {
			Provider = TestProvider;
			break;
		}
	}
}

if (Provider == null) throw new CompilerExpection(source, "Script language not supported.");

Now that we have a compiler, we just set some settings, add some references, then compile the source files:

string[] ScriptFiles = ...; // Array of source file name(s) to compile.

// Compiler settings:
CompilerParameters Parameters = new CompilerParameters();
Parameters.GenerateExecutable = false; // Class lib, not .exe
Parameters.GenerateInMemory = true;

// Add references:
Parameters.ReferencedAssemblies.Add("System.dll");
Parameters.ReferencedAssemblies.Add("Brass.exe");

// Compile!
CompilerResults Results = Provider.CompileAssemblyFromFile(Parameters, ScriptFiles);

And that's it! In my case I now pass any errors back up to the assembler, and exit if there were any errors:

// Errors?
foreach (CompilerError Error in Results.Errors) {
	Compiler.NotificationEventArgs Notification = new Compiler.NotificationEventArgs(compiler, Error.ErrorText, Error.FileName, Error.Line);
	if (Error.IsWarning) {
		compiler.OnWarningRaised(Notification);
	} else {
		compiler.OnErrorRaised(Notification);
	}
}

// Do nothing if there were errors.
if (Results.Errors.HasErrors) return;

Now the task is passed on to reflection; I go through the compiled assembly, hunt down methods and wrap them up for use as native Brass functions and/or directives.

// Grab the public classes from the script.
foreach (Type T in Results.CompiledAssembly.GetExportedTypes()) {
    // ...
}

I've used this technique in the release of my PAL demo; a C# script file is used to encode an image to the 18×304 resolution and format required by the routine.

PAL.GG.jpg

Brass 3 and software PAL

Wednesday, 14th November 2007

My work with the VDP in the Sega Master System made me more aware of how video signals are generated, so thought it would be an interesting exercise to try and generate them in software. This also gives me a chance to test Brass 3, by actively developing experimental programs.

Hardware.Thumb.jpg

I'm using a simple 2-bit DAC based on a voltage divider, using the values listed here. This way I can generate 0V (sync), ~0.3V (black), ~0.6V (grey) and 1V (white).

My first test was to output a horizontal sync pulse followed by black, grey, then white, counting clock cycles (based on a 6MHz CPU clock). That's 6 clock cycles per µs.

2-bit-DAC-Black-Grey-White.Thumb.jpg

The fastest way to output data to hardware ports on the Z80 is the outi instruction, which loads a value from the address pointed to by hl, increments hl, decrements b and outputs the value to port c. This takes a rather whopping 16 clock cycles (directly outputting to an immediate port address takes 11 clock cycles, but the overhead comes from loading an immediate value into a which takes a further 7). The time spent creating the picture in PAL is 52µs, which is 312 clock cycles. That's 19.5 outi instructions, and by the time you've factored in the loop overhead that gives you a safe 18 pixel horizontal resolution - which is pretty terrible.

Even with this technique, in the best case scenario you output once every 16 clock cycles which gives you a maximum time resolution of 2.67µs. This is indeed a problem as vertical sync is achieved by transmitting two different types of sync pulse, made of either a 2µs sync followed by 30µs black (short) or 30µs sync followed by 2µs black (long). In my case I plumped for the easiest to time 4µs/28µs and hoped it would work.

Anyhow, I made a small three-colour image for testing: Source.gif.

Of course, as I need to output each scanline anyway I end up with a resolution of 304 lines, which gives me rather irregular pixels, so I just stretch the above image up to 20×304. Eagle-eyed readers would have noticed that the horizontal resolution is only 18 pixels, but somewhere in the development process I forgot how to count and so made the image two pixels too wide.

TV.Thumb.jpg

As you can see, it shows (the entire image is shunted to the right). TVs crop the first and last few scanlines (they aren't wasted, though, and can be used for Teletext) so that's why that's missing. smile.gif A widescreen monitor doesn't help the already heavily distorted pixels either, but it does (somewhat surprisingly) work.

With a TI-83+ SE (or higher) you have access to a much faster CPU (15MHz) and more accurate timing (crystal timers rather than an RC circuit that changes speed based on battery levels) as well as better interrupt control, so on an SE calculator you could get at least double the horizontal resolution and output correct vertical sync patterns. You also have better control over the timer interrupts, so you could probably drive hsync via a fixed interrupt, leaving you space to insert a game (the only code I had space for checks to see if the On key is held so you can quit the program - more clock cycle counting). I only have the old 6MHz calculator, though, so I'm pleased enough that it works at all, even if the results are useless!

Page 28 of 53 124 25 26 27 28 29 30 31 3253

Older postsNewer postsLatest posts RSSSearchBrowse by dateIndexTags