Advertisement Jump to content
  • Advertisement
  • entries
    222
  • comments
    606
  • views
    591681

About this blog

Z80 and C#-related shenanigans - now with added electronics.

Entries in this blog

 

Z80 Light-flasher

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


Click for video (829KB WMV)
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", $)

benryves

benryves

 

Z80 computer with a primitive I/O board

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.


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.


Click for legible schematic
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).


Click to toggle labels
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.


Click to toggle labels
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.


Click for video (943KB WMV)
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.

benryves

benryves

 

Z80 computer - Lines, cubes and inverted text

I've made a few additions to the operating system for the computer. The Console module, which handles text input and output, now supports "coloured" text - that is you can set the text foreground and background colours to either black or white. This functionality is exposed via the BBC BASIC COLOUR statement. If you pass a value between 0 and 127 this sets the foreground colour (0..63 is white, 64..127 is black) and if you pass a value between 128 and 255 this sets the background colour (128..191 is white, 192..255 is black).


The image on the right also demonstrates another addition - you can set the text viewport to occupy a partial area of the display. This is most useful when coupled with the ability to define graphics viewports, which I have yet to add.

That said, I have started writing the Graphics module. So far all it can do is draw clipped lines, and this functionality is exposed via BBC BASIC's MOVE and DRAW statements. MOVE sets the graphics cursor position - DRAW also moves the graphics cursor, but also draws a line between the new position and the previously visited one.


I cannot use drawing code I've written for the TI-83+ version due to differences in the LCD hardware and the way that buffers are laid out. The popular way to lay out graphics buffers on the TI-83+ is as follows:


Each grey block represents 8 pixels - one byte in LCD memory represents 8 pixels grouped horizontally. The leftmost bit in each 8-pixel group is the most significant bit of each byte. The data is stored in the buffer so that each row of the LCD is represented by 12 consecutive bytes. This left-to-right, top-to-bottom arrangement should seem sensible to anyone who has worked with a linear framebuffer. However, due to the way that the LCD I'm using is arranged, I'm using the following buffer layout:


The LCD hardware groups pixels vertically, but when you write a byte to it its internal address pointer moves right. Furthermore, the most significant bit of each byte written is at the bottom of each group. This may sound a little confusing, but actually works out as more efficient. Writing text is easy; I'm using a 4x8 pixel font, so all I need to do is set the LCD's internal address counter correctly then write out four bytes, one for each column of the text (other sensible font sizes for the display, such as 6x8 or 8x8 are just as easy to display).

Another example of improved efficiency is if you deal with pixel-plotting routines. Each pixel on the display can be addressed by a buffer offset and an eight-bit mask to "select" the particular pixel in an eight-pixel group. With this arrangement, moving the pixel left or right is easy; simply increment or decrement the buffer offset by one. Moving the pixel up or down is a case of rotating the mask in the desired direction. If the rotation moves the pixel mask from one 8-pixel group to another (which only happens every eight pixels) the buffer offset needs to be moved by 128 in the correct direction to shunt it up or down.

On the TI-83+, moving the pixel up or down requires moving the buffer offset up or down by 12; moving the pixel left or right is a rotation as before with a simple buffer offset increment or decrement to move between 8-pixel groups.

In Z80 assembly incrementing or decrementing a 16-bit pointer by one is a single instruction taking 6 clock cycles; moving it by a larger offset takes at least 21 clock cycles, 42 if you include backing the temporary register such an operation would take.

What may be interesting to see is how well a raycaster would work on a system that has video memory arranged into columns.

Without wishing to be typecast as that programmer who loves spinning cubes, I also wrote a cube-spinning demo to test the line drawing routines as well as some integer arithmetic routines I've added (the Z80 can't multiply or divide, so these operations need to be implemented in software).


Click for video (1.86MB WMV)
It runs fairly smoothly (bearing in mind the 2MHz clock speed). The second half of the video has the Z80 running at 10MHz; it actually seems quite stable even though the LCD is being accessed at nearly five times its maximum speed (the system did need to be reset a few times until it worked without garbling the display).

benryves

benryves

 

Z80 BBC BASIC - Emulated on Windows

I've started working with the actual BBC BASIC interpreter. As it won't run in its current state on the TI calculator (it relies on a jump table at &FF80..&FFFF to interact with the host, which is protected) I'm using the Z80 emulator I wrote for Cogwheel to try and puzzle out what the host interface should be doing from the relative sanity of C# code (the jump table is populated with OUT (n), A instructions which are subsequently trapped and handled by the emulator).


One thing I hadn't realised is that the graphics operations that BBC BASIC offers are actually implemented via the OSWRCH handler (OS WRite CHaracter), which means that BBC BASIC's PLOT, MOVE and DRAW commands will also be available, as well as any commands that use them indirectly (such as CIRCLE).

benryves

benryves

 

Z80 ASM and PICAXE

I've been doing all sorts of odds and ends recently. I've acquired a Sega Game Gear, and so have started taking a look at programming on that - it has a Z80 CPU in there, so the languge isn't an issue, more so the hardware. Ah well, I can display a screen of text and cycle the colours around so far - nothing impressive, but the setup works.

I've also been playing with PICAXE microcontrollers again. Here is a pair of routines that can be used to get/send bytes using the TI hardware link protocol, for example. I am yet to do anything useful with them, however.

Windows programming has never been my favourite side of things, but I have also started work on a Z80 ASM "IDE" called Latenite:

Clicky

It's mostly there, but now needs features galore to be added. So far it can be used to compile and source code, which is the main thing.

benryves

benryves

 

YM2413 (OPLL) Emulation

As (yet another) side project to all this Z80 work, I've also decided to have a stab at the Japanese Master System's FM chip. It's a YM2413, and the documentation on it is fairly tricky to get to grips with - not only thanks to it being in typical Japanese-manual English.

So far, I have this - the VGM player is a bit buggy and extremely primitive (unfinished), but should be enough to demonstrate the current state of the OPLL. You'll need an FM VGM to try it with - the Space Harrier demo from the BIOS sounds pretty good.

