;;; Firmware for a Wake-on-LAN module for the Hauppauge Nova-T PCI 90002 card. ;;; ;;; Copyright 2005,2006 Marko Mäkelä (marko.makela (at) iki (dot) fi). ;;; ;;; This program is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2 of the License, or ;;; (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ;;; ;;; The bundled remote control of the Hauppauge DVB-T tuner card ;;; generates Philips RC5 codes. The backplate of the card has a ;;; 2.5 mm stereo jack for connecting a infrared receiver module. The ;;; module has three contacts: power, ground and signal output (active ;;; low). Although the radio frequency amplifier for the pass-through ;;; antenna connector on the tuner card is always powered on, the ;;; infrared receiver module is not. Thus, an extra module will be ;;; needed in order to be able to power on the computer from the ;;; remote control. ;;; ;;; Why power on the computer from the remote control, you might ask. ;;; First, the computer might be installed in a hard-to-reach place or in a ;;; different room to reduce noise. ;;; Second, you might want to disable audio and video output and decoding ;;; when the computer is doing timed recordings. This requires the ability ;;; to distinguish the way how the computer has been powered on. As this ;;; information is not easily available from the motherboard, this module ;;; will try to provide the information. When the computer is started up ;;; by the remote control, this module will hold the infrared input of ;;; the tuner card low until another key is pressed on the remote control. ;;; It is up to the software on the computer to detect this condition. ;;; Other than that, this module is completely independent of the firmware ;;; and software running on the computer. ;;; ;;; The RC5 decoding algorithm is a state machine with ;;; interrupt-driven transitions. The state machine, based on the ;;; diagram at http://www.clearwater.com.au/rc5/, will measure the ;;; cycle time (T, nominally 16/9 ms) from the first start bit and ;;; detect two signal durations, short (T/2, or 1/4*T..3/4*T) and long ;;; (T, or 3/4*T..5/4*T). Durations outside the allowed range will ;;; reset the decoder state machine to the idle state, as will signal ;;; transitions not depicted in the state machine. ;;; ;;; The connected states are as follows: ;;; idle <-> measure <-> start1 <-> mid1 <-> mid0 <-> start0 ;;; From each state, there are error transitions to the idle state. ;;; ;;; The non-error transitions are as follows. ;;; ;;; idle -| measure (start timer to measure half cycle width, T/2) ;;; measure =| start1 (store the measured cycle width, set bitcnt = 13) ;;; start1 => mid1 (output '1' bit, decrement bitcnt) ;;; mid1 -> start1 ;;; mid1 ==> mid0 (output '1' bit, decrement bitcnt) ;;; mid0 --> mid1 (output '0' bit, decrement bitcnt) ;;; mid0 => start0 ;;; start0 -> mid0 (output '0' bit, decrement bitcnt) ;;; ;;; The accepting (final) states are start1 and mid0 (when bitcnt = 0). ;;; ;;; Legend: ;;; <-> connected states ;;; -| high-to-low transition ;;; =| low-to-high transition ;;; ==> long high pulse ;;; --> long low pulse ;;; => short high pulse ;;; -> short low pulse ;;; ;;; The microcontroller is connected as follows: ;;; ___ ___ ;;; +5VSB ---- 500k ---- RESET 1. \/ 8 VCC +5VSB (from Wake-on-LAN cable) ;;; Wake out PB3 2 AT 7 PB2 +5V (from DVB-T tuner) ;;; Wake in/not connected PB4 3 tiny 6 PB1 IR in (from receiver module) ;;; GND 4 12 5 PB0 IR out (to DVB-T tuner) ;;; -------- ;;; +5VSB ---|<--- +5V ;;; ;;; You may omit the pull-up resistor if you disable the RESET signal ;;; (and low-voltage serial programming of the firmware). ;;; ;;; You may omit the diode between +5VSB and +5V if you always have ;;; the Wake-on-LAN cable connected, or you wish to rely on the built-in ;;; protection diodes inside the ATtiny12. ;;; ;;; The connectors are as follows. ;;; 2.5 mm stereo jack (female): tip = +5VSB, middle = IR in, body = ground ;;; 2.5 mm stereo plug (male): tip = +5V, middle = IR out, body = ground ;;; ;;; For in-circuit programming, you may want to build a cable with a ;;; stereo jack, a stereo plug, a DB-25P connector for an IBM PC style ;;; parallel port, and a +5V power supply, using some common layout. ;;; You will need to connect the RESET line with a separate wire ;;; between the DB-25P connector and the ATtiny12. ;;; ;;; The microcontroller is wired as follows on the daughter board ;;; for the Hauppauge Nova-T PCI 90002: ;;; ___ ___ ;;; 3.3Vaux ---- 500k ---- RESET 1. \/ 8 VCC +3.3Vaux (PCI pin A14) ;;; 3.3V (e.g., PCI pin A21) PB3 2 AT 7 PB2 PME# (PCI pin A19) ;;; no connection PB4 3 tiny 6 PB1 IR in (from receiver module) ;;; GND 4 12 5 PB0 IR out (to DVB-T tuner) ;;; -------- ;;; ;;; See the schematic diagram and circuit board layout for details. ;;; Compared to circuit version 1, which was connected to the 2.5 mm ;;; stereo jack of the Hauppauge Nova-T PCI 90002, this version (version 2) ;;; has the pins PB2 and PB3 swapped. Thus, this version can be programmed ;;; while being connected to the Hauppauge card, provided that the ;;; in-circuit programmer is compatible with 3.3-volt logic. ;;; ;;; Before connecting the programming adapter (such as the STK200 dongle), ;;; be sure to disconnect the infrared receiver module. ;;; Circuit version: ;;; 1 = cable with Wake-on-LAN in/out ;;; 2 = PCI daughter board with PME# out .equ VERSION, 2 .equ SREG, 0x3f .equ GIMSK, 0x3b .equ GIFR, 0x3a .equ TIMSK, 0x39 .equ TIFR, 0x38 .equ MCUCR, 0x35 .equ MCUSR, 0x34 .equ TCCR0, 0x33 .equ TCNT0, 0x32 .equ OSCCAL, 0x31 .equ WDTCR, 0x21 .equ EEAR, 0x1e .equ EEDR, 0x1d .equ EECR, 0x1c .equ PORTB, 0x18 .equ DDRB, 0x17 .equ PINB, 0x16 .equ ACSR, 0x08 .set zero, 0 ; a register that is always zero .set timer, 1 ; timer counter value at start of interrupt .set cycleQ, 2 ; quarter cycle (1/4*T, minimum signal time) .set cycleH, 3 ; half cycle (1/2*T) .set irhi, 4 ; RC5 data register, high part .set irlo, 5 ; RC5 data register, low part .set irlasthi, 6 ; last decoded RC5 data, high part .set irlastlo, 7 ; last decoded RC5 data, low part .set temp, 16 ; temporary register .set bitcnt, 17 ; bit count .set irmode, 18 ; RC5 decoder state: mode .equ RC5IDLE, 0 ; decoder idle: await low pulse .equ RC5MEAS, 1 ; measure 1st low pulse width ;; The numeric values of the following modes must be in ;; ascending sequence, and RC5ST0 must be divisible by 4. .equ RC5ST0, 4 ; start of '0' .equ RC5ST1, 5 ; start of '1' (accepting state) .equ RC5MI1, 6 ; middle of '1' .equ RC5MI0, 7 ; middle of '0' (accepting state) .set pins, 19 ; last detected pin state .set pmode, 20 ; power mode .equ POWER_OFF, 0 ; computer powered off (awaiting power key) .equ POWER_START, 1 ; starting up (awaiting other than power key) .equ POWER_ON, 2 ; computer powered on (echo the pulses) ;; I/O mapping .ifeq VERSION - 1 .equ PIN_WOL_IN, 4 ; Wake-on-LAN input .equ PIN_WOL_OUT, 3 ; Wake-on-LAN output ('1' starts computer) .equ PIN_POWER, 2 ; +5V input ('1' when the computer has power) .endif .ifeq VERSION - 2 .equ PIN_POWER, 3 ; +3.3V input ('1' when the computer has power) .equ PIN_PME, 2 ; Power Management Event ('0' starts computer) .endif .equ PIN_IR_IN, 1 ; Infrared input .equ PIN_IR_OUT, 0 ; Infrared output .text rjmp reset ; Reset vector nop ; INT0 rjmp pci ; Pin change rjmp ovf0 ; Timer 0 overflow ; rjmp error ; EEPROM ; rjmp error ; Analog Comparator ;; Pin Change Interrupt pci: mov temp, pins ; determine which pins changed in pins, PINB eor temp, pins ; which pins did change? sbrc temp, PIN_IR_IN rjmp pci_ir ; the infrared input changed sbrc temp, PIN_POWER rjmp pci_power ; got or lost power: reset sbrs pins, PIN_POWER ; ignore wake-on-LAN if powered on .ifeq VERSION - 1 rcall wol_echo ; echo the wake-on-LAN signal .endif reti ; ignore changes in other pins ;; The infrared input changed. pci_ir: in timer, TCNT0 ; read the timer out TCNT0, zero ; zero the timer cpi pmode, POWER_ON breq ir_echo ldi temp, 3 out TCCR0, temp ; start Timer/Counter 0 at 0.6..1.2 MHz/64 sbrs pins, PIN_POWER ldi pmode, POWER_OFF ; lost power: switch to POWER_OFF mode cpi pmode, POWER_START breq ir_decode ; starting up: ignore power-on events sbrc pins, PIN_POWER rjmp pci_power_on ; got power: reset .ifeq VERSION - 1 rcall wol_echo ; echo the wake-on-LAN signal .endif ir_decode: ; enter the RC5 state machine cpi irmode, RC5MEAS breq ir_measure brsh ir_data ;; in idle mode, awaiting falling edge of infrared signal sbrc pins, PIN_IR_IN rjmp ir_fail ; got a rising edge: remain idle ;; measure the width of the low pulse ldi irmode, RC5MEAS reti ir_measure: ; measure T/2 from the first start bit sbrs pins, PIN_IR_IN brne ir_fail ; got a falling edge: error => go idle mov temp, timer cpi temp, 21 ; is the pulse wider than brsh ir_fail ; 1.1 ms @ 1.2 MHz or 2.2 ms @ 0.6 MHz? cpi temp, 4 ; is the pulse shorter than brlo ir_fail ; 0.4 ms @ 1.2 MHz or 0.8 ms @ 0.6 MHz? mov cycleQ, timer ; T/2 mov cycleH, timer ; T/2 lsr cycleQ ; T/4 ldi irmode, RC5ST1 ; got a '1' bit clr irlo ; clear the unused bits of RC5 data ldi bitcnt, 13 ; receive the 2nd start bit and 12 data bits reti ir_data: ; got a data symbol sub timer, cycleQ ; ..1/4*T brlo ir_fail ; too short pulse: error => go idle sub timer, cycleH brlo ir_short ; 1/4*T..3/4*T cp timer, cycleH brsh ir_fail ; too wide pulse: error => go idle ;; got long pulse (3/4*T..5/4*T) cpi irmode, RC5MI1 brlo ir_fail ; expected short pulse ldi temp, 1 ; RC5MI1 <-> RC5MI0 ir_bit: bst irmode, 0 ; decoded one bit rol irlo rol irhi bld irlo, 0 ; copy the bit from the RC5 mode label eor irmode, temp ; switch mode, wait for next transition dec bitcnt breq ir_lastbit ; got last bit brpl ir_mid rjmp ir_fail ; too many bits (bitcnt < 0) ;; echo the infrared signal ir_echo: sbrs pins, PIN_IR_IN cbi PORTB, PIN_IR_OUT ; echo '0' signal sbrc pins, PIN_IR_IN sbi PORTB, PIN_IR_OUT ; echo '1' signal sbrs pins, PIN_POWER rjmp power_off ; lost power: reset ;; ignore PIN_WOL_IN while powered on reti ir_lastbit: ; got last bit of RC5 code cpi irmode, RC5ST1 breq ir_done cpi irmode, RC5MI0 brne ir_mid ir_done: ; got the whole code word ;; got a new keypress? cpse irhi, irlasthi rjmp ir_newkey cpse irlo, irlastlo rjmp ir_newkey rjmp main ; ignore the repeated keypress ;;; initialization reset: ldi temp, 0x80 out ACSR, temp ; disable the analog comparator (saves power) ldi temp, 0x60 out MCUCR, temp ; disable pull-ups, enable sleep ldi temp, 0x20 out GIMSK, temp ; enable Pin Change Interrupt clr zero power_off: ; lost power ldi pmode, POWER_OFF ; switch to POWER_OFF mode (wait for Power key) clr irlasthi ; forget the last received keycode clr irlastlo ir_idle: ir_fail: ovf0: ; timer overflow: switch to idle mode in pins, PINB ; remember the state of the pins ldi temp, 1 << PIN_IR_OUT out DDRB, temp ; disable all outputs except infrared main: out PORTB, zero ; PIN_IR_OUT='0' sbrs pins, PIN_POWER ldi pmode, POWER_OFF ; lost power: switch to POWER_OFF mode cpi pmode, POWER_START breq ir_reset ; starting up: ignore power-on events sbrc pins, PIN_POWER rjmp pci_power_on ; got power ;; reset the RC5 decoder state machine ir_reset: ldi irmode, RC5IDLE ; switch to idle mode out TCCR0, zero ; stop Timer/Counter 0 ldi temp, 2 out TIFR, temp ; clear Timer/Counter 0 overflow interrupt out TIMSK, temp ; enable Timer/Counter 0 overflow interrupt sei ; enable interrupts ;; idle loop sleep rjmp .-4 ir_short: ;; got short pulse (1/4*T..3/4*T) ldi temp, 3 ; RC5ST0 <-> RC5MI0, RC5ST1 <-> RC5MI1 cpi irmode, RC5ST1 ; read bit from status sbrs irmode, 1 ; if in RC5MI0 or RC5MI1, do nothing rjmp ir_bit ; got bit (was in RC5ST0 or RC5ST1) eor irmode, temp ; switch mode, wait for next transition tst bitcnt breq ir_lastbit ; got last half of the last bit? ir_mid: reti ir_newkey: ; got new (non-repeated) keypress mov irlasthi, irhi ; remember the keypress mov irlastlo, irlo mov temp, irhi andi temp, ~8 ; remove the toggle bit cpi temp, 0x17 ; got 2nd start bit? got system bits 111xx? brne ir_notpower mov temp, irlo cpi temp, 0xbd ; got system bits xxx10 and command 111101? brne ir_notpower ;; The Power key was pressed. cpi pmode, POWER_START ; is the computer already starting up? breq pci_power ; yes, start echoing pulses ;; Generate wake-on-LAN signal (150 ms) ldi temp, 5 out TCCR0, temp ; start Timer/Counter 0 at 0.6..1.2 MHz/1024 out TCNT0, zero .ifeq VERSION - 1 sbi PORTB, PIN_WOL_OUT ; output a positive Wake-on-LAN signal sbi DDRB, PIN_WOL_OUT .endif .ifeq VERSION - 2 cbi PORTB, PIN_PME ; generate a power management event sbi DDRB, PIN_PME .endif wait150ms: ; for simplicity, let us busy-wait here in temp, TCNT0 cpi temp, 176 ; 150 ms @ 1.2 MHz or 300 ms @ 0.6 MHz sbis PINB, PIN_POWER ; wait until powered on brlo wait150ms ldi pmode, POWER_START .ifeq VERSION - 1 cbi DDRB, PIN_WOL_OUT ; release the Wake-on-LAN signal .endif .ifeq VERSION - 2 cbi DDRB, PIN_PME ; release the Power Management Event signal .endif ; (this would be done at ir_idle anyway, ; but we do not want to drive the signal ; during the following delay loop.) ;; Wait 10 seconds for the computer to start up. ;; During the power-on self test (POST), the BIOS may ;; switch off the power of PCI slots. We want to ignore ;; such events. ldi temp, 46 wait10s: ; 10.05 s @ 1.2 MHz or 20.10 s @ 0.6 MHz out TCNT0, zero ; clear Timer/Counter 0 wait218ms: in timer, TCNT0 inc timer brne wait218ms ; 218 ms @ 1.2 MHz or 435 ms @ 0.6 MHz dec temp brne wait10s rjmp ir_idle ; release the Wake-on-LAN signal ;; Some other key than Power was pressed ;; Start echoing pulses (enter POWER_ON mode) ;; when the first keypress after the Power key has arrived. ir_notpower: cpi pmode, POWER_OFF breq main ; in POWER_OFF mode, ignore the key ;; fall through ;; The computer was powered on or off. pci_power: sbrs pins, PIN_POWER rjmp power_off ; lost power: reset ;; The computer was powered on by something else than ;; the remote control, or another key was pressed after ;; Power. Set PIN_IR_OUT='1' to indicate this. pci_power_on: ldi temp, 1 << PIN_IR_OUT out DDRB, temp ; disable all outputs except infrared out PORTB, temp ; PIN_IR_OUT='1' ldi pmode, POWER_ON ; power on: echo the infrared pulses clr irlasthi ; forget the last received keycode clr irlastlo rjmp ir_reset ; reset the infrared decoder .ifeq VERSION - 1 wol_echo: ; subroutine: echo wake-on-LAN pulse sbrs pins, PIN_WOL_IN rjmp wol_release ; falling edge: release the output sbi PORTB, PIN_WOL_OUT ; output a positive Wake-on-LAN signal sbi DDRB, PIN_WOL_OUT ret wol_release: cbi DDRB, PIN_WOL_OUT ; release the Wake-on-LAN signal cbi PORTB, PIN_WOL_OUT ret .endif ; Local variables: ; compile-command: "avr-as worc5.s && objcopy -O ihex a.out worc5.hex && cisp -c dt006 /dev/parport0 -e -l worc5.hex -v worc5.hex" ; End: