RC-5, NEC, JVC and Panasonic infrared codes
Wednesday, 3rd June 2009
I've rewritten the remote control signal decoding software to handle multiple protocols. As well as SIRCS, it now supports RC-5, NEC, JVC and two Panasonic codes (one "old" 11-bit code and one "new" 48-bit code). There's not much in the way of screenshots at the moment, other than a debug window that gets filled when keys are pressed:
NecCommand Address=24, Command=87, Extended=False, Repeat=1
RC5Command Address=20, Command=53, Repeat=True, Repeat=1
NewPanasonicCommand OEM Device 1=2, OEM Device 2=32, Device=144, Sub-Device=0, Command=10, Repeat=1
RC5Command Address=8, Command=35, Repeat=True, Repeat=1
OldPanasonicCommand Address=0, Command=20, Repeat=1
SircsCommand Address=2362, Command=121, Length=20, Repeat=1
SircsCommand Address=7002, Command=84, Length=20, Repeat=1
NecCommand Address=64, Command=146, Extended=False, Repeat=1
NecCommand Address=81, Command=8, Extended=False, Repeat=1
JvcCommand Address=3, Command=23, Repeat=1
The C# source code for this can be downloaded here.
A keyring remote control (courtesy of Poundland) has highlighted one possible issue in handling repeating buttons. Rather than target any particular device, it will try and brute-force a response. For example, here's the result of pressing the power button once in one particular mode:
SircsCommand Address=1, Command=21, Length=12, Repeat=2
OldPanasonicCommand Address=0, Command=32, Repeat=1
NecCommand Address=32, Command=11, Extended=False, Repeat=1
NewPanasonicCommand OEM Device 1=2, OEM Device 2=32, Device=128, Sub-Device=0, Command=61, Repeat=1
That's four different protocols from one button. I suppose some sort of mapping from protocol-specific code to a string (so those five commands would be translated into five "power" strings) and comparing the time between signals to turn the input into something meaningful may help, but that would require an enormous database of known codes.
Remote controlling Windows the Sony way
Wednesday, 27th May 2009
It's been a while since I last posted, and unfortunately this post is to do with Sony remote controls again. ![]()
This time I'm attempting to use Sony (or compatible) remote controls to control software running on a Windows PC. I've recently been watching more films in PowerDVD, and some of the keyboard shortcuts (eg Ctrl+P for the menu) are a little difficult to hit in the dark and from a distance. I have a ready supply of universal remote controls as well as the PlayStation 2 DVD remote control, all of which work with the SIRCS protocol.

Serial port infra-red receiver built into an old TI GraphLink cable
First up is the required hardware. This involves an infrared demodulator connected to a free serial port. I chose the serial port as .NET provides a way to handle pin change events and you do not need administrator rights to access it (as per the parallel port). I also had a broken Texas Instruments GraphLink cable that could be ripped apart to act as a case.

Infra-red receiver module schematic
The circuit is pretty simple. Pin 4 (DTR) and 5 (GND) from the serial port form the power supply. DTR can be set to either +12V or -12V, so a rectifier diode is used to keep the input voltage above 0V. Following that is a reverse-biased zener diode and resistor to regulate the voltage below 5.1V. Finally, the output pin of the infra-red demodulator is connected to the input pin 8 (CTS) of the serial port.

