Jump to content

  • Log In with Google      Sign In   
  • Create Account

Any Colour You Like

Repairing a PlayStation controller to USB adaptor

Posted by , 20 April 2013 - - - - - - · 3,341 views
PlayStation, electronics and 1 more...
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!


Posted Image

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.


Posted Image

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.


Posted Image

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.


Posted Image

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:


Posted Image

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.


Posted Image

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.


Posted Image

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.


Laserstrike for Zen X-Fi 2

Posted by , 23 October 2011 - - - - - - · 2,167 views
Laserstrike, Zen X-Fi 2, Lua
It's been a long time since I posted about any of my projects for the simple reason that I haven't had any real time to work on them this year. Work commitments have not been particularly kind to my free time and there has been no progress on my 3D engine for TI calculators or any new electronics projects.

I did, however, replace my ailing Zen Xtra digital audio player with a Zen X-Fi 2 earlier in the year. The X-Fi 2 supports simple application development in Lua, a language I had no experience with, so I spent a few days in April knocking together a game as a learning project. I've always been fond of Kevin Ng's Laserstrike and it seemed a good fit for a device with a touch screen.

http://www.youtube.com/watch?v=XPIFncE22pw

I used the smaller levels from Badga's Laser Mayhem as it let me use larger tiles, otherwise it would be tricky to tap the correct block on the X-Fi 2's 3" screen.

Having not used Lua before the code is far from brilliant (for some reason I chose to represent the level as a string rather than an array, by way of example) but it works well enough and has occasionally kept me occupied on bus and train journeys. Rather than let the game stagnate on my hard disk drive I added a final bit of polish and have released it on my website. If you'd like to try the game but do not own an X-Fi 2 (which would be almost everyone reading this) you can play it in the Zen X-Fi 2 Application Development Kit (extract the game to C:\Creative\ZEN X-Fi2\Applications) but be warned that the simulator is a little buggy (it doesn't detect touch input in the 16 rows and columns at the top and left edges of the screen for starters).

Fingers crossed I can get more time for what I enjoy doing in 2012. I have plenty of fun ideas, but little time to put them into practice!


Back in Classic Black

Posted by , 27 January 2011 - * * * * * · 1,229 views
gamedev.net, classic black, css
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.

Posted Image

Posted Image

Posted Image


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.


Watched Forums Userscript

Posted by , 10 January 2011 - * * * * * · 801 views
gamedev.net, watched forums and 1 more...
I hope everyone had a very pleasant Christmas and New Year break. It's been a while since I last posted — I've been very busy with work of late and as such have not had much to report on my own projects.

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

Posted Image

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

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

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




A larger level, moving sectors and failed optimisation attempts

Posted by , 29 November 2010 - - - - - - · 1,727 views

I've made a few attempts to boost the performance of the 3D engine for the TI-83+ I'm working on with little success. I had previously failed to get any improvement by adding bounding boxes around each BSP node (the idea being that if a node falls outside the view you can discard it and, by extension, all of its children) but the act of transforming the bounding box to determine whether it was inside or outside the view was more CPU intensive than blindly handling the nodes whether they were inside the view or not.

A simpler test, I reckoned, would be to use bounding circles. These only have one point to transform, and determining whether they are in the view is one comparison to ensure that they're in front of the camera followed by one multiplication (by the constant √2) and two more comparisons to determine whether they are to the left or right of the camera's view; far simpler than a bounding box!

The bounding circles did cut down the number of BSP nodes that were handled each frame but the additional checks made the engine slightly slower in general than it had been before. In some circumstances it was slightly faster, but not enough to make a noticeable difference. The additional data per BSP node added over 900 bytes to the level data, too, so the attempted optimisation had to go.


Nostromo 3D engine demonstrationNostromo 3D engine demonstrationNostromo 3D engine demonstration

Nostromo 3D engine demonstrationNostromo 3D engine demonstrationNostromo 3D engine demonstration

Nostromo 3D engine demonstrationNostromo 3D engine demonstrationNostromo 3D engine demonstration

The newly added rooms to the demo level


One tweak that did boost performance noticeably was to cache the projected X coordinate of each vertex. All vertices in the map have at least two walls connected to them and so are projected to the screen at least twice if within the view. I already had a table that was used to indicate whether a vertex had been transformed around the camera or not that frame so it was easy enough to add the X coordinate of the projected vertex to that table, adding around a 15% boost to the framerate.

Points are projected to the screen by dividing their X (left/right) or Z (up/down) component by their Y (depth) component. Division is slower than multiplication so I tried to calculate the reciprocal of the depth for the vertex then perform all subsequent projection operations by multiplying the X or Z component by this reciprocal. Unfortunately, this resulted in a lack of precision owing to my use of 16-bit fixed-point numbers (walls "wobbled" as you moved the camera) and performance was about the same as it had been before, so I rolled back the changes.

The block of screenshots in the above text shows a new region that has been added to the demo level, and the image below is a map of that level — fans of DOOM may notice that it's based on a small portion of E2M7 (The Spawning Vats).

Demo map

Map of the level

