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

About this blog

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

Entries in this blog

 

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

 

CHIP-8/SCHIP

It's not as if I didn't already have an excess of projects, but anyway...


Vinegar - CHIP-8/SCHIP interpreter for the TI-83 (+)

The binaries are here. I've tested them fairly extensively on my TI-83 Plus, but be warned that (as it's beta) they might cause odd crashes. Back up any important files!

benryves

benryves

 

Next Latenite Beta

Seeing as the MaxCoderz fora have died (I can't read or post on them at the temporary hosting), and I know at least kv reads this... [wink]

Latest beta: http://benryves.com/bin/latenite/ (full install).

General
No more hanging or locking up when running the PTI debugger (huzzah!) PindurTI debugger is still horribly, horribly incomplete :( (Boo!)
Reworked icon extraction code works for all files (no more incorrect icons!)
File tree plugins (bundled with .emr plugin) to expand certain files into a tree.
Copy/Paste doesn't accidentally copy/paste if the text editor isn't selected (Ctrl+C/Ctrl+V doesn't work anywhere else, though).
Minor bug fixes here and there; some visible, others invisible.
Brass template has old defines added for backwards compatibility.
Updated copy of Brass and Brass XML file.
Minor tweaks to help file CSS.
Blue icon bug in Windows versions prior to NT 5.1 (XP) should be fixed.

Known Issues
Holding tab/shift+tab down adds lots of surplus tabs.
Anything to do with the debugging (watch, breakpoints) has not been touched, and so old bugs remain there.
PTI debugger doens't handle errors very gracefully (infinite messagebox loops). Make sure your ROMs are correctly installed!

EarlyMorning Link
For the EarlyMorning interface to work you need to associate .emr files with EarlyMorning. You can do this by double-clicking any .emr file, then browsing to the EarlyMorning binary when prompted. Without this information, Windows won't know where to find EarlyMorning when it wants to open an EarlyMorning file.
If you run Latenite, .emr files should now appear with the EarlyMorning icon, and a little [+] by them. Click this to expand them! Something a bit like this:



Double clicking on a resource should open it in EarlyMorning.
Bear in mind that I forgot to copy kv's PM to disk, so built the interface by guessing. [rolleyes]
Also bear in mind that EarlyMorning (the version I have, at least) has a bug, as it loads itself and expects to load resources relative to the current working directory. Opening a subresource has a hack in Latenite to fix this behaviour, but opening an EMR file by right-click->open will cause EM to crash.

@kv, you need something like this:

private string FixPath(string RelativePath) {
return Path.Combine(Application.StartupPath, RelativePath);
}

private void Something() {
// ... and use it like this:
LoadFromFile(FixPath("Some/Local/Path.ext"));
// Rather than:
LoadFromFile("Some/Local/Path.ext");
}

benryves

benryves

 

TI-83 Plus Mouse

I had another go with my AT protocol routines to see if I could persuade a PS/2 mouse to do anything.
It would appear that I could...


Click to view a video clip (1MB WMV)

There are two cables for the mouse as there were for the keyboard - one goes to the TI, the other goes to the power supply. The mouse is put into the lowest resolution mode (the cursor is moved 1 pixel per mm).

There's a demo for anyone with a TI-83 Plus and an adapter. It also contains a keyboard demo - and there's a video of that demo here (1MB WMV).

Fortunately, I didn't have to butcher that mouse (it's a rather good miniature USB/PS2 one), but it is connected to the TI with Blu-Tak to keep the wires on the contacts, and isn't very mobile as I only gave the power lead about 15cms clearance. I think it's high time I grabbed myself some proper connectors and a soldering iron from Maplin... [rolleyes]

As for the mouse routines - they need a lot of tidying up and making much more reliable, especially in the recovery when a mouse is disconnected then reconnected (my keyboard routines are 'hot pluggable', after all). Extending them to support the Microsoft Intellimouse system, giving the mouse an extra axis (scroll wheel) and two extra buttons would be rather cool as well.

Ultimately, these routines are rather useless until they become widely adopted. Ideally, they could/would be added to a shell, and form part of the standard routines. For example, the shell could provide a "get key" function, which would also transparently return keyboard keys as if they were keys on the keypad, or return four "left" keys if the mouse was moved 4 mm left -- that sort of thing.

Chances of this happening are, sadly, nil. [rolleyes]

benryves

benryves

 

Microhertz

They say that pictures are what make a journal interesting... but I don't really want to show the pretty pictures I have, as they'd spoil the surprise. Of what, you might ask? Well, I've been developing a TI-83/83+ scene demo, entitled "Microhertz" (for no particular reason). You can download it (and some screenshots, if you really must!) here.

Things have been very busy - a new Latenite beta, riddled with all new exciting "features" (*cough*), has been released... I'm slowly fixing them as and when I can [smile]
It's a bit rubbish for debugging - you can start, pause, and stop a debugging session, but that's about it. Hopefully it won't be too long until I get around to actually sorting out all the debugging gubbins, but I'd rather get the main IDE and the primitive debugging more stable first.

One other TI project I'm working on is updating QuadPlayer. I have a 1.1 version which can play songs from the archive as well as RAM, has a loop function and other little fixes and tweaks. To complement it, I've written a simpler script for creating your own songs, a better Windows-based player for testing (which simulates QuadPlayer's internal sound generation system and has accurate timing) and (best of all) am working on a program that takes PSG VGM files (BBC Micro, Sega Master System &c) and converts them to QuadPlayer songs. So far, any songs that do "exciting" things - vibrato, for example - end up sounding horrible in high octaves (I'm having to limit 10-bit to 8-bit periods), but I have some very decent sounding QuadPlayer files based on Sega VGMs. Some songs use lots of short, fast notes - which sounds great on dedicated hardware, but when you have a delay as QuadPlayer stops outputting a square wave to fetch the next note, sounds crap.

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

 

Debugging...

CoBB has added an non-interactive mode to his emulator. I'd never thought about using stdin/stdout to pass information between executables; but it works well (once I've ironed out the bugs at my end, that is!) All I need to do is pass it instructions via stdin then read back the results (such as a screen dump) through stdout. This can then be, hopefully, used to pass instructions back to Latenite ("Highlight line 43 on file source.asm", for example, to show where the PC is).

benryves

benryves

 

TI Z80 Development Gets Cool

This excites me greatly - integrating a debugging emulator into Latenite is very, very awesome. CoBB's emulator is incredibly good as a standalone, being able to step through your code instruction-by-instruction with breakpoints and a watch window or two is just amazing! [smile]

I spent most of the last few days working on Brass - adding for loops to assemble blocks of code multiple times and a few file operations for people not happy with the behaviour of .incbin. I also remembered to release an XML help file for Latenite.

The Marble Madness engine project has been plodding along in the background - I've written a fast masked, clipped, 16x16 sprite routine and have started tackling the big problem of mapping the ball's 3D coordinates to the screen (easy) and then to a heightmap (hard) for Physics.


Have a marble rolling off a ledge then bouncing off the right screen boundary. [wink]

benryves

benryves

 

Scrolling Marbles, Fire Track ROM

Fire Track

Prompted by a PM from evolutional, I rebuilt a working-ish version of Fire Track.

Download

Keys should be fairly self-explanatory; hold down button 1 and 2 as the ROM loads to get access to a cheat screen. Of course, you'll need an emulator; Emukon is a highly advanced, but suprisingly fast emulator (fast in that it lets me run GG ROMs at full speed on an old Pentium PC).

I will be uploading all the source and tools in a zip at some point so you can experiment with some of the other features if need be.

Marble Madness
I started work on a 4-way smooth scrolling tilemapper last night for use with the Marble Madness project (for the main view) and have come up with a fairly simple, but sufficiently fast mapper.


The GIF is a little wasted as the level is only 128 pixels wide, and on a 96 pixel wide display this doesn't give much room for showing off the smooth x-scrolling. Having never written a mapper like this (only basic, slow horizontal scrolling and vertical scrolling, never together) it was quite an experience, but not as hard as I'd thought.

I have cobbled together a basic editor that allows me to edit the map, and that takes a folderfull of GIF files and exports a binary sprite resource file. Hooray RAD with C#!


It looks like I'm going to need a lot of sprites... [sad]

As for this crashing and burning - Fire Track failed because of my absolute lack of music skill; also, the game was too hard to be any fun.

Marble Madness is just a set of ideas; I'm not comitted to it (hence no "official" announcement to any TI people, kv83!)

benryves

benryves

 

Lost my Marbles

First up, happy Christmas (or "Seasons Greetings" to the politically-correct amongst you - I've never heard this supposed "Happy Holidays" said in this part of the world, and in any case the word Holiday seems to indicate religious goings-on in any case, so we'll be having none of that PC rubbish [wink]).

Back to the Z80
Having spent a week away in Normandy, marooned from the world of the Intarwebs and PC, I have come up with a potential new Z80 project. I had my Game Gear with me (naturally) and one of my games was the most excellent Game Gear port of Marble Madness:



The game is simple if you do not know it; you must guide a marble through an obstacle course to the finish in under a certain time. Various tricky level design elements and enemies make this a hard task. The world is presented in an scrolling isometric 3D view.

The Game Gear is not an especially powerful platform; the Z80 runs at ~3MHz. The TI-83 Plus runs at 6MHz (an underclocked 8MHz Z80). Of course, the Game Gear has the bonus of hardware-accelerated smooth-scrolling (which is how we get super-fast games like Sonic on it) but Marble Madness has fairly simple basic scrolling; vertical smooth scrolling on a TI is cheap, and horizontal smooth scrolling only needs to go one pixel at a time for Marble Madness - we can't do much about that apart from bitshifts, but a pixel/frame isn't asking for too much.



In a bizarrely well-organised move I ended up taking along a pen and paper, and ended up making a bunch of notes on how to construct a Marble Madness engine.

Graphics
For this game, it all boils down to the way the graphics are presented to make or break the gameplay. They need to accurately convey the illusion of a 3D world, whilst at the same time being handled internally as very primitive - we can't do CPU/memory expensive things like treat the world as a true 3D model and have a 3D isometric engine.

Also, the limitations of the display and LCD driver of the TI-83 Plus need to be taken into consideration. The TI-83 Plus is equipped with a 96x64 pixel monochrome LCD. It is accessed through the use of a driver chip on ports $10 and $11. As it is monochromatic, a single byte represents 8 entire pixels. The physical display is 12 bytes wide and (there are 64 rows); in reality it is 16 bytes wide, but normally the 4 bytes are off the edge of the display. It can be set into a special 6-bit mode where each byte only covers 6 pixels; the 6 pixel columns are squashed up to give us a 16-column display, but this is only really useful if you are, say, using it for a 6x8 font (the TI-OS uses this mode for the text-mode screens).
For this reason, horizontal scrolling is cheap; you can just change the byte offset as you copy to the buffer to scroll up and down. However, horizontal scrolling requires (expensive) bit-shifting. Not only that, but the display operations have to be timed using inefficient loops (push af \ inc hl \ dec hl \ pop af is common - there is no accurate timing hardware) else it decides to do useful things such as jump into 6-bit mode. The whole TI graphics system is very sluggish.

The low resolution needs consideration; the Game Gear Game relies on clean, large, colourful graphics. I will have to drop the resolution down so that you can see a decent region around your marble; and can't afford to use effects such as dithering which make the display unclear (the display is quite blurry).

Here is a sample course (thanks to Maxim for producing the map):



To fit it on the TI, I have decided to halve the resolution. If I kept the squares as tightly packed as in the Game Gear version, this would result in 4x4-ish tiles - mucky, cluttered and ugly. To remedy, I shall have to expand the tiles to double their visible width/height; so a single tile in my version would cover 4 tiles on the Game Gear version. The levels would have to be adjusted (in the image above, the tile blocks at the top are arranged in 3x3 grids; I shall probably go for 2x2 grids of my larger tiles, or just go straight to 1x1...

The way I intend to handle the levels would be to split them up into their individual parts; Background - this would be a simple 2D tilemap, made up of 8x8 tiles with a fast smooth-scrolling engine. This is rendered, then the marble/enemies are copied on top of it. Heightmap - this is a sort of 2D map of the 3D world, where each point has two values - a type (flat, steep slope, empty space, goal...) and a relative height. This would be used by the physics engine to handle the movement of the marble around the level. Depth map - this is a sort of copy of the 2D tilemap, but only contains special masked copies of the tiles on the tilemap, together with a depth (z-index). This way, when the ball is drawn the tiles that it is convering can be checked. If the corresponding tile in the depth map is closer to the user, it is redrawn, concealing part of the ball. For example, see the railings in the picture of the level above, or any of the large holes. As most of this layer will be empty, it could be RLE compressed fairly succesfully.

Other things, such as animated sprites or dynamic parts of the level, would be handled separately. For example, level 2 has this tilty thing:



It tilts up and down. For this, a special large sprite could be rendered, and the 3D heightmap in memory adjusted as required.

I'd be interested to hear any thoughts/ideas/pointers on my methods...

benryves

benryves

 

Intelligent macro-matching, VS icon nicking

Brass has got a new macro preprocessor, that hopefully makes the work of people writing things like the macro-driven TI ASM API easier, and will generate less redundant code.
TASM only supports simple find-and-replace macros; so, for example: #define draw_sprite(x,y,sprite) ld a,x\ ld b,y\ ld hl,sprite\ call put_sprite

draw_sprite(10,43,my_sprite)
; Generates the following:
ld a,10
ld b,43
ld hl,my_sprite
call put_sprite

draw_sprite(a,43,hl)
; Generates the following:
ld a,a ; Rubbish!
ld b,43
ld hl,hl ; Not going to work...
call put_sprite

Brass, however, supports more intelligent macros - like this example shows. It will switch between a variety of different macros, depending on the arguments passed in.

Latenite is still in development - nothing particularily visible, barring the new tooltips and worrying familiar icons.

benryves

benryves

 

Business with the Z80

I have been pretty busy with Brass and Latenite over the last few days - Latenite has had a few little adjustments/improvements/fixes, but also has a few new holes in it which means that it is unsuitable for release. I'm adding the ability to hook help files to projects rather than each project being loaded with every help file - this has the extra bonus that Brass will be able to compile help files straight from the source, which will then be reloaded on each build by Latenite.

I did something unheard of over the weekend as well - I read the SDK manual for the TI-83 Plus. Mainly because I was trying to work out how some of the function calls worked, but also the stuff they talk about with regards to applications (as opposed to programs - applications fill multiple 16KB pages of ROM on the calculator, programs are variable sizes and are copied to RAM location $9D93 for execution) was pretty interesting - and it sounds pretty nightmarish! I'll download some of the application development tools, see if I can puzzle them out and then try and recreate their behaviour in Brass. It's yet another output format - Brass can, with the .binarymode directive, switch between a number of binary output formats, including TI calculator formats (TI-73, TI-82, TI-83, TI-83+, TI-85, TI-86), raw binary and a number of hex text files (Intel, Intel word address, MOS technology and Motorola).

I'd never really understood how the LCD display driver worked on the TI-83 Plus, and the SDK documentation was pretty useful - even though I still can't quite work out why the X axis goes from top-to-bottom and the Y axis goes from left-to-right. It turns out direct access to the LCD can produce some very fast results... (and the world's worst greyscale):


Download binaries for TI-83/TI-83 Plus [Ion/MirageOS]

Yes, yes, I have a little 'something' about 3D tunnels.

benryves

benryves

 

It lives!

A public release of Brass has been released... it can assemble some rather nasty Z80 code (TASM macro-abusing code, that is!) fine. Hopefully people can start finding the parse errors, or just complain at the horrible colour scheme of the manual.

And why did nobody tell me you could access the LSB/MSB of IX/IY through IXL/IXH/IYL/IYH before?

@kwijibo: You can use a Latenite beta if you take this file then unzip this file on top of it. It is almost entirely data driven - all Z80 registers (that I knew about) and flags (likewise) are hard-coded (but not Z80 instructions).

benryves

benryves

 

BRASS

One logical step up for Latenite is to not have to be bundled with a 3rd party assembler.
Mainly because one (high profile!) user is having real problems getting the buggy TASM to work with it, and other people have expressed annoyance with TASM, (limited string length, limited label length, limited numbers of DBs, lack of features, buffer overflow crashes... the usual) I have decided that I need to write an assembler (BRASS).
WLA-DX is very nice but is geared towards console development and burning programs on ROMs, and defining a ROM bank map for a TI-83 Plus program is a bit silly. I will try and keep it as compatible with TASM as possible, so old TASM-geared code still compiles, but will add some new features.


(BRASS at the top, TASM at the bottom - not that it actually matters!)

A lot of work still needs to be done - supporting ZIDX/ZIX/ZBIT instructions (anything like BIT *,A or LD A,(IX*)) is a pretty important one, for starters. The expression parser (for things like "_label+21" needs to actually parse expressions (all it does at the moment is check for and substitute in labels or numbers). Directives for conditionals and macros (and macros themselves!) need to be worked in.

Currently supported directives: .org, .include (#include maps to .include), .locallabelchar and .module.

.include could do with some work, this will compile (or rather, it won't ever get into pass 2):

File: test.asm
.include "test.asm"

I also need ideas for directives. Some are already in my mind (.dbsin, .include_once (like PHP's require_once()), .incbin)... What would you implement if you were writing a Z80 assembler?

benryves

benryves

 

I hate syntax highlighting.

I spent this weekend completely rewriting the syntax highlighting control - this time, from scratch. It seems that all the free ones are rubbish. [sad] Mine is rubbish, but at least I can maintain it. [wink] Now all I have to do is wait for the people using the beta to start complaining that it's eating their source code...

My test project for it was a Lotus-like 3D racing engine.



I think I need to research the way it works a little better... still, not too bad for a little over 400 bytes.

benryves

benryves

 

Themes!

Not the www2 theme though - a couple of XP theme glitches.

First up is the ListView control in .NET 2. If you switch on gridlines and scroll with the arrows on the scrollbar, it leaves the gridlines behind as nasty artefacts. This is what I came up with - and it appears to work.

Create a new class that inherits from ListView. Then add to it this code:

///
/// Hacky override to fix the garbage lines.
///
protected override void WndProc(ref Message m) {
if (m.Msg == 0xF) { // 0xF is WM_PAINT
GridLines = false;
GridLines = true;
}
base.WndProc(ref m);
}

...compile and that new class should appear in your toolbox. Use that instead of the normal ListView control.

If anyone could come up with a better solution, I'd love to hear it...

The other theme bug appeared last night in XP.


Click for big.

I guess it can't remember if it's using Windows Classic or Windows XP styling?

benryves

benryves

 

Latenite

Latenite - looking more and more like a VS ripoff. [grin]


benryves

benryves

 

Localisation

Typing



One reason you'd want to connect a keyboard to your TI - or indeed any device - would be to type on it. After all, it offers a nice comfortable layout to type quickly on...

One easy way to deal with the keyboard as a typing device would be to write a function that converts the scancode into an ASCII code (or a special code in the 128->255 range for keys without sensible ASCII equivalents, such as the Windows or cursor keys). But here's the problem - not all keyboards have the same characters printed on the keys. A UK layout will be QWERTY, a French layout will be AZERTY and a German layout will be QWERTZ. A US keyboard will have @ over the 2, whereas my keyboard has " over the 2.

The way I get decided to get around this was a fairly inelegant (but simple) one - assume the calculator has a keyboard localisation file on it. A function call I have provided called keyi_load_table will attempt to find and load the keyboard layout definition file you can create (returning an error code if it can't find any). Any software that uses these routines can therefore instantly tailor themselves to match the keyboard layout of the individual user. The files themselves aren't very big - it contains two mappings for the entire keyboard (normal and shifted), a table of which keys Caps Lock affects (26 bytes - A->Z on an English keyboard) and a final table for alternate codes for when Num Lock is pressed.

With this all put together, a single call to keyi_translate will convert a scancode in the accumulator to the ASCII/special code, automatically adjusting for Shift/Caps Lock/Num Lock. You can even call the function keyi_check_printable to see if the code is in the printable character range, or keyi_get_status_icon to get a character code for a suitable cursor - no special status returns , shift returns ?, caps lock returns A and caps lock+shift returns a.

benryves

benryves

 

Getting it to work

Reliability

I'm sure you wouldn't want to use a keyboard that was inaccurate. Unfortunately, (as you might have seen me moaning in the past) the keyboard generates the clock signal. This basically means you need to be constantly checking the clock line to see if it goes low - that or use a hardware interrupt the jumps in and receives the scancode packet when the keyboard decides you want to stop sending something.

Well, I don't have the luxury of an extra keyboard chip or a hardware interrupt, so I needed to don my thinking cap. I had thought that one possible way around this would be to send a "disable" command to the keyboard after receiving bytes, running my handler code, then sending an "enable" command - these commands clear the keyboard's buffer, unfortunately, which is not good.

I downloaded another document on the AT protocol to see if they mentioned anything useful, and lo and behold:

The host may inhibit communication at any time by pulling the Clock line low for at least 100 microseconds.

I think I can spare 100us - I added the line ld a,1 \ out (bport),a to the end of my buffer-filling code from earlier (it fills a scancode key buffer) - and the code doesn't drop a single scancode. Result!

Providing useful functionality

All these keyboard routines do at the moment is to display the data coming in on the screen - not exactly a great use of them. What is really needed is a simple two-way handler - it calls two different user-defined routines based on what a key is doing, whether it is being pushed down or released.

For this, I should translate the scancodes into a new format - there are less than 256 keys, so there's no reason why I can't fit every single key into a byte.

As well as user-defined keyboard events, I'll have to add my own to handle toggling the status of the keyboard - the num/caps/scroll lock as well as the shift/alt/ctrl.

It's really quite simple to do. ; Now we need to run through all the keyboard events!

ld ix,_buffer ; Start at the beginning of the buffer.

_handle_next_scancode:

; Let's assume it's a normal key:

ld hl,_scancode_lut
ld bc,_scancode_lut_end-_scancode_lut

_key_down_handler:
ld de,_null_handler


ld a,(ix+0)
or a
jr z,_handled_all_keys

cp at_scs_enhance ; Is it an enhanced key?
jr nz,_not_enhanced

inc ix ; We know it's enhanced, so move along.
ld a,(ix+0)
or a
jr z,_handled_all_keys
ld hl,_scancode_e_lut
ld bc,_scancode_e_lut_end-_scancode_e_lut
_not_enhanced:

; Now HL points to our LUT, BC is the correct length
; and IX points to the next byte.

cp at_scs_keyup ; Is it a key UP event?
jr nz,_not_keyup

inc ix
ld a,(ix+0)
or a
jr z,_handled_all_keys
_key_up_handler:
ld de,_null_handler

_not_keyup:

; Move to next chunk before we do anything
inc ix
; At this point, A=scancode, HL->translation table, DE->handler, BC=table size, IX->next scancode.

; We now need to run the translation.

cpir ; Simple as that!

jr nz,_handle_next_scancode ; Not found

; So HL->scancode+1
; Here is where the magic happens:

push de
ld de,0-_scancode_lut
add hl,de
ld a,l
pop de

; Now, A = 'real' scancode.

; We need to 'call' DE.
; We can spoof this easily:

ld hl,_handle_next_scancode
push hl ; _h_n_s is on TOP of the stack.
push de ; our event handler is on top of the stack;
ret ; POP back off stack and jump to it.
; When the handler RETs it'll pop off _h_n_s and carry on scanning!


_handled_all_keys:

_null_handler:
ret
Basically, I just run through all the received bytes. I check for $E0 (if so, switch to the alternate scancode conversion table for the extended codes) then $F0 (if so, load the alternate handler for a key up event rather than a key down event) then look up the scancode on the selected code table.

I created a couple of very basic event handlers - the key down event displays ? followed by the adjusted key code, the key up event displays ? followed by the adjusted key code.



What would be ideal would be to provide internal event handlers that could be called on keyup/keydown which would then jump over to the user's custom handler. These event handlers could look for special keys and adjust the keyboard LEDs and set internal flags that could be used to detect the status of certain keys. I'd need:

Num Lock Caps Lock Scroll Lock Shift Ctrl Alt
Unfortunately, I think that there is a problem with my byte-sending code. Setting the keyboard LEDs starts to do strange things - I now have two options;

After switching status, ignore the LEDs. The status flag is set correctly internally, but you can't see it on the keyboard (which is a bit pants). Update the status flags every single time we run through the loop to check for any new bytes (which makes the keyboard lag like crazy - there's up to half a second of buffering going on!)
Mixing-and-matching the two - rewriting the branch before we bring the clock high again (for maximum speed) confuses the keyboard - the status LEDs never change and it decides to disable itself in a strop until I send $FF (the reset command) again. I think it's time to revisit the at_send_byte routine again to see what it's doing wrong!

Well, comparing it to my new notes - it's actually completely wrong at the end, when it comes to sending the parity/stop/ACK bits! A quick rewrite to how I think it should go isn't too hopeful - the keyboard LEDs flash like mad. Tweaking the timing by throwing in a few calls to _wait_bit_low and _wait_bit_high to synchronise my data to the clock stop this completely - and now the code is as it was before, at 100% accuracy - but about twice as fast.

Replacing my branch code still doesn't work all the time - sometimes the LEDs change, sometimes they do not. Not believing it would work, I threw in a check for the ACK bytes returned - if they were $FE, the 'repeat last command' byte, I'd send again.

My routines were clearly not as broken as I thought - the keyboard LEDs now change status perfectly, and as much as I hammer the Num Lock, Caps Lock and Scroll Lock keys, I cannot lock up the program or get the keyboard LEDs to display the wrong value. Not to mention that keying in other keys is back to the lightning fast response they used to be...

benryves

benryves

 

TI Keyboard

Once again, I waste my time doing something remarkably useless - it's trying to connect a keyboard to my TI graphing calculator!




Idea

Alas, this is not an original idea - I had seen some work done on this in the past, and tried fiddling with the code myself, not really understanding much of it. I decided to have a go - from scratch, and on my own.


Constructing the hardware



I decided to butcher an old, suitably discoloured keyboard (in a nice way, though, so if all went wrong I could reassemble it). I don't have a soldering iron, nor any sort of decent cabling or plugs so I planned to just cut and strip the wires inside the keyboard and attach (by twisting together and large amounts of Sellotape) a power cable and 2.5mm stereo minijack plug (the TI has a 2.5mm stereo minijack socket on it as a data port).



I'm not sure they put enough screws into this thing... (by contrast, my main keyboard has a whopping 2 screws in it - this keyboard's designer was clearly paid by the screw!)



Finally (and with a slightly sore wrist), I get to the bit I need - the keyboard controller board. The circuit board has the four leads soldered straight into it - I'd been hoping for the more typical sight of a block that the cable plugs into, but unfortunately it looks like I'll be cutting wires and hoping not to snap them off by accident. The four wires have been labelled VCDO - I'll cross my fingers and assume these stand for VCC (+5V), Clock, Data and 0V.





Out it comes, the wires are stripped and reattached. I then tested all the leads using my multimeter and created some stunning ASCII art to remind me what went where:

HARDWARE SCHEMATIC:

PS/2 SOCKET: TI CONNECTOR:

6.-++-.5 4: VCC >-[+5V] _
/o || o\ 5: Clock >--- | ++ | 1: Data >---4|o o|3 3: Gnd >-+- \ / __|__ |_|
\o__o/ --- 0V / \.
2 1 ' \___/===> To TI
^ Note that this is the SOCKET view
and not the plug view for a PS/2 port.



With that done, I taped down the connections and screwed the whole thing back together. Tapping a 9V PP3 battery to the power leads makes the keyboard boot up; doesn't look like it's broken quite yet!


Writing the code

What with the hardware now hopefully complete, it's a simple case of writing the code to support the keyboard. *cough cough*

Basically, I need to write an implementation of the AT protocol. The protocol is fairly simple, but unfortunately the keyboard generates the clock signal for us (the TI doesn't have accurate timing at all - it's an RC circuit, and so the clock rate drops as the batteries go flat). Let's hope the TI can keep up!

I guess the easiest code to write would be code that sets the output high (as it's an open collector circuit, high is the default level - either side can pull this low and hold it there) then poll the clock and wait for it to drop then go back up again. The clock line maps to bit 0 of the link port, and the data line to bit 1 of the link port. The TI's data IO port can be controlled using these two lower two bits of the hardware port equated as 'bport' (port 0) in ti83plus.inc

Here's what I tried;
init_all:
; Set link port high
ld a,%00000011
out (bport),a


wait_bit_low:
in a,(bport) ; Read in the status of the bport.
and %00000001 ; Mask out bit 0 (clock)
jr nz,wait_bit_low ; Is it non-zero (high?) If so, loop back.

wait_bit_high:
in a,(bport) ; Read in the status of the bport.
and %00000001 ; Mask out bit 0 (clock)
jr z,wait_bit_high ; Is it zero (loop?) If so, loop back.

ret ; Break out of the program
Strange, whatever I do - init the keyboard, tap keys, nothing happens. The program goes into an endless loop. Using the 9V to manually set the line low then high again, I realise something - I keep forgetting that the port works backwards, and that writing %00000011 sets the status to %00000000 - and when a line is held low by either device, it can't be brought up again very easily. Replacing the offending line with xor a to clear it to zero worked a treat - pressing any key on the keyboard exits the program.
Each "packet" on the AT protocol (it's a bit grand to call it that) is made up of 11 bits - 1 start bit, 8 data bits and 2 parity/stop bits. A djnz loop to get the 8 bits and a handful of rotate instructions to populate the result byte gives me this:

.module AT_Protocol

at_timeout = 255

at_get_byte:
; Clear Link port
xor a
out (bport),a

; Get the start bit:

call _wait_bit_low
call _wait_bit_high


; Now we need to get the 8 bits for the byte

; Reset the output byte
ld c,0

ld b,8

_get_byte_loop:

call _wait_bit_low

; Now we get the bit itself
in a,(bport)
rrca
rrca
rr c

call _wait_bit_high

djnz _get_byte_loop

; Get the parity/stop bits

call _wait_bit_low
call _wait_bit_high
call _wait_bit_low
call _wait_bit_high

; Clear flags, load code into accumulator and exit
xor a
ld a,c
ret

_get_byte_fail:
; Set nz to indicate failure, return.
or 1
ret


_wait_bit_low:
push bc
ld b,at_timeout
_wait_bit_low_loop:
in a,(bport)
and 1
jr z,_waited_bit_low
djnz _wait_bit_low_loop
pop bc
pop bc
jr _get_byte_fail
_waited_bit_low:
pop bc
ret

_wait_bit_high:
push bc
ld b,at_timeout
_wait_bit_high_loop:
in a,(bport)
and 1
jr nz,_waited_bit_high
djnz _wait_bit_high_loop
pop bc
pop bc
jr _get_byte_fail
_waited_bit_high:
pop bc
ret



Not too tricky at all! Amazingly, this code ran first time too. (Amazingly for me, that is). The test program just reveices a byte from the keyboard and displays it on the screen.



One minor problem is that sometimes the code received differs by a bit to what it should (you can see this by holding down a key and noting how sometimes the code is different - I've written a short program that just displays the code on-screen when it's received). Consulting my AT protocol notes, I find that "After the clock line goes low a 5-microsecond pause is used so that the data line has time to latch." Maybe my pause isn't long enough?

Sadly, that did quite the opposite - the results are even more unpredictable. I guess it would be more better to try speeding up my code, rather than slowing it down..?

After increasing the speed a little (without unrolling all the loops, that is) the routines are (by and large) slightly more accurate. Still not perfect, but they'll do for the time being. If I press a key the release it just before it repeats, the accuracy is 100% perfect - I suspect that the problem is that in the time the rest of my program has drawn the last keycode, the keyboard has pottered away and tried to output another byte and I'm jumping in half way through. I guess I'll have to write some clever buffering code to handle that!


Interrupts

I thought that an ideal way to handle the timing/speed problem was to create a piece of code that could be loaded as a sort of driver. The calculator would be set up into interrupt mode 2 and would call the driver 100-or-so times a second. The code could then try to see if a byte was coming in, and if so it would add it to a buffer. A routine isolated from the rest of the code could then read a byte from the buffer and shift all the other items down to replace it - a sort of FIFO stack.

The Z80 has three interrupt modes; 0, 1 and 2. Interrupt mode 0 is pretty useless to us; interrupt mode 1 is the normal mode of operation. In this mode, the CPU pushes the program counter to the stack then jumps to memory location $38 every time an interrupt occurs. The interrupt handler then swaps the main CPU registers away with their shadow register pairs, does something, then swaps the CPU registers back again. Finally, you pop the old program counter off the stack and jump back to where you were. You could think of it as having a second thread running, only a lot less hi-tech and more restrictive.

Interrupt mode 2 is a stranger beast. The main difference is that it doesn't just jump to $38 - it creates a 16-bit address using the register I as the most significant byte and a byte off the data bus as the least significant byte - effectively, we have a 16-bit number made up of i*256+?. The CPU then loads the value in the memory location pointed to by this 16-bit value, then calls this address.

What does this mean for us? Well, it means that rather relying on the interrupt at $38 we can load our own interrupt into memory!

We need to do three things: Create a 257-byte lookup table aligned to a 256-byte boundary for the CPU to read from after it has build up the 16-bit address. Set the I register to the most significant byte of the start address of our lookup table. Copy our interrupt handler to the location our table points to, switch the interrupt mode to two and enable interrupts.

The way I've done this is: Filled memory locations $8700 to $8800 (inclusive) with the byte $86. Loaded $87 into the I register. Copied my interrupt handler to $8686

My interrupt handler at $8686 is a simple jp instruction to jump back to my program for the sake of practicality.

Unfortunately, this approach doesn't work (and I tried a lot of different ways to get it to!) One reason for failure is that in im 2, the main interrupt handler at $38 isn't getting executed. The TIOS relies pretty heavily on this interrupt to work; most functions cause a pretty nasty crash or do other strange things. Fine, I say to myself, and replace my reti call at the end of my interrupt handler with a jp $38 to manually call the TIOS interrupt. The behaviour gets even stranger - calling ionFastCopy (a function, non-TIOS related, to copy the display buffer to the LCD) causes strange rippling effects to appear on the LCD, followed by a full-out crash when I finally quit the program.

On top of all this, the few times I can get a display of the key buffer I can see that it's not updating very frequently... The interrupt is not checking the port frequently. All this for nothing!

As far as I can see it, the only way for an interrupt-based technique to work would be for the TI to have a hardware interrupt - using the keyboard's clock connected to the CPU, so that whenever the clock goes low the TI could spring into action and receive the byte. Seeing as the only access to the CPU I have without invalidating my warranty is via the data port, I'm a bit stuck.


Back to square one

I guess the only way is to agressively poll the port... First up, I rewrote the code so that instead of displaying a decimal version of the code, I'd display a hexadecimal version - significantly easier to read, faster to convert. I then painstakingly noted down every key's scancode from this into a useful include file.

One problem with the original TI keyboard project was that it had problems with input; it would occasionally forget about shift being pressed, or accidentally repeat keys. I think I now know why...

On an AT protocol keyboard, scancodes are sent every time a key is pressed and again when the key is released. To differentiate between the two different actions, when a key is released the scancode is preceded by the special code $F0.

I reckon that the problem was that the function to get a byte would have been called, followed almost immediately with the code to translate/display it. What I intend to do instead is to get bytes in a loop and add them to a buffer until either the routine times out (no more bytes being sent) or the buffer is full (shouldn't happen!)



As you can see, this system works. The above codes are special ones generated by pressing some of the extended keys (the cursor keys) - they send the code $E0 followed by the key itself. Some of the codes are downright silly - PrintScreen sends the command string E0F07CE0F012 when released - Pause sends E11477E1F014F077!

Infuriatingly... this is STILL not perfect! I'm still losing some bytes. What to do - if only there was a way to control the keyboard, to stop it from scanning... wait a minute...


Controlling the Keyboard

Sending a byte is not too different from receiving a byte - you hold the clock and data lines low, then just the data low, then write out the bits as the keyboard requests them on clock. The easiest code to transmit is $FF - keyboard reset. According to my notes, the keyboard should respond with the power-on self-test byte as well as resetting. Lo and behold, the keyboard lights flash and I get $AA back - the self test has passed. I also get $E8 back, and my notes don't mention $E8 anywhere, but I'll ignore that for a minute and bask in the glory of it otherwise working perfectly.

The next code I try is $EE. This is the echo code - in theory, the keyboard should send back $EE. Sadly, it doesn't. Damn. It just resets and sends back $AA, though it sometimes sends back $B8 as well.

On close inspection, it seems pretty obvious what I've done wrong - I'm completely neglecting to send the parity and stop bits. Oops. After adding them, the keyboard responds $EE to my $EE - which is quite correct! The parity is hard coded, so I'm glad it works.

Trying another code, $F2, gets the keyboard to spit back an unfriendly $FE - which translates as "resend, you idiot". $EE is %11101110, which contains 6 set bits. $F2 is %11110010 which contains 5 set bits - the parity needs to be reversed. Hopefully calculating the parity shouldn't consume too many clock cycles - after I extract the bit to send, I need to add it to another counter. I can then use the lowest bit of this counter as my parity bit.

Thankfully, I have sufficient time to calculate the parity - the keyboard now responds FAAB83. $FA is the ACK code, AB83 are the ID bytes. Scarily, my notes say that the ID bytes are $83 then $AB - I'm going to hope that this is an error in the notes - I doubt my routines are going to be able to mix up whole bytes!

Now for the main reason you'd want to send codes to the keyboard - to flash the LEDs, of course! The code is $ED - the keyboard should respond with an ACK ($FA), to which I send the status byte (the lower 3 bits control the LEDs). The code is pretty simple (albeit without any checking on the ACK):
ld a,$ED
call at_send_byte ; Command
call at_get_byte ; ACK
ld a,%00000101 ; Caps Lock and Scroll Lock on
call at_send_byte ; Command
call at_get_byte ; ACK



Ace. I'll add the equates for the various commands to my include file. No doubt I can get some more done with this project tomorrow...

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

 

Latenite, VS 2005 and two screens.

Long time no update...
I haven't done any more work on FireTrack, that is now officially a dead project. [sad]

Rearranged PC

I spent some part of this weekend rearranging my PC so I could take advantage of my DVI port. With a DVI->VGA adapter I could connect up the other 17" monitor that was currently going unused on my PS2. I also had a new second-hand (new to me, that is) VCR to throw into the mix - here are the results! (Hover for notes).





Once again, I'm astounded as to how expensive cables are on the high street (last time it was a USB cable that threw me - GBP14 in Dixons, I ended up getting it for 99p on eBay). To connect the VCR to the VGA box I needed to convert the only output it gave me, SCART, to something usable - S-Video or composite. The only cable Dixons sold that converted SCART to composite out cost a whopping GBP39.99... no thanks! Maplin wasn't so bad, a two-way (switchable) SCART->S-Video/composite was only a tenner. Only thing left for me to get is a new VGA cable from my PC to the VGA box - my current cable is thinner than most serial cables I have! (In the photo of the VGA box, it's the one on the left) Needless to say, the image is very fuzzy and there is serious ghosting.

Visual Studio 2005

I recently received the Beta 2 in the post, and I'm (generally) highly impressed. However, I've run into a number of oddities along the way, not to mention some infuriating bugs. The worst is the image resource editor for adding images to menus and the like. Add a couple of images, set the image on a menu item and watch as it erases all your menus, adds them all back again before crashing with the error message:

...at which point the IDE closes.
Another oddity is that after such a crash my program started crashing when it closed. The error was in this code (added by the form designer);
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}

base.Dispose(disposing); was causing the error "Cannot access a disposed object. Object name: 'SplitterPanel'." I have no idea what would cause that, or which object "SplitterPanel" is at all. Changing the problematic line to this.Dispose(disposing); fixed the problem, but if anyone could shed light onto why this code was failing I'd be very grateful!

My only other major beef with VS2005 is that while most of the visual style bugs have been ironed out, some still remain. The worst one for me is the TabControl, as aligning the tab buttons to anything other than top results in some very odd results. Are MS ever going to decide on a consistent theme? [rolleyes]

Latenite

Latenite is back in redevelopment - hopefully this version will be released! I have completely rewritten it from scratch in C# (previously in VB.NET) - for one weekend's work, it's not looking too shabby.


Click for bigger.

Yes, it looks like a direct rip-off of VS, but is geared towards Z80 editing. I'm sticking to making it look/work like a simplified VS, as that seems to be the best way for things to work (after all, most image editing apps feel like variations on a theme of Photoshop). Some new features:

Fully XML-based help system makes help files considerably more manageable. HTML-based help viewer (rather than RTF) allows for better presentation and slicker (working!) hyperlinks. Error logs are also returned as XML for neatness. Compilation is now based around a bunch of sensible environment variables (the old system used a cryptic set of command-line arguments - %1 to %5). For example, this is the script for generic TI compilation:
CD %SOURCE_DIR%
SET PATH=%PATH%;"%COMPILE_DIR%\ti"
TASM -80 -i -b "%SOURCE_PATH%" "%SOURCE_FILE_NOEXT%.bin" | TASMERR > "%ERROR_LOG%"
devpac8%1 "%SOURCE_FILE_NOEXT%"
DEL "%SOURCE_FILE_NOEXT%.bin" Ability to undo more than one edit and redo again! - This is a big one! (A new class that just adds to the ) Sexy icon laden menus. If you create a compile script and save a PNG in the same folder with the same filename (so ZX Spectrum.cmd with a picture of a speccy as ZX Spectrum.png) it appears on the build menu. Faster and more accurate line selection.
There is still a lot (of very dull stuff, no less) to do - project organisation, settings saving/loading, text search... Bah. One day!

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

 

Scripted attacks

Attack patterns can now be simply scripted as a list of 4-byte chunks, covering which enemy to use, the delay between adding them, how many of them to add, the delay between them and a delay after adding the last one.
For example, this:
.db $04,16,8,100
...would add 8 enemies of type $04, adding one every 16 game ticks and then pausing for 100 game ticks after adding the last one then progressing to the next scripted enemy.
To support this, I've added some new per-level parameters, covering: Attack type (random or 'scripted') with delay between enemies or a pointer to script to follow. Speed of enemies. Speed of landscape scrolling (used very little - bonus levels with no enemies/mines scroll past extra-quickly). Maximum number of mines. Using all the above, I can easily configure each level's attack patterns quite simply.
Doing this has identified a number of bugs (mostly where new enemies were being initialised without clearing out a particular byte, which means that certain sequences would start in odd places) which have now been ironed out.

I have also picked up work again on my music system for the game. I have a very bad 12-bar-blues demo running with it - I need to find a decent pitch-to-period table as the one I calculated in Excel sounds slightly wrong. There are also some minor-ish reset bugs (the first time in-game a note is played the instrument is full volume for one frame plus some minor synch issues). Looks like I'll have to write the music in Notepad, though - who'd have thought that getting low-level access to the sound card was so bloody difficult in anything other than C (and I'm damned if I'm going to have to write a GUI system for the Windows console, and Win32 is too mucky to deal with for such as simple application). This is great fun, as you can imagine - take, for example, this: (the 12-bar-blues demo for the testbed for the music system)

; Demo tune

demo_tune:

; Instrument table:

.db 2 ; Number of instruments
.dw simple
.dw vib

; Sequence table:

.db 6 ; Number of sequences
.dw run_c
.dw run_f
.dw run_g
.dw bass_c
.dw bass_f
.dw bass_g

; Tune!

.db %00000000, %00000000
.db %00000011, %00000001
.db %10000000, %10001000

.db %00000000, %00000000
.db %10000000, %10001000

.db %00000001, %00000000
.db %00000100, %00000001
.db %10000000, %10001000

.db %00000000, %00000000
.db %00000011, %00000001
.db %10000000, %10001000

.db %00000010, %00000000
.db %00000101, %00000001
.db %10000000, %10001000

.db %00000001, %00000000
.db %00000100, %00000001
.db %10000000, %10001000

.db %00000000, %00000000
.db %00000011, %00000001
.db %10000000, %10001000

.db %00000000, %00000000
.db %10000000, %10001000

.db %11111111

; Sequences:

run_c:
.db 1

.db (144>>8)+%01000000
.db (144&%11111111)
.db $10

.db (112>>8)+%01000000
.db (112&%11111111)
.db $10

.db (93>>8)+%01000000
.db (93&%11111111)
.db $10

.db (82>>8)+%01000000
.db (82&%11111111)
.db $10

.db (77>>8)+%01000000
.db (77&%11111111)
.db $10

.db (82>>8)+%01000000
.db (82&%11111111)
.db $10

.db (93>>8)+%01000000
.db (93&%11111111)
.db $10

.db (112>>8)+%01000000
.db (112&%11111111)
.db $10

.db %11000000

run_f:
.db 1

.db (105>>8)+%01000000
.db (105&%11111111)
.db $10

.db (82>>8)+%01000000
.db (82&%11111111)
.db $10

.db (68>>8)+%01000000
.db (68&%11111111)
.db $10

.db (60>>8)+%01000000
.db (60&%11111111)
.db $10

.db (56>>8)+%01000000
.db (56&%11111111)
.db $10

.db (60>>8)+%01000000
.db (60&%11111111)
.db $10

.db (68>>8)+%01000000
.db (68&%11111111)
.db $10

.db (82>>8)+%01000000
.db (82&%11111111)
.db $10

.db %11000000

run_g:

.db 1

.db (93>>8)+%01000000
.db (93&%11111111)
.db $10

.db (72>>8)+%01000000
.db (72&%11111111)
.db $10

.db (60>>8)+%01000000
.db (60&%11111111)
.db $10

.db (53>>8)+%01000000
.db (53&%11111111)
.db $10

.db (49>>8)+%01000000
.db (49&%11111111)
.db $10

.db (53>>8)+%01000000
.db (53&%11111111)
.db $10

.db (60>>8)+%01000000
.db (60&%11111111)
.db $10

.db (72>>8)+%01000000
.db (72&%11111111)
.db $10

.db %11000000


bass_c:
.db 0
.db (307>>8)+%01000000
.db (307&%11111111)
.db 8
.db (144>>8)+%01000000
.db (144&%11111111)
.db 8
.db %11111111

bass_f:
.db 0
.db (224>>8)+%01000000
.db (224&%11111111)
.db 8
.db (105>>8)+%01000000
.db (105&%11111111)
.db 8
.db %11111111

bass_g:
.db 0
.db (198>>8)+%01000000
.db (198&%11111111)
.db 8
.db (93>>8)+%01000000
.db (93&%11111111)
.db 8
.db %11111111

; Instruments:

simple:
.db 4 ; Length
.db 255
.db -64

.db 8
.db 0
.db 32

.db 1
.db 255
.db 0

.db 0

vib:

.db 2
.db 128
.db 50
.db 2
.db 128
.db -50
.db 2
.db 128
.db 50
.db 1
.db 128
.db -50
.db 1
.db 128
.db 50
.db 3
.db 128
.db -50

.db 10
.db 128
.db 14
.db 0


Thankfully, the assembler can handle me sticking sums in rather than hard-coded values in places making things a lot simpler... but it's still a bit mucky. Ah well. There is no noise channel set up, either, so no krch-krch-krch style beats from the white-noise generator as such.

benryves

benryves

 

Enemies Galore

I completely rewrote every single attack pattern from scratch (pretty much - the precalculated paths were left intact) last night. The result of having the ability to start any enemy attack pattern at any time (within reason - I only provide 8 enemy slots!) results in nothing short of sheer mayhem. Yes, collision detection was disabled for the video. I'm looking at you, evolutional... [razz]


Is that enough enemies, do you think? [AVI/MPEG-4/1.62MB]

The small blobs that drift towards me are the aforementioned mines. The attack frequency, number of enemies on-screen and number of mines is far in excess of anything that you'd encounter in the game - this was just a test to add as much to the game as possible.

Another plus-point of the rewrite is that the game now runs correctly inside MEKA, another widely-used Sega Master System emulator. It had issues with certain attacks keeping their sprites way off screen as they ran in, which worked fine on hardware and in Emukon, but not in Meka for some bizarre reason.

One particular running bug that has been very difficult to trace was an annoying flicker in the sprites. Every so often, some of the sprites would disappear for a single frame. Normally it would be a couple, but sometimes ALL the sprites would vanish.

What could it be? Video timing issues? No, Emukon (the emulator I'm using) pops up incorrectly timed VDP writes in a handy window. Enemy drawing code that was accessing another sprite's data, switching it off? In a similar vein, had I miscounted pointer increments in one particular chunk of code for handling enemies, so in certain conditions it would offset all further array writes by a byte, writing incorrect data into the "enemy disable" byte? After much painful analysis, it appears the routines were pretty watertight... Not PUSHing/POPping registers to the stack before calling a destructive routine?

Well, after much pulling out of hair I have found the problem.

The VDP (the graphics chip I'm controlling) has a certain amount of VRAM. One area in that VRAM is dedicated to a sprite table. First you have a list of Y-coordinates, then a list of X-coordinates followed by sprite index numbers. There is a "magic" Y coordinate, $D0. When the VDP hits this particular Y coordinate, it stops drawing sprites! You can (and I do) use this as a stop byte on the sprite table to tell the VDP when you've finished adding sprites (as each frame has a variable number of sprites on it - I'm trying to keep the game nice and dynamic).

So, what's the problem with that? It turns out that SOMETHING - star code, explosion code, bullet code, enemy attack code, I'm not sure which - is trying to draw sprites with a Y coordinate of $D0! Adding this block of code into my sprite drawing routines (I have two - one for 8x16 sprites, such as the stars, bullets and score digits and another that pairs them up for 16x16 sprites, the size of everything else) fixed the rare flicker bug.
cp $D0
jr nz,+
inc a
+:
Nice and simple, but it does the trick. [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!