The USB work went back a little way, to before I had any USB. Back when we were doing shows for RISCOS Ltd we wanted to have a little camera up and running. Robin Watts wrote a little program that would capture images from a camera. We recorded a few little time-lapse videos in this way, and they turned out reasonably well. The hardware these used was a dedicated VideoDesk capture card on the RiscPC.
Back from the shows and in the office, I updated the !WebCam software to JPEG encode images, and to upload them to an FTP server. I don't seem to have the !WebCam software anywhere any more - or if I do, it's hidden inside one of the many archives I've got.
Eventually I got a little digital camera - one of the small £30 cameras that were quite common. It only had a serial output, but that wasn't too bad for the images I was capturing. Based on the STV680 controller, these images were captured using a 'bayer' pattern which meant that the colour at any given pixel was contributed to by the surrounding sensor points. I wrote some simple code to convert this to plain RGB, and augmented it with some algorithms from a Linux conversion tool for the same camera.
The images weren't brilliant, but they were quite fun. I updated the !WebCam to use a library which contained all the 'fetch the image from the camera' code, and had one implementation for VideoDesk and another that used the STV680 serial camera.
An early capture from the STV680 camera.
Later, I began work proper on some of the USB things. I got a USB card to play with, from Simtec, and so began to look at devices that I could control. Initially, I started out with one of the radio controls, which worked pretty well for me. Simtec supplied an example driver that would talk to it, so it was possible to see what they were doing to drive it. At the same time I was looking at the way that Pace had done their USB.
They were quite different ways of working, and neither seemed particularly satisfactory to me. The Simtec implementation was a very Linux-oriented solution, and expected to have callback functions to perform its operations, but didn't use the normal way of doing callbacks on RISC OS. The documentation for the interface was very hard to read, but there was a lot of it - trying to work out which bits you cared about was quite hard.
Coming to USB from a RISC OS perspective, I was expecting an interface more like that provided by the Internet module, with extra enumerations and control for the devices. This wasn't the way that Simtec were approaching it. There was some handling of loading modules automatically when devices were inserted, but it was still very difficult to work with.
It was certainly capable, and it had support for everything you could hope for, but finding the bits you needed was more difficult. It had full HID (Human Interface Device - keyboards, mice and others) support but again, adding a driver to trap particular keys, buttons or whatever was a challenge.
Because of the highly callback based design of the Simtec stack, using it from an application was very difficult. It wasn't really meant for that, which is fair enough, as you do not expect to pipe packets into the Internet interfaces from an application either. But it made things much more difficult to work work, even for me - and I'm pretty flexible when it comes to interfaces that are outside the norm.
The Simtec solution was also limited by not being able to support asynchronous transfers, which precluded the use of audio drivers, which I had been very interested in. The reason I was given for this was that the interrupt system on RISC OS couldn't handle it. I wasn't convinced by this, but without knowing more about the particular problem, I couldn't really offer much in the way of advice.
The Pace solution, on the other hand was simpler. Rather than invent another interface, it used the DeviceFS interface to provide interaction with the devices. This meant that you could interact with devices far easier from within an application - you could play with USB devices from BASIC - but forced the operations you performed into a stream interface, rather than a packetised interface. This doesn't really fit with the way that the USB devices work in general, so I didn't think it was a particularly good fit.
Additionally, using DeviceFS meant that you couldn't easily interface with
it under interrupts, as it, and the filing system interfaces you had to use,
were not reentrant. It outright precluded the use of interrupt driven
handling except for the very simple cases - writing an audio driver using it
would be difficult. The DeviceFS interfaces had to be extended to include a
number of additional calls specific to the operations of the device. These
might have been better handled through the filesystem '
interface, but that interface wasn't available prior to RISC OS 4.
I considered the Simtec solution to be good, but with an incredibly steep learning curve, and having some significant problems for casual users. The Pace solution was a reasonable entry point for casual users, giving it a far more shallow learning curve, but its design was such that you rapidly met its limitations.
In any case, I didn't have any working USB hardware for the Pace API (and had a healthy dislike for working with anything produced by Castle given their behaviour), so I worked mostly with the Simtec system.
Initially, I wanted to just talk to the devices, but I rapidly found that there was a lot of boilerplate code that you were repeating for each implementation. I decided to write an interface layer to simplify communication with the USB devices - EasyUSB.
The idea of the EasyUSB module was to provide a single interface which could talk to either type of API, and to take some of the pain out of the Simtec interface. The module converted the message-oriented interface that the Simtec API used into service calls so that other modules could handle them in a more RISC OS-like manner. From the outset, I wanted to be able to access USB devices from within applications (including BASIC), as well as from modules (to allow proper device drivers to be written).
Looking at the source, I see many
#defines for "define this
if foo doesn't actually work". Some of these are commented out now,
because the problems that they worked around were fixed by Simtec - they
were pretty good about fixing things, or explaining when I had misunderstood
If it was to be used inside applications, EasyUSB had to be very careful to report gracefully in the cases where handles became invalid due to devices being removed whilst they were in use. There was a reasonable amount of the checking within the implementation just to be sure that the operations being performed were actually valid for a given device.
Sam and Greebo sleeping on my bed, taken with the USB camera. I obtained one of the later variants of the STV680 based cameras which had support for USB, and used it as part of my testing. It wasn't actually that hard to get the device to return images, although the controller itself sometimes crashed, and I had to pull the plug to reset it. I am pretty sure that's because I was sending invalid operations, but in general it worked quite well. A new version of !WebCam (strictly a new version of the
CamLib for EasyUSB) was created and I could
capture images in my room.
I created a new module - STV680 - which implemented the
capture code that had worked within the BASIC
as a module, and wrapped a much more friendly SWI interface around it.
Capturing an image became a world easier with this interface:
I admit that the code might not be as simple as it can get, but it is significantly easier than the equivalent USB operations that must be performed. The error handling is also poor, but this is just example code, so forgive that!
I had looked at how David Pilling had implemented the TWAIN driver on RISC OS, as that would make the image capture easier in more programs, but it really was quite complicated, and quite different to how I had expected the interfaces to be. TWAIN was a standard way for applications to communicate with scanner software, in order to acquire images. Because the TWAIN drivers expected to work with some interaction from the user, they had to interact with the application. This meant that they could not be purely a modular interface as you might expect. This made the interface a little harder to integrate with applications.
I don't really know whether TWAIN is in use these days, or whether other standards like WIA ('Windows Image Acquisition') and SANE (Linux originated 'Scanner Access is Now Easy') have generally taken over. At the time I was looking at it, the advantage of TWAIN was that it was an existing mechanism for capturing images, and I wasn't keen on creating yet another interface when one already existed. Ultimately, though, I didn't take my work very far with it.
At the time, I felt that the more modern way to implement for this type of interface would be to provide a Toolbox object that provided the interaction with the TWAIN devices. Because it would sit on filters, it could provide whatever interaction was necessary without the application being involved. However, I put a 'TWAIN object' on my list of Toolbox objects to look at in the future rather than diving into it.
The STV680 module was part of the testing for the EasyUSB code, and allowed me to see how the interface worked in practice. It wasn't as bad as it might have been, although some of the operations were still very flakey due to either my code, my misunderstanding the Simtec stack, or failings in the Simtec stack. It was pretty reliable though. I sent a few people the initial documentation for the EasyUSB module, along with examples and header files.
According to my notes here, I only ever did one release to people before having to move on to other work. I think it still needed some significant work, because some of the interface still exposed the specifics of the Simtec interface, rather than the general USB device communications.
The !WebCam software used to run on my machine most of the day, and uploaded my picture, or random parts of my room throughout the day. It wasn't that exciting, but it was a good test of the software and worked quite well really.
Alongside working on the camera, I had a couple of storage devices that I wanted to use. A small red Chinese sourced MP3 player, with a little LCD display, that had about 16 MB of internal storage - and could be extended through a memory card. It was really quite nice, albeit quite simple. It had its own special driver software to access the device on Windows. The internal memory and the memory card appeared as two separate drives.
I did a little snooping on the protocol using a USB logger, and found the protocol to be pretty easy to decode. I stuck what I had found up on my website at one point in case anyone else wanted to know how to drive the mass storage part of the USB interface. I couldn't find any information on the chip at the time, so I had to do it all by reverse engineering the protocol that the player used.
Mostly it was a set of sector operations with a target byte to indicate which of the two sources you were trying to address. It wasn't too hard to get data off the device like this - although by this time I had the EasyUSB to make things significantly easier to work with. A lot of the investigation could be done in BASIC by sending commands and getting responses through the module.
Having got reasonable access to the the device working in a prototype, I needed to create a filesystem for it to use. In theory, with the disc being so small, I should be able to create a regular FileCore filesystem, and DOSFS would access the disc. That was the theory. Only I didn't feel like implementing a FileCore filesystem. I have done it before as a prototype, and I had written interface layers between FileCore as part of my ill-fated ADFSCache module, and the semi-working JADFS. Neither were particularly nice, and I really didn't want to go back to doing all that.
Instead, I thought it would be easier to implement a SCSI driver. If you know anything about SCSI, saying that implementing a driver for it would be easier than implementing a FileCore module is... well it's outright wrong . But I was only really interested in the SCSI operations that SCSIFS would perform, and those could easily be translated into the operations that the player's 3410 chip expected.
It wasn't as bad an idea as it might seem. The SCSI system had not been touched in some time. Pace, through Castle, had released some bits, including their modular SCSIDriver. Previously, the SCSIDriver had been for a single hardware device, and so you couldn't really use multiple hardware devices at the same time. With the coming of USB, having multiple devices would be very likely, so a different solution was needed. Instead of being a hardware driver, the SCSIDriver became a dispatcher - passing on the operations to handlers registered with it.
Of course, I didn't have access to that version. I only had access to the API which they'd published. So I wrote the module one evening. It really isn't a difficult module to implement - all it's doing is tracking clients and passing calls through. There's a little more involved in presenting information about the devices connected, but nothing that you can't fill in later. I later did see the Pace version of the SCSIDriver module, and it appeared to be entirely written in assembler. I am incredibly glad that I chose to write my version in C, because the hassles of writing a dispatcher module in assembler are something that I never want to get into. You would have to be pretty crazy to write such things like that these days.
Actually my module isn't complete. The operations that were used by SCSIFS
were implemented, but the others were left to be implemented later, eg
/* FIXME: We'll fake 'ready' for now */', or just returning a
'Not implemented' error.
With the dispatcher module written, I needed to provide an actual driver. Of course, a lot of the driver was written at the same time as the dispatcher. I wrote a very simple prototype driver which would merely debug the calls that the dispatcher made, so that I could see what it was doing. The prototype was then knocked into shape as my MP3 driver, providing a translation between the SCSI commands and the command set used by my MP3 player.
The SCSIDriver was implemented on 30th September 2003, and the SoftSCSI_3410 was up and running on 1st October. I spent the 2nd October updating the prototype so that it just used plain SCSI commands to talk to USB SCSI Mass Storage Devices. Instead of talking to the 3410, it would talk to the actual SCSI devices presented by the proper USB Mass Storage systems. The logs I have lying around here indicate that I got the first *SCSI:Devices working on 2nd, correctly reporting that another of my MP3 players was recognised as a 19 GB disc.
Apparently I got the first *Cat to work in the early hours (5:40am!) of the 3rd, this time using a IDE RISC OS formatted disc connected to a USB mass storage interface. So let's be very clear about this:
- That is an IDE device connected to a USB mass storage interface, which converts IDE to SCSI over USB.
- USB packets go to the Simtec USB hardware and stack.
- The Simtec software dispatches to my EasyUSB module.
- My EasyUSB passes this on to my SoftSCSI_USB.
- My SoftSCSI_USB passes through commands that come from my SCSIDriver.
- The SCSIDriver is called by SCSIFS.
- SCSIFS is called by FileCore.
- FileCore is called by FileSwitch.
It is quite amusing that it worked, given the number of translations involved. I know that the lower part is well tested, but it is still nice to know that it worked. Doing more complicated things was a little more difficult. In particular, the SCSIFS that I had did not support 'large' FileCore discs. That is 'large' in the sense of being more than 512 MB.
So, according to my notes I spent the 12th updating SCSIFS to support the SWI FileCore_SectorOp interfaces that allowed it to access bigger discs, and converting the AAsm code to the more modern structures, using ObjAsm. It was all rather nice, and well structured really - the work, that is. The code for SCSIFS was awful, but that was mostly from the point of view of someone who was used to tidier code using more structured debugging libraries and better source alignment. The actual code isn't that bad, but you have to be able to see it through all the conditionals that are present.
I found that one of my USB pen drives wouldn't even get recognised as a disc by DOSFS when connected through the interface. I spent half an hour or so trying to work out how the device was returning garbage before it dawned on me that it was working exactly as expected, but the device had a partition table present. The partition table only had a single entry for the rest of the disc, but it meant that the disc wasn't supported by DOSFS.
I updated the code in DOSFS itself to recognise this case, and to use the first active partition if it found a table present. This isn't really the way that it should be done, but as there was no central dispatcher for block devices it would have to do. I had plans for how I would change the behaviour in the future, but didn't get around to implementing them. A later ramble will go into them in more detail .
I put all the USB work on hold to return to more core development. The improvements to the system to fix bugs that I'd found due to poor handling of bad cases were obviously retained, as was the updated DOSFS. I'm pretty sure I didn't do much more work on the USB things after that, although I really wanted to go back and finish some bits off.
I had also worked on a printer driver which would very much like to be able to communicate with the printers over USB, rather than the parallel interface. It would have been really nice to have got that working as well, but I never had the opportunity to look into it.
EasyUSB never made it past the alpha releases that I gave to friends. The documentation is reasonable, although it could still do with more background and code examples.