This level now uses every one of the 256 walls that are available, so is probably a good indication of the maximum size of a single level (and at 6,626 bytes it's certainly rather taxing on the limited amount of memory in a TI-83+ calculator).

This is, however, the maximum size of a single level. It does not take long to load and unload levels, so it would be quite possible to construct a continuous level that appears larger by unloading the current one and loading a different one when the user moves to a particular region. This could be implemented in an obvious manner (such as the player stepping into a teleporter) or transparently (by moving the player into an identical copy of the room he left to hide the transition). The latter option also introduces the option of level geometry that would otherwise be impossible in a 2D-based engine, such as rooms above rooms. Special effects could also be tried, such as an infinite corridor that warps you back to the beginning when you reach its end.

Moving sectors and triggers

However this feature is implemented, there would need to be some way to trigger the action. The above animated screenshot demonstrates the current trigger system which is used to set a sector in motion. A sector, in this instance, is a region with a particular floor height and ceiling height. Each wall indicates which sector is in front of it and which sector is behind it. Convex sub-sectors contain sets of walls and also indicate which sector they are part of, and are attached to the leaves of the BSP tree. Given a point, you can quickly find out which convex sub-sector it is in by walking the BSP tree. When you have found the convex sub-sector you can then look up its sector. This is currently used to set the player's height, as the sector tells you the floor height.

If you keep track of the player's sector each frame you can tell when they have moved from one sector to another. This then fires an event, reporting which sector the player used to be in and which they are in now. In the above screenshot, the platform is set to descend whenever the sector surrounding it is entered from any sector other than the platform itself (this is to stop it from automatically descending when the player walks off the top of the raised platform). It is also set to rise whenever the platform's own sector is entered. This produces a simple lift; doors are handled in a similar fashion elsewhere in the level.

If you'd like to try this demo on your calculator, you can download the binaries for the TI-83 and TI-83+ in Nostromo.zip. As ever, please back up any important files on your calculator before running the demo; it may well clear your RAM. For those without calculators, an animated screenshot is available.


Collision detection makes the world feel solid

Posted by , 21 November 2010 - - - - - - · 1,664 views

One of the larger problems with the 3D engine for the TI-83+ calculator series I have been working on is that it's possible to move the camera through walls. This doesn't make the world feel especially solid, so I've started working on some collision detection routines.

Work commitments have left me with little time to spend on this project over the last couple of weeks so progress has been very slow, but I've got a basic collision detection system mostly working.

Nostromo settings screen

Click to view an animated GIF of the collision detection routines in action.

I spend most of the above screenshot running into walls. The code seems to work relatively well and quite quickly, though it's far from perfect. The still image shows the new settings screen, which is hopefully a little easier to use than remembering which keys do what. It also has the advantage of displaying the state of the current settings.

The walls are stored as line segments between two 2D vertices, and the collision detection has to ensure that the player does not get too close to any of these walls. The technique I have used starts by calculating the closest point on the line to the player.

Closest point to a line segment

The above image shows a wall (the solid line segment) and three possible player positions (the heavy dots). The arrows point to the closest point on the wall's line. The closest point on the line to the top player position is past the end of the line segment, so it is ignored. The other two closest points lie on the line segment, so these are checked in more detail.

Threshold distance between the line segment and player position

The distance between the closest point on the line and the player position is then calculated and compared to a threshold value (the radius of the player). The above image highlights the out-of-bounds region in tan. The lower player position is outside this region so is ignored, but the upper player position is inside it and needs to be corrected.

Corrected player position

The correction is quite straightforward. We know the closest point on the wall to the player. The angle of the wall's normal is stored in the level file, so we can easily calculate a vector from that to push the player a fixed distance away from the wall.

In addition to the above 2D checks, a very simple height check is performed for "upper and lower"-type walls. These are walls with a central hole so you can pass over or under them, and are used to connect sectors with varying floor and ceiling heights. The top of the player's head is used to check the ceiling height. Rather than use the height of the player's feet to check the floor height their knee height is used. This is to allow the player to climb low walls (such as the edges of steps).

When I first implemented these collision detection techniques I checked every wall in the map. This halved the framerate in places, and as the framerate is not particularly high in the first place I needed to find a way to reduce the number of tests. Taking further inspiration from DOOM I implemented a "blockmap". This breaks the map down into square blocks and each block contains a list of which walls pass through it. To perform collision detection I look up which block the player is in and from that I can retrieve a reduced list of which walls they may end up walking into. The original implementation had to check well over a hundred walls for each movement; the blockmap reduces this to 26 in the worst case scenario for the current level design.

Sadly, this additional blockmap enlarged the size of the map quite a bit, so I've attempted to reduce it a little. For simplicity and performance most structures referred to other structures by pointer (for example a sub sector contained a list of pointers to walls and each wall contained pointers to a front and back sector). I've changed most of these to now refer to other structures by index, which shaved a few hundred bytes off the map at the cost of a few hundred clock cycles. Overall performance still isn't great, though I haven't found it noticeably slower than the previous demos.

I added very primitive physics for moving the player up and down relative to the floor to complement the collision detection. This retrieves the floor height from the sector directly under the centre of the player and compares it to the current player height. If the new floor height is higher than the old floor height then the player's foot height is set to a point half way between the two; this smoothes the animation slightly when climbing up stairs (rather than just snapping to the new floor height). When moving from a higher floor to a lower floor the player's downward speed is increased to roughly simulate gravity.

A demo for the TI-83+ series and TI-83 can be found in Nostromo.zip. As always, this is a piece of software in development and there may be calculator-crashing bugs, so please back up any important files before running it.


Enlarging the world

Posted by , 07 November 2010 - - - - - - · 1,639 views

There have been very few changes to the features of Nostromo recently. I have tried a number of ways to optimise the performance and whilst the handful of micro-optimisations I have made have boosted the frame rate a little none of the higher-level optimisations have done much. I did try, for example, storing a bounding box around each BSP node and ignoring it (and all its children) should this bounding box fall outside the field of view; the additional code to check the bounding box ended up halving the framerate rather than improving it.

Nostromo 3D engineNostromo 3D engineNostromo 3D engine

Nostromo 3D engineNostromo 3D engineNostromo 3D engine

I have, however, enlarged the level quite considerably. A staircase connects the central room with the pit to a rather strangely-shaped arrangement of walls (again copied from E2M7). The room with a pit continues to cause issues; looking across it towards the room with the small central staircase forces the engine to step through a very large number of convex sub-sectors and check many walls. This drops the frame rate down to about 3 FPS on a TI-83+. However, this is specific to that room; the newly-added rooms have not noticeably affected the frame rate in other parts of the level.

Another minor improvement is that the engine now supports different sprites. I'm not too good at drawing them, as you can probably tell from the above screenshots, but at least the code is there to support them.

You can download a TI-83 and TI-83+ binary to try the demo on your calculator (please back up any important files first). Alternatively, here is an animated screenshot.


Adding sprite objects to the 3D world

Posted by , 02 November 2010 - - - - - - · 1,257 views

The previous entry showed a room from a map copied from DOOM's E2M7. I have since added the adjacent room:

DOOM E2M7Nostromo 3D engine comparison

DOOM E2M7Nostromo 3D engine comparison

It may not look as interesting as the other room but it is significantly more costly to render due to the sheer number of lines visible at a time in it. Looking across it from the far corner dropped performance down to around 2 FPS on the 6MHz TI-83+, which was really not a very good effort. I spent a fair amount of time over the weekend trying to optimise the code as much as I could, and manage to bring the frame rate in the player's starting position from 6 FPS up to the target 10 FPS. Looking across the length of the new room still dropped the framerate to around 4 FPS at 6MHz, but it's a start.

Once the engine had been made a little faster it seemed a good idea to slow it back down again by adding a feature. I had been pondering how to add objects in the form of scaled sprites to the world. Working out where to put them on the screen isn't so difficult, but clipping them against the world geometry so that they couldn't be seen through walls is another matter entirely. One way that seemed popular is to draw the objects in reverse depth order (drawing the sprites that were further away before those that were closer) and using a depth buffer for the world geometry to clip each pixel of the sprite against the world. This would take up a lot of memory on the calculator and run very slowly (populating the buffer with a depth value for each pixel would be a very expensive operation, as you'd have to interpolate depth values between the ends of walls and edges of floors).

The engine makes use of three per-column clipping tables when rendering the scene. One keeps track of columns that have been completed (usually by drawing a "middle" wall to that column); once completed no more pixels may be drawn to that column. The other two tables are used to define the upper and lower clipping bounds. At the start of the scene these are reset to the top and bottom edges of the display. As the world is rendered from front-to-back these regions are reduced to clip geometry that is further away against geometry that is nearer (think of looking through a hole in a wall — things that are further away from you will never be drawn in the space above or below that hole).

Fortunately, you can use this clipping information to clip sprites against the world geometry too. Each sprite object needs to be associated with a convex subsector. These subsectors are made up of walls and are drawn from front-to-back (sorted by the BSP tree) as the world is rendered. Before each one of these subsectors is drawn it is checked to see if it contains any sprite objects — if it does, the current clipping buffers and a reference to the subsector are pushed onto a stack. When all of the walls and floors have been drawn this stack contains a list of all of the subsectors containing sprites and the clipping regions used to draw those subsectors in front-to-back order. Stacks are Last In, First Out structures and so when you pull the data back out of this stack you end up retrieving a list of sprites to draw and the associated clipping regions in back-to-front order. This allows you to effectively unwind the clipping operations, so as you draw the sprites from back-to-front you can gradually enlarge the clipping regions in the opposite order to the way that they were reduced when drawing the walls. You would still need to sort the sprites manually from back-to-front within each subsector, but for the time being I've limited myself to one sprite per subsector for ease of development.

Sprite object testSprite object test

The above screenshots demonstrate an initial test of the "things" (as they are apparently technically called), rendering them as solid black squares.

Sprite object testSprite object test

Scaled sprites tend to be more useful than solid black squares, however, so here are a pair of candlesticks (well, that was the intention at any rate; call them cacti if you must). The sprite was simply ORed to the display, so pixels could be black or transparent.

Lights

I then added support for "white" pixels too. The above screenshot is a link to an animated GIF showing the engine in action. The sprites appear to jiggle up and down and have invalid data drawn underneath them in places, which was caused by my accidentally overwriting an important register before rendering each column (fortunately an easy one to spot)! The relatively high frame rate in the above image was helped by running at 15MHz and using the old single-room map.

The two-room map with animated doors

The above screenshot (click for the animated GIF) fixes the dancing sprites and restores the second room, though is still running at 15MHz. For a bit of fun I added animated doors; all this does is adjust the floor heights of the sectors used to represent "doors" (pressing Alpha will toggle both doors open or shut) but it makes the world look a little more dynamic.

There are still some rendering bugs in the engine. The above animated screenshot demonstrates one; when close to a wall edge you will sometimes see a temporary vertical line the height of the screen or the screen will flash white. I reckon this is probably an integer overflow issue causing the projected height of a line to be on the opposite side of the screen than the one it should be (the bottom edge of a hole in a wall may be projected above the screen rather than below it, causing the entire screen to be clipped out, for example). One bug that took a while to identify (it only appeared in very particular positions; moving one unit in any direction cured it) was caused by truncating a 32-bit integer to a 24-bit one. When viewing a long wall from a long distance the result of a 16-bit (difference between end and start X coordinates) by 16-bit (Y coordinate of the start of the wall) multiplication was resulting in a value of $00800000 or so (a large positive number). When truncated to 24 bits this becomes $800000, which has the most-significant bit set and was therefore treated as a large negative number. As this was part of the wall clipping code it would end up clipping a wall end a long way behind the camera instead of within the view; fortunately this obvious mistake is easy to spot and correct (the answer can only be positive, so if you get a negative one just negate it).

If you'd like to try the demo on your own calculator please download Nostromo.zip. As this is a work in progress there are likely to be bugs so please back up any important files first!


Adapting a room from DOOM's E2M7 to the TI-83+ calculator

Posted by , 28 October 2010 - - - - - - · 1,124 views

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 E2M7Nostromo 3D engine comparison

DOOM E2M7Nostromo 3D engine comparison

DOOM E2M7Nostromo 3D engine comparison

DOOM E2M7Nostromo 3D engine comparison

DOOM E2M7Nostromo 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 navely 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!


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

Posted by , 25 October 2010 - - - - - - · 736 views

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

TI-83+ 3D engine screenshotTI-83+ 3D engine screenshot

TI-83+ 3D engine screenshotTI-83+ 3D engine screenshot

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

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

Bounding rectangles around wallsFilled trapezia behind wall outlines

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

'Coloured' trapeziaVariable height walls

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

Streaky LCD

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

Wireframe world imported from a C# level

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

Per-column completed wall counter

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

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

Culled completed columns

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

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

Columns clipped against upper and lower bounds

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

Columns clipped against upper and lower bounds

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

Nostromo 3D engine demo running on a TI-83+
Download Nostromo.zip

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


A primitive 3D engine for the TI-83+

Posted by , 17 October 2010 - - - - - - · 1,603 views

As you may have guessed from the number of spinning cubes in my projects, I am quite fond of primitive 3D. As you may also have guessed from the number of TI-83+ calculator projects I have undertaken, I'm also quite fond of programming on low-end machines. I have never really successfully put 3D and the TI-83+ together, though.

TI-83+ Raycaster

One way to build a 3D world in software is raycasting (e.g. Wolfenstein 3D). This typically results in blocky worlds where all walls are at 90° angles to each other. There are several games using raycasting engines on the TI-83+ already; they are much faster and better-looking than my sorry attempt pictured above.

TI-83+ 3D 'Quake'

Another method is to use true 3D geometry (e.g. Quake). Many years ago I attempted to work on something that looked a little like Quake. I built this on the Matt3D engine, which supported basic 8-bit coordinates and lines, but not solid objects. The result was even less useful than the above raycaster!

Attempt at a 2.5D engine.

Another method somewhere between the two is a "2.5D" engine, where level geometry is defined between points in 2D space but projected in 3D (e.g. DOOM). This allows for walls that are not at 90° angles to each other, whilst simplifying the rendering procedure significantly. I spent some weeks working on such an engine a few years ago yet never managed to get any further than the above screenshot. As you can probably tell from the fact that you can see the walls through each other I never found a good way to handle occlusion, and the project ended up stagnating.

TI-83+ 3D engine screenshotTI-83+ 3D engine screenshot

TI-83+ 3D engine screenshotTI-83+ 3D engine screenshot

Looking for a quick weekend project I thought back to the work I'd done with the DOOM and Quake engines. These engines use a BSP tree structure to sort the level geometry for rendering. I reckoned that if simplified a little a similar tree structure could be used to render a 3D world on the TI-83+ calculator. The four screenshots above show that this technique is indeed quite successful. My implementation could certainly do with a lot of work but I think the theory is at least sound.

Target level for the BSP renderer.

I decided that one way to make this project a bit more fun was to set myself a challenge; to design a level that I would, ultimately, be able to walk around in. This level is shown above, and contains a number of walls that are not parallel to the X or Y axis and a pillar. I have split the world into eight convex "sectors" (labelled 0 to 7) with a dotted line between them to show where the BSP tree is partitioned. All of the partitions are either horizontal or vertical to speed up tree traversal; the TI-83+'s Z80 CPU does not support floating point arithmetic, let alone multiplication or division, so being able to decide which side of a partition you're on quickly is very useful.

C# prototype of the BSP renderer

Rather than dive straight into Z80 assembly programming I knocked together a quick prototype in C#. This allows for quick and easy debugging; the blocks of colour allow me to quickly identify walls and the application title bar contains the order in which the sectors have been rendered. These can then be checked against the version running on the TI-83+ in case there are problems.

Vertex transformation

With the C# version running satisfactorily I started converting it to Z80 assembly. The above screenshot shows the first step; transforming the level's vertices around the camera. Clicking on the screenshots will take you to an animated version; as some of them are quite large I have linked to them rather than embedding them directly.

BSP tree traversal

The next step was to traverse the BSP tree. The numbers across the top of the screen indicate the order in which to render the sectors, from back to front — however, due to a simple bug, they are actually listed from front to back. This was fortunately very easy to fix.

Walls

Walls are connected between the vertices, so I quickly threw something together to display all of the walls on the screen. The walls will have to be clipped against the camera's view (or discarded entirely if they are outside the view) so being able to see them is a great debugging aid!

Clipping against Y=0

We are only interested in drawing walls that are in front of the camera, so the first bit of clipping code deals with clipping the walls against Y=0.

Culling from Y=+X and Y=-XClipping against Y=+XClipping against Y=-X

The above screenshots show the final three stages of clipping to the camera's view, defined by Y>0, Y>+X and Y>-X. The first screenshot shows culling of any wall that does not satisfy this in any way; walls that are completely outside the view are discarded. The second screenshot shows walls being clipped against Y=+X, and the third finally adds clipping against Y=-X. The lack of hardware floating-point arithmetic makes the code fairly slow and ugly but it does seem to be working relatively well.

Backface culling

We are only really interested in dealing with walls that are facing the camera; we don't want to draw the back of walls. To work out which we want to keep and which we want to ignore, we project the wall to the screen and check whether its projected start vertex appears to the left or the right of its end vertex.

Perspective projectionPerspective projection with ceiling-to-wall lines

A simple perspective projection is performed to turn this clipped 2D world into what appears to be a 3D one. The X coordinate of each vertex is divided by its Y to get the X coordinate on-screen and the height of the wall is divided by the vertex Y to get the Y coordinate on-screen. The left screenshot shows the top and bottom of wall edges; the right screenshot adds lines between the floor and the ceiling to produce a more convincing "wireframe" view of the world.

Solid walls

The final step is to make the world appear solid, by hiding walls that are far away behind walls that are closer to us. Traversing the BSP tree gives us the order in which to draw the walls, so all that is required is to draw solid quadrilaterals for each wall rather than the lines around its outside. A fast clipped quadrilateral filler would take me some time to write so I cheated by drawing a solid white rectangle the width of the wall and the height of the entire screen before drawing the wall outlines. As the camera is half-way up each wall and all of the walls are the same height there are no cases where a foreground wall only partially covers a background one so this trick works for the time being.

I'm glad I achieved my goal of walking around the 3D world I'd sketched in pencil at the start of the weekend but I'm not sure where I'll be able to take the project now. Turning it into a useful 3D engine for a game would certainly require a lot of work. The level and its BSP tree were generated by hand, which would not lend itself well to anything but the simplest of levels. However, the lack of variation in wall heights produces fairly dull levels in any case; DOOM-style levels would be something to strive for, but I'm not sure how well the calculator would be able to cope with them. I'm also unsure how well the engine would scale; this very primitive version only achieves around 12 FPS on a 6MHz TI-83+. It's certainly given me something interesting to think about!

If you would like to try the program on your calculator, please download Nostromo.8xp. It requires an Ion-compatible shell to run. It is very primitive, likely to be quite buggy (you may encounter rendering bugs when very close to walls due to integer overflow in the clipping and projection code) and may well crash your calculator; please back up any important files before running it. Use the arrow keys to move around, Trace and Graph to strafe and Clear to quit.


TV Demonstrator for TI calculators

Posted by , 11 October 2010 - - - - - - · 946 views

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

TV Demonstrator showing the STAT PLOT settings on a monitor.

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

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

Inside the TV Demonstrator.
Click for a gratuitous 360° view

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

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

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


A parallel port and a demonstration of the Z80 computer

Posted by , 05 September 2010 - - - - - - · 963 views

The last piece of hardware to add to the computer was a parallel port. These have eight data lines and nine assorted control and status lines. My last two 8-bit I/O expanders provide sixteen of these seventeen lines, and the final one was provided by the DS1307 real-time clock chip which happily has a spare pin on it that can be used as an output.

Parallel port I/O expanders Parallel port connector

This parallel port can be used to print from the computer. Some software has printing capabilities built in (such as the text editor VEDIT Plus), but by pressing Ctrl+P in CP/M any text sent to the display will be simultaneously sent to the printer.

I also needed to mount the LCD inside the case. I bought a plastic strip to try to make a nice frame for it, but couldn't cut it accurately enough by hand so have had to make do with merely sticking the LCD behind a rectangular hole cut in the aluminium. It's not the neatest arrangement and doesn't protect the LCD from scratches but is better than nothing.

To demonstrate the computer's hardware and software, I recorded a video:

Demonstration video thumbnail
Watch video on YouTube

I'm not desperately happy with the way it came out; I really need to find a better microphone and the angle of the sun and variable weather when I made the video threw the white balance off. On the plus side, I did find out how to capture crisp black and white video with my TV capture card; I connected the composite video output from the computer to the luma pins on the S-video input on the capture card, then dropped the saturation to zero in VirtualDub. For some reason this produces great quality video, in comparison to the composite input which produces a fuzzy mess there shouldn't really be any difference with a black and white signal (regular television sets don't have any problems).


A clock and a serial port for the Z80 computer

Posted by , 17 August 2010 - - - - - - · 1,210 views

At the end of the previous entry I mentioned that I was going to start developing my own programs for the Z80 computer. The first is a graphical clock, taking advantage of my implementation of the BBC Micro's VDU commands and the ability to use those commands to draw graphics onto the screen as well as text:

Graphical analogue clock for CP/M 3

I have uploaded the code and binary to my site for anyone who is interested, though it will only work on a machine running CP/M 3 and that is equipped with a display that implements a handful of BBC Micro VDU commands.

The computer features a display for output and a keyboard for input which is sufficient if you're interacting with a human but it's often nice for computers to be able to speak to eachother, so I've added an RS-232 serial port.

RS-232 driver and port from the inside

RS-232 is a bit of an unfriendly beast. Whereas the computer's logic uses 0V to indicate a logic low (0, "false") and 5V to indicate a logic high (1, "true") RS-232 uses around +12V for a logic low and -12V for a logic high. This requires that the outgoing signals are inverted and boosted and the incoming signals are inverted and reduced to protect the inputs of the receiver circuit. Fortunately you can easily get hold of chips that perform this task for you when aided by a number of capacitors; in my case I'm using an ST232, which is shown in the bottom left of the above photo. A DE-9M connector is provided on the outside of the case, much like the one you'd find on your desktop if you were trapped in the 1990s.

One issue I have yet to solve is handshaking. The serial port sends or receives data on two wires (TXD and RXD respectively). The receiver has to handle each incoming byte from the transmitter. As the receiver may be busy performing other tasks at the time it may end up receiving data faster than it can process it and it will start losing bytes. There are a number of different ways to avoid this problem. The simplest electronically is to use XON/XOFF handshaking; in this configuration, the receiver can send the XOFF byte to the transmitter when it's busy and the transmitter will stop sending data temporarily. The receiver can then send XON back to the transmitter when it's ready to receive more data. This technique has one major drawback it prevents you from sending binary data containing the XOFF or XON bytes.

An alternative solution is to add two wires to the serial connection Request To Send (RTS) and Clear To Send (CTS). These can be used to signal when each device is available to accept data. This allows you to send XOFF and XON directly over the serial port (extremely useful for binary data) yet requires the addition of two more wires to the port.

Unfortunately whilst implementing both techniques is possible, CP/M only internally refers to XON/XOFF handshaking; there is no way to select RTS/CTS handshaking. I think what I will end up doing is have CP/M's XON/XOFF refer to handshaking in general and then add a hardware-specific utility that lets me choose which particular type of handshaking I wish to use. This utility could also help me select other serial port configuration settings that CP/M doesn't expose (such as parity, number of stop bits or number of data bits).

Z80 computer session in PuTTY

With the hardware installed, the AVR I/O controller updated to use it and the BIOS reprogrammed to expose it to CP/M it is possible to interact with other computers over the serial port. CP/M features five logical I/O devices: CONIN and CONOUT for general console input and output, AUXIN and AUXOUT for general "auxiliary" output and LST for printer output. The BIOS exposes two physical devices; CRT for the keyboard and video display controller and RS232 for the serial port. By using the DEVICE utility you can connect these logical and physical devices together. In the above screenshot I have connected the serial port to both CONIN and CONOUT. This allows me to connect my desktop PC to the Z80 computer using a null modem cable and use terminal emulation software (such as PuTTY) to talk to it.

Simulated BBC Micro VDU mirroring console output

The above screenshot shows VirtualDub capturing the output of the video display controller next to an instance of BBC BASIC for Windows which is running the following program:

aux%=OPENIN("COM2: baud=9600 parity=N data=8 stop=1")
REPEAT
REPEAT:UNTIL EXT#aux%
VDU BGET#aux%
UNTIL.

This passes any data received over the serial port to the simulated VDU in BBC BASIC for Windows. As both video devices accept the same commands the result is that both show approximately the same thing.

I have been slightly improving the video display controller as I've gone along. One feature I had to add for the clock was the ability to draw text characters at the graphics cursor position, as opposed to the fixed text grid (this is used to draw the numbers around the dial). At the same time I added the ability to redefine the appearance of characters. One obvious use of this feature is to change the font, but when combined with the ability to render text anywhere on the screen some simple sprite-based games could be written for the computer. Each letter is just a 88 pixel sprite, after all.

MODE 2

Another feature I added was a simple implementation of MODE 2 where characters are stretched to sixteen pixels wide. You can't get much text on the screen in this mode but it may be useful for games.


A useful Z80 computer in a project box

Posted by , 14 August 2010 - - - - - - · 2,104 views

Work continues on the Z80 computer. The two final modifications to the box itself are the holes for the status LEDs and the power switch.

Status LEDs Power switch

The green LED indicates power and the orange one disk activity. Unfortunately, the project box is fairly scratched on the outside (one scratch on the front is my own fault, but the sides and back were fairly scuffed and scratched when I bought it). If anyone has any tips for polishing scratches out of ABS I'd be glad to hear them; the usual household polishing abrasives (such as toothpaste) remove most of the light scuffs and result in a lovely mirror finish, but don't do anything to the deeper scratches. I'll probably invest in the finest grade wet-and-dry sandpaper I can find and have a go with that followed with a Brasso polish, and if that doesn't help (or makes it worse) just sand the whole thing down and paint it.

Pin header connector

The circuit board inside the case needs to be attached to the case-mounted components somehow. In simpler projects I've resorted to soldering these connectors directly to the board, but this can make maintenance a problem (to remove the circuit board one would have to cut and resolder the wires). For this project I've left pin header strips on the board. The external connectors have leads soldered to them terminated with pin headers cut to size using some wire cutters and a rotary tool to polish them off; these headers are pictured above.

Circuit board mounted inside the case

The main circuit board can then be easily installed or removed from the case as required. The small circuit board for the video display controller is connected to the main circuit board in the same way.

Z80 and SRAM pin numbers marked

A Z80 computer can't live up to its name without some sort of a Z80 inside it, so I thought that that was the most obvious part to add next. Computers also generally need access to memory so I decided to add the 128KB SRAM chip at the same time. The Z80 communicates with the memory over an eight-bit data bus, a sixteen-bit address bus (to indicate which address in memory it is reading from or writing to) and a number of control lines (to indicate whether the current operation is a memory read or a memory write, for example). This provides a fairly tedious amount of soldering work; each pin on the memory needs to be connected to the corresponding pin on the Z80. To aid in the construction I stuck masking tape to the bottom of the perfboard around the outline of where the two chips would go and wrote the pin numbers onto the tape, shown in the photograph above.

Z80 and SRAM address, data and control buses

I put the two chips close together so I could put all of the bus wires on the inside of the IC holders rather than going around the outside. This saves a bit of space and avoids having to route the wires around the chip holders which gets a little untidy. The above photograph shows all of the wires in place before the chip holders were soldered in. Adding those in should be a quick and easy job, at least...

SRAM IC socket soldered in the wrong way around

Well, you'd have thought so, but somehow I managed to solder in the 32-pin SRAM socket the wrong way around. Each socket has a notch to help you align the chip using its corresponding notch. As you can see in the above photo the notch points right when it should point left like all of the other sockets. It wouldn't affect the operation of the circuit (as long as the SRAM chip was inserted with the notch to the left) but it looks untidy and I may as well do the job properly.

SRAM IC socket soldered in the correct way around

On the positive side I suppose I got to practice my desoldering skills.

Z80 and AVR data bus connections

The computer design uses an AVR microcontroller to manage the I/O devices (such as the keyboard, video display controller and SD card) and to load the OS into the Z80's memory on reset. To achieve this the Z80 and the AVR need to be connected together. The above photograph shows some new wires between the AVR (bottom left) and Z80 (bottom middle) to connect the Z80's data bus to the AVR's PORTA and a number of other wires to connect the Z80's control lines to several other I/O pins on the AVR. A number of pull-up resistors have been added to control lines on the Z80 so that when nothing is driving the control bus they rise high (the de-asserted state). If left disconnected ("floating") the other components connected to the control bus may think these lines had gone low (asserted) and treat that as a read or write operation, corrupting data.

I/O expanders Soldering detail of the I/O expanders

The AVR also needs to be connected to the Z80's address bus. This would take another sixteen pins if driven directly by the AVR; sixteen pins that aren't available to me! I am therefore using two MCP23S08 eight-bit I/O expanders, pictured above, to drive the address bus from the AVR. These are controlled over the SPI bus, which only takes up three pins on the AVR (these pins are shared with other SPI peripherals, such as the SD card) plus a single chip select pin that is unique to the I/O expanders. Four pins is better than sixteen, at any rate.

All ICs to date installed Computer in its project box

I keep mentioning chips even though the sockets are quite clearly empty in the above photographs. As I was approaching a useful computer circuit at this point I plugged all of the chips into their sockets to test the connections. As there was no SD card, real-time clock or keyboard I had to modify the boot loader on the AVR quite considerably; I started with a test program that wrote random data to blocks of memory then read them back to verify that they had written correctly. Once I had verified that the AVR was able to access memory correctly I reprogrammed it to copy a small Z80 program to memory and then let the Z80 take over. This Z80 program repeatedly output the string 'Z80' to the console output port. With everything plugged in I switched on the computer and saw the screen fill with Z80Z80Z80… so I was pretty certain that I'd wired everything up correctly!

DS1307 and battery clip

At this point I could start reintroducing the various peripherals to the computer. A DS1307 is used as a real-time clock. This clock needs to keep running when the computer is switched off, so I've added a 3V battery connector to the computer to keep it ticking.

SD card slot

As the computer uses a 512MB SD card for storage, I have added a pin socket strip to the board to plug in the SD card slot I scavenged from a card reader. The card is connected to the SPI bus along with the I/O expanders used to drive the Z80 address bus. SD cards run at 3.3V rather than the 5V that nearly everything else on the board uses so I've used a series of voltage dividers to drop the voltage on each input pin from 5V to around 3V (the resistor values I have don't allow me to get to 3.3V; 3V is the closest I can manage without going over 3.3V). The video display controller board also runs on 3.3V so I do at least have a suitable voltage supply for the card!

Keyboard connector

The final part of the computer that was on the breadboard prototype but not yet in the final build was the keyboard connector. This is simply a four pin header on the board that is connected to the PS/2 port screwed to the case. However, when I tried to use the computer, the keyboard didn't appear to work. Pressing Num Lock, Caps Lock or Scroll Lock would toggle the associated LED and hitting Ctrl+Alt+Del would reboot the computer but no other key worked. This implied that the AVR was handling the keyboard correctly but the Z80 wasn't receiving any notification of key presses. A bit of digging identified the problem; I'd forgotten to connect the Z80's interrupt pin to the AVR! When a key is pressed the AVR triggers an interrupt to let the Z80 know that a key is available. By soldering a wire between the two chips it started working as intended.

Z80 computer in its enclosure

The computer is now up to the same standard as it was when assembled on the breadboard, but is much more practical to work on. I hope to add a serial and parallel port to the computer soon, and would like to mount an LCD into the lid of the project box, but for the time being I am happy that I have managed to get this far.

Z80 computer running VEDIT

One of the advantages of running CP/M on the computer rather than my own operating system is the availability of existing software. The above photograph shows the computer running VEDIT, which is an excellent visual text editor.

VEDIT for CP/M

Zork for CP/M

With the hardware in a decent configuration I can start writing my own software. I think the first CP/M program I'll write is a graphical analogue clock, as this is the sort of program that can be left running for long periods as a way to check the stability of the computer.


Mounting circuit boards and rear panel connectors

Posted by , 09 August 2010 - - - - - - · 2,054 views

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 £40, it seems), but the one I picked out was a slightly more reasonable £7. 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!


Integrating the dsPIC33 VDC with the Z80 computer

Posted by , 31 July 2010 - - - - - - · 1,127 views

The ultimate goal for the video display controller module I have been working on is to drive the display in my Z80 computer project. As I have now got a pretty good set of features I thought it would be a good idea to join the two projects together.

Z80 computer with dsPIC33 VDC

The big board in the lower middle of the above photograph is the main body of the computer, including the Z80, its RAM, the ATmega644P that is used to handle I/O, an SD card for storage and a DS1307 real-time clock. The small board in the bottom left of the photo is the power supply (supplying both 5V and 3.3V) and clock generator (providing a 20MHz and 10MHz clock).

At the top of the photo is the video display controller, connected to a 320×240 graphical LCD. A pin header is used to connect this VDC board to the rest of the computer. Three pins are required for power; 0V, 3.3V (dsPIC33 and output buffer) and 5V (LCD). The VDC is connected to the computer's ATmega644P I/O controller using the two-wire I2C bus (the same bus that is used to access the DS1307 clock). Rather than run a series of graphical demos, the VDC now waits for commands to be written to the I2C slave address 0xEE which it acts on to control what is shown on the screen. I'm aiming for these commands to work in the roughly same way as they did on the BBC Micro VDU, which should make porting the enhanced TI-83+ version of BBC BASIC to this computer a bit easier. The BBC Micro's VDU could be accessed by calling OSWRCH (assuming it was being used as the current output stream), which typically has an address of &FFEE — hence my choice of 0xEE as the I2C slave address!

Detail of the LCD connected to the VDC

A handful of these VDU commands have been implemented, which is sufficient to run simple CP/M software. The generic CP/M version of BBC BASIC does not, naturally, support any hardware-specific features and as such lacks advanced text or drawing support (one can send commands directly to the output stream with the VDU statement but this isn't very user-friendly). I will need to work on this now that the hardware is coming together! The current VDC code can be downloaded here if you are interested in the changes that have been made.


Click for a 360° view

The above photo shows the newly constructed VDC hardware. All of my previous projects have been assembled on stripboard; as the projects have become more complex or simply smaller I've found stripboard to be increasingly awkward to work with. ICs can only really be orientated in one direction, and to reduce the size of circuits I've had to start cutting the tracks between holes (rather than the usual method which is to drill out an entire hole). The supplier I normally acquire parts from, Bitsbox, recently added three different sizes of perfboard to their catalogue so I thought I'd give it a go. I've found it much more pleasant to work with than stripboard, though not as easy to correct if you make a mistake and need to desolder a connection. You can certainly perform some interesting space-saving tricks on the underside of the board!

The underside of the VDC showing the soldering technique

The Kynar insulation on the wire I switched to using also has the advantage of not melting when heated with a soldering iron, as I've had problems in previous projects where tightly-spaced wires will end up getting shorted together as the insulation between them melts.

I have mentioned that one pin header is used to connect the VDC to the computer. There are three others on the board; the two-pin one is for the composite video output, the six-pin one is for connection to a PICkit to reprogram the dsPIC and the four-pin one for the VGA output.

Detail of VGA output from the VDC

Now that I have moved the VDC onto a permanent circuit board I feel that I can start moving the rest of the computer in the same direction. The software is far from complete and the hardware is pretty rudimentary but it does basically work and having a more robust system to work on should make life a bit easier.


VGA output for the dsPIC33 VDC

Posted by , 25 July 2010 - - - - - - · 2,201 views

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 640×480 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!


Text and filled shapes for the dsPIC33 VDC

Posted by , 22 July 2010 - - - - - - · 948 views

The dsPIC33 video display controller project I am working on needs to support several common text output and drawing operations offered by existing BBC BASIC implementations. The previous demo included basic point, line and circle outlining functions, but I also need to output text and outline (or fill) rectangles, circles, ellipses and triangles. On top of that the drawing operations need to support multiple colours and plotting modes. Owing to processing power and memory limitations the output is black and white only but different "shades" can be implemented with dither patterns. The plotting modes allow you to perform logical operations between what you are drawing and what's currently on the buffer — for example, you could fill a circle that is logically ORed with the existing background or draw a line that inverts every pixel along its length rather than applying the new colour.

dsPIC33 VDC text output demo
Filled rectangles and text output produce the above image.

Finding suitable algorithms for some of these routines has been a little tricky at times. Due to the way that filled shapes can be set to invert (rather than overwrite) what's on the background there has to be zero overdraw and the outline of filled triangles should exactly match the outline of a triangle drawn by plotting a line between its three vertices; this makes combining triangles to form more complex shapes possible, as you can guarantee that the overlap between the two shared vertices of a pair of triangles covers the same pixels as a line drawn between those two vertices.

dsPIC33 VDC spinning cube demo
Filled triangles produce a solid cube.

I ended up writing a program in C# that would plot a random triangle using the triangle filler I was attempting to write and then compare its outline to that of a triangle drawn by plotting lines between the three vertices. The final code is chock full of special cases and workarounds but has been tested against hundreds of thousands of random triangles and seems to be working!


Download a schematic for the project.

Due to a shortage of memory there is only a single frame buffer, which (naturally) means there is no double-buffering and hence smooth animation becomes a little tricky. When connected to a TV one can take advantage of the vertical blanking period to update the buffer (this is a period below and above the active display where you only need to feed sync signals, not image data, to the TV) and still get decent effects as long as you don't try to do too much. The LCD has no such vertical blanking period and so some of the demos look rather flickery.

dsPIC33 VDC demo video
View the demonstration video on YouTube

I have captured a video of the output of the circuit when running the demo which can be seen above. The horizontal grey lines are a limitation of my video capture card; these lines appear correctly as alternating black and white pixels on a real TV set! You can download the code for this demo from my site along with a PDF of the schematic. As this is a work in progress I'm sure there are plenty of bugs left to squash but I think it's getting there, slowly but surely!


360 degree photos from Lego, a PICAXE, C# and JavaScript

Posted by , 08 July 2010 - - - - - - · 1,309 views

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 81×3=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 <current>,<desired><CR><LF>. 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 <CR><LF><desired>,<~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 8×8 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[i] == null) {
Console.WriteLine("Image {0} missing!", i);
} else {
maxSize = new Size(Math.Max(images[i].Width, maxSize.Width), Math.Max(images[i].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[i] != null) {
images[i].Dispose();
images[i] = 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!