• entries
    222
  • comments
    608
  • views
    588243

About this blog

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

Entries in this blog

benryves

Back in Classic Black

I've been working on a "classic black" style for the GameDev.net website for a little while. Being a creature of habit I could never get used to the lighter skins that the site has tended towards in recent years.

forum-index.png

topic-index.png

topic-post.png



GameDev.net is a large site and I haven't checked every page yet, so it is highly likely that you'll end up with some white text on white backgrounds or black text on black backgrounds. Unfortunately it seems that people are using inline colours more with the new system which I can't do much about through CSS, however !important I try to make the rule.

The stylesheet can be found here: classic-black.css. Firefox users can install Stylish and use benryves_classic-black-fixed-userstyle.css (many thanks to coldacid for this version and for fixing some bugs). I'm not sure how other browsers deal with user stylesheets (guides for IE indicated that the stylesheet would be loaded on every site, which isn't much use), I'm afraid, but in Opera you can right-click the page, select Edit site preferences and then select the stylesheet at the bottom of the Display tab.

For browsers that can't apply user stylesheets to specific websites, here's a bookmarklet that applies the stylesheet when clicked. Drag the link to your bookmarks, then visit a page on GameDev.net and click the link to apply the style. It's only temporary (and will be lost when you change page) but it is at least a preview.

If anyone tries this, I'd be happy to hear any feedback (especially bug reports!) I didn't write all of the CSS by hand; certain rules were set up in an INI file which is then "compiled" with a little command-line tool (usage: "CSS Compiler" classic-black.ini classic-black.css). The tool has no error handling whatsoever and will keel over if you so much as look at it funny but it sped up the process quite considerably.
benryves
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.

VGA monitor showing the output of the dsPIC33 VDC

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.

dsPIC33 VDC on a breadboard

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
One way of constructing solids is to use a method named constructive solid geometry (or CSG for short). I added two simple CSG operators - intersection and subtraction - that both take two surfaces and return the result.


In the above image, the surface that makes up the ceiling is created by subtracting a sphere from a plane. Of course, much more interesting examples can be created. For example, here is the surface created by taking a sphere and subtracting three cylinders from it (each cylinder points directly along an axis).


One problem with the camera implementation was that it couldn't be rotated. To try and aid this, I used a spherical to cartesian conversion to generate the rays - which has the side-effect of images with "fisheye" distortion.


The above left image also demonstrates a small amount of refraction - something that I've not got working properly - through the surface. The above right image is the result of the intersection of three cylinders aligned along the x,y and z axes.

To try and combat the fisheye distortion, I hacked together a simple matrix structure that could be used to rotate the vectors generated by the earlier linear projection. The result looks a little less distorted!


The final addition to the raytracer before calling it a day was a spotlight. The spotlight has an origin, like the existing point light - but it adds a direction (in which it points) and an angle to specify how wide the beam is. In fact, there are two angles - if you're inside the inner one, you're fully lit; if you're outside the outer one, you're completely in the dark; if you're between the two then the light's intensity is blended accordingly.


In the above screenshot, a spotlight is shining down and backwards away from the camera towards a grid of spheres.

If you're interested in an extremely slow, buggy, and generally badly written raytracer, I've uploaded the C# 3 source and project file. The majority of the maths code has been pinched and hacked about from various sites on the internet, and there are no performance optimisations in place. I do not plan on taking this project any further.


Building and running the project will result in the above image, though you may well wish to put the kettle on and make yourself a cup of tea whilst it's working. [smile]
benryves
Assembling a circuit on breadboard is a good way to experiment with electronics, but the result is not something you could really use - it's bulky, fragile and awkward to set up. It's far nicer to solder the components of the circuit together to form a more permanent device and put it in a enclosure to make it robust. This is not something I'm especially good at, but something I thought I'd try with the VGA line blanker and LCD shutter glasses controller I've been experimenting with recently.

VGA line blanker and LCD shutter glasses controller

In the past I've struggled along with a hand drill and the nail file on a Swiss Army knife, but have more recently acquired a high-speed rotary tool and an assortment of attachments which make things much easier. I took some photos when building this project, which I've documented below; I'm not sure my techniques are very efficient, but I do get there in the end. I'd be very glad to hear any advice anyone has!

A plain project box Back of the project box marked for cutting

I started with a plain project box. Having planned roughly where I was going to put the VGA ports and DC power socket, I covered one side of the box in masking tape and drew on where I was going to put the holes.

VGA socket holes drilled out VGA socket holes roughly cut

To cut straight-edged holes, such as those required for a D-subminiature connector, I drill a hole in each corner and use a small cylindrical burr to cut between the holes. This leaves a very rough edge, but is a good start.

Upper VGA socket hole widened sufficiently to accomodate a VGA connector

I then widen the hole using a large cylindrical burr and a needle file until the part I'm attempting to mount fits snugly.

Holes for the jack post marked Both VGA socket holes widened, with holes for the jack posts drilled

When I had both VGA connectors in place, I marked and drilled the holes for the jack posts that the VGA leads will screw into. Neither hole is especially neatly cut, but the D-subminiature connector overlaps the hole sufficiently to hide any shoddy workmanship.

Hole for the power socket drilled All of the sockets installed in the back of the project box

The last part of the back is the DC power socket. As I don't have a drill bit large enough to cut the hole on its own, I drill it as large as I can then widen it using the cylindrical burrs mentioned before. With all of the holes cut, I inserted the components to see how they look and identified one problem - I'd underestimated how fat the connectors on the end of VGA leads are. Fortunately, I have a slim VGA cable that fits, but a regular sized one does not - in future I'll need to remember to put the VGA connectors further apart!

Hole for the LCD glasses socket cut 3.5mm stereo jack socket for the LCD glasses installed

With that mistake fresh in my mind, I thought I'd move onto something a bit more difficult to get wrong - the 3.5mm stereo jack on the front of the box to plug the glasses into. This is just another round hole, cut in the same way as the DC power socket.

Holes for the control switches marked Holes for the control switches cut

The two control switches on the top of the box require much larger holes. These were cut in the same way as before - a small hole is gradually widened by using a cylindrical burr. This is a very tedious job, not helped by having to keep stopping to clean the melted plastic that adheres to the burr.

Control switches installed

Finally, the switches were installed. I was originally going to use latching push buttons, but had previously used those nice round rocker switches as the power switch on the AVR TV Game project so opted to use them instead.

Stripboard cut to fit Stripboard installed in enclosure

The final bit of physical work was to cut some stripboard down to size to fit inside the enclosure. These were cut by first scoring along the tracks where the cut was to be made, then snapping the board over the edge of a table. This results in a clean break, but to ensure a snug fit the boards were tidied up with a sanding drum. The lid (or, in my case, base) of the enclosure has a raised edge that fits inside the box, so the sanding drum was also used to remove two of the corners of the stripboard pieces to allow the base to fit.

Preliminary tracks cut in stripboard

The next stage was to move onto the electronics, and I started with the circuit board that was to host the video amplifier IC, voltage regulator and Schmitt trigger on vsync/hsync. The video amplifier is attached to a TSSOP14 adaptor that has a D-shaped pin configuration, with two rows of four pins and two rows of three pins. Having cut through the tracks in the stripboard to mount the amplifier, I needed to find some suitable pin sockets.

8-way pin socket cut in two 8-way pin socket cut in two and neatened up

As I don't have any pin sockets with just three pins in them (only two, four and eight) I cut two eight-way pin sockets in two with a pair of wire cutters then tidied up the ragged edges with a sanding drum and needle file.

Video amplifier socket soldered in place

With the pin sockets soldered in place you can see the D shape I mentioned above. I don't generally plan stripboard circuits very thoroughly, preferring to start by placing large components in approximately the right location with respect to where the external connectors are and how they need to relate to other components. Once those are in place I add smaller components (such as discrete resistors or capacitors) before finishing by adding the wire links to connect all of the parts together. This does lead to situations where I wish that I'd placed a component one hole along to give myself more space or to avoid having to insert so many wire links, but it generally works.

Stripboard with cuts between holes Video amplifier resistors in place

With the video amplifier in position, I added the resistors that are required on its inputs and outputs. To keep the circuit reasonably compact I cut through stripboard tracks between the holes using a conical HSS burr with a small tip - this is an especially useful tool when you need to deal with double-row pin sockets

Power supply support components Socket for Schmitt trigger IC and pin headers for vsync/hsync jumpers

I then added the support circuitry for the voltage regulator (smoothing capacitors and a rectifier diode to protect the circuit if the polarity of the power supply is incorrect) and a socket for the Schmitt trigger IC. I find the easiest way to keep components in place on any sort of through-hole board is to tape them down firmly with masking tape before soldering - bending the legs out makes the parts much harder to remove if you make a mistake. Blu-Tack is easier to use but has a habit of melting when soldering and leaving an unpleasant blue residue on your circuit, so I'd advise against it! To make this part of the circuit slightly more future-proof a pair of jumpers are used to connect the sync lines (vsync and hsync) from the VGA input and VGA output together. These could be removed if I decided to change the logic board to override these signals - for example, as part of a sync-doubler, which injects a vsync pulse half way down the screen.

Connector between the video amplifier circuit board and the rest of the system Populated video amplifier circuit board

I finally added the bulkiest components; the 5V regulator and the pin header to connect the upper and lower boards together. Soldering pin headers to the underside of a board is a fiddly job, but is required in this instance to connect the bottom of the upper board to the top of the lower board.

Top view of the populated video amplifier circuit board Video amplifier circuit board installed in the enclosure

With the upper board completed it was time to put it into the enclosure and solder the VGA connectors and DC power socket to it. This is the part I least enjoy.

VGA connectors with stranded wires attached

I started by soldering some stranded wire to the VGA connectors. Most of the wires are the same length, as they are required to carry signals to and from the circuit, but some wires are shorter and only connected to one of the VGA connectors. These are the white, yellow, orange and brown wires in the above photo, and these are attached to pins used to exchange information between the PC and the monitor (e.g. supported resolutions and refresh rates). As we're not interested in these, they're connected straight through from one connector to the other.

Pins used for monitor identification passed through hole in enclosure Both VGA connectors installed

I inserted the VGA connector with these identification pins into the top hole, passed the shorter identification wires through the other and soldered them to the second VGA connector. This leaves the red, green, blue, vsync, hsync and ground pins loose inside, ready to be connected to the upper circuit board.

DC power socket

The DC power socket also needs to be connected to the circuit board, but at only two wires that's a much simpler job.

External connectors soldered to the video amplifier circuit board Video amplifier circuit board hooked up and installed in the case

All of the loose leads are soldered onto the circuit board and stripboard is slotted into place inside the enclosure. The wires could be shorter, but that would have made soldering them a bit harder.

Cut tracks for the logic circuit board

The lower circuit board will host the main logic for the project - it receives the vsync and hsync signals, and uses these to control whether the video signal should be blanked or not, and which shutter on the glasses should be closed and which should be open. It also contains the oscillator that generates the AC voltage that drives the glasses. I arranged the three logic ICs roughly next to eachother according to their layout on the breadboard version of the circuit and cut the stripboard tracks as appropriate.

IC sockets soldered to the logic circuit board Discrete components added to the logic circuit board

I started by adding the sockets for the ICs and pin header to connect this circuit board to the video amplifier one, then added the discrete components. As before, I taped the components down before soldering them in place to make the task easier. Being able to copy the circuit directly from the breadboard version also made the task much easier.

Top view of the wire links on the logic circuit board Bottom view of the wire links on the logic circuit board

The last step for this part of the project was, as before, adding the wire links. Rather than run long wires around ICs I found it more practical to solder a few wires onto the underside of the stripboard.

Pin sockets and wires for the connector cable One end of the connector cable soldered

The two circuit boards needed to be connected together somehow. Without the facilities to make a proper ribbon cable, I just soldered some lengths of stranded wire (rather messily) between two pin sockets. As I'm not outputting anything to vsync or hsync (I'm feeding the input sync signals straight back to the output via the jumpers previously discussed), I didn't need to connect anything to these pins - hence the apparently missing wires in the photos.

Connector cable bent to fit Both circuit boards installed in the enclosure

The cable to connect the two boards together needed to be bent to fit - it's getting snug, but everything's in there without having to be forced, which is a good sign.

3.5mm stereo jack socket for LCD glasses connector Stereo jack socket soldered to the logic circuit board

The next job was to attach the 3.5mm stereo jack that the LCD shutter glasses are plugged into. This is pushed through the hole in the enclosure from the inside and screwed on from the outside, so it can be soldered directly to the circuit board without having to thread it through the hole first. The small red "washer" is a length of enamelled wire that has been bent around the thread of the jack socket and is used as a spacer - without it, quite a lot of the thread protrudes from the front of the box, looking rather untidy.

Control switches with connecting wires All parts installed in the enclosure

Last of all are the two control switches. These are soldered to the track side of the stripboard like the stereo jack, but must be snapped through their holes in the enclosure first, which is why they were left until last. Everything is slotted into place, the base of the enclosure is screwed on, and the project is pretty much complete.

Tightly packed VGA cables