Infra-red receiver module assembled on stripboard
The software handles the SerialPort.PinChanged event to time the length of input pulses. Once it detects a start bit (2.4mS) it starts decoding the rest of the command. When it's finished receiving a command it fires an event of its own, which the main software can react to.
using System; using System.Diagnostics; using System.IO.Ports; namespace BeeDevelopment.Sircs { /// <summary> /// Represents a command sent by a SIRCS remote control. /// </summary> public struct SircsCommand : IEquatable<SircsCommand> { #region Properties private byte command; /// <summary> /// Gets or sets the command value. /// </summary> public byte Command { get { return this.command; } set { this.command = value; } } private short device; /// <summary> /// Gets or sets the device identifier. /// </summary> public short Device { get { return this.device; } set { this.device = value; } } private int length; /// <summary> /// Gets or sets the length of the command in bits. /// </summary> public int Length { get { return this.length; } set { this.length = value; } } #endregion #region Construction /// <summary> /// Creates an instance of a <see cref="SircsCommand"/> structure. /// </summary> /// <param name="command">The command value.</param> /// <param name="device">The device identifier.</param> /// <param name="length">The length of the command in bits.</param> public SircsCommand(byte command, short device, int length) { this.command = command; this.device = device; this.length = length; } #endregion #region Methods /// <summary> /// Converts the <see cref="SircsCommand"/> into a string. /// </summary> /// <returns>A string representation of the <see cref="SircsCommand"/>.</returns> public override string ToString() { return string.Format("Command={0:X2}, Device={1:X4}, Length={2}", this.command, this.device, this.length); } /// <summary> /// Returns the hash code for this instance. /// </summary> /// <returns>The hash code for this instance.</returns> public override int GetHashCode() { return this.command ^ this.device ^ this.length; } /// <summary> /// Returns a value indicating whether this instance is equal to another <see cref="SircsCommand"/> instance. /// </summary> /// <param name="other">The instance to compare to this one for equality.</param> /// <returns>True if the instances are equal, false otherwise.</returns> public bool Equals(SircsCommand other) { return this.command == other.command && this.device == other.device && this.length == other.length; } /// <summary> /// Returns a value indicating whether this instance is equal to another <see cref="SircsCommand"/> instance. /// </summary> /// <param name="other">The instance to compare to this one for equality.</param> /// <returns>True if the instances are equal, false otherwise.</returns> public override bool Equals(object other) { return other != null && other is SircsCommand && ((SircsCommand)other).Equals(this); } #endregion #region Operators public static bool operator ==(SircsCommand a, SircsCommand b) { return a.Equals(b); } public static bool operator !=(SircsCommand a, SircsCommand b) { return !a.Equals(b); } #endregion } #region Events /// <summary> /// Represents the method that will handle the <c>SircsCommandReceived</c> event. /// </summary> /// <param name="sender">The object that fired the event.</param> /// <param name="e">Information about the event.</param> public delegate void SircsCommandReceivedEventHandler(object sender, SircsCommandReceivedEventArgs e); /// <summary> /// Provides data for the <c>SircsReceived.SircsCommandReceived</c> event. /// </summary> public class SircsCommandReceivedEventArgs : EventArgs { #region Properties /// <summary> /// Gets the <see cref="SircsCommand"/> that was received. /// </summary> public SircsCommand Command { get; private set; } /// <summary> /// Gets the number of times that the incoming command has been repeated when held. /// </summary> public int Repeat { get; private set; } #endregion #region Construction /// <summary> /// Creates a <see cref="SircsCommandReceivedEventArgs"/> instance. /// </summary> /// <param name="command">The <see cref="SircsCommand"/> that was recieved.</param> /// <param name="repeat">The number of times that the incoming command has been repeated when held.</param> public SircsCommandReceivedEventArgs(SircsCommand command, int repeat) { this.Command = command; this.Repeat = repeat; } #endregion #region Methods /// <summary> /// Converts the <see cref="SircsCommandReceivedEventArgs"/> into a string. /// </summary> /// <returns>A string representation of the <see cref="SircsCommandReceivedEventArgs"/>.</returns> public override string ToString() { return string.Format("{0}, Repeat={1}", this.Command, this.Repeat); } #endregion } #endregion /// <summary> /// Provides a way to receive SIRCS commands from a simple receiver attached to a serial port. /// </summary> public class SircsReceiver : IDisposable { #region Constants /// <summary> /// The minimum time length for a start bit (nominally 2.4ms). /// </summary> private const double StartBitMinLength = 2.0E-3; /// <summary> /// Threshold time length between a "low" (0.6ms) and a "high" (1.2ms) bit. /// </summary> private const double DataBitLengthThreshold = 0.9E-3; /// <summary> /// The maximum time length between data bits. If this is exceeded, any data command transfer is cancelled. /// </summary> private const double IntraBitMaxLength = 0.8E-3; /// <summary> /// The maximum time length between repeating commands. Commands are supposed to repeat every 45ms. /// </summary> private const double RepeatCommandMaxLength = 120.0E-3; #endregion #region Private Fields /// <summary> /// The <see cref="SerialPort"/> that the receiver is connected to. /// </summary> private SerialPort Port = null; /// <summary> /// The last time that the pin state changed in ticks. /// </summary> private long LastPinChangedTime = 0; /// <summary> /// A <see cref="Stopwatch"/> instance used to time incoming bits. /// </summary> private Stopwatch BitTimer = null; /// <summary> /// Set to <c>true</c> when receiving a command, <c>false</c> otherwise. /// </summary> private bool ReceivingCommand = false; /// <summary> /// Counts the number of bits currently received. /// </summary> private int BitsReceived = 0; /// <summary> /// Stores the command as it gets built up. /// </summary> private uint Command = 0; /// <summary> /// Stores the last received command. /// </summary> private SircsCommand LastCommand = default(SircsCommand); /// <summary> /// Stores the number of times the received command has been repeated. /// </summary> private int LastCommandRepeatCount = 0; /// <summary> /// A <see cref="Stopwatch"/> instance used to time repeating commands. /// </summary> private Stopwatch RepeatTimer = null; #endregion #region Construction/Destruction /// <summary> /// Creates an instance of a <see cref="SircsReceiver"/> from a serial port name. /// </summary> /// <param name="portName">The name of the serial port the receiver is connected to.</param> public SircsReceiver(string portName) { // Set up the serial port. this.Port = new SerialPort(portName); this.Port.PinChanged += new SerialPinChangedEventHandler(PinChanged); // Open the port for access. this.Port.Open(); this.Port.DtrEnable = true; this.Port.RtsEnable = true; // Get the timers running. this.BitTimer = new Stopwatch(); this.BitTimer.Start(); this.RepeatTimer = new Stopwatch(); this.RepeatTimer.Start(); } /// <summary> /// Releases the resources used by this <see cref="SircsReceiver"/> instance. /// </summary> public void Dispose() { if (this.Port != null) { this.Port.PinChanged -= new SerialPinChangedEventHandler(PinChanged); this.Port.Dispose(); this.Port = null; } } ~SircsReceiver() { this.Dispose(); } #endregion #region Events /// <summary> /// An event that is fired when a <see cref="SircsCommand"/> is received. /// </summary> public event SircsCommandReceivedEventHandler SircsCommandReceived; /// <summary> /// A method that is invoked when a <see cref="SircsCommand"/> is received. /// </summary> /// <param name="e"></param> protected virtual void OnSircsCommandReceived(SircsCommandReceivedEventArgs e) { if (this.SircsCommandReceived != null) this.SircsCommandReceived(this, e); } #endregion #region SIRCS protocol handling void PinChanged(object sender, SerialPinChangedEventArgs e) { // Respond to changes on the CTS pin. if (e.EventType == SerialPinChange.CtsChanged) { // Quickly grab the current time and current CTS level. long CurrentPinChangedTime = this.BitTimer.ElapsedTicks; bool CurrentLevel = this.Port.CtsHolding; // Calculate the time elapsed. long DeltaTime = CurrentPinChangedTime - this.LastPinChangedTime; double SecondsElapsed = (double)DeltaTime / (double)Stopwatch.Frequency; this.LastPinChangedTime = CurrentPinChangedTime; if (CurrentLevel) { // If the current signal level is high, we may assume that we've just timed a low pulse. // Have we received a start bit? if (SecondsElapsed > SircsReceiver.StartBitMinLength) { this.ReceivingCommand = true; this.BitsReceived = 0; this.Command = 0; } else if (this.ReceivingCommand) { // Process incoming bit. this.Command >>= 1; if (SecondsElapsed > SircsReceiver.DataBitLengthThreshold) { this.Command |= unchecked((uint)(1 << 31)); } // Have we received enough bits? switch (++this.BitsReceived) { case 12: case 15: case 20: // We've received enough bits to handle the input as a received command. // Check to see if there's any more data forthcoming. long EndTime = CurrentPinChangedTime + (long)(Stopwatch.Frequency * SircsReceiver.IntraBitMaxLength); while (BitTimer.ElapsedTicks < EndTime) { if (!(CurrentLevel = this.Port.CtsHolding)) break; } // The input is still high - there's no more data coming in; we've received a command. if (CurrentLevel) { // Construct a struct to hold information about the recieved data. SircsCommand ReceivedCommand = new SircsCommand( (byte)((this.Command >> (32 - this.BitsReceived)) & 0x7F), (short)(this.Command >> ((32 + 7) - this.BitsReceived)), this.BitsReceived ); // Reset the timer. this.ReceivingCommand = false; this.BitTimer.Reset(); this.BitTimer.Start(); this.LastPinChangedTime = 0; // Calculate the repeat count. // Quickly grab the current time and current CTS level. long RepeatTimeTicks = this.RepeatTimer.ElapsedTicks; this.RepeatTimer.Reset(); this.RepeatTimer.Start(); // Calculate the repeat time elapsed. double RepeatTimeSeconds = (double)RepeatTimeTicks / (double)Stopwatch.Frequency; // Is the command repeating? if (ReceivedCommand == this.LastCommand && RepeatTimeSeconds < SircsReceiver.RepeatCommandMaxLength) { ++this.LastCommandRepeatCount; } else { this.LastCommandRepeatCount = 1; this.LastCommand = ReceivedCommand; } // Fire the event. this.OnSircsCommandReceived(new SircsCommandReceivedEventArgs(ReceivedCommand, this.LastCommandRepeatCount)); } break; } } } else { // If the current signal level is low, we may assume that we've just timed a high pulse. // If a high pulse is too long, cancel any incoming commands. if (SecondsElapsed > SircsReceiver.IntraBitMaxLength) { this.ReceivingCommand = false; this.BitTimer.Reset(); this.BitTimer.Start(); this.LastPinChangedTime = 0; } } } } #endregion } }
Currently, the software reacts to input events by running through a list of scripts, passing the command ID, device ID and command length (in bits) to each until one of them returns zero (ie, success) to indicate that it has processed the button.

Scripts list
The advantage to this method is that the end-user could customise the behaviour of the software to their own liking very easily. For example, here's the PowerDVD.js file from above, which allows me to control PowerDVD from a PlayStation 2 DVD remote control:
// Table of commands. var Commands = [ { Command : 0x00, Device : 0x093A, Length : 20, Shortcut : '1' }, // 1 { Command : 0x01, Device : 0x093A, Length : 20, Shortcut : '2' }, // 2 { Command : 0x02, Device : 0x093A, Length : 20, Shortcut : '3' }, // 3 { Command : 0x03, Device : 0x093A, Length : 20, Shortcut : '4' }, // 4 { Command : 0x04, Device : 0x093A, Length : 20, Shortcut : '5' }, // 5 { Command : 0x05, Device : 0x093A, Length : 20, Shortcut : '6' }, // 6 { Command : 0x06, Device : 0x093A, Length : 20, Shortcut : '7' }, // 7 { Command : 0x07, Device : 0x093A, Length : 20, Shortcut : '8' }, // 8 { Command : 0x08, Device : 0x093A, Length : 20, Shortcut : '9' }, // 9 { Command : 0x09, Device : 0x093A, Length : 20, Shortcut : '0' }, // 0 { Command : 0x0B, Device : 0x093A, Length : 20, Shortcut : '{ENTER}' }, // Enter { Command : 0x0E, Device : 0x093A, Length : 20, Shortcut : '{ESC}' }, // Return { Command : 0x1A, Device : 0x093A, Length : 20, Shortcut : 'lt' }, // Title { Command : 0x2A, Device : 0x093A, Length : 20, Shortcut : 'x' }, // A<->B { Command : 0x28, Device : 0x093A, Length : 20, Shortcut : 'd' }, // Time { Command : 0x2C, Device : 0x093A, Length : 20, Shortcut : '^r' }, // Repeat { Command : 0x30, Device : 0x093A, Length : 20, Shortcut : 'p' }, // Previous { Command : 0x31, Device : 0x093A, Length : 20, Shortcut : 'n' }, // Next { Command : 0x32, Device : 0x093A, Length : 20, Shortcut : '{ENTER}' }, // Play { Command : 0x33, Device : 0x093A, Length : 20, Shortcut : 'b' }, // Scan << { Command : 0x34, Device : 0x093A, Length : 20, Shortcut : 'f' }, // Scan >> { Command : 0x38, Device : 0x093A, Length : 20, Shortcut : 's' }, // Stop { Command : 0x39, Device : 0x093A, Length : 20, Shortcut : ' ' }, // Pause { Command : 0x54, Device : 0x093A, Length : 20, Shortcut : 'z' }, // Display { Command : 0x60, Device : 0x093A, Length : 20, Shortcut : '^b' }, // Slow << { Command : 0x61, Device : 0x093A, Length : 20, Shortcut : 't' }, // Slow >> { Command : 0x63, Device : 0x093A, Length : 20, Shortcut : 'u' }, // Subtitle { Command : 0x64, Device : 0x093A, Length : 20, Shortcut : 'h' }, // Audio { Command : 0x65, Device : 0x093A, Length : 20, Shortcut : 'a' }, // Angle { Command : 0x79, Device : 0x093A, Length : 20, Shortcut : '{UP}' }, // Up { Command : 0x7A, Device : 0x093A, Length : 20, Shortcut : '{DOWN}' }, // Down { Command : 0x7B, Device : 0x093A, Length : 20, Shortcut : '{LEFT}' }, // Left { Command : 0x7C, Device : 0x093A, Length : 20, Shortcut : '{RIGHT}' }, // Right ]; // Search for the matching command. var Command = null; for (var enumerator = new Enumerator(Commands); !enumerator.atEnd(); enumerator.moveNext()) { var TestCommand = enumerator.item(); if (TestCommand.Command == WScript.Arguments(1) && TestCommand.Device == WScript.Arguments(2) && TestCommand.Length == WScript.Arguments(3)) { Command = TestCommand; break; } } // No command. if (!Command) WScript.Quit(1); // Find the PowerDVD process ID. var PowerDvdId = null; var WmiService = GetObject('winmgmts://./root/cimv2'); var Processes = WmiService.ExecQuery('Select ProcessId From Win32_Process Where Name="PowerDVD.exe"'); for (var enumerator = new Enumerator(Processes); !enumerator.atEnd(); enumerator.moveNext()) { PowerDvdId = enumerator.item().ProcessId; break; } // If we haven't found the process ID, quit with an error. if (!PowerDvdId) WScript.Quit(1); // Activate the PowerDVD instance. var WshShell = new ActiveXObject('WScript.Shell'); WshShell.AppActivate(PowerDvdId); // Send the shortcut keys. WshShell.SendKeys(Command.Shortcut); WScript.Quit(0);
Unfortunately, this method has quite a lot of overhead. This becomes a problem when you consider that commands are repeated every 45ms. Currently I avoid the issue by not allowing any keys to repeat, but some keys - such as the volume keys - would need to repeat when held.
I'm unsure as the best path to take. One idea that has crossed my mind would be to set up each remote control you were going to use beforehand (though I suppose I could build up a database of remote controls and bundle them with the software). You could then set whether each key should repeat or not, and attach a meaningful string to each button. This would also allow for more protocols to be supported other than SIRCS, and you could set it up so that the Play button on a Sony remote control generated the string "play" and passed that to the script(s) as well as the Play button on a Panasonic or Toshiba remote control rather than juggling control codes.
Decoding SIRCS commands with a PIC16F84
Sunday, 1st March 2009
Some time ago I was working on a simple Z80-based computer. It has a PS/2 keyboard and mouse port for user input, and these are implemented using a large number of discrete parts - transistor drivers with all manner of supporting latches and buffers. The AT protocol (which the PS/2 keyboard and mouse inherit) is entirely implemented in software by the Z80.
On the one hand this design has a certain purity, but it ties the CPU up every time data is to be transferred. The keyboard sends data when it feels like it, so if you wished to perform some function based on a key press event you'd need to poll the port periodically, assuming that if communications time out there's no key waiting. All this hanging around does nothing good for performance.
As it turns out I found a PIC16F84 in an old school project over the weekend, so downloaded its datasheet and the MPLAB IDE and tried to puzzle it out.
The 16F84 is a pretty venerable microcontroller with a 1K flash memory for program code, 68 bytes of data RAM and 64 bytes of data EEPROM. It can run at up to 10MHz, and is based on a high-performance RISC CPU design. It has 13 digital I/O pins, each of which can be configured individually as either an input or an output. I'm well aware there are far better microcontrollers around these days, but this one was just sitting around doing nothing.
Above is the circuit I constructed to work with the 16F84. The HRM538BB5100 in the top-right is an infrared demodulator and amplifier module; it will output 5V until it receives a 38kHz infrared signal (such as the one emitted by most remote controls) at which point it outputs 0V. By timing the lengths of the IR pulses one could decode a remote control signal, and that's the aim of this project - decode a command from a Sony remote control and display it on the two 7-segment displays. The 10MHz crystal is probably overkill for this simple task, but it's the slowest I had available!
In fact, the 10MHz crystal works out quite neatly. Most instructions execute in one instruction cycle, which is four clock cycles. Four clock cycles at 10MHz is 400nS. The 16F84 has an internal timer that counts up after every instruction cycle and triggers an interrupt when it overflows from 255 back to 0; 400nS*256=102.4µs. If we call that 100µs (close enough for jazz) then it overflows 10 times every millisecond. The SIRCS protocol is based around multiples of 0.6ms, which makes this rate very easy to work with.
; ========================================================================== ; ; Pins: ; ; RB0~RB6: Connected to A~G on the two seven-segment displays. ; ; RB7: Connected via a 220R resistor to cathode of the left display. ; ; Inverted and connected via a 220R resistor to right display's ; ; cathode. ; ; RA0: Connected to the output of the HRM538BB5100. ; ; ========================================================================== ; #include <p16F84.inc> list p=16F84 __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC ; ========================================================================== ; ; Variables ; ; ========================================================================== ; udata IsrW res 1 ; Temporary storage used to preserve state during the IsrStatus res 1 ; interrupt service routine. Display res 1 ; Value shown on 7-segment displays. PulseTimer res 1 ; Counter to time the length of pulses. BitCounter res 1 ; Number of bits being received. Command res 1 ; SIRCS command. ; ========================================================================== ; ; Reset ; ; ========================================================================== ; ResetVector code 0x0000 goto Main ; ========================================================================== ; ; Interrupt Service Routine ; ; ========================================================================== ; ISR code 0x0004 ; Preserve W and STATUS. movwf IsrW swapf STATUS,w movwf IsrStatus ; Update value shown on two 7-segment displays. movfw Display btfsc PORTB,7 swapf Display,w andlw h'F' call Get7SegBits btfss PORTB,7 xorlw b'10000000' movwf PORTB ; Increment pulse timer. incfsz PulseTimer,w movwf PulseTimer ; Acknowledge timer interrupt. bcf INTCON,T0IF ; Restore W and STATUS. swapf IsrStatus,w movwf STATUS swapf IsrW,f swapf IsrW,w retfie ; ========================================================================== ; ; Times the length of a "low" pulse. ; ; ========================================================================== ; ; Out: W - Length of pulse. ; ; ========================================================================== ; TimeLow clrf PulseTimer TimeLow.Wait btfsc PORTA,0 goto TimeLow.GoneHigh incfsz PulseTimer,w goto TimeLow.Wait TimeLow.GoneHigh movfw PulseTimer return ; ========================================================================== ; ; Times the length of a "high" pulse. ; ; ========================================================================== ; ; Out: W - Length of pulse. ; ; ========================================================================== ; TimeHigh clrf PulseTimer TimeHigh.Wait btfss PORTA,0 goto TimeHigh.GoneLow incfsz PulseTimer,w goto TimeHigh.Wait TimeHigh.GoneLow movfw PulseTimer return ; ========================================================================== ; ; Convert a hex nybble (0-F) into a format that can be displayed on a 7-seg ; ; display. ; ; ========================================================================== ; ; In: W. Out: W. ; ; ========================================================================== ; Get7SegBits addwf PCL, f dt b'00111111' ; 0 dt b'00000110' ; 1 dt b'01011011' ; 2 dt b'01001111' ; 3 dt b'01100110' ; 4 dt b'01101101' ; 5 dt b'01111101' ; 6 dt b'00000111' ; 7 dt b'01111111' ; 8 dt b'01101111' ; 9 dt b'01110111' ; A dt b'01111100' ; b dt b'00111001' ; C dt b'01011110' ; d dt b'01111001' ; E dt b'01110001' ; F ; ========================================================================== ; ; Start of the main program. ; ; ========================================================================== ; Main ; Set PORTB to be an output. bsf STATUS,RP0 clrw movwf TRISB bcf STATUS,RP0 ; Configure TMR0. bsf STATUS,RP0 bcf OPTION_REG,T0CS ; Use internal instruction counter. bcf STATUS,RP0 ; Enable TMR0 interrupt. bsf INTCON,T0IE bsf INTCON,GIE clrf Display ; ========================================================================== ; ; Main program loop. ; ; ========================================================================== ; Loop WaitCommand ; Loop around waiting for a low to indicate incoming data. btfsc PORTA,0 goto WaitCommand ; Start bit (2.4mS). call TimeLow ; Check that it's > 2mS long. sublw d'20' btfsc STATUS,C goto WaitCommand ; w<=20 ; Reset the command variable and get ready to read 7 bits. clrf Command movlw d'7' movwf BitCounter ReceiveBit ; Time the pause; should be < 1mS. call TimeHigh sublw d'10' btfss STATUS,C goto WaitCommand ; Time the input bit (0.6ms = low, 1.2ms = high). call TimeLow sublw d'9' ; Shift into the command bit. rrf Command,f decfsz BitCounter,f goto ReceiveBit bsf STATUS,C rrf Command,f comf Command,f movfw Command movwf Display goto Loop ; ========================================================================== ; ; Fin. ; ; ========================================================================== ; end
The final source code is above. I'm not sure how well-written it is, but it works; pointing a Sony remote control at the receiver and pressing a button changes the value shown on the seven-segment display. PICmicro assembly is going to get take a little getting used to; instructions are ordered "backwards" to the Intel order I'm used to (op source,destination instead of the more familiar op destination,source) and as far as I can tell literals default to being interpreted as hexadecimal as opposed to decimal.
With some luck I can now teach the 16F84 the AT protocol and replace a large number of parts on the Z80 computer project with a single IC. It does feel a little like cheating, though!
Expression Evaluation in Z80 Assembly
Tuesday, 24th February 2009
The expression evaluators I've written in the past have been memory hungry and complex. Reading the BBC BASIC ROM user's guide introduced me to the concept of expression evaluation using top-down analysis, which only uses a small amount of constant RAM and the stack.
I took some time out over the weekend to write an expression evaluator in Z80 assembly using this technique. It can take an expression in the form of a NUL-terminated string, like this:
.db "(-8>>2)+ceil(pi())+200E-2**sqrt(abs((~(2&4)>>>(30^sin(rad(90))))-(10>?1)))",0
and produce a single answer (or an error!) in the form of a floating-point number. The source code and some notes can be downloaded here.
I initially wrote a simple evaluator using 32-bit integers. I supported the operations the 8-bit Z80 could do relatively easily (addition, subtraction, shifts and logical operations) and got as far as 32-bit multiplication before deciding to use BBC BASIC's floating-point maths package instead. The downside is that BBC BASIC has to be installed (the program searches for the application and calls its FPP routine).
I'm not sure if the technique used is obvious (I'd never thought of it) but it works well enough and the Z80 code should be easy to follow - someone may find it useful.
Nibbles and Logo
Thursday, 19th February 2009
Work on BBC BASIC has slowed down quite a bit, with only minor features being added. A *FONT command lets you output large or font sized text to the graphics cursor position regardless of the current MODE:

10 MODE 3 20 VDU 5 30 MOVE 0,255 : PRINT "Small" 40 *FONT LARGE 50 MOVE 0,227 : PRINT "Large" 60 VDU 4 70 PRINT TAB(0,3) "Small (VDU 4)"
Another new command is the dangerous *GBUF that can - when used correctly - let you switch the location of the graphics buffer. You can simulate greyscale by quickly flickering between two different images on the LCD, which is where this command may come in use.

Snake/Nibbles is a fun game and an easy one to write, so here's a simple implementation that features variable speeds and mazes. The game runs quickly on a 6MHz TI-83+, which I'm happy with. And yes, I know I'm terrible at it.
One thing I've always been pretty bad at is writing language parsers resulting in poor performance and bugs. I've started writing a primitive Logo interpreter in C# to try and improve my skills in this area. So far it supports a handful of the basic language features and statements:
- print [Hello World] Hello World - make "animals [cat dog sheep] show :animals [cat dog sheep] - make "animals lput "goat :animals show :animals [cat dog sheep goat] - print last :animals goat - repeat 2 [ print "A repeat 2 [ print "B ] ] A B B A B B - show fput [1 2 3] [4 5 6] [[1 2 3] 4 5 6] - [10 9 8] Not sure what to do with [10 9 8]
One issue I've already run into are the parenthesis rules: for example the sum function outside parentheses only allows two arguments, but inside parentheses works until the closing parenthesis:
- print (sum 4 5) 9 - print (sum 4 5 6) 15 - print sum 4 5 9 - print sum 4 5 6 9 Not sure what to do with 6
Extending BBC BASIC
Sunday, 1st February 2009
BBC BASIC may have originated with the 8-bit home computer era, but it's still being updated and its most up-to-date incarnation - BBC BASIC for Windows - has a wealth of features that are unavailable on the Z80 version.
The BBC BASIC graphics API is primarily accessed via the multi-purpose PLOT statement. PLOT is followed by three arguments - the type of graphics operation being carried out followed by an X and a Y coordinate. For example, to draw a line between (20,30) and (100,120) you could do this:
PLOT 4,20,30 : REM Move graphics cursor to (20,30) PLOT 5,100,120 : REM Draw a line to (100,120)
This results in needing to remember a lot of different plot codes (there is a logic to how they are formed but I still need to consult a list of codes from time to time). All implementations of BBC BASIC feature two helper statements to aid the user:
- MOVE x,y (equivalent to PLOT 4,x,y)
- DRAW x,y (equivalent to PLOT 5,x,y)
- CIRCLE x,y,r (equivalent to MOVE x,y : PLOT 145,r,0)
- ELLIPSE x,y,w,h (equivalent to MOVE x,y : PLOT 0,w,0 : PLOT 193,0,h)
- FILL x,y (equivalent to PLOT 133,x,y)
- RECTANGLE FILL x,y,w,h (equivalent to MOVE x,y : PLOT 97,w,h)
Or, so I thought - until I read through the copy of BASIC ROM User Guide that a friend had rescued and sent to me. It has a section on adding statements, which it achieves by using a clever - but simple - trick.When BBC BASIC encounters a statement it doesn't recognise it triggers the Mistake error. On the BBC Micro the error handler is vectored, meaning that it loads the address of the error handling routine from RAM first instead of jumping to a fixed address. This allows the user to override the normal error handler, detect the Mistake condition and try and parse the erroneous statement themselves. If they can't handle the statement either control is passed back to BBC BASIC's usual error handler, otherwise the error condition is cleared and execution continues as normal.
BBC BASIC (Z80) follows the same procedure but with one major difference - the error handler routine is not vectored. Unfortunately, the only practical workaround I can think of is to patch the interpreter's error handler routine directly. Richard Russell somehow managed to add support for additional commands to the Z88 version via a patch that runs from RAM, but I haven't been able to work out how he managed to do that yet.
The first series of additional statements I added were the graphics helper statements listed above, WAIT (which pauses execution for a certain number of centi-seconds) and SWAP which exchanges the contents of two variables. These are all relatively simple statements to implement as they do not affect the state of BBC BASIC in any other way; they perform a single, simple task then exit.One of the more useful additions to more recent versions of BBC BASIC is the WHILE...ENDWHILE loop structure. A limitation of BBC BASIC (Z80)'s statement blocks is that their contents must be executed at least once, hence IF statements must fit on one line, multi-line procedures or functions should be placed at the end of the file after an END statement and REPEAT...UNTIL loops - where the looping conditional is at the end of the block, rather than the start - are provided. If a WHILE condition evaluates to FALSE, control needs to resume at the matching ENDWHILE. This is an interesting technical challenge, as it needs to handle nested WHILE...ENDWHILE stataments when searching through the code to find the terminating ENDWHILE, but appears to work pretty well now.
Another useful recent addition is EXIT (in three variations - EXIT FOR, EXIT REPEAT and EXIT WHILE) which breaks out of a loop structure. This has the same technical challenges as the WHILE...ENDWHILE loop structure (searching for the matching loop terminator) with the additional difficulty of unwinding the stack to the correct position.
By combining WHILE loops and EXIT WHILE you can simulate multi-line IF statement blocks, so
IF <condition> THEN <statements>
becomes
WHILE <condition> <statements> EXIT WHILE:ENDWHILE
These additions are not without their downsides. Most of the statements supported natively by BBC BASIC (Z80) are represented by single-byte tokens, whereas these extensions are stored as ASCII text. This makes them take up more room in the source file and slower to execute (searching for and handling strings is a much more complex operation than searching for bytes). Using them makes your programs incompatible with other versions of BBC BASIC (Z80). I personally feel that these disadvantages are far outweighed by the advantage of easier to read code, however.
To round the entry off, have a fractal.
BBC BASIC for the TI-83+/TI-84+ beta release
Wednesday, 21st January 2009
Work commitments have prevented me from doing much on my own projects recently, but zipping up a few files to get BBC BASIC tested is not a time-consuming process so I've started to release test builds.
The documentation is available online. It's generated by a little tool I hacked together to turn a MediaWiki database into a CHM file.
I've had a few issues with the TI-84+ hardware, such as LCD corruption, difficulty in getting key presses to register and crashes when USB devices are unplugged. I think I've fixed the LCD and key issues by dropping the CPU speed down to 6MHz when the full 15MHz is not required, but am still stumped by the USB issues. Unfortunately documentation on the USB hardware is rather thin on the ground and I don't own a TI-84+ for testing.
The package comes with a few demo programs from the CP/M release and a few I cobbled together myself. Most recently I've tried putting together a few little graphics demos.
There's still a fair amount of work to go on this project (especially optimising - some of the code is extremely inefficient) but it feels nice to have something out there for people to try.
C# emu2413
Thursday, 8th January 2009
This is fairly embarrassing; somebody sent me an email that was flagged as spam which I accidentally deleted. So if you sent me an email and I haven't replied, I'm not deliberately being rude; could you send it again? ![]()
After encountering strange crashes (not .NET exceptions, full out crashes) with emu2413 I decided to port it to straight C# instead from its existing C incarnation (emu2413.h.cs and emu2413.c.cs). Even though the original was macro-heavy it was relatively simple to port, and so there's no dependency on an unmanaged DLL to generate FM sound any more. However, the C# version is significantly slower (Cogwheel now takes about 50% extra CPU time when FM sound is enabled), possibly due to many extraneous method calls that were macros in the original.
However, the emulator still crashes when FM sound is enabled. And I have no idea why, as it only happens in Release mode and outside the IDE. The Debug build works fine inside and outside the IDE, and Release mode works fine when run within the IDE.
Controller input updates to Cogwheel
Monday, 5th January 2009
I hope you all had a good Christmas and New Year period!
I received an Xbox 360 controller for Christmas, so have done a bit of work on Cogwheel to add support for it. (You can download a copy of the latest version 1.0.2.0 with SlimDX here).
The first issue to deal with was the D-pad on the Xbox 360 controller. When treated as a conventional joystick or DirectInput device the D-pad state is returned via the point-of-view (POV) hat. The joystick input source class couldn't raise events generated by the POV hat so support for that had to be added. This now allows other controllers that used the POV hat for slightly bizarre reasons (eg the faceplate buttons on the PlayStation controller when using PPJoy) to work too.
The second issue was the slightly odd way that the Xbox 360's DirectInput driver returns the state of the triggers - as a single axis, with one trigger moving the axis in one direction, the other trigger moving it in the other. You cannot differentiate between both triggers being held and both being released, as both states return 0. To get around this, I've added support for XInput devices, where all buttons and triggers operate independently.
The Xbox 360 controller now shows up twice in the UI - once as an XInput device and again as a conventional joystick. Fortunately, you can check if a device is an XInput device by the presence of IG_ in its device ID. Here's some C# code that can be used to check with a joystick is an XInput device or not.
using System.Globalization; using System.Management; using System.Text.RegularExpressions; namespace CogwheelSlimDX.JoystickInput { /// <summary> /// Provides methods for retrieving the state from a joystick. /// </summary> public class Joystick { /* ... */ /// <summary> /// Gets the vendor identifier of the <see cref="Joystick"/>. /// </summary> public ushort VendorId { get; private set; } /// <summary> /// Gets the product identifier of the <see cref="Joystick"/>. /// </summary> public ushort ProductId { get; private set; } /* ... */ /// <summary> /// Determines whether the device is an XInput device or not. Returns true if it is, false if it isn't. /// </summary> public bool IsXInputDevice { get { var ParseIds = new Regex(@"([VP])ID_([\da-fA-F]{4})"); // Used to grab the VID/PID components from the device ID string. // Iterate over all PNP devices. using (var QueryPnp = new ManagementObjectSearcher(@"\\.\root\cimv2", string.Format("Select * FROM Win32_PNPEntity"), new EnumerationOptions() { BlockSize = 20 })) { foreach (var PnpDevice in QueryPnp.Get()) { // Check if the DeviceId contains the tell-tale "IG_". var DeviceId = (string)PnpDevice.Properties["DeviceID"].Value; if (DeviceId.Contains("IG_")) { // Check the VID/PID components against the joystick's. var Ids = ParseIds.Matches(DeviceId); if (Ids.Count == 2) { ushort? VId = null, PId = null; foreach (Match M in Ids) { ushort Value = ushort.Parse(M.Groups[2].Value, NumberStyles.HexNumber); switch (M.Groups[1].Value) { case "V": VId = Value; break; case "P": PId = Value; break; } } if (VId.HasValue && this.VendorId == VId && PId.HasValue && this.ProductId == PId) return true; } } } } return false; } } /* ... */ } }
When the joysticks are enumerated they are only added to the input manager if they are not XInput devices.
To round up the entry, here's a screenshot of a minesweeper clone I've been working on in BBC BASIC.

You can view/download the code here and it will run in the shareware version of BBC BASIC for Windows. The code has been deliberately uglified (cramming multiple statements onto a single line, few comments, trimmed whitespace) to try and keep it within the shareware version's 8KB limit as this is a good limit to keep in mind for the TI-83+ version too.
Sega Master System emulation in Silverlight
Monday, 15th December 2008
I've had to quickly learn Silverlight for work recently, which has been an interesting experience. I've had to write new code, which is fine but doesn't really excite me as far as Silverlight is concerned - it doesn't really matter which language new code is developed in, as long as it gets the job done.
What does interest me more is that Silverlight is ".NET in your browser", and I'm a big fan of .NET technology with a handful of .NET-based projects under my belt. Silverlight therefore gives me the opportunity to run these projects within the browser, which is a fun idea. ![]()
To this end, I've turned Cogwheel, a Sega 8-bit system emulator, into a Silverlight application. It took about an hour and a half, which was not as bad as I'd expected! (Skip to the bottom for instructions for the demo).
Raster graphics
Silverlight's raster graphics support is somewhat lacking. You can display raster graphics in Image elements, but - as far as I can see - that's about it. If you wish to generate and display images dynamically via primitive pixel-pushing, you're out of luck as far as Silverlight's class library is concerned.
Thankfully, Ian Griffiths has developed a class named PngGenerator that can speedily encode a PNG from an array of Colors that can then be displayed in an Image. Cogwheel's rasteriser returns pixel data as an array of integers so there's a small amount of overhead to convert these but other than that it's easy to push pixels, albeit in a fairly roundabout manner.
Render loop
The render loop is based around an empty Storyboard that invokes an Action every time it completes then restarts itself.
using System; using System.Windows; using System.Windows.Media.Animation; namespace Cogwheel.Silverlight { public static class RenderLoop { public static void AttachRenderLoop(this FrameworkElement c, Action update) { var Board = new Storyboard(); c.Resources.Add("RenderLoop", Board); Board.Completed += (sender, e) => { if (update != null) update(); Board.Begin(); }; Board.Begin(); } public static void DetachRenderLoop(this FrameworkElement c) { var Board = (Storyboard)c.Resources["RenderLoop"]; Board.Stop(); c.Resources.Remove("RenderLoop"); } } }
I'm not sure if this is the best way to do it, but it works well enough and is easy to use - just grab any FrameworkElement (in my case the Page UserControl) and call AttachRenderLoop:
private void UserControl_Loaded(object sender, RoutedEventArgs e) { this.UserControlRoot.AttachRenderLoop(() => { /* Update/render loop in here. */ }); }
Missing .NET framework class library features
This is the big one; Silverlight does not cover the entire .NET framework class library, and so bits of it are missing. Fortunately this can be resolved, the difficulty depending on how you want the functionality of the original app to be affected.
Missing types you're not interested in.
These are the easiest to deal with, and this includes attributes and interfaces that the existing code uses that you're not especially interested in. For example, Cogwheel uses some of .NET's serialisation features for save states - a feature I wasn't intending on implementing in the Silverlight version. The [Serializable] and [NonSerialized] attributes are not available in Silverlight, nor is the IDeserializationCallback interface. To get the project to compile some dummy types were created.
namespace System { class SerializableAttribute : Attribute { } class NonSerializedAttribute : Attribute { } interface IDeserializationCallback { void OnDeserialization(object sender); } }
Missing types or methods that you don't mind partially losing.
Cogwheel features some zip file handling code that uses System.IO.Compression.DeflateStream, a class not available in Silverlight. Rather than remove the zip classes entirely (which would require modifications to other files that relied on them) it was easier to use conditional compilation to skip over the DeflateStream where required.
switch (this.Method) { case CompressionMethod.Store: CompressingStream = CompressedStream; break; #if !SILVERLIGHT case CompressionMethod.Deflate: CompressingStream = new DeflateStream(CompressedStream, CompressionMode.Compress, true); break; #endif default: throw new NotSupportedException(); }
Missing instance methods.
C# 3.0 adds support for extension methods - user-defined methods that can be used to extend the functionality of existing classes that you cannot modify directly. Silverlight is missing a number of instance methods on certain classes, such as string.ToLowerInvariant();. By using extension methods the missing methods can be restored.
namespace System { public static class Extensions { public static string ToLowerInvariant(this string s) { return s.ToLower(CultureInfo.InvariantCulture); } public static string ToUpperInvariant(this string s) { return s.ToUpper(CultureInfo.InvariantCulture); } } }
Missing static methods.
These are the most work to fix as extension methods only work on instance methods, not static methods. This requires a change at the place the method is called as well as the code for the method itself.
I've got around this by creating new static classes with Ex appended to the name then using using to alias the types. For example, Silverlight lacks the Array.ConvertAll method.
namespace System { static class ArrayEx { public static TOut[] ConvertAll<TIn, TOut>(TIn[] input, Func<TIn, TOut> fn) { TOut[] result = new TOut[input.Length]; for (int i = 0; i < input.Length; i++) { result[i] = fn(input[i]); } return result; } } }
First, a replacement method is written with Ex appended to the class name. Secondly, any file that contains a reference to the method has this added to the top:
#if SILVERLIGHT using ArrayEx = System.ArrayEx; #else using System.IO.Compression; using ArrayEx = System.Array; #endif
Finally, anywhere in the code that calls Array.ConvertAll is modified to call ArrayEx.ConvertAll instead. When compiling for Silverlight it calls the new routine, otherwise it calls the regular Array.Convert.
Demo
The links below launch the emulator with the selected ROM image.
To run your own ROM image, click on the folder image in the bottom-right corner of the browser window to bring up a standard open file dialog.
Zip files are not handled correctly, but if you type *.* into the filename box, right-click a zip file, pick Open, then select the ROM from inside that it should work (it does on Vista at any rate).
The cursor keys act as you'd expect; Ctrl or Z is button 1/Start; Alt, Shift or X is 2 (Alt brings up the menu in IE). Space is Pause if you use an SMS ROM and Start if you use a Game Gear ROM. Keys don't work at all in Opera for some reason, but they should work fine in IE 8 and Firefox 3. You may need to click on the application first!
Issues
There are a number of issues I have yet to address. Performance is an obvious one; it's a little choppy even with 100% usage of one the cores on a Core 2 Duo. Sound is missing, and I'm not sure what Opera's doing with keys. Other than that, I thought it was a fun experiment.
Once I've tidied it up a bit I'll merge the source code with the existing source repository.




