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. smile.gif

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:

Mixed-Fonts.gif
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.

2009.02.11.01.gif

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. wink.gif

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]

(No, no turtle graphics yet wink.gif). There's no support for infix operators yet. The BBC BASIC ROM manual describes the top-down parsing method it uses to evaluate expressions so I'm going to attempt to reimplement that.

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

I'm not sure whether a "this statement was preceded by an opening parenthesis" flag would be sufficient.

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)

More recent versions, such as BBC BASIC for Windows, also implements the following helper statements, amongst others:

  • 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)

This is all very well, but the BBC BASIC (Z80) interpreter is a sealed box as far as I am concerned. I can ask it to perform tasks for me ("evaluate this expression") and it can ask me to perform tasks for it ("output this character to the display") but I can't modify its behaviour.

BASIC ROM User Guide for the BBC Microcomputer and Acorn Electron coverOr, 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.

Demonstration of ELLIPSE FILLThe 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. smile.gif

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. smile.gif

Page 18 of 53 114 15 16 17 18 19 20 21 2253

Older postsNewer postsLatest posts RSSSearchBrowse by dateIndexTags