psxsdk

Controllers

The library reads both controller ports at every vertical blank, by itself, into two buffers of 34 bytes. yaroze.Pad reads the standard controller's buttons out of the first one, which is all most programs need. Everything else that plugs into a port, analog controllers, joysticks, the mouse, the neGcon and the GunCon, sends more, and says what it is so a program can tell how to read the rest. yaroze.ReadController reads any of them:

// A Controller is what one port's controller sent at the last vertical
// blank.
type Controller struct {
	// Connected is whether anything is plugged in. The rest is zero when
	// nothing is.
	Connected bool

	// Kind is what it is, one of the Kind constants.
	Kind uint8

	// Buttons are the buttons held, in PadRead's word, the PAD constants
	// naming the standard and analog controllers' and the Mouse, NeGcon and
	// GunCon constants the others'. The direction buttons of a neGcon are
	// the PAD constants'.
	Buttons Buttons

	// Axes are the four bytes after the buttons. An analog controller's or
	// joystick's are its right stick's X and Y and its left stick's X and Y,
	// 0 to 255 and about 128 when centred, X growing to the right and Y
	// downwards. A neGcon's are its twist, about 128 when straight, and how
	// far I, II and L are pressed.
	Axes [4]uint8

	// DX and DY are how far a mouse moved since the last vertical blank.
	DX, DY int8

	// X and Y are where a GunCon points, X counted in the GPU's dot clock
	// from the horizontal sync and Y in lines, both in the gun's own
	// units, which a program calibrates against the screen. Both are
	// small, under 10, when it points off the screen.
	X, Y uint16
}

A buffer starts with a byte that is 0 while something is connected and 0xff while nothing is, then a byte whose high four bits say what kind of controller it is and whose low four the length of what follows, in 16-bit halves. The buttons come first, as on the standard controller, held buttons as 0 bits, and then whatever else the kind sends: an analog controller's four axes, a mouse's movement since the last read, a neGcon's twist and its analog buttons, a GunCon's position.

Sticks and dead zones#

An analog stick reads 0 at one end of its travel and 255 at the other, but at rest it is near 128 rather than on it, and a different near on every controller. A program that moved by the raw value would drift. Stick centres an axis on zero and calls anything within a dead zone of the middle no movement:

// Stick returns an analog controller's or joystick's left (0) or right (1)
// stick as -128 to 127 across and down, with a dead zone of dead either
// side of the centre reported as 0, as Sony's checklist asks of a game:
// the sticks rest near 128, not on it.
func (c Controller) Stick(n, dead int) (x, y int) {
	i := 0
	if n == 0 {
		i = 2
	}
	axis := func(v uint8) int {
		d := int(v) - 128
		if d > -dead && d < dead {
			return 0
		}
		return d
	}
	return axis(c.Axes[i]), axis(c.Axes[i+1])
}

The tutorial's analog example had the player calibrate the stick first. Sony's checklist for the DUAL SHOCK (Sony's requirements) forbids a calibration screen, since its sticks calibrate themselves, so a dead zone is all a program does now.

An analog controller starts in digital mode, sending what a standard controller sends, until its player presses the analog button. A program that wants the sticks says so on its screen, and goes on working with the directions.

The mouse, the neGcon and the GunCon#

A mouse sends how far it moved since the last read, -128 to 127 each way, rather than where it is: a program adds it to a cursor of its own. A neGcon is a controller twisted in the middle, sent as an axis like a stick's, with its I and II buttons as axes too, pressed further for more. A GunCon sends where on the television it is pointing, measured as the television draws: dots of the GPU's clock from the start of a line, and lines from the top. Where a 320-wide picture starts and ends in those units depends on the television, so a game had the player shoot a few targets to calibrate it.

The pads example names what is in each port, lists what it sends, and moves a cursor with any of them:

// move moves the cursor by what the controller is sending.
func move(c *yaroze.Controller) {
	if !c.Connected {
		return
	}
	switch c.Kind {
	case yaroze.KindStandard, yaroze.KindAnalog, yaroze.KindAnalogJoystick:
		if c.Buttons&yaroze.PADLleft != 0 {
			cursorX -= 2
		}
		if c.Buttons&yaroze.PADLright != 0 {
			cursorX += 2
		}
		if c.Buttons&yaroze.PADLup != 0 {
			cursorY -= 2
		}
		if c.Buttons&yaroze.PADLdown != 0 {
			cursorY += 2
		}
		if c.Kind != yaroze.KindStandard {
			x, y := c.Stick(0, 24)
			cursorX += int32(x / 32)
			cursorY += int32(y / 32)
		}
	case yaroze.KindMouse:
		cursorX += int32(c.DX)
		cursorY += int32(c.DY)
	case yaroze.KindNeGcon:
		if t := int32(c.Axes[0]) - 128; t > 8 || t < -8 {
			cursorX += t / 16
		}
		cursorY += (int32(c.Axes[2]) - int32(c.Axes[1])) / 64
	case yaroze.KindGunCon:
		if c.X < 10 {
			return // pointing off the screen
		}
		cursorX = (int32(c.X) - gunLeft) * 320 / (gunRight - gunLeft)
		cursorY = (int32(c.Y) - gunTop) * 240 / (gunBottom - gunTop)
	}
}

In the emulator#

The emulator plugs in a standard controller by default. Its tests plug in the others with emu.NewPad and Machine.ConnectPad, which is how the pads example's test drives an analog stick, a mouse, a GunCon and a neGcon.

The SCEE tutorial's peripherals chapter and Sony's peripherals notes have the original programs and the buffers' layouts in full.