The VGA cables don't fit especially well - the D-subminiature sockets are a bit too close to eachother. If I use a thin VGA extension cable and wiggle the leads I can just about get both to screw in.

LCD shutter glasses showing the left eye view of a row-interleaved image

The demonstration pattern from some previous ramblings of mine is quite useful for testing 3D glasses, and by holding the left eye of the shutter glasses to the screen you can see that only the "L" part of the image is let through.
benryves


This computer needs a name - I'd welcome any suggestions!

I have built a circuit on another piece of stripboard that will handle memory, clock signal generation and the Z80 itself.

A few posts ago I was wondering about how I'd partition memory. To date I've been using a very simple circuit where the lower 32KB of addressable memory is mapped to ROM and the upper 32KB is mapped to RAM. As my ROM chip is 128KB and I have two 32KB RAM chips, this seems a bit wasteful.

The memory layout I'm now using is quite simple: the upper 32KB is still mapped to RAM. However, only the first 16KB is mapped to ROM, and the three most significant bits of the ROM chip's address lines are connected to a device on the I/O board so that one of its eight 16KB "pages" can be swapped in. The next 16KB will be mapped to RAM, and the most significant bit of the RAM chip's address is connected to the same device on the I/O board so one of its two 16KB "pages" can be swapped in.


For more information, see the Wikipedia article on bank switching. There is a potential problem here; the Z80 uses particular fixed addresses for certain operations. The three most obvious ones are $0000 (jumped to on reset), $0038 (address of maskable interrupt handler) and $0066 (address of non-maskable interrupt handler). As which 16KB bank switched in at power-on is effectively random, the easy way around this problem is to ensure that the first 256 bytes or so of every ROM page has the same code assembled on it. This means that whichever page is swapped in on boot doesn't matter, as the same common boot code is available on each page.

The assembled memory board looks like this:


Click to toggle labels

I have only attached one of the 32KB RAM chips. The wiring was becoming a bit of a nightmare (I think I'll need to solder to the track side of the stripboard to fit in that other RAM chip) so for the moment the system can only access the fixed 32KB RAM. I haven't yet added the device on the I/O board to handle bank switching, so for the moment the ROM is permanently configured to access the first 16KB page by pulling the its three externally controllable address lines low.

That said, this machine does genuinely run BBC BASIC (the last system only ran a mockup with a dummy header at the top of the screen). I've done quite a bit of work on the OS in the emulator and it works pretty well there, and with a minor adjustment to cram it onto a single 16KB page it works well on hardware too.


Click for video (664KB XviD MPEG-4 AVI)

The row of chips along the bottom of the memory board are responsible for generating the clock signals that drive the computer. If this looks needlessly complex, that's because it can run at either 10MHz or 2MHz and generates the E signal for LCD access. The CPU needs to drop to 2MHz when accessing the LCD (the LCD driver can't keep up, otherwise) so I'll probably end up connecting the input for this 2MHz/10MHz switch to the LCD chip enable pins so that normally the system runs at 10MHz but drops to 2MHz when accessing the LCD. Allowing the user to drop to 2MHz to save power is an appealing idea, however...
benryves

Jump straight in with the latest video (2.27AVI, MPEG-4)

Apologies for quality/wonkiness.

Well, we just had a weekend which meant I got a bit more time to spend on Fire Track (I have no social life whatsoever. Hooray).
Last time I showed you the simple scrolling level. There is now:

  • Player sprite.

  • Bullets being fired by the player.

  • Hit detection with the terrain below, swapping out hit blocks with "blown up" ground.

  • Support for collision detection with 1x1, 2x1 and 2x2 "buildings" on grid.

  • Eye-watering abuse of sine tables and vcount - yes, it's the pause screen.

  • Simple animated title screen to mimic the BBC Micro title screen.

  • 'Levels' table, telling the engine which landscape and palettes to use.

  • Sprite helper functions reset_sprites, any number of calls to draw_sprite or draw_large_sprite finished with finish_sprites to handle writing to the sprite table.

  • Four complete landscapes copied from the BBC Micro version, accomplished with a BBC Micro emulator, a tool to record an area of the screen to a GIF and a LOT of patience.

  • Yet another revision of the background tiles.

  • Upgraded support tools to allow for easy production of multiple level sets, minor bug fixes.

  • Minor bugfixes and one new awesome feature to Latenite Z80 editor.


Good grief, I need to get out more!
First of all, the NEW AWESOME feature to Latenite. Actually, it's not that awesome, but I like it.
Before, if you hit F5 or clicked 'Compile-[Target Platform]' it would compile the file open in the current tab. So, if you made a fix to scroll.asm you'd have to edit, swap to ftrack.z80, hit F5, swap back to scroll.asm and so on. URGH. Now, when you start a compile it goes through all the current loaded documents. If it encounters a file with the directive

;#latenite:main

in it it assumes that that is your main source file and compiles that instead. Much tab clicking has been reduced! The bug fixes were in the support tools (wlaerr.exe and tasmerr.exe) to fix some of the broken line number reporting.

The new tile set is this:


One day I'll get 'em right. [grin]

Anyhow, I thought I'd explain how Fire Track handles levels.

What you do is play through a number of zones. Each zone is made up of a couple (or more) levels. For most of the levels, they end with a large block of terrain that looks a bit like a snarling alien face.


Grrr. To move on to the next level, you must shoot out both eyes. (Of course, shoot all the other tiles around first to get more points). This same face loops around and around until you have shot out both eyes.

At the end of each zone you don't get the face at the end of the level, you instead get the starting platform again and as it approaches you get a sort of rising scale until you pass it when you get a 'waaaouwaaaouwaaaou' from the amazing square-wave sound chip and the name of the next zone. Rinse, lather, repeat.


The starting platform.


Now, it would be crazy to store hundreds of levels on such a low end platform. It would also be crazy to have very few levels. So... why not swap the palette around? Yeah, that'll convince 'em that it's actually a different level. Hmm, maybe not. Well.. swapping somes of the tiles around from time to time? You know that sort of big ring, swap that for the asterisk-shaped block. Or that doughnut-with-a-plus-sign-through-it, swap that for the weird pyramid. Hmm, maybe not. The still might notice. Ah, here is the best solution - as well as switch palettes and a few tiles, let's make the levels we've already seen go past a bit faster. They won't notice then, will they? Oh, as a final touch, in some levels make the player and enemy sprites white and magenta rather than red and yellow. GENIUS. [smile]

And so begins the tedious task of sitting through the BBC Micro game, noting which landscape, palette and tiles are in use. So far I have done four of the levels. My VB.NET editor dumps them out as .db lists for the assembler, but also as PNG files for the Ben. Here they are, GIFfed up and ready to go (note: they are very tall images, so make sure your browser doesn't scales them down too small to see!)

Level 1
Level 2
Level 3
Level 4

(Remember that we start from the bottom of the levels and move upwards). The original level designer(s) have 'hidden' a number of patterns in the levels - can you spot the question mark (easy), Pac-Man (complete with pips, a fruit and ghost), a hungry-looking snake or the sad face (easy)? Stay tuned for more Where's Wally-styled fun as some of the other levels have such gems as the number 42 made up out of question marks (someone liked Mr. Adams, I'm guessing), a stealth bomber and a Yin-Yang. I guess these little things make up for the RSI-inducing copying over and adapting from Beeb to Game Gear...

I am, at heart, a programmer so it is at this point that I need to muse on how I'm going to display the score on-screen. My idea was as follows:

Wait for vblank signal.
Jump screen scroll location to top-left of name table.
Write out a completely black palette apart from colour #15 (the last one) which is white.
Make sure that the score is displayed in the top-left of the name table (it is in the blank space to the left of the vertical scrolling block - see some of my earlier screenshots from the emulator).
Wait for the vertical line counter to hit a point 8 pixels down the visible screen.
Jump back the scrolling window to the "normal" position, write the normal full colour palette out.

This works fine... in an emulator. On hardware it ends up frying the name table, so the data gets garbled beyond all recognition. ARGH!
I've singled it down to aggressive reading of port $7E, the vertical line counter. However, my pause screen (which reads this port, uses it to look up a vertical shift from a sine table, sets the x-scroll from this value) is even more aggressive and works fine on hardware!
There seems to be a way to enable an interrupt to fire when the vertical line counter hits a particular line, but looking through documentation I have, I have found that information on this interrupt is:
  • Different in every document.

  • Confusingly phrased.

  • Not mentioned at all.

Oh dear. Commercial games use a similar trick (waiting for the vertical line counter to hit a certain point, then do crazy things with the scroll/palette) but I can't work out how to do it myself... [sad]
*shrugs*
I will keep you posted!
benryves
STMicroelectronics recently released the STM8S-Discovery, an exceedingly cheap (RRP $7) evaluation kit for one of their 8-bit microcontrollers. It features the microcontroller itself (an STM8S105C6), running at up to 16MHz and offering 32KB of program memory, 2KB of RAM and 1KB of data EEPROM. This microcontroller has a solid set of on-board peripherals, including four timers (one advanced, one general-purpose, and one basic), SPI, I2C, UART, and ADC - so there are plenty of connectivity options. The device offers up to 38 general-purpose I/O pins.

STM8S-Discovery running a debugging session.

The evaluation board is pretty feature-packed, and includes an integrated ST-LINK for programming and debugging over USB. The circuit board has been designed so that you can simply snap off the ST-LINK part if you'd like to use the microcontroller on its own. Standard 0.1" pin headers are provided to permit you to connect the board to other components, and a small wrapping area is also present. A jumper can be used to select 5V or 3.3V operation.

A green LED and a touch sensitive key are built into the evaluation board; the device comes pre-programmed with a demo program that flashes this LED at different rates when you touch the key.

All in all, a decent piece of kit - but what really sets it apart is its price. ST's recommended price is $7; I bought mine for GBP4.25 from Farnell. All you need to do is provide a USB A to USB B cable and download the various development tools (Windows only at the time of writing), datasheets and libraries.

STM8S-Discovery in its packagingFirst impressions

I really wasn't expecting much for GBP4.25, but am very impressed with the hardware. It's solidly built and comes attractively packaged, with clear instructions on the back of the box on how to run the demo program (just plug it in to a USB port!) and where to go next for development tools.

Unfortunately, what appears to be lacking at the moment - not surprisingly for a new product - is guidance for absolute beginners with ST's microcontrollers. Hopefully that will improve as more people get hold of these new kits, as their incredibly low price and self-contained nature makes them ideal for beginners (no need to build up a collection of parts or buy a programmer to get started, just plug and play). I personally found the documentation quite baffling, and judging by a thread on Hack a Day I wasn't the only one.

Disclaimer

