Remapped IO.DLL

Sunday, 24th August 2008

A common problem with software that uses the parallel port is that is hard-coded to use particular port addresses, such as 0x378 for LPT1. This is all well and good on older machines that have integrated or ISA ports that can be assigned this base address, but newer machines with PCI cards don't get any choice over which port address range is assigned to the parallel port.

The supporting software for the Willem programmer has this problem, and so far I've been using an old XP laptop with a flaky WiFi adapter to program chips. This has been rather painful, understandably!

Fortunately, in Windows NT Microsoft forbade us from accessing hardware ports directly when in user-mode. This probably doesn't sound very fortunate, but the result is that various libraries for accessing the I/O ports using an embedded kernel-mode driver sprang up. Whilst this still isn't perfect (you need to run the calling app with Administrator privileges) it does mean that apps that access the parallel port will have one of these libraries kicking around as a DLL in their installation directory. The Willem programmer software is no exception; it uses IO.DLL.

The trick, then, is to simply replace the IO.DLL bundled with the app with a custom one that performs the same task, but redirects port writes in the LPT1 range (0x378~0x37F) to a user-specified base address (in my case, 0xCCD8). I couldn't get IO.DLL itself to work from within a DLL, and using it meant that you'd have to also rename the existing IO.DLL to something else, so I used Inpout32.dll instead to access the ports from the new DLL.

IO.DLL provides many helper functions, Inpout32.dll only offers two - Inp32() and Out32(). Fortunately, the Willem software doesn't seem to use any of the helper functions, and only uses PortIn() and PortOut(). (It does also use IsDriverInstalled(), but I've hard coded that to always return -1). The sample C++ interface code doesn't check the return value of GetProcAddress(), so the software still initialises, but will crash with an access violation if it tries to use any of the unsupported functions.

