TV Demonstrator for TI calculators

Monday, 11th October 2010

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.

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.

Booting CP/M 3 from an SD card

Wednesday, 23rd June 2010

Up to this point I have been running CP/M 2.2 on the Z80 computer. CP/M 3 adds a number of useful features, including the following:

  • Support for more than 64KB RAM via banked memory.
  • Standardised access to real-time clock for file date and time stamping.
  • Improved text entry on the command-line when using the memory-banked version, such as the ability to move the cursor when editing and recall the previously entered line.
  • Support for disks with physical sectors larger than the default record size of 128 bytes.

Switching to a banked memory system would require some new hardware in the form of a memory management unit so I have stuck with the simpler non-banked system for the time being. Support for physical disk sectors larger than 128 bytes is more interesting (SD cards use 512 byte "blocks") and real-time clocks are always useful so I have started working on updating to CP/M 3.

Z80 computer with new SD card slot and real-time clock
Z80 computer with new SD card slot (bottom left) and real-time clock (top right)

CP/M consists of three main pieces of software:

  • A BIOS which exposes a small number of routines to perform primitive, hardware-specific operations (e.g. output a character to the console, read a raw sector from a disk, check if a key has been pressed).
  • The BDOS which provides the main API for transient programs (e.g. read a complete line of input from the console, create a file, read a record from a file).
  • The CCP, or console command processor, which provides the main user interface for loading and running other programs or performing some basic tasks via its built-in commands. This would be analogous to COMMAND.COM on DOS.

When working with CP/M 2.2 I had source files for these three pieces of software, so I just needed to implement the 17 BIOS functions, reassemble the three files to fixed addresses in memory and load them to these fixed addresses using the AVR when booting the computer. These three files were stored in the lower 8KB of the flash memory chip and were not accessible from within CP/M itself.

CP/M 3 proved to be a bit more of a challenge, as it is loaded slightly differently. The CCP is stored as a regular file named CCP.COM on the floppy disk you're booting from, so only the BIOS and BDOS need to be loaded from their hiding place at the start of the boot disk. These two pieces of software are merged into a single file named CPM3.SYS by a CP/M utility named GENCPM. To get this utility to work I needed to provide GENCPM with a hardware-specific BIOS3.SPR file that implemented the 31 BIOS routines. Fortunately, a file named BIOSKRNL.ASM is provided that implements most of the boilerplate code involved with writing a BIOS (you still have to provide the hardware-specific routines yourself, but your task is made much easier by following the template) so I just needed to recompile that for a non-banked system and link it with my handful of hardware-specific routines.

A log of a session in CP/M 3

Ideally, CPM3.SYS would be stored on the regular file system with CCP.COM and the hidden boot loader would load CPM3.SYS for you. CP/M 3 does provide a small boot loader for this purpose (aptly named CPMLDR) which employs a cut-down BDOS and BIOS to load CPM3.SYS from the file system into memory for you. I haven't been able to get it to work, though, so I currently parse and load CPM3.SYS using some C code on the AVR. This works well enough for the time being, as can be seen in the above output generated by the computer when testing the real-time clock.

DS1307 real-time clock

The time and date is maintained by a DS1307, an inexpensive eight-pin real-time clock and calendar chip that is shown in the middle of the above photograph. It is accessed over the I2C bus using a protocol that is natively supported by the AVR hardware. It uses binary-coded decimal to represent dates and times, which corresponds nicely to the time format used by CP/M; however, CP/M represents dates as a 16-bit integer counting the number of days since the 31st December 1977. I have used the algorithms on this website to convert dates to and from this format and the individual components.

The only downside of the DS1307 is that it only stores a two-digit year number, not the four digits one would hope for. This means that the century is discarded when setting the real-time clock, allowing for you to set a date that is then retrieved differently (truncated to the range 1930..2029). I haven't thought of a suitable solution to this problem just yet. I could use the AVR to act as the real-time clock, but I would then lose the advantage of the DS1307's battery backup that kicks in when the main power supply is removed.

The state of the DS1307 is effectively random at power-up. One of the first things the computer does when booting is to read the current date and time and check that all fields are within range. If not it resets them to midnight on the 1st January 1978 and displays a message to indicate that it has done so.

SD card in slot