I'm not an expert with ST's microcontrollers, but I have at least managed to get something running on the microcontroller after a few frustrating hours spent with the current documentation. The following code may not be the best, but it is simple and it should work (if I've missed anything out or not been clear enough anywhere, please let me know so I can fix it). It doesn't go into any great detail; my assumption is that if you can get some code you've written yourself running on your evaluation board and understand how to use the basic peripherals offered by the microcontroller to work with the board's LED and touch key you should be ready enough to dive into the official documentation and sample code yourself!

Getting started

If you visit the STM8S-Discovery page, you will see options to download various pieces of software and development tools. You'll need to download the following:
There is a document that accompanies the development package, Developing and debugging your STM8S-DISCOVERY application code, which contains instructions on setting up a project - confusingly, these directions don't appear to apply to Raisonance's tool chain, and rely on copying and pasting files from the demo programs. You will need to register the compilers to be able to use them, and Cosmic's registration process is handled by a human so may take some time for you to receive your licence key.

Debugging one of the sample programs

One way to verify that everything is set up correctly and to try out the development tools is to build one of the sample programs included as part of the application development package. In this zip file you will find a directory named STM8S-Discovery_dev; extract this somewhere sensible. Run ST Visual Develop, and select File->Open Workspace. Open the file STM8S-Discovery_dev\Project\Discover\STVD\Cosmic\Discover.stw - this is the project that came pre-loaded onto the evaluation board. If you click Build->Build it should crunch away and after a few seconds should report that there were no errors.

We'll now need to set up the IDE to use your evaluation board's ST-LINK as its debugging instrument. Click Debug instrument->Target Settings and select Swim ST-Link in the dropdown. You can leave the other settings as they are; click OK to close the dialog.

STVD's debug instrument settings form

If you now click Debug->Start Debugging (or the blue "D" in the toolbar) the IDE should program the evaluation board and then enter the debugger in "Break" mode. Press Continue (F5) to start running the program; at this point you should be able to use the touch sensitive key on the board to change the rate at which the green LED flashes. When you're done, click Debug->Stop Debugging (or the red cross button in the toolbar) to stop debugging.

Hitting a breakpoint in the STVD debugger

The debugger shouldn't be especially surprising to anyone who has used a graphical debugger (e.g. Visual Studio) before. Try going to line 148 in main.c - BlinkSpeed++; - and selecting Edit->Insert/Remove Breakpoint. Start debugging as before, and you'll notice that when you touch the key this time the debugger breaks on that line. Click Debug->Continue and the program will continue. Marvellous - all pretty intuitive thus far.

Creating your own project

Creating your own project is a rather more involved process, as there's quite a lot you need to set up first. Hopefully this step-by-step guide should help!

New Project dialog
  1. Firstly, click File->New Workspace, and select Create Workspace and Project from the dialog that appears.
  2. Now, we need to store our workspace (analagous to a solution in Visual Studio) somewhere; create a new directory for the workspace that is preferably not inside Program Files and give the workspace a name. I'm going to go for "Blinkenlight" as my workspace name.
  3. Create a new project with the same name as the solution name. It should default to the same directory; keep this as it is. Select STM8 Cosmic as the toolchain.
  4. Select STM8S105C6 as the microcontroller.
With that done, you should have a shiny new workspace and project containing two files - main.c, containing the entry point for your application, and stm8_interrupt_vector.c, used to associate interrupt requests with interrupt service routines - more on those later. If you build the project and try to debug it you'll note that it does precisely nothing of use. Let's make it do something useful!

Adding the standard firmware library

To access the various peripherals of the microcontroller, ST have provided an extensive standard firmware library. You will need to download this from the STM8S documents and files page; it's the zip archive named STM8S firmware library. Open the zip archive, and copy the FWLib\library folder to your own project folder. You may wish to rename the library folder FWLib, so you should end up with the two folders Project\FWLib\inc and Project\FWLib\src.

There is a folder named project in the firmware library download - copy stm8s_conf.h from this folder into the root of your project folder.

Now, return to ST Visual Develop. Use the workspace panel to the left to create two new folders in your project - Source Files\FWLib and Include Files\FWLib. Add all of the files from FWLib\inc to Include Files\FWLib and stm8s_conf.h to the root of Include Files. You should now have something that looks like this:

A project set up with the firmware library and initial source files.

You will also need to inform the firmware library that you are using an STM8S105 microcontroller as opposed to the default STM8S208. Here are two ways of doing this - pick whichever seems easiest to you.
  1. Open stm8s.h in Project\FWLib\inc, comment out #define STM8S208 and uncomment #define STM8S105 near the top of the file.
  2. Pass the definition directly to the compiler by clicking Project->Settings, selecting the C Compiler tab and typing STM8S105 into the Preprocessor Definitions field. You will need to do this twice; once for the Debug configuration and once for Release (select the configuration with the drop-down box in the top left).
Adding a preprocessor definition to set the microcontroller type

Illuminating the LED using GPIO

The cathode of the evaluation board's LED is connected to PD0 on the microcontroller - that is, pin 0 of port D. By driving this pin low we could therefore illuminate the LED. If you consult the documentation for the standard firmware library - it's the stm8s_fwlib_um.chm file in the zip archive - you can see a number of helper functions dedicated to GPIO, or "general-purpose I/O". To use the GPIO functions, we need to do two things:
  1. Add FWLib\src\stm8s_gpio.c to the project under Source Files\FWLib.
  2. Enable inclusion of the relevant GPIO header files, achieved by uncommenting #define _GPIO (1) in stms8_conf.h
Both steps will need to be carried out whenever you want to use a new peripheral (e.g. a timer or the UART). Once that's done, you can modify main.c to read as follows:

#include "stm8s.h"

int main(void) {

// Reset ("de-initialise") GPIO port D.
GPIO_DeInit(GPIOD);

// Initialise pin 0 of port D by setting it as:
// - an output pin,
// - using a push-pull driver,
// - at a low logic level (0V), and
// - 10MHz.
GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);

// Infinite loop.
for(;;);
}

There are more comments than code there - using the firmware library makes life rather easy, once it's set up! If you start debugging that, you'll note that the LED does indeed light up. It's not much, but it's a sign of life.

Flashing the LED using a delay loop

The GPIO library provides a handy GPIO_WriteReverse() function, which inverts the state of a pin. By toggling PD0, we can make the LED flash. To slow this flashing down at a rate we can see, a delay loop is introduced that delays for 50,000 nops, plus overhead of the for loop structure, between calls to GPIO_WriteReverse().

#include "stm8s.h"

int main(void) {

// Reset ("de-initialise") GPIO port D.
GPIO_DeInit(GPIOD);

// Initialise pin 0 of port D.
GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);

// Infinite loop.
for(;;) {

// Delay for a short while.
u16 d;
for (d = 0; d < 50000; ++d) {
// Without a nop() in here, the entire loop would be optimised away!
nop();
}

// Invert the LED pin's state to flash it.
GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
}
}

The reason for the nop() is that without it, the compiler optimises away the entire for loop as it does nothing useful.

Well, that's a bit more dynamic, but surely there's a better way to do this than a hard-coded delay loop?

Timers

Timers - of which the STM8S has four with varying capabilities - are an extremely versatile peripheral. They are typically based around a counter, which counts up or down, and various events can be triggered when this counter reaches particular values. We'll start here by using the TIM3 peripheral.

To use TIM3 we need to perform the following steps, as we did previously for GPIO:
  1. Add FWLib\src\stm8s_tim3.c to the project under Source Files\FWLib.
  2. Uncomment #define _TIM3 (1) in stms8_conf.h

By default, the microcontroller uses its internal 16MHz RC oscillator ("HSI", or high-speed internal) divided by eight as a clock source. This results in a base timer frequency of 2MHz. When configuring a timer, you can specify a prescaler to further divide the clock frequency - if you were to divide the 2MHz clock frequency by 2000, the timer would count up once every millisecond.

TIM3 restricts the prescaler to powers of two between 1 and 32,768, so we'll use a prescaler of 2048 (that's close enough for jazz). You also need to specify a timer period; this is the value up to which the timer will count before resetting itself. With a period of 999, the timer will run from 0 to 999 before resetting - approximately one thousand milliseconds, or one second, from start to finish.

// Reset ("de-initialise") TIM3.
TIM3_DeInit();

// Set TIM3 to use a prescaler of 2048 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_2048, 999);

// Enable TIM3.
TIM3_Cmd(ENABLE);

Using the standard firmware library allows for easy timer configuration. Now that we have a timer running, we can query its counter value to flash the LED - if it's in the range 0~499, switch the LED on; if it's in the range 500~999, switch the LED off. Building on the ealier source code, here's a program that does just that:

#include "stm8s.h"

int main(void) {

// Reset ("de-initialise") GPIO port D.
GPIO_DeInit(GPIOD);
// Initialise pin 0 of port D.
GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);

// Reset ("de-initialise") TIM3.
TIM3_DeInit();
// Set TIM3 to use a prescaler of 2048 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_2048, 999);
// Enable TIM3.
TIM3_Cmd(ENABLE);

// Infinite loop.
for(;;) {
if (TIM3_GetCounter() < 500) {
// Output a low on the LED pin to illuminate it.
GPIO_WriteLow(GPIOD, GPIO_PIN_0);
} else {
// Output a high on the LED pin to switch it off.
GPIO_WriteHigh(GPIOD, GPIO_PIN_0);
}
}
}


Pulse-width modulation for flashing

One of the many features of these timers is the ability to generate pulse-width modulation - PWM - output on dedicated pins. When this feature is enabled, the timer will set the output pin to one logic level when it starts or restarts and to another when it reaches a used-defined threshold. This is effectively what we're doing in our current program, just manually - far better if the timer could do it for us automatically!

The green LED is connected to PD0, which also acts as TIM3_CH2, or TIM3's channel 2. We can remove most of the code from our previous program, including the GPIO code, leaving us with the following:

#include "stm8s.h"

int main(void) {

// Reset ("de-initialise") TIM3.
TIM3_DeInit();

// Set TIM3 to use a prescaler of 2048 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_2048, 999);

// Initialise output channel 2 of TIM3, by setting:
// - PWM1 mode (starts activated, deactivates when capture compare value is hit),
// - output is enabled,
// - capture compare value of 500, and
// - an active signal is low (0V).
TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 500, TIM3_OCPOLARITY_LOW);

// Enable TIM3.
TIM3_Cmd(ENABLE);

// Infinite loop.
for(;;);
}

In PWM1 mode, the output starts in the activated state. As we have specified that TIM3_OCPOLARITY_LOW is an activated state, this means that the output will start at a logic low (the LED will be illuminated). When the capture compare value (500) is reached, the output will switch to the deactivated state (logic high) and the LED will switch off. If you run this program as before you will see that the LED does indeed flash on and off automatically.

Pulse-width modulation to change brightness

As the LED is on for approximately 500ms and off for approximately 500ms it is on half of the time. On average, therefore, it is at half its possible brightness. If you modify the 500 in the TIM3_OC2Init function call to 250 and run the program again you will see that on for a quarter of the possible time, and at 750 it is on for three quarters of the possible time. By increasing the rate at which the LED flashes so that it appears to be continuously lit we can control its apparent brightness by adjusting the relative amount of time it is switched on in comparison to the time it is switched off.

We can increase the rate at which the LED flashes by reducing the prescaler of TIM3. Try changing the TIM3_TimeBaseInit call to use TIM3_PRESCALER_16 instead of TIM3_PRESCALER_2048, and change the capture compare value in the TIM3_OC2Init call to 100. When you run your program, the LED on the board will appear to be dimly lit. If you pick up the board and very carefully shake it from side to side you should be able to see that the LED is flashing from the dotted trace it leaves in the air. If you drop the prescaler all the way down to TIM3_PRESCALER_1 you will find that you have to shake the board much faster, but take care not to damage anything!

The brightness of the LED can be modified at runtime by changing the value of the capture compare register with the TIM3_SetCompare2 function.

#include "stm8s.h"

// Short delay loop.
void delay(void) {
u16 d;
for (d = 0; d < 150; ++d) {
nop();
}
}

int main(void) {

// Reset ("de-initialise") TIM3.
TIM3_DeInit();

// Set TIM3 to use a prescaler of 1 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_1, 999);

// Initialise output channel 2 of TIM3.
TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 0, TIM3_OCPOLARITY_LOW);

// Enable TIM3.
TIM3_Cmd(ENABLE);

// Infinite loop.
for(;;) {

u16 brightness;

// Set the brightness from 0 to 999 in a loop (fade up).
for (brightness = 0; brightness < 1000; ++brightness) {

// Set the brightness of the LED by modifying the capture compare register
// for TIM3's channel 2.
TIM3_SetCompare2(brightness);

// Delay a short while.
delay();
}

// Set the brightness from 1000 to 1 in a loop (fade down).
for (brightness = 1000; brightness > 0; --brightness) {

// Set the brightness of the LED.
TIM3_SetCompare2(brightness);

// Delay a short while.
delay();
}
}
}

The above program fades the LED up from the minimum brightness to the maximum brightness then back down again in an infinite loop. It also reintroduces our old enemy, the delay loop, which leads us neatly on to the next subject - interrupts.

Interrupts

Interrupts provide a way to respond to events in a way that doesn't require that we constantly check (poll) the event source. One such event is a timer overflowing - we can use this event to update the brightness of the LED every millisecond without having to poll a timer's counter manually or use delay loops.

We'll use TIM1 to generate the interrupt; it provides a few additional features that are not present on the other timers, but we'll need to keep TIM2 and TIM4 free for later. As before, you'll need to add stm8s_tim1.c to your project and uncomment #define _TIM1 (1) in stm8s_conf.h.

We'll start by adding some skeleton interrupt handler code and reference it in the interrupt vector table to ensure that it is called when the timer updates itself. Firstly, add the two following files to your project:

stm8s_it.c
#include "stm8s.h"
#include "stm8s_it.h"

void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void) {
// TODO: Implement TIM1 update interrupt handler.
}

stm8s_it.h
#ifndef __STM8S_IT_H
#define __STM8S_IT_H

@far @interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void);

#endif

These stm8s_it files contain the interrupt request handlers. Interrupt handler functions are called via the interrupt vector table, which is defined in the stm8_interrupt_vector.c file that was automatically generated when you created the project. Open this file and add #include "stm8s_it.h" to the top of it so that it can see your interrupt handler functions. According to the STM8S105xx datasheet the TIM1 update/overflow interrupt is mapped to IRQ 11, so scroll down the table of interrupt vectors and change NonHandledInterrupt on the line marked irq11 (some lines omitted for clarity):

struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
/* [...] */
{0x82, NonHandledInterrupt}, /* irq10 */
{0x82, (interrupt_handler_t)TIM1_UPD_OVF_TRG_BRK_IRQHandler}, /* irq11 */
{0x82, NonHandledInterrupt}, /* irq12 */
/* [...] */
{0x82, NonHandledInterrupt}, /* irq29 */
};

Now we have that in place we can start writing the interrupt handler code. Internally, interrupts are signalled by setting a flag in a control register, which the microcontroller periodically checks. If you do not clear this flag the microcontroller will call your interrupt handler again as soon as you return from the function, so you must remember to do so - this is done with the TIM1_ClearITPendingBit(TIM1_IT_UPDATE) function. Using a variable to store the current brightness "direction" (positive to get brighter; negative to get dimmer) the LED brightness could be adjusted every time the timer overflowed using the following code:

stm8s_it.c
#include "stm8s.h"
#include "stm8s_it.h"

s16 brightness_direction = +1; // Start by getting brighter.