If anybody has had any experience with the YM2413, I'd be interested to hear if you had any helpful tips...

In other news, I've been adding multipage program support to Brass, and Latenite has had a lot of debugger integration work done on it. It's still a long slog before it's in a presentable state, sadly.

benryves

benryves

 

XNA Quake

After pratting around with DOOM with MDX, it's logical to move up to Quake with XNA. After all, the levels are "true" 3D, and there's a healthy amount of new textures (including bump and gloss maps) so that it would make creating an advanced engine easier. My problem is always finding resources.

On the subject of resources, I'm using a system whereby the various classes for game resources (such as the Level or Model class) can implement ResourceLoader.ILoadable, so I can do things like this:

ResourceLoader = new ResourceLoader(@"C:\Program Files\Quake\ID1");

Palette DefaultPalette = ResourceLoader.Load("gfx/palette.lmp");
Level SomeLevel = ResourceLoader.Load("maps/start.bsp");



This way the ResourceLoader can handle dragging data out of multiple pack (.pak) files or loading override files.

First things first - loading levels. This is nice and easy (unlike DOOM) - the .bsp files contain a list of vertices and a list of edges (each edge contains a start and end vertex index). For testing purposes, I used the Graphics class to draw out each level in 2D:



Next up, I create a point list and render those in 3D:



...and now the edges as a line list:



That seems to be on its side. Quake uses (x, y) as a 2D floor plan and z for height, so I need to swap z and y (and negate y whilst we're at it).



Quake uses faces for solid walls, which are simply convex polygons made up from lists of the aforementioned edges (and may have more than three sides). I'm creating a simple triangle list, and applying some fog to make the world look 3D (there's no lighting):



