Jump to content
  • Advertisement
  • entries
    222
  • comments
    606
  • views
    593031

Decoding SIRCS commands with a PIC16F84

Sign in to follow this  
benryves

1665 views

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.


Click to toggle labels

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.4us. If we call that 100us (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

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!
Sign in to follow this  


4 Comments


Recommended Comments

Hi Your Z80 project looks great + it's good to see someone working on the Z80.
I am posting this comment because you have said recently that although you are using an AT keyboard you would like to get it working properly with your Z80 computer.
I also had one working (more or less) for a while with a PIC chip on my Z80 Interak computer - but I have recently been through the code carefully and made it as simple and efficient as possible. It now works very well and is reliable.
This is the link to the code and hex files
www.interak.pwp.blueyonder.co.uk/PCtoASCII.htm

I hope you will find it useful, Alan
(or type 'interak' into google and it is usually the top site)

Share this comment


Link to comment
Thank you very much for the information and source code. That's a very impressive project and I look forwards to reading the documentation on the website! [smile]

My intention was for the PIC to merely handle the AT byte transfer protocol and leave the Z80 to do the scancode to ASCII translation, though having the PIC do that as well is appealing; BBC BASIC needs to routinely poll for the Escape key being pressed and having the check for the Escape key outsourced to the PIC (where it could notify the Z80 via an interrupt) could speed things up considerably.

Share this comment


Link to comment
It is funny that you are saying that using uC feels a bit like cheating. I am doing some hobby electronics also and had same feeling few times. It is indeed wery tempting to use microcontroller for things like that. On the other hand there is always question in back of a head - hmm, should i use such a powerful controller for some relatively simple thing, or not. Because given some problem it is wery often possible to trow more micros at it as a solution. For example, there is not enought outputs in one uC so do i add more of them, or think of some clever schematic to expand outputs by other means.
For me it is not always clear when to use an uC and when make schematic from discrete parts.

Share this comment


Link to comment
Part of my notion of "cheating" may be related to my A Level electronics course where the emphasis was more on discrete logic. In a similar vein to the use of a PIC to handle keyboard input, I managed to get away with one PIC that would scan a numeric keypad and output the current number pressed as a four-bit value (or %1111 for no key).

I just took a look at your website, good stuff there! [smile]

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!