void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void) {

// Get the current brightness.
u16 current_brightness = TIM3_GetCapture2();

// Check whether we've hit the maximum/minimum brightness yet.
if (brightness_direction > 0) {
// We're currently getting brighter.
if (current_brightness == 1000) {
// We're already at the maximum brightness; start getting darker.
brightness_direction = -1;
}
} else {
// We're currently getting dimmer.
if (current_brightness == 0) {
// We're already at the minimum brightness; start getting brighter.
brightness_direction = +1;
}
}

// Update the brightness of the LED according to the brightness "direction".
TIM3_SetCompare2(current_brightness + brightness_direction);

// Clear the interrupt pending bit for TIM1.
TIM1_ClearITPendingBit(TIM1_IT_UPDATE);
}

We also need to configure TIM1 to generate interrupts. This can be done with the TIM1_ITConfig function, in addition to the existing code used to configure TIM3:

main.c
#include "stm8s.h"

int main(void) {

// Reset ("de-initialise") TIM3.
TIM3_DeInit();
// Set TIM3 to use a prescaler of 1 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_1, 999);
// Initialise output channel 2 of TIM3.
TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 0, TIM3_OCPOLARITY_LOW);
// Enable TIM3.
TIM3_Cmd(ENABLE);

// Reset ("de-initialise") TIM1.
TIM1_DeInit();
// Set TIM1 to:
// - use an exact prescaler of 1000,
// - to count up,
// - to have a period of 1, and
// - to have a repetition counter of 0.
TIM1_TimeBaseInit(1000, TIM1_COUNTERMODE_UP, 1, 0);
// Set TIM1 to generate interrupts every time the counter overflows (every ms).
TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
// Enable TIM1.
TIM1_Cmd(ENABLE);

// Enable interrupts (no, really).
enableInterrupts();

// Infinite loop.
for(;;);
}


Interrupts are globally disabled by default, hence the need to call enableInterrupts(). If you run this program, you should find that the LED fades in and out as before, but without the need for hacky delay loops. As you can see, TIM1 takes a few additional parameters to its TIM1_TimeBaseInit function; you aren't limited to powers of two for its prescaler, it can count in a number of different ways and you can specify a "repetition count" that will only update the timer registers after a given number of cycles of the counter (in this case, we've disabled that feature).

Touch key input

As well as an LED for output, the evaluation board provides a touch key for input. This requires considerably more computing power to handle than a conventional push switch, but is considerably more interesting! Thankfully, ST have provided a royalty-free library to handle touch sensing keys, sliders and wheels with their microcontrollers which we can use:
  1. Visit the documents and files page again to download the STM8S Touch Sensing Library; it is packaged as an installer, which should be run.
  2. Go to the installation directory and copy Libraries\STM8_TouchSensing_Driver to your own project folder as you did for FWLib previously.
  3. Move Inc\STM8_TSL_RC_Configuration_TOADAPT.h to the root of your project folder and rename it STM8_TSL_RC_Configuration_TOADAPT.h (remove "_TOADAPT").
When you have copied the files, switch back to your project and follow these steps:
  1. Create a folder Touch Sensing Library under Source Files and add all of the files in STM8_TouchSensing_Driver\Src apart from STM8_TSL_RC_MultiChannelKey.c to it.
  2. Create a folder Touch Sensing Library under Include Files and add all of the files in STM8_TouchSensing_Driver\Inc to it.
  3. Add STM8_TSL_RC_Configuration.h to the root of Include Files.
Due to some functions needing to be aligned to even memory addresses, you will need to modify your linker settings. In the IDE, click Project->Settings and switch to the Linker tab. Set the Category dropdown to Input, expand the Code, Constants section and add a section named .TSL_IO_ALCODE with its options set to -r2. You will need to do this to both Debug and Release configurations.

Adding the .TSL_IO_ALCODE section to the linker.

Now we need to go and configure STM8_TSL_RC_Configuration.h for our particular hardware. Open this file, and make the following amendments:
  1. TIMACQ will need to be changed to TIM2 as we're using TIM3 to drive our LED.
  2. TIMACQ_CNTR_ADD needs to be changed to 0x530A to match the change to TIM2.
  3. The touch key is attached to GPIO port C so LOADREF_PORT_ADDR needs to be changed to GPIOC_BaseAddress.
  4. The load reference is connected to pin PC2, so we need to change LOADREF_BIT to 0x04 (1<<2 = 0x04).
  5. We only have one key, so change SCKEY_P1_KEY_COUNT to 1.
  6. The touch key input is connected to PC1, so leave SCKEY_P1_PORT_ADDR at GPIOC_BaseAddress and SCKEY_P1_A at 0x02 (1<<1 = 0x02). As there are no other keys on that port, set all of the other key masks (SCKEY_P1_B to SCKEY_P1_H) to 0.
  7. As we don't have any keys on a second port, set SCKEY_P2_KEY_COUNT, SCKEY_P2_PORT_ADDR and SCKEY_P2_A--SCKEY_P2_H to 0.
  8. As we don't have any multi-channel keys, set NUMBER_OF_MULTI_CHANNEL_KEYS to 0.
  9. The touch key electrodes are connected to PC1 and PC2, so set GPIOC_ELECTRODES_MASK to 0x0A (0b00001010). Set all of the other electrode masks to 0.
Whew, quite a lot of work there! Now we've set that up, we can get programming. Try building your project; it should take a bit longer than before, but not emit any errors if you've set things up correctly!

The first thing we need to change in our program is to switch to running at 16MHz, a requirement of the touch sensing library. To do this, we need to use the CLK peripheral library; add stm8s_clk.c to Source Files\FWLib as before, and uncomment #define _CLK (1) in stm8s_conf.h. Now add CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1) to the start of your main() function to set the internal high-speed oscillator prescaler to 1 (it defaults to eight). If you now build and run your project you will notice that the LED fades up and down much faster - eight times faster, in fact. Change the TIM1 prescaler to 8000 to revert to the old speed:

#include "stm8s.h"

int main(void) {

// Set the internal high-speed oscillator to 1 to run at 16/1=16MHz.
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);

// Reset ("de-initialise") TIM3.
TIM3_DeInit();
// Set TIM3 to use a prescaler of 1 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_1, 999);
// Initialise output channel 2 of TIM3.
TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 0, TIM3_OCPOLARITY_LOW);
// Enable TIM3.
TIM3_Cmd(ENABLE);

// Reset ("de-initialise") TIM1.
TIM1_DeInit();
// Set TIM1 to use a prescaler of 8000 and to have a period of 1.
TIM1_TimeBaseInit(8000, TIM1_COUNTERMODE_UP, 1, 0);
// Set TIM1 to generate interrupts every time the counter overflows (every ms).
TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
// Enable TIM1.
TIM1_Cmd(ENABLE);

// Enable interrupts.
enableInterrupts();

// Infinite loop.
for(;;);
}

Now we've got that organised, we can go ahead with using the touch sensing library. Start by adding #include "STM8_TSL_RC_API.h" to the top of main.c. We need to initialise the library and the touch key in our main function; add the following lines after the CLK_HSIPrescalerConfig call:

// Initialise the touch sensing library.
TSL_Init();
// Initialise the key (we only have one key).
sSCKeyInfo[0].Setting.b.IMPLEMENTED = 1; // It's implemented...
sSCKeyInfo[0].Setting.b.ENABLED = 1; // ...and enabled.

The touch sensing library makes use of a timer interrupt. We've set TIMTICK to TIM4, so need to attach the TIM4 update/overflow interrupt (IRQ 23) to TSL_Timer_ISR. Open stm8_interrupt_vector.c, add #include "STM8_TSL_RC_API.h" to the top of it, then modify the vector marked irq23:

#include "stm8s_it.h"
#include "STM8_TSL_RC_API.h"

/* [...] */

struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
/* [...] */
{0x82, NonHandledInterrupt}, /* irq22 */
{0x82, (interrupt_handler_t)TSL_Timer_ISR}, /* irq23 */
{0x82, NonHandledInterrupt}, /* irq24 */
/* [...] */
{0x82, NonHandledInterrupt}, /* irq29 */
};

Some lines are, as before, omitted for clarity.

The infinite loop at the end of the program will need to be modified to call the TSL_Action function to update the touch sensing library's internal state machine, then check the state of the touch sensing library to see if there's any input to be processed:

// Infinite loop.
for(;;) {
// Update the touch sensing library's state machine.
TSL_Action();
// Check to see if something has happened, and that we're in the idle state before handling it.
if ((TSL_GlobalSetting.b.CHANGED) && (TSLState == TSL_IDLE_STATE)) {
// Clear the "something has changed" flag.
TSL_GlobalSetting.b.CHANGED = 0;
// Has our key been pressed/detected?
if (sSCKeyInfo[0].Setting.b.DETECTED) {
nop(); // <-- Set a breakpoint here.
}
}
}

Set a breakpoint on the nop() line, then build and run the program. The LED will fade up and down as before, but if all has gone to plan touching the key should break execution on the nop() line. A slightly more useful program is shown below, modifying the current LED fading code to only fade out and using the touch key to set the LED to its maximum brightness when tapped.

main.c
#include "stm8s.h"
#include "STM8_TSL_RC_API.h"

int main(void) {

// Set the internal high-speed oscillator to 1 to run at 16/1=16MHz.
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);

// Initialise the touch sensing library.
TSL_Init();
// Initialise the key (we only have one key).
sSCKeyInfo[0].Setting.b.IMPLEMENTED = 1; // It's implemented...
sSCKeyInfo[0].Setting.b.ENABLED = 1; // ...and enabled.

// Reset ("de-initialise") TIM3.
TIM3_DeInit();
// Set TIM3 to use a prescaler of 1 and have a period of 999.
TIM3_TimeBaseInit(TIM3_PRESCALER_1, 999);
// Initialise output channel 2 of TIM3.
TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, 0, TIM3_OCPOLARITY_LOW);
// Enable TIM3.
TIM3_Cmd(ENABLE);

// Reset ("de-initialise") TIM1.
TIM1_DeInit();
// Set TIM1 to use a prescaler of 8000 and to have a period of 1.
TIM1_TimeBaseInit(8000, TIM1_COUNTERMODE_UP, 1, 0);
// Set TIM1 to generate interrupts every time the counter overflows (every ms).
TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
// Enable TIM1.
TIM1_Cmd(ENABLE);

// Enable interrupts.
enableInterrupts();

// Infinite loop.
for(;;) {
// Update the touch sensing library's state machine.
TSL_Action();
// Check to see if something has happened, and that we're in the idle state before handling it.
if ((TSL_GlobalSetting.b.CHANGED) && (TSLState == TSL_IDLE_STATE)) {
// Clear the "something has changed" flag.
TSL_GlobalSetting.b.CHANGED = 0;
// Has our key been pressed/detected?
if (sSCKeyInfo[0].Setting.b.DETECTED) {
TIM3_SetCompare2(1000);
}
}
}
}

stm8s_it.c
#include "stm8s.h"
#include "stm8s_it.h"

void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void) {

// Get the current brightness.
u16 current_brightness = TIM3_GetCapture2();

// If it's brighter than zero, dim it by one unit.
if (current_brightness > 0) {
TIM3_SetCompare2(current_brightness - 1);
}

// Clear the interrupt pending bit for TIM1.
TIM1_ClearITPendingBit(TIM1_IT_UPDATE);
}


Conclusion

Now that you've got this far, you should be able to delve into the documentation and samples provided by ST to find out more about this platform. If ST can keep the price as low as they currently recommend, then this is an extremely attractive platform for hobbyists, especially beginners as you don't need any additional tools barring a USB A to USB B cable - I have deliberately avoided interfacing with external components, for that reason. The hardware is extremely capable, low price or not, so I'm sure we'll see many interesting projects created with this board as a starting point!

Further Reading
benryves
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.

USB remote control receiver prototype using an ATmega168

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.

USB remote control receiver prototype using an ATtiny84

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.

USB remote control receiver assembled on stripboard

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.

USB remote control receiver circuit in its enclosure

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.

Finished USB remote control receiver

benryves
I recently purchased an inexpensive PlayStation controller USB adaptor for my PC. Several reviews confirmed that it was compatible with the controller's analogue joysticks so I thought it would be what I was after. Life is rarely that easy with cheap electronics, unfortunately!


adaptor-product.jpg

When it arrived I plugged it in and Windows installed the appropriate HID drivers for it automatically, but as much as I waggled the joysticks on a connected DualShock 2 controller the axis preview in Control Panel remained resolutely in the zero position. PlayStation controllers have an "Analog" button that can be pressed to toggle between digital and analogue modes, but any attempts to press this resulted in the "Analog" light briefly flashing before immediately switching off again.

Thinking it may be a driver issue I tried to install the drivers from the mini CD that had been included with the adaptor. My PC could not read the disc (it appeared to be scratched, and was not very well protected in postage) so I hunted around online until I found a package that worked using the device's USB ID (VID_0810&PID_0001). This enabled the controller's rumble/vibration feature, but I still couldn't get analogue input to work. Thinking that if one driver package could add vibration support, another might add analogue support I contacted the Amazon seller to ask them if they could send me a copy of the correct drivers - they instead chose to send me a whole other unit in the post.