The SD card has been a bit of a headache to get working and though it currently only supports reading, not writing, it should hopefully be a useful addition to the computer. Rather than the previous arrangement of series rectifier diodes to drop the supply voltage and zener diodes to protect the inputs I'm using a dedicated 3.3V regulator to power the card and resistor voltage dividers to drop the 5V logic signals to around 3V (the closest I could get to 3.3V with the resistors I had to hand). I'm using the disk image from the old 512KB flash chip and treating the card as having 128 byte sectors so the arrangement is no more capable than before and in some cases quite a lot slower (reading a 128 byte record now entails reading a whole 512 byte block from the card then returning the desired 128 byte range within that block) but it seems to be as reliable as it used to be at least. SD cards append a CRC16 checksum when transferring data blocks so I can hopefully detect errors more easily and their on-board flash memory controller should perform wear-levelling, prolonging the life of the card.

To write the disk image to the card I used HxD which makes the job as easy as copy and paste. One problem I did have is that it displayed an "Access denied" error when attempting to write data, which I assume to be because something in Windows was using the card at the same time as HxD. I knocked together a short program for the AVR that wrote junk to the first block of the card, the result being that Windows no longer recognised the card's file system and HxD managed to write the data to the disk with no further problems.

An SD card reader from Poundland

Sockets for regular SD cards seem to be relatively expensive for what they are, but the above SD card reader cost a pound (what else?) from Poundland. A bit of work with a soldering iron and some desoldering tools yielded some useful components:

Parts from the disassembled SD card reader

The crystal is unmarked and I'm hardly short of LEDs but the USB A connector could be a good way to reduce the size of a project that plugs into a USB port (USB B connectors are rather bulky) and the SD card slot works brilliantly for my needs here. There are cheaper and nastier ways to add an SD card slot to your project, but something like this feels more robust and has the advantage of reporting the state of the card's write protection switch.

Keyboard input and RAM disks make CP/M more useful

Wednesday, 16th June 2010

The hardware for the computer has changed in (mostly) subtle ways since the last post, with the exception of a PS/2 socket for connection to a keyboard.

Z80 computer with PS/2 keyboard socket

PS/2 keyboards (which use the same protocol as the older AT keyboard) communicate with the host by clocking data in either direction (keyboard to host or host to keyboard) over two wires, appropriately named "clock" and "data". An AVR pin change interrupt is used to detect a change in state of the clock line and either input or output a bit on the data line depending on the current direction of data transmission. Incoming bytes generally relate to the scancode of the key that has just been pressed or released. These scancodes are looked up on a series of hard-coded tables to translate them into their corresponding ASCII characters. CP/M accesses the keyboard via two BIOS routines: CONST (2), which checks whether a character is available or not, and CONIN (3), which retrieves the character. I initially implemented these by simply reading from I/O port 2 (CONST) or port 3 (CONIN).