With some help from ibutsu to get around the C function name mangling problem (resolved by adding a simple .def file), you can simply extract three files into the Willem software directory (new io.dll, inpout32.dll for port access and io.ini for the user-specified port address) and the software appears to work fine! (I haven't tested all chip modes, as I only have one type of flash memory chip and an I2C serial EEPROM, but they work).

Download DLLs and source or documentation.

Z80 computer with a primitive I/O board

Wednesday, 20th August 2008

A computer needs some way of interacting with the outside world via input and output devices. It's about time, then, that the Z80 computer project acquires a section dedicated to I/O.

2008.08.20.04.Overview.jpg

The Z80 differentiates between memory and I/O devices, though both share the data bus and the address bus. You can control I/O devices using the in (input) and out (output) instructions. When you input or output you must specify a device address and a value or target register. For example,

    in a,($20) ; Read a value from device $20 and store it in A.
    ld a,123
    out ($40),a ; Output 123 to device $40.

When you write to a device, the following happens:

  • The address bus is set to the address of the device to output to.
  • The data bus is set to the value to be written to the device.
  • The /IORQ and /WR pins on the Z80 go low.
  • The device processes the written data.
  • The /IORQ and /WR pins on the Z80 go high.

Reading from the device is very similar:

  • The address bus is set to the address of the device to read from.
  • The /IORQ and /RD pins on the Z80 go low.
  • The device puts the value to read onto the data bus.
  • The Z80 reads the value on the data bus.
  • The /IORQ and /RD pins on the Z80 go high.

(Accessing memory is a similar procedure, except with the /IORQ pin replaced by the /MREQ pin).

Interfacing I/O devices to a Z80 CPU should be rather straightforwards, then. I am using a 74HCT138N 3-to-8 line inverting decoder to handle the address bus input and /IORQ signal. This IC has three address inputs and 8 outputs. If the address input is %000, output 0 is low and all the other pins are high; if the address input is %001 output 1 is low and all the others are high; if the input is %010 output 2 is low and all the others are high (and so on and so forth). /IORQ is connected to another input on the chip, /E1, which causes all of the pins to go high when it is high regardless of the address input.

What does this mean in practice? Well, most devices have a "chip enable" or "chip select" input pin. When this input is active the device performs its function, but when the input is not active the device is deactivated and doesn't respond to any other inputs or output anything. By connecting each output of the 3-to-8 decoder to a particular device's chip enable pin I can ensure that each device is only activated when its address is specified on the address bus and the /IORQ pin on the Z80 is low.

I have connected the Z80's A5-A7 to A0-A2 on the 3-to-8 decoder. This means that the first device has a base address of $00, the second $20, the third $40 and so on at increments of $20. This might sound a little odd, but has a reason. Some devices, such as the LCD, have sub-addresses of their own. In the case of the LCD, it has a pin that specifies whether you're dealing with an instruction (such as a command to switch the display on or off or read the LCD status) or some data (which forms part of the picture on the LCD). By attaching this pin directly to the Z80's A0 and the LCD's chip select pin to output 1 from 3-to-8 decoder you end up with an LCD instruction port at $20 and an LCD data port at $21.

An LCD is all well and good, but we'll need to take input from the user. To accomplish this, I'm going to supply two PS/2 ports and implement the AT protocol (as used by PS/2 keyboards and pointing devices) in software. Each device only requires two open-collector data lines (data and clock), so a single I/O device that provides eight I/O lines would be useful.

The design I'm going for uses two 74AC373 octal transparent latches. When the latch enable input pin is held high whatever value is on the input passes through to the corresponding output. When the latch enable pin goes low, the last value that was latched at the input is still output. These particular latches also have an output enable pin that can be used to disable the outputs and let them "float" (ie, other devices can then drive that particular connection high or low as required). In this instance, one latch has its output enable pin activated so that it always outputs the last value written to it and has its latch enable pin connected to the Z80's /WR pin. The other latch has its latch enable pin activated so that it always outputs the values at its input and has its output enable pin connected to the Z80's /RD pin.

The transistors on each output are used to provide open-collector outputs. When the base of the transistor is held low, the transistor is "switched off" and its output floats, and so can be driven by external circuitry. When the base of the transistor is held high, it switches on and effectively connects the output to ground. A pull-up resistor ensures that the pin has a high signal when not connected to anything. This arrangement is useful as each pin can be driven low by either device and so works as an input or an output (for a real-world example, an AT keyboard usually outputs a clock signal on one line to the host when sending data, but if the host pulls the clock line low it can inhibit communication and the keyboard buffers the data to send instead).

Rather than build the circuit on breadboard, I went straight to stripboard. The above photo shows an incomplete version of the output board. Only one PS/2 port is wired up at all! The pin header to the left is to connect the LCD to. The coloured wires at the extreme left connect this I/O board to the rest of the computer.

I have modified the Z80 board I was using last time to add support for RAM. The 3-to-8 decoder in the bottom right is used to partition the address space into two 32KB regions. The lower 32KB is mapped to ROM, and the upper 32KB is mapped to RAM. This wastes 75% of the ROM chip (it's a 128KB chip) but without a more complex memory management unit this will have to do for the moment. The most significant bit of the address bus, A15, is fed into the 3-to-8 decoder along with the /MREQ pin.

The test software is a Z80 program that displays an animation on the LCD using 20 frames (1KB per frame) stored in ROM.

The Z80 is still not breaking MHz speeds, but there are problems here. I have not interfaced the LCD correctly, as its timing patterns for reading and writing data are quite different to those used by the Z80. Bizarrely, holding the E pin on the LCD permanently high appears to work 99% of the time, even though the datasheet indicates that it should be used to clock data in or out. The result is glitches in the data sent to the LCD, usually on the left hand side (the left hand side of the display has a propensity to believe it's been sent the "switch off" command). I'm not sure I'll be able to remedy this situation. Judging by the datasheet it looks like the LCD does its stuff when the E pin goes from a low to high state (the Z80 does everything when /IORQ goes low), so maybe simply inverting /IORQ and pumping it into E will do the trick.

Z80 Light-flasher

Wednesday, 13th August 2008

Now armed with a flash programmer, I thought it about time to try and build a Z80-based system.

Not much to look at, and it doesn't do much either. The large IC in the bottom-left, prominently marked Z, is the Z80 itself. To its left is a 555, generating a ~220Hz clock signal (yes, Hz, not MHz or even kHz). Above the Z80 is another large chip - this is the 128KB flash ROM. The eight parallel wires between them are the address bus - only A0 to A7 are connected. This only lets the Z80 address 256 bytes, but that should be enough for testing.

To the right of the flash ROM is an octal latch. This is used to provide an 8-bit output port for the system, which is connected to the LEDs to its right. As the latch's latch enable pin is active high, unlike everything else in the system (which is active low - ie, it does something when you drive it low) I have to put a NOT gate - the final black IC to the right of the Z80 - between it and the Z80's /WR (write) pin. I do not do any address decoding or even check the /IORQ pin, so any value written to to any hardware device or memory address will end up on the LED display. Not that that really matters, as there is a conspicuous lack of RAM in the system!

The large physical size and tedium of wiring even such a primitive system as this makes me wonder whether it's worth jumping straight to stripboard for subsequent hardware revisions...

For the curious, the program running on the Z80 is as follows.

.for p = 0 to 7
.defpage p, kb(16), $0000
.loop
.emptyfill $FF

.page 0

	im 1
	di

--	ld hl,LightSequence
	ld b,LightSequenceEnd-LightSequence
-	ld a,(hl)
	out (0),a
	inc hl
	djnz -
	jr --

LightSequence
	.db %00000001
	.db %00000010
	.db %00000100
	.db %00001000
	.db %00010000
	.db %00100000
	.db %01000000
	.db %10000000
	.db %01000000
	.db %00100000
	.db %00010000
	.db %00001000
	.db %00000100
	.db %00000010
	.db %00000001
	.db %00000010
	.db %00000100
	.db %00001000
	.db %00010000
	.db %00100000
	.db %01000000
	.db %10000000
	.db %01000000
	.db %00100000
	.db %00010000
	.db %00001000
	.db %00000100
	.db %00000010
	.db %00000001
	.db %00000011
	.db %00000111
	.db %00001111
	.db %00011111
	.db %00111111
	.db %01111111
	.db %11111111
	.db %11111111
	.db %00000000
	.db %11111111
	.db %00000000
	.db %11111111
	.db %00000000
	.db %11111111
	.db %00000000
	.db %11111111
	.db %00000000
	.db %11111111
	.db %00000000
	.db %11111111
	.db %11111110
	.db %11111100
	.db %11111000
	.db %11110000
	.db %11100000
	.db %11000000
	.db %10000000
	.db %00000000
	.db %10000000
	.db %11000000
	.db %11100000
	.db %11110000
	.db %01111000
	.db %00111100
	.db %00011110
	.db %00001111
	.db %10000111
	.db %11000011
	.db %11100001
	.db %11110000
	.db %01111000
	.db %00111100
	.db %00011110
	.db %00001111
	.db %10000111
	.db %11000011
	.db %11100001
	.db %11110000
	.db %01111000
	.db %00111100
	.db %00011110
	.db %00001111
	.db %00000111
	.db %00000011
	.db %00000001
	.db %00000000
LightSequenceEnd

.echoln strformat("Size: {0} bytes", $)

Emulators and neatened wiring

Tuesday, 12th August 2008

I've decided to switch to a regular 10MHz Z80 rather than a Z180, given the difficulty of using an SDIP 64. I now have a DIP 40 Z80 ready for use, but as I don't have the programmer for the Flash chip (which will hold the OS) there's not much I can do with it physically. I have therefore cobbled together a basic emulator to help develop some of the software beforehand.

2008.08.12.02.Emulator.png

To cut hardware costs I'm going to try and handle input in software. One bit of hardware I'm planning on having is an eight-bit open collector I/O port. Open collector pins float high in their reset state, and any device connected to the pin can drive it low. AT devices (keyboard and mouse) use this type of electrical connection, as does the I2C bus and the TI calculator link port. I can use up the eight pins easily - two pins per AT device (keyboard and mouse) makes four, two pins for the I2C bus and two pins for a TI calculator link port.

The I2C bus I mentioned above is a simple way to enhance the computer once built. There will be one device permanently attached to the bus, a DS1307 real-time clock, which will be used to provide time-keeping functions for the OS as well as generating periodic interrupts (the chip could be configured to trigger an interrupt 100 times a second, useful for timing game logic). I could then leave empty space on the circuit board to add other I2C devices over time, or have a socket on the case that could be used to plug in additional I2C modules.

Now that I have some more tools, namely a desoldering pump, I tidied up the horrible hack job I'd done on the graphical LCD (replacing the multiple wires with a single pin header).

2008.08.12.01.Sonic.jpg

Yes, still the PICAXE here, but I'm using its 256 byte EEPROM to store a 32×64 pixel image of Sonic that is repeated four times horizontally.

I'm still not sure what I'm doing with regards to memory or storage. I'm still working on the simple assumption that ROM is 32KB ($0000..$7FFF) and RAM is 32KB ($8000..$FFFF) but this wastes a lot of memory and isn't very flexible at all. I've planned a bank-switching MMU, but as this will require at least four registers to store what appears in each of the four 16KB windows it will end up being physically very large and painful to wire.

As for storage, I have no idea. I have some 32KB I2C EEPROMs, but 32KB isn't exactly very large. Alternatively, I have an old 512MB SD card, and could try talking to it over bit-banged SPI. (SD cards use 3.3V, though, which complicates matters - not to mention that bit-banged SPI is going to be a little sluggish). I also have a USB module which can talk to USB mass storage devices over a serial connection, so maybe I should add a UART to the project. Adding a fully-blown USB module (which also plays WMA, MP3 and MIDI files) to such an otherwise low-tech computer feels like heresy, though.

Experimenting with a 32KB RAM

Monday, 4th August 2008

The next component I thought I'd experiment with is the RAM. The project is an analogue recorder - a circuit that samples an analogue input periodically and saves it in RAM, and can be configured to play the recorded signal back afterwards.

2008.08.04.02.Terminal.png
Yes, it says plating.

A single RAM chip offers 32K with an eight-bit word size. This requires fifteen lines to address it, A0..A14. The PICAXE-28X1 that is to control the circuit does not have enough output pins to be able drive this address bus and a data bus (to transfer values to and from RAM) and a still have enough pins left over to control the various components. To get around this, two octal (eight-bit) latches are used to drive the address lines, A0..A7 from one chip and A8..A14 from another. The inputs to these latches are connected to the data bus (PortC on the PICAXE), and two pins on the PICAXE are set aside to trigger the latch enable pins on either latch.

What this means in practice is that if you wished to change the current address to $1234 you would put $34 on the data bus and trigger the latch that corresponds to the least significant byte of the address, then put $12 on the data bus and trigger the latch that corresponds to the most significant byte of the address.

2008.08.04.01.Recorder.jpg
Any hobbyist can have wire insulation in any color that he wants so long as it is black.

A 10K potentiometer provides the required analogue input and an LED provides the output. The switch on the left is used to change between recording and playback modes. The large chip at the top is the RAM, the two small ones in the middle are the octal latches and the medium one on the right is the PICAXE-28X1.

As only 15 lines are needed to address 32KB, the most significant bit of the address bus is wired to the /WE pin of the RAM chip. This pin determines whether we're writing to (low) or reading from (high) the chip. This effectively means that addresses $0000..$7FFF are used when writing and addresses $8000..$FFFF are used when reading.

The only remaining connections to the RAM chip required are chip enable (/CE) and output enable (/OE). When chip enable is low, the RAM chip can be accessed; when high, it ignores all input. When not in use we therefore make sure that chip enable is high. When output enable is low, the RAM chip puts the value at the current address onto the data bus, so we need to pull this low when reading but make sure it's left high most of the time so that the RAM chip doesn't interfere with other devices trying to put a value on the data bus.

The code for the test program is as follows:

; Pins:
Symbol RamChipDisable   = 4
Symbol RamOutputDisable = 5
Symbol AddressLatch0    = 6
Symbol AddressLatch1    = 7

; Registers:
Symbol RamValue         = B0
Symbol RamAddress       = W1 ; B3:B2
Symbol RamAddressLow    = B2
Symbol RamAddressHigh   = B3
Symbol RamPointer       = W2 ; B5:B4

Symbol RecordingLength  = W3 ; B7:B6

Boot:
	Let DirsC = $00
	High RamChipDisable
	High RamOutputDisable
	Low AddressLatch0
	Low AddressLatch1
	Let RamPointer = 0
	SetFreq M8
	
Main:

StartPlaying:
	SerTxd ("Started playing: ", #RecordingLength, " bytes", CR, LF)
	Let RamPointer = 0
	Pause 100
PlayingLoop:
	If PortA Pin1 = 1 Then StartRecording

	; Read stored value from RAM.
	Let RamAddress = RamPointer	
	GoSub ReadRam
	
	; Set LED brightness to stored value.
	Let W4 = RamValue * 4
	HPwm PwmSingle, PwmHHHH, %0100, 255, W4

	; Increment playback pointer and loop if hit end.
	Let RamPointer = RamPointer + 1
	If RamPointer = RecordingLength Then
		RamPointer = 0
	EndIf

	; Loop back.
	GoTo 	PlayingLoop


StartRecording:
	SerTxd ("Started recording...", CR, LF)
	Let RecordingLength = 0
	Pause 100
RecordingLoop:
	If PortA Pin1 = 0 Then StartPlaying
	
	; Read value from ADC.
	ReadAdc 0, RamValue
	
	; Set LED brightness to read value.
	Let W4 = RamValue * 4
	HPwm PwmSingle, PwmHHHH, %0100, 255, W4

	; Store value read from ADC into RAM.
	Let RamAddress = RecordingLength
	GoSub WriteRam
	
	; Increment record pointer.
	Let RecordingLength = RecordingLength + 1
	
	GoTo RecordingLoop

WriteRam:
	; Set up address bus:
	Let DirsC = $FF
	Let RamAddressHigh = RamAddressHigh & %01111111
	Let PinsC = RamAddressHigh
	High AddressLatch1 : Low  AddressLatch1
	Let PinsC = RamAddressLow
	High AddressLatch0 : Low  AddressLatch0
	; Set up data bus and write:
	Let PinsC = RamValue
	Low RamChipDisable
	High RamChipDisable
	Let DirsC = $00
	Return

ReadRam:
	; Set up address bus:
	Let DirsC = $FF
	Let RamAddressHigh = RamAddressHigh | %10000000
	Let PinsC = RamAddressHigh
	High AddressLatch1 : Low  AddressLatch1
	Let PinsC = RamAddressLow
	High AddressLatch0 : Low  AddressLatch0
	; Set up data bus and read:
	Let DirsC = $00
	Low RamOutputDisable
	Low RamChipDisable
	Let RamValue = Pins
	High RamChipDisable
	High RamOutputDisable
	Return

As before, there's a simple video of the circuit in action.

Page 15 of 45 111 12 13 14 15 16 17 18 1945

Older postsNewer postsLatest posts RSSSearchBrowse by dateIndexTags