In the meantime, I experimented with another controller plugged into the adaptor. I was surprised to find that with two controllers plugged in at once I could enable analogue mode on one of the controllers. This made me think there could be a power issue - the second controller increased the capacitance across the power supply, which would make it more resilient to voltage spikes and reduce ripple that could be causing the controller to reset out of analogue mode. This was further confirmed by plugging the adaptor with a single controller into a powered USB hub - in this scenario the controller would only leave analogue mode when vibrating. I checked the power supply pins on the controller ports and was very surprised to see that there was apparently nothing connected to pin 5, which is supposed to deliver +5V to the controllers. At this point I decided to dismantle the adaptor to see what was going on.


adaptor-circuit-top.jpg

On the inside of the adaptor I could see that several components had been omitted. This could be to blame on cost-cutting measures (e.g. the LEDs D1 and D2 which are purely cosmetic) but the removal of D3 puzzled me the most - this diode is connected between USB VCC and the controller port pin 5, and is presumably responsible for providing power to the connected controller. I put this down to an oversight at the factory, and soldered a 1N4001 rectifier diode in the marked place.


diode-polarity.jpg

The above image shows a close-up of the place the missing diode should appear - D3 is indicated by a silk-screened diode symbol. Unsurprisingly the 1N4001 silicon diode has far superior characteristics to the silk-screen diode it replaced.


diode-soldered-in.jpg

With the diode in place both controller ports started working flawlessly, even allowing me to use a wireless Guitar Hero controller receiver (though not the whammy bar - Guitar Hero controllers lack the "Analog" button to manually enable the analogue mode and instead rely on the PlayStation to enable it via software). Whilst I had the soldering iron out I thought I should add the missing LEDs, once again using the existing markings to establish the correct polarity:


led-polarity.jpg

If the markings are unclear, the anode (+) is always to the left when viewing the bottom of the circuit board when the other markings are upright.


leds-bottom.jpg

As the enclosure is blue and I seem to remember some fuss being made of the PlayStation 2's blue LED when it first came out I opted to use two blue LEDs with 1K5 resistors. I do not have any surface-mount resistors but through-hole ones fit quite easily though they can be a little fiddly to solder down.

When the replacement adaptor arrived in the post I was surprised to see that (once again) the diode D3 was missing and it demonstrated the same problems as the other one I'd fixed. I find it unlikely that the same mistake could be made twice, so this seems to be a genuine cost-cutting measure. Microcontroller I/O pins often have an internal protection diode between them and the positive power supply, which is how I assume the circuit works at all when the controllers are left unpowered - a small amount of current flows from the I/O (data) pins to the positive rail via these protection diodes, which is just enough to let the controller work in digital mode but once they draw more current (e.g. when sampling analogue inputs or driving the vibration motors) the voltage droops far enough for the controller to reset and leave analogue mode.


adaptor-fixed.jpg

With these fixes in place I now have two working PlayStation USB adaptors for the price of one (and two 1N4001 diodes). I'm still rather perplexed by why there's such a blatent flaw in the hardware, but it is at least an easy fix which is why I've written it up. In summary: if your cheap PlayStation to USB adaptor ("Twin USB Vibration Gamepad", "Twin USB Joystick") is not working correctly, unscrew it and see if D3 is missing. If it is, solder a 1N4001 or similar diode between the two holes left for that purpose.
benryves

Sega: Enter the Pies

The Z80 core is now a bit more accurate - ZEXALL still reports a lot of glitches, and this is even a specially modified version of ZEXALL that masks out the two undocumented flags.