As keyboard input is polled, CP/M was wasting a lot of time reading from the AVR. Due to the AVR's relatively slow way to respond to I/O requests this was slowing down any program that needed to periodically call CONST (for example, BBC BASIC constantly checks for the Escape key when interpreting BASIC programs). I converted this polling system into an event driven one by connecting the AVR to the Z80's maskable interrupt pin, /INT. When a new key is received by the AVR it pulls /INT low to assert it. The Z80 responds to the interrupt request by setting an internal flag to remember that a key has been pressed and acknowledges the interrupt by outputting a value to port $38 (the Z80's maskable interrupt handler resides at a fixed address of $38 in memory, so this seemed like a sensible choice). The AVR detects this write to port $38 and returns /INT to its high state. The CONST routine can now directly return the value of this flag when polled (rather than having to request the flag from the AVR) which noticeably speeds up running programs. The flag is cleared when a key is read by calling CONIN.

I did have some difficulty getting the interrupt system to work; the Z80 has a number of different ways of responding to interrupts, two of which rely on fetching a value from the data bus by asserting /IORQ before an interrupt is serviced. IM 0 fetches an instruction from the bus and executes it, and IM 2 fetches the least significant byte of the address of the interrupt service routine to combine with the most significant byte stored in the I register. IM 1 (which is what I'm using) just jumps to the fixed address $38. However, I hadn't taken this additional data read into account and when the Z80 attempted to read from an I/O device the AVR was either putting nonsense on the bus or (deliberately) locking up with a message to indicate an unsupported operation. Fortunately you can easily tell the difference between a regular I/O request and an interrupt data request by checking the Z80's /M1 output pin, so with that addition things started working a bit more smoothly!

BBC BASIC test session with the Z80 computer

I'm still using terminal emulation software on my PC to view the output of the computer, though as I now have keyboard entry the results are a little more impressive than the few boot report lines and a prompt that were in the last entry. I still haven't worked out why my PC switches off or blue-screens when programming AVRs over the serial port, so I've soldered together a parallel port programmer for the time being.

Programming hardware

The pinout of the programmer matches that of the website where I found the SI Prog design. The ATmega644P's SPI, power and reset pins that the programmer interfaces with are all adjacent, but not in the same order as the ones in the SI Prog, hence the small board to the right of the above photo which swaps the pin order around using wires soldered to its reverse (this saves a lot of breadboard space). The board in the middle plugs directly into the parallel port programmer and is used to program the 512KB flash memory chip I'm using for storage.

I haven't got around to implementing writing to this flash memory yet, unfortunately, though I have implemented a simple way to test a writable disk drive. The RAM chip I am using is a 128KB one, as Farnell didn't sell 64KB ones. The Z80 can only address 64KB without additional memory banking hardware, so I'd simply tied A16 low and was ignoring half of the memory. I have now edited the BIOS to expose two disk drives; the default A: (512KB of flash memory) and now B:, a 64KB RAM drive. A16 is now driven by the AVR; during normal operation, it is held low (giving the Z80 access to its usual 64KB) but during disk operations it can be driven high to grant the AVR access to the previously hidden storage.

Testing the RAM disk

In the above test I use the STAT command to check free space, the PIP command to copy BBCBASIC.COM from A: (flash) to B: (RAM) then run BBC BASIC from the RAM disk, save a program then run it again by passing its filename as a command-line argument to BBC BASIC. At the end I try to copy the new program back to A:, but as there is no writing support for flash it keels over with a fairly unhelpful generic CP/M error.

Now that I've finally got something working in a vaguely usable manner, I hope I can start to research ways to make it better. Sorting out writing to flash would be a good start (I'm sorely tempted by jbb's suggestion to use an EEPROM to map logical floppy sectors to physical flash sectors) and I certainly hope to dig out my 320×240 pixel graphical LCD and driver for output instead of relying on a desktop PC. I'd also like to upgrade to CP/M 3 (I'm currently using CP/M 2.2) but when I last looked at that it seemed like a much more involved process so I decided to keep it simple. There's a fair mountain of stuff I need to take in, but I'm certainly learning a lot as I go (I only just realised tonight that CP/M was capable of graphics output, for one). I'd be a very happy chap if I could eventually run WordStar on this computer!

Combining a Z80 and an ATmega644P to boot CP/M

Monday, 14th June 2010

I've been working on a new Z80 computer over the last few days. I would say that I had been working on the existing Z80 computer were it not for the fact that this a completely new design.

The previous computer had two 32KB RAM chips to provide a total of 64KB RAM. To run a user program you need to get it into RAM somehow, so I also included a 128KB ROM chip which occupied the lower 16KB of the Z80's address space to provide the fixed operating system that could be used to load programs. By adding memory banking hardware I could select one of eight 16KB pages of ROM. The next 16KB was one of two banks of RAM from one RAM chip, and the final 32KB was mapped directly to the other RAM chip.

Previous Z80 computer memory map

This is all fairly complicated, and not very flexible. Programs written for CP/M tend to be loaded into memory starting at address $0100, which is impossible with my old design as that section of memory is taken up by ROM.

Giving another device access to the buses

The Z80 accesses memory and other hardware devices using three buses; an eight-bit data bus which shuttles bytes of data between the various chips, a sixteen-bit address bus which addresses a location in memory or a particular I/O device, and a control bus which contains numerous lines that specify the type of operation (for example, if /MREQ and /WR go low together it indicates that a byte is being written to memory, or if /IORQ and /RD go low together it indicates that a byte is being read from an I/O device).

There is also a pin named /BUSREQ that can be used to request access to these buses. The Z80 will periodically check this pin and if it is held low it will put the data, address and control buses into a high-impedance state and drive /BUSACK low to acknowledge this. This effectively removes the Z80 from the circuit, and another device can now drive the buses.

This is the feature which I have based the new design around — the current prototype is pictured above. It features a Z80 and 128KB of SRAM (only 64KB is currently addressable) on the upper board. On the lower board is an ATmega644P microcontroller, which is used to start the computer.

When the circuit is reset, the ATmega644P requests access to the buses from the Z80. When access has been granted, it proceeds to copy the CP/M BIOS from the 512KB flash memory IC to a specific location in RAM (currently $F200). It then writes the Z80 jump instruction jp $F200 to the start of memory, returns control of the buses to the Z80 and pulses its /RESET pin. The CP/M BIOS then runs directly on the Z80.

As the ATmega644P doesn't have enough pins to drive all of the buses directly, I've added sixteen GPIO pins by using two MCP23S08 8-bit I/O expander chips. These are used to drive or sample the Z80 address bus; the data and control buses are driven or sampled directly by the GPIO ports on the ATmega644P.

Using a slow to respond microcontroller for I/O

The Z80 is most useful if it can talk to the outside world somehow, which is usually achieved by reading from or writing to I/O devices. In my previous design I built these out of latches and lots of glue logic. As I've added a powerful microcontroller to the computer which features a number of useful on-board peripherals, it would seem sensible to use that instead.

One problem with this idea is that the Z80 expects to read or write to an I/O device in a mere four clock cycles. The AVR has a delay between an interrupt occurring (such as a pin state changing) and executing interrupt service routine of at least five clock cycles. Even though the AVR is running at twice the clock speed of the Z80 this still doesn't provide much time to sample the address bus and perform some useful action before returning a value to the Z80. Fortunately, the Z80 has another useful pin, /WAIT, specifically to address this concern. By pulling this pin low the Z80 can be stalled, allowing the I/O device plenty of time to respond. I have included a 7474 D-type flip-flop as an SR latch to control the /WAIT pin. When the Z80's /IORQ pin goes low the flip-flop is reset, which pulls the /WAIT pin low. When the AVR notices that the /IORQ line has gone low it samples the address bus, performs the requisite task then sets the flip-flop, which drives the /WAIT pin high again and the Z80 continues executing the program.

The 7474 is a dual D-type flip-flop, so I have used the second flip-flop to halve the AVR's 20MHz clock signal to provide the 10MHz clock for the Z80.

CP/M interacts with the host computer by calling numbered BIOS functions. I have implemented a number of these BIOS functions by outputting a value to a port number that matches the BIOS function number. For example, CONOUT is function number four and is used to send the character in register C to the console.

    ld a,c
    out (4),a

The AVR detects a write to port 4 and sends the incoming byte to one of its UARTs. I have connected this UART to a simple transistor inverter (pictured in the top right of the above photograph) and plugged the output from that into one of my PC's serial ports, so by running a terminal emulator I can see the output of CP/M on the screen. I have implemented only a handful of other functions (WBOOT outputs a value to port 1 to indicate that I should load the BDOS and CCP into RAM from the flash memory and READ can be used to copy 128 byte floppy disk sectors from flash memory to Z80 RAM) so the results are not exactly impressive:

Loading BIOS...OK
Loading BDOS...OK
Loading CCP...OK


As I haven't implemented console input yet there's no way to type at the prompt, but that it gets that far is encouraging.

I haven't implemented writing to the flash memory due to a mistake I made when reading its datasheet. When writing to flash memory the value you write is ANDed with the data that's already there (you can only set a 1 bit to a 0 bit, but not vice-versa) – this is referred to as programming. If you want to write a 1 bit you have to erase the memory before writing to it (this is unsurprisingly referred to as erasing). Flash memory can be split into pages (small regions, in this case 256 bytes) and sectors (large regions, in this case 64KB). You can often program any number of bytes (up to a page at a time, aligned to page boundaries) but can only erase in larger blocks — pages, sectors, or the entire memory (bulk erase). I thought that the flash memory ICs I bought supported page erasing, but they only support sector erasing. CP/M transfers data between floppy disks and RAM in 128 byte floppy disk sectors, so to write an updated sector I would need to read 64KB from the flash memory, update a 128 byte region within it, erase an entire flash sector, then program the 64KB back to it. This would be very slow and quickly wear out the flash memory, so I am looking for some replacement flash memory ICs which do support page erase.

SPI flash memory programmer

To copy the system files and a sample disk image to the flash memory I cobbled together the above parallel port programmer which is driven by an application cobbled together in C#. It's rather slow but gets the job done — unlike my AVR programmer. After finally managing to get CP/M to boot in a satisfactory manner I made a few tweaks to the AVR program and hit the "Build and Program" button in the editor. The code built, but rather than program the AVR my computer switched off. No error message, not even a blue screen, just a sudden and surprising power down. Since then I've only managed to talk to the AVR once; every other time has resulted in either a power down or blue screen. I had hoped to add some keyboard handling routines to the project to at least be able to interact with CP/M, but after fiddling around for an hour and a half without managing to get anything working again I gave up. I wish I knew why it suddenly stopped working, after hours of reliable service — maybe it's a hint that it's time to buy a proper USB debugger rather than the cheap and cheerful home-made serial port programmer I've been using!

Power supply insidesPower supply enclosure

One equally cheap but useful addition to my tools is the above 5V power supply (yes, it's just a 7805 regulator in a box). Every project I have built needs a 5V supply from somewhere, which usually comes from a 7.5V wall wart power supply unit regulated to 5V with a 7805. This takes up valuable breadboard space and the weight of the cable from the power supply tends to drag the breadboard around the smooth surface of my desk, so having a dedicated box with an on-off switch, indicator LED, reverse voltage protection and an easy way to connect to the circuit via 2mm sockets is very handy indeed.

I now need to find a way to program AVRs without my PC switching itself off before I can make any more progress on the project...

USB remote control receiver for PowerDVD

Monday, 7th June 2010

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

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

USB remote control receiver prototype using an ATmega168

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

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

USB remote control receiver prototype using an ATtiny84

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

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

USB remote control receiver assembled on stripboard

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

USB remote control receiver circuit in its enclosure

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

Finished USB remote control receiver

ATmega644P CHIP-8/SCHIP interpreter

Sunday, 7th March 2010

In an attempt to solve the screen resolution problem issue I've bought a very cheap 320×240 pixel graphical LCD – a PG320240H-P9 on eBay for $24. Part of the reason for its cheapness may be down to its the lack of a controller; you need to constantly refresh the LCD with pixel data yourself (easier to use modules have integrated controllers that refresh the display for you from some on-board RAM). If I manage to get it working I'll have a 128×64 pixel graphical LCD going spare – finding a use for it could make an interesting project.

I have a bit of a soft spot for the CHIP-8 programming language, having previously written an few implementations. The CHIP-8 environment requires just under 3.5KB of RAM, and my recent investment in an ATmega644P boasting 4KB of RAM provided me with a microcontroller that was up to the task.

Complete CHIP-8/SCHIP system

Beyond the ATmega644P and LCD the hardware is pretty simple; a potentiometer is provided to adjust the speed of the interpreter when it's running, from 1/8th speed up to 8× speed. Sound is output using a piezo transducer, which I've taped to the hard plastic lid from a tube of chocolates to amplify it. Games rely on a 4×4 hex keypad for input, and as I do not have a 4×4 keypad – hex or otherwise – I assembled my own on another breadboard. I don't even have sixteen switches of the same type, hence the mixture in the above photo. A schematic of the hardware can be downloaded in PDF format.

Game menu

When you reset the circuit a list of all of the programs stored on the microcontroller is shown on the LCD. The 64KB of flash memory on the ATmega644P is enough to store the code for the interpreter and all of the CHIP-8 and SCHIP games available on the Internet. For a change I've decided to have a go at designing a variable width font rather than use one of my existing fixed-width fonts; I don't think it looks too shabby.

'Joust' summary

When a game has been selected a (gramatically incorrect) summary of the game is shown. To the right of the screen is a 4×4 grid informing the player which key does what; arrows for directional controls, a diamond for "fire" or confirmation actions and a tick/cross for yes/no input. There doesn't seem to be any particular convention for keypad input in CHIP-8/SCHIP games, which makes this feature invaluable!


Click here to download the source code.

Ejecting discs from a damaged camcorder with a remote control

Tuesday, 29th December 2009

I hope that those of you who celebrate it had a good Christmas break and will have an excellent new year!

I recently attempted to repair a DVD camcorder that had been dropped; the eject button no longer worked, though the disc could be ejected by connecting to camera to a PC, right-clicking the DVD drive that subsequently appears in Explorer, then selecting Eject.

I started by removing all of the screws around the affected area, but the plastic casing remained strongly held together by some mysterious internal force. I removed more and more screws, but it soon became apparent that the only way to get into the camera would be to force it open – not being my camera, I didn't feel comfortable doing so, as the rest of the camera worked well and I didn't want to damage any fragile internal mechanisms. I couldn't find any dismantling guides online, so gave up on the idea of fixing the button.

Fortunately, I own the same model of camcorder – a Panasonic VDR-D250 – myself. With my interest in infrared remote controls I had previously found information about the Panasonic protocol it uses. The supplied remote control only has a few simple buttons on it (no eject button, sadly), but I reckoned that the camcorder may accept a number of other commands that the stock remote didn't include.

Remote control to eject discs from a Panasonic camcorder

I started by modifying a universal remote control program for the TI-83+ that I had previous written to allow me to send specific commands to the camcorder, then ran through all of the possible command IDs, noting down those that appeared to have some effect. Eventually I had a pretty decent list, albeit one with quite a few gaps in it. Fortunately, I had found the Eject button code, along with codes to switch mode (which is done on the camera by rotating a mode dial), one that powers the camcorder off, another that appears to restart the camera and another one that resets all settings (not so useful, that one).

Having found the eject code, I set about building a dedicated remote control. I picked the ATtiny13 microcontroller as a base, as that's a more than capable microcontroller with its 9.6MHz internal clock, 1KB program memory, 64 bytes SRAM and 3V operation.

Panasonic Eject remote control circuit diagram

I was a bit surprised to see that AVR-GCC supports the ATtiny13, and whilst C may seem overkill for such a project I'll gladly take advantage of anything that makes my life easier. smile.gif

// Requisite header files.
#include <avr/io.h>
#include <util/delay.h>

// Frequency of the IR carrier signal (Hertz).
#define F_IR_CARRIER (37000)

// Timing of the data bits (microseconds).
#define T_DX_MARK   (440)
#define T_D0_SPACE  (440)
#define T_D1_SPACE (1310)

// Timing of the lead-in and lead-out bits (microseconds).
#define T_LEAD_IN_MARK    (3500)
#define T_LEAD_IN_SPACE   (1750)
#define T_LEAD_OUT_MARK    (440)
#define T_LEAD_OUT_SPACE (74000)

// Commands definitions.
#define OEM_DEVICE_1_CODE         (2)
#define OEM_DEVICE_2_CODE        (32)
#define CAMCORDER_DEVICE_ID     (112)

// Transmits a single unformatted byte.
void panasonic_send_byte(uint8_t value) {
    // Send eight data bits.
    for (uint8_t bit = 0; bit < 8; ++bit, value >>= 1) {
        // Send the mark/burst.
        DDRB |= _BV(1);
        // Send the space.
        DDRB &= (uint8_t)~_BV(1);
        // Extend the space if it's a "1" data bit.
        if (value & (uint8_t)1) {
            _delay_us(T_D1_SPACE - T_D0_SPACE);

// Transmits a formatted command packet to the IR device.
void panasonic_send_command(uint8_t oem_device_code_1, uint8_t oem_device_code_2, uint8_t device_code, uint8_t sub_device_code, uint8_t command) {
    // Send the lead in.
    DDRB |= _BV(1);
    DDRB &= (uint8_t)~_BV(1);

    // Send the five command bytes.

    // Send the checksum.
    panasonic_send_byte(device_code ^ sub_device_code ^ command);

    // Send the lead out.
    DDRB |= _BV(1);
    DDRB &= (uint8_t)~_BV(1);

// Main program entry point.
int main(void) {

    TCCR0A |= _BV(COM0B0) | _BV(WGM01);     // Toggle OC0B when on CTC reload. Use CTC mode.
    TCCR0B |= _BV(CS00);                    // Set clock source to CPU clock/1.
    OCR0A = (F_CPU / F_IR_CARRIER / 2) - 1; // Set the CTC reload value to generate an IR signal at the correct carrier frequency.

    // Send the "eject" command ad infinitum.
    for(;;) {

The code is about as simple as the circuit. IR signals are transmitted as carefully timed bursts of a particular carrier frequency (37kHz in this case). For example, to send a "0" bit 440μS of this 37kHz signal are sent followed by 440μS of silence. To send a "1" bit, 440μS of carrier signal are sent as before, but a 1310μS period of silence follows it.

The AVR's timer is used to generate a ~37kHz carrier signal. The timer is an eight-bit counter that counts up at a user-defined rate (in my case I've chosen to increment the counter by one every CPU clock cycle). I've configured it to invert the output level of pin OC0B and reset every time it hits a particular value. By setting whether this pin is an output or an input the output of a burst of 37kHz IR signal or silence can be selected. Simple delay loops, generated with the helper function _delay_us, are used to time the transmission of data bits.

Insides of the Panasonic ejecting remote control.

The final step was to assemble the circuit on stripboard and install it in a smallish project box. I've put the switch adjacent to the LED for two reasons; to conserve space and to protect it a little from accidentally being pressed by the protruding LED bezel.

Building a single-button remote control is a relatively straightforward affair, so whilst the above code has a very specific purpose it should be easy enough to modify it to control other devices.

ATmega168 Snake

Tuesday, 24th November 2009

In addition to the Tetris game from the previous post, I've added an implementation of snake to the ATmega168 project.

Either game can be selected from a menu that appears when the circuit is powered on. To exit menus I've added a second fire button; this allows you to step back to the main menu to pick a different game if need be. The source code and binary can be downloaded as before.

I've written a number of different Snake implementations in the past. The early versions used a single array to represent every cell that the snake's body lay in (head as the first element, tail as the last element) that I would manually shift every frame and resize when the snake ate some food. This gets slower and slower as the snake gets longer, which isn't very good. When I wrote a version for the TI-83+ calculator in BBC BASIC, I switched to using a ring buffer with a pointer to the head element and another to the tail element that would be shunted along every frame, unless the snake ate some food in which case the tail pointer would stay where it was.

As I have even less memory on the ATmega168, I went for a different tactic again; by using "pretty" graphics for the various parts of the snake in the tilemap, I didn't need to store the snake's path anywhere other than this tilemap. That is, if I wanted to advance the tail one unit, I merely need to look at the current tile graphic being used to represent the tail (which will be pointing up, down, left or right) and follow it along to the tile in front of it. By inspecting this tile, I can see if the snake turned a corner at that point or went straight ahead and so adjust the tail position and graphic accordingly.

void advance_tail(void) {
    // Find the current snake tail graphic.
    char tail = tvtext_buffer[tail_y * TVTEXT_BUFFER_WIDTH + tail_x];
    // Where is the body in relation to the tail?
    int8_t body_x = tail_x, body_y = tail_y;
    switch (tail) {
        case FONT_SNAKE_TAIL_UP:
        case FONT_SNAKE_TAIL_DOWN:
        case FONT_SNAKE_TAIL_LEFT:
    // Ensure the body is on the buffer.
    if (body_x < WORLD_LEFT) body_x = WORLD_RIGHT;
    if (body_x > WORLD_RIGHT) body_x = WORLD_LEFT;
    if (body_y < WORLD_TOP) body_y = WORLD_BOTTOM;
    if (body_y > WORLD_BOTTOM) body_y = WORLD_TOP;
    // Find the current body graphic.
    char body = tvtext_buffer[body_y * TVTEXT_BUFFER_WIDTH + body_x];
    // Is it a bend? If so, we'll need to rotate the tail graphic.
    switch (body) {
    // Erase the old tail.
    tvtext_buffer[tail_y * TVTEXT_BUFFER_WIDTH + tail_x] = tvtext_cleared;
    // Draw the new tail.
    tail_x = body_x;
    tail_y = body_y;
    tvtext_buffer[tail_y * TVTEXT_BUFFER_WIDTH + tail_x] = tail;

Similar code is used to advance the head and draw the correct tile behind it.

On an unrelated note, I've released a version of BBC BASIC that should run on the Nspire. The Nspire has an emulator on it to run applications for other calculators, but this emulator doesn't implement undocumented instructions. The TI-83+/TI-84+ BBC BASIC host interface makes use of the sl1 instruction, which shifts a register left one bit and sets the least significant bit to 1. Unfortunately, when this code is run on an Nspire it triggers a crash. Apparently the quick fix I've implemented seems to have done the trick, so unless I hear any further bug reports I'll release the latest version formally soon!

ATmega168 Tetris

Sunday, 22nd November 2009

The tvText library I discussed last entry allows you to display text on a PAL TV in black and white using a 20MHz ATmega168 and a pair of resistors. If this doesn't sound terribly exciting, it's probably because it isn't. However, if you bear some limitations in mind and change the font, you can use this text output as a more general tile-mapping system and use it for games that employ simple graphics.

The new circuit, featuring five sloppily-wired input buttons.
The new circuit, featuring five sloppily-wired input buttons.

I added five buttons to the test circuit — up, down, left, right and fire — to act as game input. This circuit is shown in the photograph above. I also added support for 8×8 characters alongside the existing 6×8 characters to the library, set as a compile-time option. This drops the number of characters per line from 32 to 24, but having square tiles makes producing graphics much easier. The reduction in size of the text buffer also frees up more of the precious 1KB of SRAM for the game!

Diagram of the game circuit.

Even though it was always recommended as an excellent game for beginners to write, I don't believe I've ever written a Tetris clone before. Its simple block graphics makes it an ideal candidate for this system, and it always helps to work on a game that's fun to play. Armed with a Game Boy and a stopwatch I attempted to recreate a moderately faithful version of what is probably the most popular rendition of the game.

I think the result plays pretty well, but don't take my word for it — if you have an ATmega168 lying around, you can download the source and binaries here.

USB joypads and text on your TV courtesy of an ATmega168

Saturday, 14th November 2009

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

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

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <avr/sleep.h>

#include "usbdrv.h"

/* Joystick port bits */
#define JOY_1     (1<<0)
#define JOY_2     (1<<1)
#define JOY_UP    (1<<2)
#define JOY_DOWN  (1<<3)
#define JOY_LEFT  (1<<4)
#define JOY_RIGHT (1<<5)

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

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

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

ISR(TIMER0_OVF_vect) {

    /* Fetch input */
    uchar input = ~PINC;
    /* X-axis */
    switch (input & (JOY_LEFT | JOY_RIGHT)) {
        case JOY_LEFT:
            reportBuffer[0] = 0;
        case JOY_RIGHT:
            reportBuffer[0] = 255;
            reportBuffer[0] = 128;

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

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

    usbSetInterrupt(reportBuffer, sizeof(reportBuffer));

int main(void) {

    usbInit();              /* Initialise USB. */

    PORTC = 0b00111111;     /* Pull high PORTC0..PORTC5 */
    TCCR0B = 0b00000101;    /* CS2..CS0 = 101:  prescaler = /1024 */
    TIMSK0 |= (1 << TOIE0); /* Enable timer 0 overflow interrupt. */
    sei();                  /* Enable global interrupts. */
    for (;;) {
        /* Infinite loop */

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

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

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

tvText demo screen

In the end, I ended up writing my own library. It currently has the following specifications:

  • 32×16 characters: 512 bytes (half of the total SRAM on the ATmega168) are used to store the text buffer.
  • Full 256 characters at a resolution of 6×8 pixels each.
  • Total screen resolution: 192×128.

The library is interrupt-driven, and uses the sixteen-bit TIMER1 to schedule events. This means that the AVR is only busy generating video signals when it absolutely has to, leaving some CPU time to the user program. When outputting at full quality, the AVR appears to be capable of running user code at 3.3 MIPS, but by skipping alternate scanlines (each scanline is scanned twice anyway, so this mainly just makes the display appear darker) the AVR appears to be running user code at 9.9 MIPS. (I say "appears" as my calculation has been to execute a busy loop that would normally take one second on the AVR running at its normal 20 MIPS then seeing how long it takes with the video output driver enabled).

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

#include "tvtext/tvtext.h"

void type_string_P(const char* s) {
    char c;
    while ((c = pgm_read_byte(s++))) {

int main(void) {


    tvtext_puts_P(PSTR("AVR Computer 1K\r\n\nATmega 168\r\n\nBASIC\r\n\n>"));
    type_string_P(PSTR("10 PRINT \"AVR Rules! \";\r\n"));
    type_string_P(PSTR("20 GOTO 10\r\n"));

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

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


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

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

Subscribe to an RSS feed that only contains items with the AVR tag.

FirstLast RSSSearchBrowse by dateIndexTags