Texturing would make a welcome addition. Quake uses a rather novel texture coordinate system (at least, I've never seen anything quite like it) whereby each face references a TextureInfo structure, which contains the index of the texture itself, a pair of vectors indicating the horizontal and vertical axis (for aligning the texture against the surface of the face) and an offset in texels (so the texture can be aligned to the edges of the face). Fortunately, the BSP documentation I have indicates how to use this information to calculate the texture offset per-vertex, and by dividing by the texture's width and height I can get "conventional" texture coordinates.



The real Quake textures would be nice, I suppose. As I break up the faces into triangle indices, I group them by texture index. The final triangle list is made up of groups of triangles by texture, so that (for example) triangles 0 to 10 will be texture 0, triangles 11 to 14 will be texture 1, triangle 15 will be texture 2 and so on. I also maintain a list of which triangles use which texture, so to render I simply set the texture, draw a range of triangles from the single list, then move on to the next texture.





It's all pretty brute-force stuff at the moment, but it's still very fast on modern hardware. Next up will be lightmaps. [smile]

benryves

benryves

 

XNA DOOM3

This journal is starting to look a little drab, so here's a splash of colour.


Not really all that colourful, on second thoughts. [smile] I fancied a short break from BBC BASIC, and seeing that the current XNA CTP supports VS 2008 I thought I'd try a bit more work on hardware-accelerated 3D. I've never worked with shadows, bump mapping or visibility portals, and have a copy of DOOM 3, so thought that would provide a nice set of resources to experiment with.


The screenshots are generated by simply brute-force rendering of all surfaces of all models in a level (.proc) file. The odd colours and lighting are courtesy of the default lighting provided by the BasicEffect class. The level files are simple to parse (they're just text files breaking each surface down into vertex and index arrays, ready to be fed to the video hardware), so though I've not found much documentation on them it's been pretty easy to guess what's what so far.


The above textures are loaded by taking the material name and appending .tga, which seems to return a random mixture of specular, diffuse or normal maps. There is a materials directory that appears to contain definitions for which image file to use for each different type of texture map, so that looks like the next thing to investigate.

benryves

benryves

 

XML Overload

Everything in the new version of Latenite is XML-based - the project files, the help files, the error logs... Never mind, it seems to work well.


Click for massive.

So far, the only thing that's useful (complete and working) is the 8XP Linker - linker is a bit of a fancy name for it, as all it really does is wrap a number of .bin COM files up into a single .8x? or 83? file for the TI-83(+). It's a Win32 app, which should help some people stuck with the old DOS apps who can't get them to run under WinNT.

The Build menu seems to work; all Build scripts are now based around the project (rather than the old method of a number of fixed ones). After all, a "Build->Sega Game Gear" option is a bit useless to a TI-8X app. Any .cmd file in your project's Build folder should appear on the menu for you to run.

There are still a few issues - remembering which files were opened when you close the project, remembering which the default build script is, search in entire project (so far only current file and all open files are supported), jump to line, jump to previous instance of selected word, open file at cursor... I also need to complete the Z80 instruction set help file, add a WLA-DX help file... Lots for me to be getting on with!

benryves

benryves

 

World's Worst Sector Splitter

As with many programming problems, a potential solution presents itself at about 2AM when you suddenly awake and hunt paper and pencil before the brilliant idea vanishes into the groggy murk that is forgetfulness.

In this instance, a solution did present itself, but it's far from 'brilliant'.

I was pondering DOOM's floors, and how to split them up. I came up with a simple, optimal solution - optimal, provided that the sector I was splitting into triangles was a rectangle with sides perfectly aligned the the (x,y) axes. Happily, DOOM follows that pattern fairly often.



Here we have the typical sector. It's sector number one from E1M1, the ground that surrounds the acid pool outside the window. As you can see, it's not entirely convex, and has a hole in the middle. I have access to the lines and points that surround the sector.

My technique runs like this: Split the sector up into horizontal spans. To do this, I cycle through every single line in the sector, and for every different Y coordinate I split the sector. For each individual span, go through each line in the sector. If it's completely outside the span, discard it. If it's partially outside (no line will ever have a point half-way inside the span), clip it to the upper/lower Y bounds. Sort these lines from left to right, then group into pairs. Each pair, when combined with the top and bottom Y boundary, forms a trapezium. Split this into two triangles - there's a part of your floor.

This method is fairly simple, and appears to work pretty well:



Note the large magenta shape at the top (maze in E1M2). That's a single sector!

The image is less pretty if I dump out an image showing the triangles generated:



Hopefully that makes my method slightly clearer.

For some areas, it does pretty well. In others (sectors not perfectly aligned to the (x,y) axes) it generates an absolutely horrible mess of triangles.

Grouping walls by texture and generating a new vertex buffer for each texture group worked pretty well, so I've done the same for floors.

The first floor test looked pretty good (minus texture coordinates):



...so I fixed it up a bit, and flipped the vertex order to support ceilings as well.



One problem I had been worried about had reared it's ugly head, however - hairline cracks between triangles, showing the lovely bright blue through the dark and dismal UAC facility. It was trivial to fix, however; replace the floating-point linear interpolation (to clip lines to the single horizontal span in the floor splitting code) with the integer-based method. This got rid of all the rounding errors, and the cracks were gone.

The floor splitting code might inefficient, but it is simple - even then, however, it does fail on certain segments resulting in slightly-larger-than-hairline cracks on certain levels.



I'm not quite sure why that's happening. I have a hunch that in certain cases there is an extra line when breaking up sectors into horizontal spans and you end up with an odd number of dividing lines. In some cases, this extra line is at the end - and it is ignored safely. If it's at the beginning, though, everything is shunted along one and the whole sector span is drawn incorrectly.

Also, (and this can be verifed), some sector floors/ceilings end up being drawn back-to-front, and fall prey to the backface culling. Ducking below the surface of the floor, you can clearly see the missing floor deciding to be a ceiling.



One potential improvement would be to split the floor twice - once in horizontal spans, once in vertical spans - and use the one that produces the least triangles.

The texturing issues from last post were fixed with a variety of code changes - some sector-to-sector wall segments were being drawn upside down (the code now flips the heights around so they're in the correct order); the light level of a sector's wall is calculated by the brightest between it and any adjacent sector; lower texture pegging fixed to align correctly.

benryves

benryves

 

Web design

Seeing as I spend my working time doing web development, I thought it might be time to rework my site and remove the nested mess of tables [wink]

For once, I actually have content, but at the moment it's spread out in seperate mini websites in my "bin" folder. It would be nice to unify it...

After an evening of tinkering, I've got this far. (Hovers won't work in IE6).

Any comments? I'm not so sure of the red, myself.

benryves

benryves

 

Watched Forums Userscript

I hope everyone had a very pleasant Christmas and New Year break. It's been a while since I last posted -- I've been very busy with work of late and as such have not had much to report on my own projects.

As you have no doubt noticed GameDev.net has undergone some fairly radical changes recently; most of these seem to be for the better, but as with any change there are some downsides. GameDev.net is a large and busy site and the sheer volume of forums can be a little overwhelming. One feature that was available with the old forum software that I have not been able to find in the new software is the ability to flag certain forums as your favourites and to have a short list of these at the top of the forum listing.



To restore this feature I have written a small userscript for Opera and Firefox (using Greasemonkey). Chrome apparently has some support for Greasemonkey-style userscripts so the Firefox version may work there -- I'm afraid I do not have it to hand to test.

You can add a forum to your Watched Forums list by clicking the Watch Forum button at the top of its page. I hope you find this as useful as I do!

Update: I've modified the userscript to work with collapsed categories. If forums in collapsed categories are missing from your favourites please update your copy of the userscript using the download links above.


benryves

benryves

 

VMusic2 - USB for the 83+

The TI-83+ lacks something the 84+ series has - a USB port.


Enter the VMusic2. This low-cost (GBP25) module offers a USB host controller with a simple serial interface that can be used to read/write FAT-formatted USB mass storage devices. It can also play MP3 files straight from the drive!



This is all very well, but the TI doesn't have a standard serial port either. To handle communications between the two, therefore, is a PICAXE-28X1 microcontroller.

The TI can then run a program that communicates using its standard linking protocol.


I've posted a thread on MaxCoderz with more information about the project. For those interested in the VMusic2 device, here's a datasheet and here are the commands.


1.48MB AVI (MPEG-4, MP3)
Yes, I know I should probably get a life. I blame the solder fumes.

benryves

benryves

 

Vista and MIDI

I have a Creative Audigy SE sound card, which provides hardware MIDI synthesis. However, under Vista, there was no way (that I could see) to change the default MIDI output device to this card, meaning that all apps were using the software synthesiser instead.

Vista MIDI Fix is a 10-minute application I wrote to let me easily change the default MIDI output device. Applications which use MIDI device 0 still end up with the software synthesiser, unfortunately.

To get the hardware MIDI output device available I needed to install Creative's old XP drivers, and not the new Vista ones from their site. This results in missing CMSS, but other features - such as bass redirection, bass boost, 24-bit/96kHz output and the graphical equaliser - now work.

The Creative mixer either crashes or only displays two volume sliders (master and CD audio), which means that (as far as I can tell) there's no easy way to enable MIDI Reverb and MIDI Chorus.

benryves

benryves

 

Virtual screen resolutions for BBC Micro compatibility

The BBC Micro had a virtual resolution of 1280x1024, meaning that if you drew a circle centred on (1280/2,1024/2) it would appear in the middle of the screen regardless of its pixel resolution. On top of that, (0,0) was in the bottom-left hand corner of the screen with the Y axis pointing upwards.


Thus far I'd been using the slightly more intuitive fixed resolution of 96x64 with (0,0) in the top-left hand corner with the Y axis pointing downwards. This means that any graphics program for the TI-83+ version appears upside down and squashed into the bottom-left corner when run on the BBC Micro.

I have worked on attempting to remedy this. 1280x1024 does not divide cleanly into 96x64, so I've used a constant scale factor of 16 on both axes resulting in a virtual resolution of 1536x1024. (Flipping the Y axis is easy enough). This means that programs drawing shapes (lines, circles, triangles, rectangles, parallelograms) will run on both BBC Micro "compatible" versions of BBC BASIC and the TI-83+ and produce roughly the same results.


As this may not be to everyone's tastes the two options may be independently controlled with two new star commands;

*YAXIS UP|DOWN *GSCALE ON|OFF
The default is to have *YAXIS UP and *GSCALE ON. To revert to the old behaviour you could specify *YAXIS DOWN and *GSCALE OFF.

I have also been working on implementing the OS call OSGBPB. This call allows you to read/write multiple bytes of data in one go, so is much faster than using the byte-at-a-time BGET# and BPUT# commands. It also provides a way to enumerate filenames, a feature I have yet to implement but one that should be useful (eg to search for data files for your own program without having the filename hard-coded or having to prompt the user).

benryves

benryves

 

VGM Player now works!

Lots of fixes...
Download here! (New package includes loading music from Fire Track as a sample)

- Fixed reading of files. I'd used 'char' instead of 'unsigned char'. Files
that played back as plain silence now play back fully, also play at correct
speed. What a stupid mistake - it's amazing it worked at all!
I ended up rewriting the entire PSG emulation thinking it was that that was
to blame... evidently not!
- Halved frequency of noise channel by only switching output once on 2 clocks.
It was an octave too high before!
- Fixed odd jumping/incorrect (after loop) seek bar.
- Changed [x] exit button to use 'x' character rather than the cross symbol -
the cross appeared as a "Pt" sign under Windows 2000.
- Removed display of Japanese tags.
- VGM files that loop back to the exact start of the file are now looped rather
than ignored.
- Added display of system name for noise mode and very stupid autodetection.
- Sound output is now averaged inside each 44100Hz sample to "antialias" the
output. Sound output that relies on channels at 0 outputting fully now work.
Sampled audio still sounds a bit noisy/weird. Sound is now output at 16-bit.
- Option to enable/disable sound channels individually.
- Accelerated seeking considerably.

benryves

benryves

 

VGA output for the dsPIC33 VDC

I have spent quite a while working on different projects that generate PAL video signals in software. This may seem a bit odd if you consider the fact that I don't own a TV, so tend to rely on a video capture card or VGA box to see the output of these projects on a computer monitor -- something I do have a fair number of.

This reliance on another piece of technology between my project and the display device is not something I'm too keen on, so have spent some time adding native 640x480 60Hz VGA output to my dsPIC33 video display controller.


Another advantage of using a VGA monitor directly is that individual pixels are shown very crisply, unlike my video capture card or VGA box which tend to blur the image horizontally. This is shown in the zoomed in part of the above photo.

Generating a video signal for a VGA monitor is easier than generating a composite video signal for a PAL TV, as there are distinct pins for the image data, horizontal sync and vertical sync. One problem I did have, however, is with the length of the vertical sync pulse. I started with a very brief pulse (the same duration as the horizontal sync pulse) which worked fine with my old analogue CRT monitors but didn't work at all with my modern LCD monitor. The documentation I was using for timing information indicated that there were "two scanlines" for vertical sync so I extended the pulse to last for those two frames, which worked on the LCD but didn't on the CRTs. My final compromise has been to assert the vertical sync pin for the duration of a single scanline, which seems to work on all of my monitors.


When connected to a TV two microcontroller pins are used to drive a single load (composite input). When connected to a VGA monitor, however, a single microcontroller pin is used to drive three loads (red, green and blue inputs). I thought it prudent to check the datasheet for the dsPIC before connecting this increased load to the output pin where I was surprised to discover that the maximum source or sink current for each output pin is a measly 4mA -- not even enough to drive an LED! I have added a buffer to each video output pin to protect the dsPIC -- any buffer capable of sourcing up to 30mA or so should be sufficient (I'm using a 74F125, which can be seen in the bottom right of the above photo). I had previously been occasionally using the video output pins as inputs to check if there is a load on the output or not (such a load would indicate whether a TV or VGA monitor is plugged in or not) but I can no longer do this with the external buffer IC so have had to revise the circuit somewhat. Updated source code featuring the new VGA output code and an accompanying schematic are available for those who are interested!

benryves

benryves

 

VDP Interrupts

The VDP can generate two different types of CPU interrupt.

The first, and easiest, is the frame interrupt, which is requested when an entire frame has been generated. This is requested, therefore, at a regular 60Hz in NTSC regions and 50Hz in PAL regions - it's a useful timer to synchronise your game to.

The second, and more complex, is the line interrupt. This interrupt is requested when a user-definable number of scanlines have been displayed. An internal counter is decremented each active line (and one more just after), and when it overflows it resets to the value held in a VDP register and requests the interrupt (so 0 would request an interrupt every line, 1 every other line and so on). For every other line outside the active display area, the counter is reset to the contents of the VDP register.

Both interrupt types can be enabled or disabled by defined bits held in the VDP registers.

(The above should be loosely correct, the below is a little more uncertain).

Once an interrupt is requested, a flag for said interrupt is set. The flag is not reset until the VDP control port is read, so you must read the VDP control port if you expect any further interrupts.

To differentiate between line and frame interrupts you can check the value read from the control port. If the most significant bit is set, a frame interrupt (at least) was requested. Reading the vertical counter port (which returns the current scanline's vertical position) will also let you know where you are.

Something is a little wonky with my vertical counting code, as all lines end up being one too large. For the moment I'm subtracting one before returning the value (and waiting one extra scanline before triggering the frame interrupt) which is a horrible solution, but for the moment it has fixed a number of games that weren't working at all before.



Earthworm Jim, which relies on line interrupts to switch on zoomed sprites to dipslay the status bar at the bottom, now plays. It's missing some graphics on the title screens, though.

For some reason, rebuilding my Z80 emulator (which is in a different project) fixed some other interrupt-related glitches, so I have a sneaking suspicion most of my earlier problems are related to using an out-of-date DLL.



Road Rash highlighted another bug. The VDP can draw doubled sprites - that is, when a particular bit is set it will draw sprites as 8x16 pixels, stacking two consecutive sprite tiles on top of each other. Road Rash uses this mode, but also uses odd sprite indices (odd as opposed to even, not strange). The VDP will only take even indices, so a line of code to clear the least significant bit if using doubled sprites fixed that.

Still no sound, though.

benryves

benryves

 

Varying wall heights in a 3D engine for the TI-83+

I've done a little more work on the 3D engine for TI-83+ calculators that I mentioned in the previous entry. The main difference is in limited support for varying the heights of floors and ceilings, illustrated in the following screenshots.

????

????
Walls now refer to one or two "sectors". A sector is a 2D region of the map of any size or shape; it can be concave or even have holes in it. Walls are also grouped into convex regions named subsectors for rendering purposes. Each wall has a sector in front of it and a sector behind it; these sectors have a specified floor and ceiling height. There are now two types of wall; a "middle" wall which connects the floor and ceiling of the sector in front of it and an "upper and lower" wall which connects the ceilings of the sectors on each side and the floors of the sectors on each side.

This makes occlusion a little trickier and determining where to draw lines around the edges of walls even more so!

????
The previous version of the renderer worked by drawing walls back-to-front, clearing rectangles the height of the screen behind the wall segments as they were drawn. The first attempt to improve this exchanged rectangles the full height of the screen with trapezia. The screenshot to the left shows the bounding rectangle around each wall segment being filled and the one to the right shows each wall filled as a trapezium. (As before, clicking an image with a border will display an animated screenshot).

????
Rather than attempt to calculate where lines should be drawn around wall edges I thought I'd experiment with dithered wall fills instead. The left screenshot shows this addition (each wall has a different shade) and the right screenshot shows the addition of support for wall heights (still drawn in the simple back-to-front technique, resulting in significant amounts of overdraw).


Unfortunately, the LCD on the calculator copes rather poorly with dithered fills; the above photograph was taken at the highest contrast setting. Rotating the camera to look at walls with a different dither pattern brings the world back into view. This is rather unacceptable, and is something I ran into with my previous implementation. I think I'll stick to stroked wall outlines rather than filled walls.


I had been experimenting with a new level design in the C# prototype of the engine that added another room accessible via a tunnel from the starting room. I added some code to the C# program to output the level data in a form that could be assembled into the Z80 version. This is shown above, having reverted to a simple wireframe view in anticipation of the new wall drawing code.


The new way to implement occlusion works very differently to the previous one. I had been sorting the geometry from back-to-front and rendering it in order, drawing walls in the foreground on top of walls in the background. This wasn't very efficient and wouldn't scale well. The new approach renders from the front to the back and works on information stored about each column of pixels on the screen. The screen is 96 pixels wide, so there are 96 columns to deal with. A counter is set to 96 at the start of rendering. When a column of a wall is rendered, a flag is set to indicate that that column has been completed and the counter is decremented. When the counter reaches zero, that means that every column on the screen has been completed and the renderer terminates. This is demonstrated in the above screenshot when compared to the previous one; walls that are some distance away from the camera (and behind other wall segments) are not always drawn as the renderer has decided that it has finished before reaching them.

An obvious issue with the above screenshot is that even though some of the geometry is culled, individual walls can still be seen through other ones.


Part of the solution is to use a custom line-drawing routine that checks each pixel against the completed columns table. If a column is marked as completed, the pixel is not drawn; if it isn't, the pixel is drawn. This is shown above.

I previously mentioned that there were two types of wall; "middle" walls (solid ones from the floor to the ceiling) and "upper and lower" ones (these have a hole in the middle). Only "middle" walls flag a column as being completed, as you need to be able to see through the hole in "upper and lower" ones. This causes the rendering bugs in the previous screenshot above and below the holes in the wall. The way to fix this is to add two new per-column clipping tables; one which defines the top edge of the screen and another which defines the bottom edge. These both start at the maximum values (0 for the top edge and 63 for the bottom) and are reduced whenever an "upper and lower" wall type is encountered.


The new code to do this is demonstrated in the above screenshot. There is still, however, a bug in this implementation. The per-column clipping tables are updated by the code that draws the line along the bottom or top edge of the hole in the wall. If this line is partially (or completely) off the screen, these tables are not updated and the rendering bugs appear again (as demonstrated at the end of the above screenshot). A final manual pass over parts of the line that are clipped off the screen corrects the issue:


As can be seen in the bottom left corner of the above screenshot I have added an FPS counter. This is accompanied by code that scales movement by elapsed time so you move at the same speed regardless of how long each frame takes to render. The engine is quite slow (and could no doubt be heavily optimised by someone who's good at assembly) and has quite a few bugs in it but it's certainly looking a little better than it did a week ago. As I only have a regular TI-83+ I'm aiming for something usable at 6MHz; the more modern calculators can run at 15MHz but this feature is not used in the demo.


Download Nostromo.zip
For those interested in trying the demo on their calculators, click on the above image to download an archive containing a TI-83+ and TI-83 binary. As before, this is experimental and may well crash your calculator, so please back up any important files first!

benryves

benryves

 

Variable height sectors

I switched from 16/8 division to 24/16 division, and wall heights are now calculated rather than being dragged off a lookup table.



There's also backface culling (the cylinder around the central cube is marked as double-sided, hence no culling there) but there is still no clipping or occlusion.

@philipptr: I'm guessing your technique is to calculate the angle of the point, add an offset to it, then convert back into a new coordinate? I just adapted some 3D point rotation code I had, simplifying (mathematically) it and removing any references to z.

I will probably not use textured walls - not so much for performance issues, but for aesthetic reasons. Scaling a texture down on a wall displayed on a 96x64 monochrome display (without antialiasing) looks pretty ugly. Simple lines look a lot cleaner.

Oh, and hello aCiD2 [wink]

benryves

benryves

 

Useful script?

If you have a website of your own, you might want to have your journal on it. (I do [wink]).
This script can be used to generate a simple HTML document (such as this one!) containing a brief description of your recent entries, a title and a link to the item itself.

# UPDATE_JOURNAL.PHP
# Ben Ryves 2005.

# CONFIG:
$journal_id = 273102; # ID number of your journal.
$items = 8; # How many recent journal entries do you want?
$saved_page = 'journal.htm'; # Which page do you want to save the journal to?
$password = 'password'; # What's the password required to fire off an update?


if (isset($_POST['submit'])) {
if ($_POST['password']==$password) {
# Run the update script:
$url='http://www.gamedev.net/community/forums/lib/rss.asp?mode=journal&jn='.$journal_id;
$handle = fopen($url,'r');
$writer = fopen($saved_page,'w');
if (!$handle) {
echo "The RSS feed is not available at the moment - sorry. ";
} else {
global $cur_pos, $buffer;
$buffer = '';
while (!feof ($handle)) {
$buffer .= fgets($handle, 4096);
}
fclose($handle);
$cur_pos = strpos($buffer,"");
for ($i=0; $i $title = get_between_text("","");
if ($title===false) break;
$link = get_between_text("","");
$description = html_entity_decode(preg_replace("#<(.*?)>#i","",get_between_text("","")),ENT_QUOTES);
$description = preg_replace("#,"...",$description);
fwrite($writer,"$link\" target=\"_blank\">$title
$description ");
}
fclose($writer);
echo "$saved_page has been updated! ";
return;
}
} else {
echo "Invalid password: sorry! ";
unset($_POST['submit']);
}
}

?>
"post">
Please enter your password:
"password" name="password" />
"submit" value="Go!" name="submit" />




function get_between_text($before, $after) {
global $buffer, $cur_pos;
$cur_pos = strpos($buffer, $before, $cur_pos);
if ($cur_pos===false) {
return false;
} else {
$cur_pos+=strlen($before);
$e = strpos($buffer, $after, $cur_pos);
return substr($buffer, $cur_pos, $e-$cur_pos);
}
}

?>

You need to set a password just to stop people from running the script (it's a pretty basic system). You'll also need to set the correct ID number. It's a very quick-and-dirty script, so if you have any problems with it give me a shout!

benryves

benryves

 

USB remote control receiver for PowerDVD

I enjoy watching films and mainly do so sitting at my desktop PC. This has taught me that cheap office chairs are not the most comfortable things to sit on for extended periods of time, especially when the next room contains a comfortable bean bag and a good place to stick a screen. A gap between the two rooms allows me to pass cables from one to the other, and after purchasing a 10m DVI-D cable and a USB extension lead on eBay I had both picture and sound sorted out (I use a USB sound "card"). This left me with one final problem: how to control the PC through a wall.

One possibility would be to extend the lead on my keyboard, but its media buttons light up (bothersome in a darkened room) and some of the keyboard shortcuts in PowerDVD (such as Ctrl+P for the popup menu when watching Blu-ray discs) are tricky to hit in the dark. Given my fondness for infra-red remote controls building a remote control receiver would seem like both an interesting and useful way to spend a weekend.


Rather than build something that relied on some Windows software to translate received remote control signals into keystrokes I decided to use the free V-USB library to construct something that showed up in Windows as a standard USB keyboard. One of the sample V-USB projects is a USB keyboard, which made getting started much easier! The above photograph shows the initial prototype, based around an ATmega168. The tall three-legged component sticking up out of the board is a TSOP2438, which is an infra-red receiver and demodulator. This is tuned to the 38kHz carrier employed by most remote controls and outputs a logic low or logic high depending on the presence or absence of such a signal. The ATmega168 is programmed to time the incoming signal and passes this timing information to a collection of routines that attempt to decode it. I have currently two decoders, one for the NEC protocol and another for SIRCS -- information about some common protocols can be found on this website.

The choice of these two protocols is down to the remote controls I have around me. The one that offered me the most useful buttons was the PlayStation 2 DVD remote control (SIRCS), though this is missing some useful controls, such as volume and the red, green, yellow and blue buttons. To remedy this I went and bought a cheap universal remote control from Clas Ohlson. After hunting through several of the modes I settled on the Clas Ohlson DVD one (0815) as most of the buttons work in this mode (the only unshifted one that doesn't is the record button, and I can live without it). In this mode the remote control uses the NEC protocol.


To turn the receiver into something more conveniently sized I decided to switch from the 28-pin ATmega168 to the 14-pin ATtiny84, shown in the above photograph. The compiled program was already small enough to fit into the reduced memory, and the only modification I had to make was to amend two timing routines to share the same timer peripheral as the ATtiny84 only has two timers, not the three I'd been using on the ATmega168.

I also opted to add a switch to the design. One problem with supporting both Blu-ray and DVD is that the way you navigate menus is quite different between the two; Blu-ray discs use a simple popup menu (Ctrl+P) which appears on top of the film, whereas DVDs seem to offer a number of different menu commands -- the two most common ones being "Title menu" (no shortcut) and "Root menu" (J). PowerDVD also lets you choose from a list of DVD menus in a context menu with one shortcut (L). I set the button on the receiver to switch between "Blu-ray" and "DVD" modes; in Blu-ray mode, the menu button sends Ctrl+P and in DVD mode the menu button sends L.


I bought an enclosure that is, in retrospect, a little too small. The above photograph shows the receiver assembled on stripboard with a fairly cramped layout. Fortunately there was sufficient room to include pin headers on the board, which will allow me to plug in a programmer to modify the software should I need to in the future. The LED on the front serves as simple user feedback -- it flashes whenever it receives a valid command and sends a keystroke back to the PC. When the mode is toggled between Blu-ray and DVD menus it flashes to indicate the new mode -- a long flash followed by a single short one for Blu-ray, a long flash followed by two short ones for DVD.


Overall, I'm quite happy with the way it turned out. It works well enough for my needs, though as those needs only extend as far as PowerDVD and a particular remote control it's rather basic and much more could be done with the hardware. I have uploaded the source code and a schematic for the project to my website as it currently stands for those who are interested.



benryves

benryves

 

USB joypads and text on your TV courtesy of an ATmega168

Nearly a month since my last update - my, how time flies when you're having fun (or a heavy workload).

I ended up building myself a cheap and cheerful SI Prog programmer for AVR development. After installing the development tools, scanning through the documentation and writing the microcontroller equivalent of Hello, World (flashing an LED on and off) I needed to find a suitable project. The first one was getting to grips with V-USB, a software USB implementation for AVRs. All you need for this are a couple of I/O pins, a few configuration file changes to set your USB device's vendor ID, product ID and device class, and a few lines of C code to actually implement your device. I attached six tactile switches to an ATmega168 and made the most uncomfortable USB joypad I've ever used. I managed two levels of Sonic the Hedgehog before my thumbs admitted defeat, but it's nice to know that building USB devices is very easy with an AVR.

#include
#include
#include
#include
#include

#include "usbdrv.h"

/* Joystick port bits */
#define JOY_1 (10)
#define JOY_2 (11)
#define JOY_UP (12)
#define JOY_DOWN (13)
#define JOY_LEFT (14)
#define JOY_RIGHT (15)

/* USB HID report descriptor */
PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x05, // USAGE (Game Pad)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x02, // USAGE_MAXIMUM (Button 2)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0 // END_COLLECTION
};

static uchar reportBuffer[3]; /* Buffer for HID reports */
static uchar idleRate; /* 4 ms units */

uchar usbFunctionSetup(uchar data[8]) {
usbRequest_t *rq = (void*)data;
usbMsgPtr = reportBuffer;
if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
switch (rq->bRequest) {
case USBRQ_HID_GET_REPORT:
return sizeof(reportBuffer);
case USBRQ_HID_GET_IDLE:
usbMsgPtr = &idleRate;
return 1;
case USBRQ_HID_SET_IDLE:
idleRate = rq->wValue.bytes[1];
break;
}
}
return 0;
}

ISR(TIMER0_OVF_vect) {

/* Fetch input */
uchar input = ~PINC;

/* X-axis */
switch (input & (JOY_LEFT | JOY_RIGHT)) {
case JOY_LEFT:
reportBuffer[0] = 0;
break;
case JOY_RIGHT:
reportBuffer[0] = 255;
break;
default:
reportBuffer[0] = 128;
break;
}

/* Y-axis */
switch (input & (JOY_UP | JOY_DOWN)) {
case JOY_UP:
reportBuffer[1] = 0;
break;
case JOY_DOWN:
reportBuffer[1] = 255;
break;
default:
reportBuffer[1] = 128;
break;
}

/* Buttons */
reportBuffer[2] = input & (JOY_1 | JOY_2);

usbPoll();
usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
};

int main(void) {

usbInit(); /* Initialise USB. */

PORTC = 0b00111111; /* Pull high PORTC0..PORTC5 */

TCCR0B = 0b00000101; /* CS2..CS0 = 101: prescaler = /1024 */
TIMSK0 |= (1 /* Enable timer 0 overflow interrupt. */
sei(); /* Enable global interrupts. */

for (;;) {
/* Infinite loop */
}
}

I should only really call usbSetInterrupt when a button or axis has changed, rather than every loop, but the above code works as is.

One thing that always bothers me when it comes to electronic projects is the difficulty of providing text output. LCDs are generally quite expensive and low resolution, and typically require a great many pins to drive them. Video display processor chips are difficult to find, and appear to require quite complex external circuitry (the best thing I've found thus far are some TMS9918 chips being sold as spares for MSX computers). Having briefly experimented with generating PAL video signals in software before, I thought I'd try the two-resistor approach to getting PAL video output on an ATmega168.

I had a hunt around and found AVGA, which is close to what I wanted - video output from an AVR using cheap hardware. However, it outputs RGB directly, and I don't own a TV or RGB converter so couldn't use that - all I have is a VGA box (accepting composite or S-Video input) and a TV capture card (also only accepting composite or S-Video input). AVGA does work with VGA monitors, but I'd like to keep the hardware interface simple - just two resistors, ideally.


In the end, I ended up writing my own library. It currently has the following specifications:
32x16 characters: 512 bytes (half of the total SRAM on the ATmega168) are used to store the text buffer. Full 256 characters at a resolution of 6x8 pixels each. Total screen resolution: 192x128. The library is interrupt-driven, and uses the sixteen-bit TIMER1 to schedule events. This means that the AVR is only busy generating video signals when it absolutely has to, leaving some CPU time to the user program. When outputting at full quality, the AVR appears to be capable of running user code at 3.3 MIPS, but by skipping alternate scanlines (each scanline is scanned twice anyway, so this mainly just makes the display appear darker) the AVR appears to be running user code at 9.9 MIPS. (I say "appears" as my calculation has been to execute a busy loop that would normally take one second on the AVR running at its normal 20 MIPS then seeing how long it takes with the video output driver enabled).

">
tvText demo on YouTube
The above video demonstrates some of the currently rather limited features of the library. The text console handles a subset of the BBC Micro VDU commands - I'd like to support as many of its features as possible. The code behind the BASIC-like part of the demo is simply written like this:

#include "tvtext/tvtext.h"

void type_string_P(const char* s) {
char c;
while ((c = pgm_read_byte(s++))) {
tvtext_putc(c);
delay_ms(100);
}
}

int main(void) {

tvtext_init();

tvtext_clear();
tvtext_puts_P(PSTR("AVR Computer 1K\r\n\nATmega 168\r\n\nBASIC\r\n\n>"));
delay_ms(2000);
type_string_P(PSTR("10 PRINT \"AVR Rules! \";\r\n"));
tvtext_putc('>');
delay_ms(500);
type_string_P(PSTR("20 GOTO 10\r\n"));
tvtext_putc('>');
delay_ms(500);
type_string_P(PSTR("RUN"));
delay_ms(1000);
tvtext_puts_P(PSTR("\r\n"));

for (int i = 0; i 200; ++i) {
tvtext_puts_P(PSTR("AVR Rules! "));
delay_ms(20);
}

tvtext_puts_P(PSTR("\r\nEscape at line 10\r\n>"));
delay_ms(1000);
type_string_P(PSTR("CHAIN \"DEMO\""));
delay_ms(1000);

// ...

}

All of the high-level console code - text output, viewport scrolling, cursor positioning &c - has been written in C, so should be relatively easy to be customised. The output driver itself has been written in assembly as timing is critically important.

With a few more features and a bit of tidying up I hope that people would find this a useful library. I'd certainly like to get a blinking cursor working within the driver, and if I add support for a reduced 128-character version I could save quite a bit of ROM space and add support for "coloured" - inverted, that is - text. NTSC support would also be quite useful.

benryves

benryves

 

TV Demonstrator for TI calculators

I've been tinkering with a number of small projects recently. I've resumed work on an LED clock for my bedroom (using a 32x8 LED display) and written an experimental BASIC interpreter in C# which I may try to turn into an assembler (implementing assembly statements as BASIC ones). In the mean time, I have finished one project -- a device to display a calculator's screen on a television set.


Texas Instruments also manufacture a product that allows you to view the screen contents of a calculator with supported hardware on a TV; ">here is a video demonstrating it. The additional hardware (either on special "ViewScreen" calculator models or built into the more advanced calculators such as the TI-84+) allows the device to mirror what is sent to the calculator's LCD in real-time.

I do not have one of these calculators, just a plain old TI-83+. However, this calculator (as well as the older TI-83 and TI-82) allows you to capture a screenshot over the link port. Pressing a button on my device captures a screenshot in this manner and displays it on the TV. This relies on the calculator being in a state where it can respond to these screenshot requests, so is not ideal, but considering that the TI Presenter costs $300 and relies on special hardware inside the calculator and mine should cost less than a tenth of that in parts and work with older calculators I think it's a decent compromise.


Click for a gratuitous 360? view
I had previously believed that NTSC composite video signals used a negative voltage for sync pulses. I have since found documents that indicate that the sync, black and white levels are the same as those for PAL. The timing is, naturally, different but as there's no need to change the hardware it makes supporting both NTSC and PAL relatively straightforwards. This contraption can be set to operate in either NTSC or PAL mode by sending the real variable M to it from the calculator, with a value of 60 for NTSC and 50 for PAL.

I acknowledge that this is not the most useful of projects (unless you're a maths teacher with an interest in electronics) but the code may be of interest for other projects. A handful of inexpensive parts can get you a picture on a TV from a 96x64 monochromatic frame buffer (the 1KB RAM on the ATmega168 doesn't allow for much more, alas).

More information and downloads can be found on the TV Demonstrator project page.

benryves

benryves

 

Turning Japanese

The Z80 CPU uses 16-bit addressing, which limits it to a 64KB address space. As well as fitting the program ROM into this space, we need to fit in the machine's 8KB RAM, limiting us even further.

To get around this limitation, the memory is broken down into a series of windows ("frames"), and you can change what is visible in some of these windows.

The memory range $C000..$DFFF can be used to address the 8KB RAM. This is mirrored from $E000..$FFFF; that is, reads and writes to $E000 work as if you were reading and writing to $C000.

There are three other windows; from $0000..$3FFF, $4000..$7FFF and $8000..$BFFF. By changing the contents of RAM (addresses $FFFC..$FFFF) you can adjust what is accessible from these memory ranges.

The lower 1KB cannot be paged out - this is because when the device boots the contents of RAM (and thus paging registers) are undefined, and you need something fixed in place to set up the machine correctly.

I had already implemented most of the above, with two omissions. The first was that certain cartridges contained their own additional RAM chips (accessible by setting flag bits in paging register $FFFC) that could be used as saved game area. Ys appears to use this area, and so didn't work without it. That was the garbled screenshot I posted earlier:



The Flash now works too.



The second omission was that Codemasters games use a different mapping system to the standard one. Fortunately it is significantly simpler; the 32KB from $0000..$7FFF is locked, and the 16KB from $8000..$BFFF is offset based on a value previously written to address $8000. ($C000..$FFFF is 8KB work RAM as usual).



The Excellent Dizzy Collection as seen at the top of this post doesn't entirely work; when you pick a game it resets. Some other emulators do this too. The Game Gear games Cosmic Spacehead and Micro Machines (1 and 2) work well, though.



After some fiddling around with the VDP emulation and interrupts, some more games work (but I'm not sure why they now work):



Other parts of the emulator have been improved. It can now be set to either domestic (Japanese) or export (everyone else) modes, which affects some games in a variety of ways - from translated text, to removing the Mark III bumper screen, to changing the title of the game.



For Game Gear games, detecting whether it's a Japanese machine or not is simple - test the 6th bit returned when you read port $00. If it's set, you've got an export machine.

However, port $00 is Game Gear-specific - the Master System hardware has a peculiarity that is used to detect region. There are two ports for controllers, and each controller has up, down, left, right, TL, TR and TH lines. You can set the TR and TH lines to be inputs or outputs (and the output level if configured as output) by writing to the I/O control port. If you set them to outputs and try and read from them, on export hardware you get the values you are telling them to output. On Japanese machines, however, they return 0s regardless of the chosen output level.



A more fun addition is Game Genie support. A simple call to Emulator.AddGameGenieCode("3A0-21C-2A2"); and Sonic can stand here all day:



Of course, the technical information above is my interpretation of what I've gathered so far from various documents from far brainier chaps than I, and the fact that all I have is a broken emulator indicates that it is probably complete rubbish. [smile]

benryves

benryves

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!