The VDP (Video Display Processor - the graphics hardware) has been given an overhaul and is now slightly more accurate. A lot more software runs now. I have also hacked in my PSG emulator (that's the sound chip) from my VGM player. It's not timed correctly (as nothing is!) but it's good enough for testing.





Picohertz, the demo I have been working on (on and off) now runs correctly. The hole in the Y in the second screenshot is caused by the 8 sprite per scanline limit. The first screenshot shows off sprite zooming (whereby each sprite is zoomed to 200% the original size). The background plasma is implemented as a palette shifting trick.





Fire Track runs and is fully playable. The second shot shows a raster effect (changing the horizontal scroll offset on each scanline.

Seeing as I understand the instructions that my programs use (and the results of them), and have my own understanding of parts of the hardware, it's not really surprising that the programs I've written work perfectly, but ones written by others don't, as they might (and often do) rely on tricks and results that I'm not aware of, or on hardware that I haven't implemented accurately enough. At least I do not need to emulate any sort of OS to run these programs!

SMS Power! has been an amazing resource in terms of hardware documentation and homebrew ROMs. I've been using the entries to the 2006 coding competition to test the emulator.



Bock's game, KunKun & KokoKun, nearly works. The cannon don't fire, which would make the game rather easy if it wasn't for the fact that the switch to open the door doesn't work either. I suspect that a CPU flag isn't being set correctly as the result to an operation somewhere.



Haroldoop's PongMaster is especially interesting as it was not written in Z80 assembly, but C. It's also one of the silkiest-smooth pong games I've come across.



An!mal/furrtek's Paws runs, but something means that the effect doesn't work correctly (the 'wavy' bit should only have one full wave in it, not two - it appears my implementation of something doubles the frequency of the wave). The music sounds pretty good, though.



Sega's Columns gets the furthest of any official software - the Sega logo fades in then out.



I do like the idea that Sega is an ENTERPI?S. (From the Game Gear BIOS). (I believe this is a CPU bug).



Charles Doty's Frogs is a bit of a conundrum. The right half of the second frog is missing due to the 8 sprites per scanline limitation of the VDP. However, Meka, Emukon, Dega and now my emulator draw the rightmost frog's tongue (and amount if it showing) differently, as well as whether the frog is sitting or leaping. There's a lot of source for such a static program (it doesn't do anything in any emulator I've tried it on, nor on hardware). Dega is by far the strangest, as the tongue moves in and out rapidly. I'm really not sure what's meant to be happening here.

Here are the results of ZEXALL so far.
Z80 instruction exerciser

ld hl,(nnnn).................OK
ld sp,(nnnn).................OK
ld (nnnn),hl.................OK
ld (nnnn),sp.................OK
ld ,(nnnn)............OK
ld ,(nnnn)............OK
ld ,nnnn..............OK
ld (+1),nn............OK
ld ,nn......OK
ld a,(nnnn) / ld (nnnn),a....OK
ldd (1)...................OK
ldd (2)...................OK
ldi (1)...................OK
ldi (2)...................OK
ld a,<(bc),(de)>.............OK
ld (nnnn),............OK
ld ,nnnn........OK
ld ,nn...OK
ld (nnnn),............OK
ld (),a...............OK
ld (+1),a.............OK
ld a,(+1).............OK
shf/rot (+1)..........OK
ld ,(+1).........OK
ld (+1),.........OK
ld ,(+1).....OK
ld (+1),.....OK
c..................OK
de.................OK
hl.................OK
ix.................OK
iy.................OK
sp.................OK
n,(+1)......OK
bit n,(+1)............OK
a..................OK
b..................OK
bc.................OK
d..................OK
e..................OK
h..................OK
l..................OK
(hl)...............OK
ixh................OK
ixl................OK
iyh................OK
iyl................OK
ld ,.......OK
cpd.......................OK
cpi.......................OK
(+1)........OK
..........OK
shf/rot .OK
ld ,.......OK
....................OK
n,....OK
neg..........................OK
add hl,.........OK
add ix,.........OK
add iy,.........OK
aluop a,nn................... CRC:04d9a31f expected:48799360
hl,... CRC:2eaa987f expected:f39089a0
bit n,...OK
............ CRC:43c2ed53 expected:9b4ba675
aluop a,(+1).......... CRC:a7921163 expected:2bc2d52d
aluop a,.... CRC:c803aff7 expected:a4026d5a
aluop a,. CRC:60323322 expected:5ddf949b
Tests complete




The aluop (add/adc/sub/sbc/and/xor/or/cp) bug seems to be related to the parity/overflow flag (all other documented flags seem to be generating the correct CRC). daa hasn't even been written yet, so that would be the start of the problems with the daa,cpl,scf,ccf group. adc and sbc bugs are probably related to similar bugs as the aluop instructions.

The biggest risk is that my implementation is so broken it can't detect the CRCs correctly. I'd hope not.

In terms of performance; when running ZEXALL, a flags-happy program, I get about ~60MHz speed in Release mode on a 2.4GHz Pentium 4. When ZEXALL is finished, and it's just looping around on itself, I get ~115MHz.

The emulator has not been programmed in an efficient manner, rather a simple and clear manner. All memory access is done by something that implements the IMemoryDevice controller (with two methods - byte ReadByte(ushort address) and void WriteByte(ushort address, byte data)) and all hardware access is done by something that implements the IHardwareController interface (also exposing two methods - byte ReadDevice(byte port) and void WriteDevice(byte port, byte data)).

Most of the Z80's registers can be accessed via an index which makes up part of an opcode. You'd have thought that the easiest way to represent this would be, of course, an array. However, it's not so simple - one of the registers, index 6, is (HL) - which means "whatever HL is pointing to". I've therefore implemented this with two methods - byte GetRegister(int index) and void SetRegister(int index, byte value).

Life isn't even that simple, though, as by inserting a prefix in front of the opcode you can change the behaviour of the CPU - instead of using HL, it'll use either IX or IY, two other registers. In the case of (HL) it becomes even hairier - it'll not simply substitute in (IX) or (IY), it'll substitute in (IX+d), where d is a signed displacement byte that is inserted after the original opcode.

To sort this out, I have three RegisterCollections - one that controls the "normal" registers (with HL), one for IX and one for IY. After each opcode and prefix is decoded, a variable is set to make sure that the ensuing code to handle each instruction works on the correct RegisterCollection.

The whole emulator is implemented in this simplified and abstracted manner - so I'm not too upset with such lousy performance.

I'm really not sure how to implement timing in the emulator. There's the easy timing, and the not-so-easy timing.

The easy timing relates the VDP speed. On an NTSC machine that generates 262 scanlines (60Hz), on a PAL machine that generates 313 scanlines (50Hz). That's 15720 or 15650 scanlines per second respectively.

According to the official Game Gear manual, the CPU clock runs at 3.579545MHz. I don't know if this differs with the SMS, or whether it's different on NTSC or PAL devices (the Game Gear is fixed to NTSC, as it never needs to output to a TV, having an internal LCD).

I interpret this as meaning that the CPU needs to be run for 227.7 or 228.7 cycles per scanline. That way, my main loop looks a bit like this:
if (Hardware.VDP.VideoStandard == VideoStandardType.NTSC) {
for (int i = 0; i < 262; ++i) {
CPU.FetchExecute(228);
Hardware.VDP.RasteriseLine();
}
} else {
for (int i = 0; i < 313; ++i) {
CPU.FetchExecute(229);
Hardware.VDP.RasteriseLine();
}
}

The VDP raises an event when it enters the vertical blank area, so the interface can capture this and so present an updated frame.

The timing is therefore tied to the refresh rate of the display.



Here's the fictional Super Game Gear, breezing along at 51MHz. The game runs just as smoothly as it would at 3MHz, though - as the game's timing is tied to waiting for the vertical blank.

Actually, I tell a lie - as Fire Track polls the vertical counter, rather than waiting for an interrupt, it is possible for it to poll this counter so fast (at an increased clock rate) that it hasn't changed between checks. That way "simple" effects run extra fast, but the game (that has a lot of logic code) runs at the same rate.

This works. The problem is caused by sound.

With the video output, I have total control of the rasterisation. However, with sound, I have to contend with the PC's real hardware too! I'm using the most excellent FMOD Ex library, and a simple callback arrangement, whereby when it needs more data to output it requests some in a largish chunk.
If I emulate the sound hardware "normally", that is updating registers when the CPU asks them to be updated, by the time the callback is called they'll have changed a number of times and the granularity of sound updates will be abysmal.

A solution might be to have a render loop like this:
for (int i = 0; i < 313; ++i) {
CPU.FetchExecute(229);
Hardware.VDP.RasteriseLine();
Hardware.PSG.RenderSomeSamples(1000);
}

However, this causes its own problems. I'd have to ensure that I was generating exactly the correct number of samples - if I generated too few I'd end up with crackles and pops in the audio as I ran out of data when the callback requested some, or I'd end up truncating data (which would also crackle) if I generated too much.

My solution thus far has been a half-way-house - I buffer all PSG register updates to a Queue, logging the data written and how many CPU cycles had been executed overall when the write was attempted. This way, when the callback is run, I can run through the queued data, using the delay between writes to ensure I get a clean output.

As before, this has a problem if the timing isn't correct - rather than generate pops or crackles, it means that the music would play at an inconsistent rate.

Of course, the "best" solution would be to use some sort of latency-free audio solution - MIDI, for example, or ASIO. If I timed it, as with everything else, to scanlines I'd end up with a 64us granularity - which is larger than a conventional 44.1kHz sample (23us), so PWM sound might not work very well.



Incidentally, this is not the first emulator I have written - I have written the obligatory Chip-8 emulator, for TI-83 calculator and PC. Being into hardware, but not having the facilities to hand to dabble in hardware as much as I'd like to, an emulator provides a fun middle-ground between hardware and software.

benryves
As you may have guessed from the ratio of photos to actual content in my entries I do quite enjoy taking photos of things. One of the reasons I enjoy working with electronics over writing software for computers is that a finished product results in something physical, which I find much more rewarding than a purely virtual hobby.

One type of photograph I particularly enjoy on other websites is the interactive 360? view of a product. The ability to click and drag to rotate an object on the screen makes it seem more real.

What do you need to take this sort of photograph and show it on a web page? There are four components I could think of:
  1. A rotating platform that could be controlled to rotate to a specific angle.
  2. A fixed camera that can be triggered once the platform has advanced to the correct angle.
  3. A way to combine all of the photos taken at different angles into a single file.
  4. An piece of code that would allow the user to rotate the object on-screen and display the correct single view of the object.
My final solution is a bit of a Heath Robinson affair but it seems to work quite well!

The rotating platform

The most obvious way to build such a platform is to use a stepper motor, as that is specifically designed to be positioned to a particular angle. The problem is that I don't have any stepper motors, and even if I did it would be quite tricky to connect one to a platform. A more practical alternative is to use something I do have -- Lego Technic.

360? photo hardware built out of Lego Technic pieces

A Lego motor cannot be set to rotate to a particular position, so some additional electronics are required. The motor drives a worm gear which in turn rotates a three-bladed propeller relatively slowly (shown with red pieces attached to it in the photo). This propeller cuts the path of a beam of infra-red light between an LED and an infra-red receiver module. A microcontroller (in this case, a PICAXE-08M) is used to advance the platform in steps by switching the motor on, waiting for the beam to be unblocked, waiting for the beam to be blocked again then switching the motor off. The gears I am using have twenty-four or eight teeth, so each pair of gears divides the rotational speed by 24/8=3. I am using four pairs of gears which results in a division of 34=81. The propeller has three blades which further divides the rotational speed by three resulting in the ability to set the platform to 81x3=243 distinct angles.

' This code is for a PICAXE-08M
#PICAXE 08M

' This pin is used to generate the 38kHz IR carrier. It should be connected to the IR LED's cathode (-).
Symbol IRPwmPin = 2
' This pin is connected to the IR demodulator's output.
Symbol IRReceiverPin = Pin3

' This pin is connected to the motor enable output.
Symbol MotorPin = 4

Symbol SerialControlIn = 1

' The desired position of the "stepper" motor.
Symbol StepDesired = B8
' The current position of the "stepper" motor.
Symbol StepCurrent = B9

Symbol StepDesiredConfirm = B10
Symbol StepDesiredPotential = B11

' Returned from the CheckBeam routine.
Symbol BeamBlocked = B12

' Rather than spin once at a time (slow) spin up to this many times between exchanging position information with the computer.
Symbol SpinLoopCount = 3

' Stores the spin loop time.
Symbol SpinLoop = B13

' The number of steps in a complete revolution.
Symbol TotalSteps = 243


Main:

' Reset the current and desired steps.
StepDesired = 0
StepCurrent = 0

' Switch the motor off.
Low MotorPin

'StepDesiredConfirmCount = 0

Do
' Fetch the desired position.
SetFreq M8
SerIn SerialControlIn, N4800_8, (CR, LF), #StepDesiredPotential, #StepDesiredConfirm
SetFreq M4

' Check the received data - the second value should be the logical inversion of the first.
StepDesiredConfirm = Not StepDesiredConfirm
If StepDesiredPotential = StepDesiredConfirm Then
StepDesired = StepDesiredPotential
End If


' Adjust the position if required.
For SpinLoop = 1 To SpinLoopCount

' Broadcast the current step position.
SerTxd(#StepCurrent, ",", #StepDesired, CR, LF)

' Do we need to run the motor?
If StepCurrent <> StepDesired Then

' Switch the motor on.
High MotorPin
Pause 20

' Wait for the beam to be unblocked.
Do GoSub CheckBeam
Loop Until BeamBlocked = 0


Pause 20

' Wait for the beam to become blocked again.
Do GoSub CheckBeam
Loop Until BeamBlocked = 1

' Switch the motor off.
Low MotorPin

' Increment step current to indicate a change of step.
Inc StepCurrent
If StepCurrent = TotalSteps Then
StepCurrent = 0
End If
End If


Next SpinLoop

Loop

' Checks whether the beam is blocked or not.
' Returns BeamBlocked = 0 for an unblocked beam, BeamBlocked for a blocked beam.
CheckBeam:
PwmOut IRPwmPin, 25, 53 ' 38kHz, calculated via PICAXE->Wizards->pwmout
Pause 1
BeamBlocked = IRReceiverPin
PwmOut IRPwmPin, Off
Return

The BASIC program on the PICAXE constantly outputs the current position and desired position via the serial programming cable as ASCII in the format ,. It also checks for the desired position every loop on via a serial input pin (sadly not the one used for programming the PICAXE as that is not permitted on the 08M) in the format ,<~desired>. (again in ASCII). The desired position is transmitted twice, once normally and the second time inverted (all zero bits set to one and all one bits set to zero) as a simple form of error detection; should the second value received not be a logical inversion of the first then the value is discarded.

Schematic thumbnail
Click to download the schematic

A copy of the schematic can be downloaded by clicking the above thumbnail. It is pretty simple; serial data is input on pin IN1 (move the serial input from the programming cable from SERIAL_IN to IN1), an IR LED is driven from pin PWM2 via a current-limiting resistor, an IR receiver sends its input to pin IN3, a Darlington pair drives the motor via pin OUT4 and information is sent out via the SERIAL_OUT pin (no need to move the programming cable for that one).

Triggering the camera

My camera does not have a standard remote control, but does has some software that allows you to capture shots when it's connected to your USB port. Unfortunately the Canon PowerShot SDK is rather old and is no longer maintained, which means that any software that uses it is bound to its bugs and limitations. One of its bigger problems is that it doesn't work on Vista; by setting the Remote Capture utility into XP compatibility mode I could set up a shot and see a live viewfinder but attempting to release the shutter caused the app to hang for about a minute before claiming the camera had been disconnected.

Fortunately VirtualBox emulates USB and serial ports so I set up Windows XP in a virtual machine and installed the Remote Capture utility. It still doesn't work very well (taking about thirty seconds between releasing the shutter and transferring the image) but it's better than nothing.

To control platform I use the following C# code. It's very poorly written (you need to make sure that you quickly set the Remote Capture application as the foreground window when you start it, for example, and it has a hard-coded 10 second delay after taking the photo to transfer the photo from the camera to the PC -- when my camera's batteries started going flat it started to drop frames).

using System;
using System.Globalization;
using System.IO.Ports;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.Linq;

class Program {

const int StepsInRevolution = 243;

enum ApplicationState {
AligningStepper,
WaitingStepperAligned,
WaitingStartPistol,
Photographing,
Exiting,
}

static void Main(string[] args) {
StringBuilder receivedData = new StringBuilder();
using (var serialPort = new SerialPort("COM1", 4800, Parity.None, 8, StopBits.Two)) {
serialPort.WriteTimeout = 1;

serialPort.Open();

var packetFieldsRegex = new Regex(@"^(\d+),(\d+)$");

int? currentPosition = null;
int desiredPosition = 0;
int? confirmedDesiredPosition = null;
int startPosition = 0;

int angleCount = 64;
int currentAngle = 0;

serialPort.DataReceived += new SerialDataReceivedEventHandler((sender, e) => {
if (e.EventType == SerialData.Chars) {
receivedData.Append(serialPort.ReadExisting());
string receivedDataString;
int newLinePosition;
while ((newLinePosition = (receivedDataString = receivedData.ToString()).IndexOf("\r\n")) != -1) {
var packet = receivedDataString.Substring(0, newLinePosition);
receivedData = receivedData.Remove(0, packet.Length + 2);
var packetFields = packetFieldsRegex.Matches(packet);
if (packetFields.Count == 1) {
currentPosition = int.Parse(packetFields[0].Groups[1].Value, CultureInfo.InvariantCulture);
confirmedDesiredPosition = int.Parse(packetFields[0].Groups[2].Value, CultureInfo.InvariantCulture);
}
}
}
});

ApplicationState appState = ApplicationState.AligningStepper;

// Main loop.
while (appState != ApplicationState.Exiting) {
// Update the stepper position.
try {
serialPort.Write(string.Format(CultureInfo.InvariantCulture, "\r\n{0},{1}.", desiredPosition, (byte)~desiredPosition));
} catch (TimeoutException) {
serialPort.DiscardOutBuffer();
}
Thread.Sleep(10);
// What are we doing?
switch (appState) {
case ApplicationState.AligningStepper:
if (currentPosition.HasValue) {
desiredPosition = (currentPosition.Value + 5) % StepsInRevolution;
appState = ApplicationState.WaitingStepperAligned;
}
break;
case ApplicationState.WaitingStepperAligned:
if (currentPosition.Value == desiredPosition) {
startPosition = desiredPosition;
appState = ApplicationState.WaitingStartPistol;
//while (Console.KeyAvailable) Console.ReadKey(true);
//Console.WriteLine("Press any key to start rotating...");
}
break;
case ApplicationState.WaitingStartPistol:
//while (Console.KeyAvailable) {
// Console.ReadKey(true);
appState = ApplicationState.Photographing;
//}
break;
case ApplicationState.Photographing:
if (currentPosition == desiredPosition) {
Console.Write("Taking photo {0} of {1}...", currentAngle + 1, angleCount);
SendKeys.SendWait(" ");
Thread.Sleep(10000);
Console.WriteLine("Done!");
if (currentAngle++ == angleCount) {
appState = ApplicationState.Exiting;
} else {
desiredPosition = (startPosition + (currentAngle * StepsInRevolution) / angleCount) % StepsInRevolution;
}
}
break;
}
}

Console.WriteLine("Done.");
Console.ReadKey(true);
}
}
}

It was meant to prompt to press a key before starting to allow you to re-align the object to the starting position (if required) but this would switch focus away from the Remote Capture utility. I'll probably fix this to switch the focus explicitly to the Remote Capture utility before sending the key to trigger a capture, and will also add code that polls the photo destination directory to spot when the file has been downloaded from the camera instead of the hard-coded 10 second delay. Working in the virtual machine and with the buggy Remote Capture utility is a frustrating endeavour so I left it as it is for the time being!

Stitching the photos together

Once the photos had been taken they needed to be stitched together into a single file. I decided to use 64 angles for a complete revolution as this seemed a good trade-off between fine control over rotation and a decent file size. It also allowed the images to be arranged into a neat 8x8 grid.

I first used VirtualDub to crop each image. VirtualDub allows you to open an image sequence and export to an image sequence so it seemed ideal for the task. Once I had the object neatly cropped I stitched all of them together into a large single PNG file using the following C# program:

using System;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;

class Program {
static void Main(string[] args) {
var middleImage = 14; // Index of the "middle" (default angle) image.
var nameRegex = new Regex(@"Processed(\d{2})");
var images = new Bitmap[64];
try {
foreach (var file in Directory.GetFiles(@"D:\Documents\Pictures\Digital Photos\Projects\Line Blanker\Insides 360\Processed", "*.png")) {
var matches = nameRegex.Matches(file);
if (matches.Count == 1) {
images[int.Parse(matches[0].Groups[1].Value)] = new Bitmap(file);
}
}
var maxSize = new Size(0, 0);
for (int i = 0; i < images.Length; i++) {
if (images == null) {
Console.WriteLine("Image {0} missing!", i);
} else {
maxSize = new Size(Math.Max(images.Width, maxSize.Width), Math.Max(images.Height, maxSize.Height));
}
}
using (var finalImage = new Bitmap(maxSize.Width * 8, maxSize.Height * 8)) {
using (var g = Graphics.FromImage(finalImage)) {
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
for (int x = 0; x < 8; ++x) {
for (int y = 0; y < 8; ++y) {
var image = images[(x + y * 8 + middleImage) % images.Length];
if (image != null) {
g.DrawImage(image, new Point(x * maxSize.Width + (maxSize.Width - image.Width) / 2, y * maxSize.Height + (maxSize.Height - image.Height) / 2));
}
}
}
}
finalImage.Save("out.png");
}
} finally {
for (int i = 0; i < images.Length; i++) {
if (images != null) {
images.Dispose();
images = null;
}
}
}
}
}

The program requires that the input images are named Processed00.png to Processed63.png, which is easily arranged when exporting an image sequence from VirtualDub. The resulting image can be tidied up in a conventional image editor.

Resulting image grid

Embedding the result on a web page

The final bit of code required is to allow the 360? image to be embedded and manipulated on a web page. I opted to use JavaScript for this task as it seemed the lightest and simplest way to work.

if (typeof(Rotate360) == 'undefined') {
var Rotate360 = new Class({
Implements : [Options, Events],
options : {
width : 320,
height : 240,
container : null,
element : null
},
sign : function(v) {
return (v > 0) ? +1 : (v < 0 ? -1 : 0);
},
initialize : function(source, options) {
this.setOptions(options);
this.source = source;
var rotate360 = this;
this.element = new Element('div', {
'class' : 'rotate360',
styles : {
width : this.options.width + 'px',
height : this.options.height + 'px',
background : 'transparent no-repeat url("' + this.source + '") scroll 0 0'
},
events : {
mouseenter : function(e) {
if (typeof(rotate360.mouseHandlerDiv) != 'undefined') {
var myPosition = rotate360.element.getCoordinates();
rotate360.mouseHandlerDiv.setStyles({
left : myPosition.left + 'px',
top : myPosition.top + 'px',
width : myPosition.width + 'px',
height : myPosition.height + 'px'
});
}
}
}
});
this.mouseHandlerDiv = new Element('div', {
styles : {
position : 'absolute',
cursor : 'e-resize'
},
events : {
mousemove : function(e) {
if (typeof(rotate360.mouseHeld) != 'undefined' && rotate360.mouseHeld && typeof(rotate360.previousPageX) != 'undefined' && typeof(rotate360.previousPageY) != 'undefined') {
var currentBackgroundPosition = rotate360.element.getStyle('background-position').split(' ');
currentBackgroundPosition[0] = parseInt(currentBackgroundPosition[0]);
currentBackgroundPosition[1] = parseInt(currentBackgroundPosition[1]);
if (typeof(rotate360.rotateX) == 'undefined') rotate360.rotateX = 0;
rotate360.rotateX += (e.page.x - rotate360.previousPageX) / (360 * (rotate360.options.width / 270) / ((rotate360.image.width * rotate360.image.height) / (rotate360.options.width * rotate360.options.height)));
var workingAngle = parseInt(rotate360.rotateX);
currentBackgroundPosition[0] = -rotate360.options.width * (workingAngle % (rotate360.image.width / rotate360.options.width));
currentBackgroundPosition[1] = -rotate360.options.height * Math.floor(workingAngle / (rotate360.image.height / rotate360.options.height));
while (currentBackgroundPosition[0] > 0) currentBackgroundPosition[0] -= rotate360.image.width;
while (currentBackgroundPosition[0] <= -rotate360.image.width) currentBackgroundPosition[0] += rotate360.image.width;
while (currentBackgroundPosition[1] > 0) currentBackgroundPosition[1] -= rotate360.image.height;
while (currentBackgroundPosition[1] <= -rotate360.image.height) currentBackgroundPosition[1] += rotate360.image.height;
rotate360.element.setStyle('background-position', currentBackgroundPosition[0] + 'px ' + currentBackgroundPosition[1] + 'px');
rotate360.previousPageX = e.page.x;
rotate360.previousPageY = e.page.y;
} else {
rotate360.previousPageX = e.page.x;
rotate360.previousPageY = e.page.y;
}
},
mousedown : function(e) {
e.stop();
rotate360.mouseHeld = true;
rotate360.mouseHandlerDiv.setStyles({
left : 0,
width : '100%'
});
},
mouseup : function(e) {
e.stop();
rotate360.mouseHeld = false;
rotate360.element.fireEvent('mouseenter');
}
}
}).inject(document.body, 'top');
this.image = new Asset.image(this.source, {
onload : function() {
if (rotate360.options.element) {
rotate360.element.replaces(rotate360.options.element);
} else if (rotate360.options.container) {
rotate360.options.container.adopt(rotate360.element);
}
}
});
}
});
window.addEvent('domready', function() {
$$('img.rotate360').each(function(rotate360) {
var src = rotate360.src.replace(/\.([a-zA-Z]+)$/, '_360.$1');
var img = new Asset.image(src, {
onload : function() {
new Rotate360(img.src, {
width : rotate360.width,
height : rotate360.height,
element : rotate360
});
}
});
});
});
}

The above code requires MooTools (both "core" and "more" for its Asset classes). It can be invoked manually or (preferably) will replace any image with a class of rotate360 with the 360? version -- if the file was example.jpg the 360? version would be example_360.jpg.

Examples

I've taken photos of a few of my previous projects using this technique -- USB remote control, AVR TV game and VGA line blanker. The process could use some refinement but it certainly seems to work!
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: Red Tip /_\.
| ++ | 1: Data >---< 2: White Ring }_{
4|o o|3 3: Gnd >-+-< 3: Copper Base | |
\ / __|__ |_|
\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:
  1. 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.
  2. Set the I register to the most significant byte of the start address of our lookup table.
  3. 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
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

Fire Track

When people ask, in the future, what the greatest feature of Fire Track is, they will not say the graphics. Nor the gameplay. In fact, they will... well, see for yourself. (Sorry, it's a bit quiet). [smile]
There is one more attack pattern. I have redesigned the attack system so that the enemies do NOT have their clocks auto-incremented (though they can call a function in their update code to do so if they really need it). This is so I can use the clock to store states instead. The new attackers slide in from the top-right, move down until they are level with the player then slide right again.
One peculiarity of gameplay in Fire Track is that you are permanently firing. You can never let go of the fire button. Also, the enemy ships do not fire at you - they just try to crash in to you.
I ran out of room. The game refused to compile. So, with a bit of tweaking in the map editor and a rewritten scrolling routine, I have doubled the amount of free space (without the music, about 10000 bytes are free). I'm using RLE compression on each 20 bytes of the level. If I get even tighter for space, I could probably pack the 8 bit level bytes into 6 bits.

Here's an awful sprite of an explosion. It looks really ropey, but it's as close as I can get to the authentic Fire Track (ship) explosions. I don't know how good it'll look in action!

benryves

Oldschool 3D

All this DOOM work had got me interested again in simple 3D engines, and so thoughts turned to my favourite Z80 platform, the TI-83 Plus.

There are a handful of 3D engines out there for it already; Matt3D is a vector-based wireframe engine put to good use in a roller-coaster builder/simulation game and a racing game (that even supports two-player over the link port). However, it's 8-bit and so worlds created with it tend to be very small or distorted thanks to low-resolution (I tried writing a Quake game with it years ago).

The best "wall" engines (displaying a 3D world rather than a 3D object) have been the raycasters. Gemini impresses me the most; it's a Wolfenstein-level engine with objects, sliding "half-block" doors and moving wall blocks, all neatly texture-mapped.

I'm going to try, therefore, to get a vector-based "wall" engine (there must be a proper term for it) up and running. Keeping the map to 2D simplifies the maths a lot; hopefully with a few tricks things should be fast enough! Also, I'll use 16-bit arithmetic throughout to enhance the quality and scale of the maps.



Like DOOM, I'm isolating the vertices from the wall definitions. This way, I can cut down on the number of rotations/transformations required. The maths required to rotate a point around the origin is fairly simple;

Xr = Xo x sin(a) + Yo x cos(a)
Yr = Xo x cos(a) - Yo x sin(a)


For the moment, I'll use a simple lookup-table for wall heights. Ultimately, I'd like to have variable height walls, but these will do for the moment:



The translated X is just 48+(Xr/Yr), as the screen is 96 pixels wide.

Throwing in a few extra lines for walls...



As you might be able to see, there is no clipping if any part of a wall falls outside the viewing range. Also, the walls are not occluding eachother, something you'd really want.
benryves
...and here is level 6. This, however, worries me:



Hopefully I can cram in all the rest of the code, tables, graphics and music into 5K. Failing that, I might have to start compressing my levels...

I don't believe I've introduced you to my enemies yet? Here's the complete sprite table - don't worry, the letters P, A, U, S, E and D do not feature as enemy craft!



I spent some time adding a new command systen to QuadPlayer, so you can now set a song to loop back X times (or infinitely) to a particular spot. It'll only loop from the end, sadly. Nested repeats will be too evil to try and implement. I'd like to experiment more with white noise - maybe a command to set up certain channels as being white noise rather than tones? Not sure how I'd generate the white noise, as the register R (which is the source of my random number generators) isn't going to be very random if accessed in a short loop!

Sample song, from a Kirby game (I added the fade at the end manually, that's not produced by the calculator!) Not bad, seeing as it has been generated by a calculator with no proper sound hardware. [smile]
benryves
The level I've been working with as a test for the TI-83+ 3D engine was something I quickly threw together. I've never been much good at the design side of things, and my lack of imagination was producing something very simple that wasn't really challenging the engine and testing whether it could be used in a game. Looking for inspiration, I played through map E2M7 in DOOM and found a fairly interesting room to try to convert.

DOOM E2M7????Nostromo 3D engine comparison

DOOM E2M7????Nostromo 3D engine comparison

DOOM E2M7????Nostromo 3D engine comparison

DOOM E2M7????Nostromo 3D engine comparison

DOOM E2M7????Nostromo 3D engine comparison

I'm sure you can tell which is the original room from DOOM and which is my adaptation of it.

Since the last post I have had to make quite a few tweaks to the engine. In the previous build there was a bug which cropped up when the top or bottom edges of walls appeared above or below the screen bounds. This turned out to be caused by a routine that was intended to clip a signed 16-bit integer to the range 0-63; it would return 0 for values 128 to 255 instead of 63. Fortunately this was an easy fix.

Another bug was in the way "upper and lower" walls were handled. Sectors have different heights and "upper and lower" walls go between two adjacent sectors and connect the ceiling of one to the other and the floor of one to the other, leaving a gap in the middle.

Sector transitions

The above picture shows the four main types of sector-to-sector transition through an "upper and lower" wall type. Different transitions require different numbers of horizontal wall edges to be traced; in the bottom left example (going to a sector that has a lower ceiling and higher floor than the current one) four lines would be required and in the top right example (going to a sector that has a higher ceiling and lower floor than the current one) two lines would be required. The previous version of the engine always drew four lines, which would produce a spurious line above or below the "hole" for three out of the four different combinations of sector-to-sector transition. By comparing sector heights the right number of horizontal lines can be drawn, which greatly improves the appearance of the world and slightly increases the performance, too.

A less immediately obvious limitation was in my implementation of the BSP tree structure. Each node on the tree splits the world geometry into two halves; one half is in "front" of the partition and the other is "behind" it. Each chunk of split geometry can be further subdivided by additional partitions until you're left with a collection of walls that surround a convex region. You can then walk the tree, checking which side of each partition you are on to determine the order that the walls should be rendered in. For more detailed information the Wikipedia article on binary space partitioning is a good starting place but the basic requirement is that you should be able to slice up level geometry into convex regions with partitions. I had na?vely assumed that horizontal or vertical partitions would be sufficient (and they are useful as you can very quickly determine which side of a horizontal or vertical line the camera is on). However, this room demonstrated that such a limitation would not be practical.

Geometry that cannot be partitioned into convex regions with horizontal/vertical lines

Consider the above geometry. The black lines are walls and the small grey lines represent the wall normals; that is, the walls face the inside of the "Z". The wall in the middle is double-sided; you could put the camera above or below it and see it. However you slice that map up with horizontal or vertical partitions you will still end up with regions that are not convex.

Arbitrary partitions can split up the geometry into convex regions

A single partition along the central wall divides the map into two convex regions. I had initially thought that checking which side of such a partition the camera lay would be an expensive operation, but it's not too bad; as a line can be represented by the expression y=mx+c I can store the gradient m and y-intercept c in the level data and simply plug in the camera's x and compare to y to determine the side. A single multiplication and an addition isn't too much to ask for.

Map for the room adapted from E2M7

Fortunately, there are only two of these partitions in the level!

I have added some other features to the demo program. Pressing Zoom when using a calculator that can run at 15MHz (a TI-83+ SE or any TI-84+) toggles the speed between 6MHz and 15MHz. Pressing Mode or X,T,?,n allows you to look up or down. Pressing Window toggles between the default free movement and a mode which snaps you to a fixed height above the floor. These additions are shown in the below screenshot (click to view the animation):

E2M7 room demo

Unfortunately, performance is lousy. Viewing the stairs drops the framerate to a rather sluggish 6 FPS when running at 6MHz (most of the above screenshot is recorded at 15MHz). The LCD's natural motion blur helps a little (it feels a lot more fluid on the calculator than it does on a PC emulator) but I'm aiming for a minimum of 10 FPS, so I need to make quite a few optimisations. There are several low-level ones that could be made; for example, when clipping the 2D line segments to the display I'm using a generic line clipper that clips the line both horizontally and vertically. As wall has been clipped to the horizontal field of view by that point I only really need to clip it to the top and bottom edges of the display. There are also some high-level optimisations to be made; for example, double-sided walls are currently stored as two distinct walls with the vertex order swapped. This means that to handle both sides of the wall the engine has to clip and project it twice, which involves lots of expensive divisions and multiplications. The results of these operations could be cached so that they only needed to be calculated once.

A TI-83 and TI-83+ binary is available if you'd like to try the demonstration on your calculator: please download Nostromo.zip. The usual disclaimers about backing up your data before running the program apply!
benryves


A silly project from over the weekend: JavaScript raycaster.

I couldn't work out how to handle keypresses in Firefox, and Firefox is too slow and flickery to make it worthwhile. IE runs it fine but at less than 1FPS, meaning that more often than not you end up walking through the walls. Opera is about the only browser it works happily in (~12FPS). Ah well.
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
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:
  1. 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.
  2. 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.
  3. 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

Cogwheel on Google Code

On bakery2k1's suggestion, I took a look at my sprite collision flag code. It only checked for collisions between sprites appearing in the foreground - sprites that were hidden behind a background tile weren't checked, which caused the Fantastic Dizzy bug.


I have decided to give Google's free project hosting a whirl. As usual, they can't get it working in Opera [flaming] (no big surprises there) and it's horrifically slow, but I can't really complain given that it's free, and the issue tracking is pretty handy.


Another game that has been fixed is the conversion of that arcade classic, Gauntlet. This was partially due to buggy controller port emulation, but a long-standing bug has been the lack of the y-scrolling inhibition flag. Games could set a flag on the VDP that would prevent the rightmost 64 pixels from scrolling vertically - this is useful to create a locked status bar on the right for a vertical scrolling game, for example.


I'm slowly getting there with FluBBa's VDP tester...
benryves

Screenshots galore

I downloaded a collection of homebrew releases from SMS Power! for testing. Here are the obligatory screenshots.

Sega Master System



Copyright Violation is displayed incorrectly - the text should be on top of the stars, but that VDP feature is as yet unsupported.

Sega Game Gear



Yes, Windows and DOOM were ported to the Game Gear. That second screenshot is DOOM's automap. [wink]

There are still lots of ROMs that won't go - that could be due to poor VDP emulation, or it could be due to ROM paging errors (the emulated memory is just straight 64KB RAM), or could be down to the remaining bugs in the Z80 emulation.
benryves
Cheers for the comments. [smile] As EasilyConfused pointed out, I have done calculator programming in the past, which makes this much easier - learning Z80 assembly to program a calculator influenced the choice of CPU in this computer, and porting BBC BASIC to the calculator showed that with a minimal amount of code to sit between it and the hardware you'd have a decent operating system with very little work. And if a Terminator-related name is good enough for the UK military, it should be good enough for this project...

The I/O board from a few posts ago has undergone a few revisions:


Both PS/2 ports are now fully wired up, though only the lower one is currently used by the OS for keyboard input. I will need to adjust the AT protocol routines (the AT protocol is used to control both keyboard and mouse) to support multiple physical ports, as it was adapted from code I wrote for the TI-83+ calculator and as such only supports one device at a time. The mouse position will be polled by calling ADVAL(axis%), which on the BBC Micro would return the joystick position (axis% specifies the type of information to retrieve from the mouse - a value of 0 returns the buttons as a bitfield, 1 returns the movement in the X axis, 2 the movement in the Y axis and 3 the amount the scrollwheel has been scrolled).

At the very bottom of the circuit board is another 8-bit latch. This is for the (currently) write-only control port. The three least significant bits specify the current ROM page (one of eight 16KB ROM pages can be swapped in for a total of 128KB) and the next bit specifies one of two 16KB RAM pages accessible in the $4000..$7FFF address range. One of the other bits will be used to switch the LCD backlight on and off in software, one more may be connected to a buzzer, and I'm sure I can find some use for the last two. As it's write-only, its current state needs to be stored in RAM so that you can change bits of it (eg when changing the ROM page you wouldn't want to change the backlight status at the same time; you'd need to retrieve the current state and mask in the bits you wish to preserve). This is obviously an ugly hack, and I'm hoping I'll be able to use some of the space to the right of the latch IC on the circuit board to add the other latch to allow the port to be read as well (an I/O port needs two latches - an output latch that takes data from the data bus and outputs it to external hardware, and an input latch that takes data from external hardware and puts it back on the data bus).

The first test of the new ROM paging hardware was to display a simple animation. Assuming 1KB on each ROM page was taken up by the animation playback program, that leaves 15KB per ROM page. A frame (128x64 pixels) is 1KB, so that's 15 frames per page, or 120 frames total. I converted a clip from Pink Floyd's Arnold Layne music video to a suitable format and wrote a playback routine that could run from RAM. When the computer booted it would copy the player to RAM and run it from there as it could then run uninterrupted when different ROM pages were swapped in to read the frame data.


Click for video (526KB XviD MPEG-4 AVI)

An animation like this is a useful test, as if the ROM paging didn't work properly (simulated by holding the three ROM page selection lines low) the software would still run, but would just loop the first 15 frames (or play chunks of 15 frames out of sequence) instead of crashing.

Another addition to the circuit above is the cluster of discrete transistors and resistors under the lowest PS/2 port. This is the same sort of pair of open-collector I/O data lines that drive each PS/2 port, except that the two data lines are fed out of the I/O board and back to the breadboard that's currently sitting between the memory board and the I/O board to these two simple 8-pin chips:


This is the I2C bus, a simple, low-speed, two-wire bus that will allow other components be easily connected to the computer. The I2C protocol is implemented in software. The two chips in the above image are a DS1307 real-time clock (foreground, with quartz crystal) which I hope to use for timing purposes and a 24LC256 32Kx8 EEPROM which I hope to use for file storage. I would need to have some way of accessing the I2C bus externally (to plug in EEPROMs as removable storage) as well as supporting the internal devices.

I haven't yet done any work on supporting I2C devices properly, but I have added I2C bus emulation to the emulator I'm using to develop the OS. BBC BASIC will pass commands prefixed with a *STAR to the operating system, so I've added a *I2CPROBE command that will hammer through all available addresses and list any devices that acknowledge a write request.


$A0 is the EEPROM and $D0 is the clock.


I think I may have dug myself into a hole for CPU timing. I mentioned that I will need to drop the CPU clock to 2MHz when accessing the LCD; unfortunately, switching between 2MHz and 10MHz doesn't seem to work very well. I can run the system relatively stably at either speed (though at 10MHz data sent to the LCD is occasionally corrupted) but if I try and switch dynamically (eg switching from the 10MHz to the 2MHz clock when /IORQ goes low to indicate an I/O request) the system locks up. My assumption is that during time it takes the logic gates that perform the 10MHz/2MHz switch to properly settle into their new state (which is in the tens of nanoseconds) the clock signal stutters a little, effectively producing a clock signal (albeit a brief one) well over 10MHz. I don't have an oscilloscope to verify this, however. [sad]
benryves
One of the fun things about working with electronics is that you can end up with a physical product at the end of your hard work. To this end I have started moving my Z80 computer from its current breadboard to a more permanent enclosure.

Project box outside Project box inside

Large project boxes can be quite expensive (around GBP40, it seems), but the one I picked out was a slightly more reasonable GBP7. It's not the prettiest enclosure I've seen but it should be large enough to house the computer and provide space on the lid for the LCD and on the rear surface for a collection of connectors (as you'd expect to find on the rear of any computer).


Perfboard shown inside the computer.

The first challenge was how I intended to mount the circuit board within the box. The perfboard I will use for the main computer circuit doesn't fit the marked mounting posts on the bottom of the project box; it's too narrow and too deep. What the photo doesn't show very well is that the perfboard is not able to lie flat in the box due to the curve at the rear of the box. To raise the board above the bottom of the box I decided to use four PCB spacers, which required two new holes to be drilled into the perfboard away from its corners.

Two new holes for PCB spacers Underside of the perfboard showing PCB spacers

I decided that the video display controller, which resides on its own board, should be mounted on the main circuit board using PCB spacers too.

Holes drilled to support the VDC VDC mounted on the main circuit board

This required four more holes to be drilled into the main circuit board. I tried to align the small video display board so that its 16-way pin socket for connection to the LCD was as close to the horizontal centre as possible.

Holes drilled to support the main circuit board Using the main circuit board to find the position of all of the screw holes

The base of the project box needed to have four holes drilled into it to support the main circuit board. Once the two nearest the front edge had been drilled, I screwed the circuit board to the back of the project box to mark the position for the other two holes to ensure that they lined up exactly with the holes drilled in the circuit board.

Both circuit boards mounted inside the box

Screws come through the bottom of the project box to hold the main circuit board in place. Some sticky foam feet are provided with the project box which will raise it off the surface it is resting on to prevent these four screws from leaving scratches! Due to the curve at the back of the box the circuit board is only a few millimetres above its surface, which is why I reversed the screws holding the video display board to leave the long threaded ends pointing upwards.

Power supply Power supply soldering detail

As working on the enclosure is a fairly noisy activity I switched my attention to the electronics for a brief spell. The first part of the circuit I assembled was the power supply; this just uses a pair of voltage regulators to provide 5V and 3.3V from an external power supply (I use a cheap wall wart affair rated at 7.5V DC).

Oscillator

I decided that the next part to tackle would be the oscillator. This uses a 20MHz crystal and a 74LS04 according to the design on z80.info to generate a 20MHz clock signal which will be further divided by two to produce a 10MHz clock signal for the Z80. I had some real problems with this design; it would run at 20MHz until I attached a load to it, at which point it would generate a fairly random-looking signal or stop oscillating entirely. I experimented with a few different capacitors and found that if I remove the 120pF capacitor and replace it with a 33pF capacitor on the other end of the crystal it works reliably. I'm not entirely sure why this is, but it's the design I've been using for a while with the computer on a breadboard so I'm happy to keep it this way for the time being.

ATmega644P

I added a D flip-flop to divide the 20MHz clock to 10MHz and then added the ATmega644P microcontroller to the board. This has a jumper next to its clock input allowing for the selection of either 20MHz or 10MHz operation; a pin header to the left of this jumper allows for it to be programmed in-circuit.

VDC reinstalled in the case

With those new parts in place I reinstated the video display board to check that everything still fit. My main concern now was how far the connectors screwed into the rear of the case would intrude and whether there'd be any problems with them getting in the way of the circuit boards.

Rear panel marked for mounting connectors

I sketched a design of how I saw the connectors would fit on the back of the case and then copied the layout to some masking tape stuck to the case. The computer naturally needs a power supply and keyboard input, and the video display board accounts for the VGA connector and an RCA connector for composite video (which I neglected to mark). I also hope to include a serial port and a parallel port in the final design (though neither are currently supported by the software) so left space for those two connectors.

Hole drilled for the keyboard connector Keyboard connector mounted in the case

The 6-way mini-DIN connector for the keyboard is the deepest one to contend with so I decided to start with it. I cut the hole in the case by drilling a small hole in the plastic which I then enlarged with a burr tool to the correct shape and size.

Keyboard connector screwed in

Fortunately it looks like there's plenty of room in the case for connectors!

Connectors for the serial port, composite video output and DC input Inside view of the case with some more connectors installed

The next few connectors confirm this. I really do not enjoy cutting the holes for D-sub connectors (such as the one for the serial port); they don't have much of a metal lip to hide a botched hole, so I have to cut very slowly and very carefully, taking a very long time to slowly enlarge each hole until the connector fits. I'm therefore not really sure why I decided to have three D-sub connectors in this computer design; maybe I'm just a glutton for punishment.

Completed rear panel Rear panel as seen from the inside of the case

Finally, the rear of the case is completed. I will leave the masking tape on there as scratch protection until I have finished the front of the case (this will be significantly simpler -- just a power switch, power LED and disk activity LED). Once that is done I can resume working on the electronics!
benryves
One notable gap in my TMS9918 (video) emulation was its "Multicolor" mode. This mode broke the screen down into 4x4 pixel squares, resulting in a 64x48 grid. Each cell could be assigned a unique colour, giving you a crude bitmapped video mode.

No Master System or SG-1000 software used this mode to my knowledge, which reduced the likelihood of it being supported at all (if I could't test it, how could I emulate it?) I was tipped off that some ColecoVision software made use of it, so set about emulating the ColecoVision.


The ColecoVision hardware is very similar to the SG-1000 in terms of what is inside the case - a Z80 CPU, TMS9918 video and SN76489 sound. The memory map (as in, which address ranges map to which memory devices) is different, as is the I/O map (as in, the I/O ports that the various hardware components are connected to). To handle this case, the emulator now has a Family field, which can be set to Sega or ColecoVision. This controls which of the different mappings it uses for memory and hardware I/O.

Another difference is the presence of a BIOS ROM. The Sega Master System and Sega Game Gear consoles had the option of BIOS ROMs, but all these did was very basic initialisation and header/checksum checking. The ColecoVision, however, has an 8KB BIOS ROM that offers a lot of functionality to the programmer, so this must be present to run most ColecoVision games.


The controllers are also quite different. As well as the typical eight-direction joystick and two fire buttons, it added a 12-key keypad (0-9, * and #). This many keys is making my InputManager class look thoroughly idiotic, so that will certainly need a rewrite.

Apart from that, it's pretty simple. RAM is 1KB instead of 8KB; the sound generator uses the standard 15-bit wide shift register (instead of the 16-bit wide one used in the SMS); the video display processor interrupt output is connected to NMI rather than INT.

With those differences applied, it's easy to add the few lines of code to emulate the Multicolor video mode.


Smurf Paint 'n' Play Workshop

ColecoVision emulation has not been tested at all thoroughly, so chances are it doesn't work very well; Multicolor emulation has been tested with precisely one game!

You can download the latest build of Cogwheel from its website, featuring the new ColecoVision emulation.