Projektrapport för Mikrodatorteknik HT02


 

En projektrapport på kursen Mikrodatorteknik, 5 p, ht 2002, vid Fysikum, Lunds universitet.

Location: http://www.df.lth.se.orbin.se/~mikaelb/micro/mbproj.html

Datum: 27/12 2002
Copyright © 2002 by Mikael O. Bonnier <
mikael.bonnier@gmail.com>, Lund, Sweden.
Skicka gärna synpunkter och rättelser.

 

Projektrapport för Mikrodatorteknik HT02

I denna rapport beskrivs ett bygg- och programmeringsprojekt för enchipsdatorn/mikrokontrollern PIC16C74A, en 8-bitars mikroprocessor från företaget Microchip. Tack vare att PIC:arna (Peripheral Interface Controller) är RISC-datorer (Reduced Instruction Set Computer) med Harward-arkitektur är de snabba trots att klockfrekvensen i detta projekt endast är 4 MHz. Jag skrev programmet i assemblerspråket MPASM och använde MPLAB IDE (Integrated Development Environment) som utvecklingsmiljö. Det finns även utvecklingsmiljöer för programspråket C. En använd PIC16C74A måste raderas med UV-ljus i 20 minuter innan man kan bränna ner programmet med en EPROM-programmerare som t.ex. PICSTART Plus. Det finns PIC:ar med FLASH-minne som kan omprogrammeras direkt. MPLAB och datablad kan hämtas gratis från http://www.microchip.com/. Eftersom PIC:arna är så populära så finns det många intressanta byggprojekt att hämta på Internet; t.ex. har jag hittat implementationer av I2C, USB, IrDA och andra intressanta protokoll. Följande projekt är dock helt egenutvecklat.

 

Problembeskrivning

Problemet är lite konstgjort och är mest uttänkt för att vara lärorikt. Ingen har egentligen något behov av exakt denna produkt men kanske av något liknande. När jag konstruerade problemet så utgick jag ifrån vilka komponenter jag hade och vad som kunde bli en imponerande demonstration. Tänk dig en vanlig CPU-fläkt som styrs av en fjärrkontroll för en stereo. Man kan öka och minska varvtalet och dessutom visas antalet varv per sekund binärt med lysdioder.

Ursprungligen var det även tänkt att varvtalet skulle regleras exakt, men p.g.a. problem med störningar orsakade av metoden med vilken fläktens varvtal styrs (PWM) så har denna extrauppgift lagts ner. Jag har förövrigt provat alla sätt som föreslagits mig att minska störningarna, men inget fungerar speciellt bra. Som en liten ledtråd om vad man skulle kunna använda regleringen till nämner jag att fläkten har sju blad och låter man den snurra med 63 varv/sekund blir detta 441 Hz – nära ettstrukna A. Man skulle kunna lysa med en laserstråle genom fläkten och få denna hackad med denna och andra bestämda frekvenser: användbart för optiska experiment eller musiktillämpningar.

 

Framtida projekt

Man skulle kunna koppla in en termistor på en analog ingång och på så sätt få en fläkt som även är temperaturstyrd. Vidare skulle man kunna lägga till en serieport (inbyggd i de flesta PIC:ar) och på så sätt få en fläkt som styrs av en persondator och som även skulle kunna skicka tillbaka information om varvtal och temperatur till persondatorn. Ett alternativ till att styra och övervaka med persondator vore att i stället göra detta med en grafritande miniräknare, t.ex. TI-82, TI-83 eller TI-89. Detta skulle kräva ett man implementerar miniräknarens kommunikationsprotokoll.

 

Handhavandeinstruktion

I och med att man kopplar spänning till kretsen, så startar fläkten på 90 % av maximalt varvtal och en lysdiod (B7) blinkar med en period på 1 s och en duty-cycle på 50 % vilken visar att kretsen är i läge on. Varvtalet visas binärt på lysdioderna B0 – B7. Lysdiod B7 har alltså dubbla roller, men eftersom man oftast kör fläkten i mindre än 128 varv/s (7680 varv/min) så gör inte detta så mycket. Med Power-knappen på fjärrkontrollen kan man slå av och på fläkten. Läge off visas genom att lysdioden (B7) blinkar med en duty-cycle på 10 %. Med nuvarande version av programmet så har fläkten sju olika varvtal som väljs genom att trycka på Volume+ och Volume- på fjärrkontrollen. Varvtalen är numrerade från 0 till 6 och är på 0 %, 75 %, 80 %, 85 %, 90 %, 95 % och 100 % av maximal effekt. Varvtalet vid power-on har alltså nummer 4. Anledningen till valet av effekter (PWM duty-cycles) är att störningarna från fläkten är mindre vid höga varvtal.

En speciell finess är att lysdioderna blinkar till kort när ett kommando från fjärrkontrollen har mottagits: B6=Power, B5=Volume+, B4=Volume-, B3=Upprepning (d.v.s. en knapp har hållits nedtryckt länge – används inte), B2=Okänt kommando (d.v.s. någon annan knapp än de som används i detta system har tryckts ned), B1=Okänd fjärrkontroll (händer oftast vid störningar).

 

Teknisk specifikation

Här beskriver jag vilka krav som ställs på komponenterna i detta projekt och vilka signaler de avger och tar emot.

 

CPU-fläkt

CPU-fläkten som man kan använda i detta projekt är en standardfläkt för kylning av processorn i en persondator. Fläkten bör ha 3 sladdar: jord (svart), spänning (röd) och varvtal (gul). Utgången för varvtal är av open collector-typ, och alltså måste man koppla ett pull-up-motstånd på ca 11 kW från 5 V för att få en normal logisk utgång. Varvtalsutgången ger ut en fyrkantsvåg med en frekvens som är dubbla varvtalet, alltså 2 pulser för varje varv. Jag vet inte närmare vilken teknik som används för att ursprungligen generera pulserna, men varvpulserna kan genereras utan spänningstillförsel genom att snabbt rotera fläkten vilket enklast ordnas genom att blåsa kraftigt genom fläkten. Alltför låga varvtal ger inte upphov till pulser.

Fläkten jag råkade använda var en Evercool EC6025H12C som medföljde en AMD Duron 800 MHz CPU. Den blev över då jag bytte ut den mot en tystare temperaturstyrd fläkt. Evercoolen har egenskaperna 12 V, 0.2 A, 2.4 W.

Jag gjorde en mätning över antalet varv per sekund som funktion av tillförd effekt till fläkten enligt PWM-styrningen. P.g.a. störningarna kunde jag inte mäta varvtalet säkert med PIC:en utan fick använda ett gammalt oscilloskop. PIC:ens mätvärden bör dock betraktas som noggrannare eftersom den drevs av en mycket noggrann kristalloscillator. Man kan i alla fall konstatera att varvtalet varierar mindre än vad tillförd effekt varierar och jag förklarar detta med att friktionen, främst p.g.a. luftmotståndet, ökar med ökande varvtal.

Nummer

 PWM/Effekt

 PIC RPS

 OSC RPS

6

100 %

83 (100 %)

89.3 (100 %)

5

95 %

 

87.7 (98 %)

4

90 %

 

83.3 (93 %)

3

85 %

 

80.6 (90 %)

2

80 %

 

78.1 (87 %)

1

75 %

 

74.6 (84 %)

0

0 %

0 (0 %)

0 (0 %)

PWM (Pulsviddsmodulering) är ett sätt att omvandla från ett digitalt tal i mikrokontrollern till en analog effekt. Man slår på och av spänningen till den yttre utrustningen och kan variera effekten genom att variera den tid som spänningen är på. Man slår på spänningen med en fix frekvens, i vårt fall 4 kHz, men varierar tiden innan man slår av den linjärt beroende på vilken uteffekt man vill ha. I vårt fall är perioden 250 µs, och vill man t.ex. ha en effekt på 80 % så skall man slå av spänningen efter 200 µs, d.v.s. efter 80 % av periodtiden. Man har då en PWM-signal med en duty-cycle på 80 %. Problemet är att en fläktmotor innehåller spolar och att slå på och av en spänning på 12 V genom dessa ger upphov till självinduktion och kraftiga störningar. Helst hade jag velat driva fläkten med en stabil likspänning.

 

Effekttransistor

I projektet använder jag en komponent som klarar att driva en induktiv last och som har en logisk ingång. Det är alltså ingen enkel analog effekttransistor utan en MOSFET-transistor för krävande miljöer med inbyggd driv- och skyddselektronik. Transistorn är ESD-, temperatur- och överströmsskyddad och är försedd med en clamp-diod mellan drain och source. Typen är IPS021 (Intelligent Power Switch) av märket International Rectifier. Jag inser nu att man enklare hade kunnat bli av med störningarna om man hade valt en analog fälteffekttransistor och gjort om styrsignalen till en stabil likspänning. Troligen hade en PowerMOS-transistor fungerat utmärkt. Med denna störningsfria lösning förutser jag ett problem med att få den slutgiltiga effekten som en linjär funktion av PWM duty-cyclen.

 

IR-sensor

I kretsen används en avstämd IR-sensor med inbyggd förstärkare och komparator. Den reagerar endast på IR-ljus på ca 940 nm som är modulerat med en frekvens omkring 38 kHz med 50 % duty-cycle och går då låg – normalt ligger utgången hög. Räckvidden är ca 8 m med en standardsändare i en standardomgivning. IR-sensorn är av märket Everlight, typ IRM-8601S eller IRM-8608S. I projektet använde jag inte den i databladet rekommenderade kopplingen med resistor och kondensator. Möjligen hade denna förhindrat en del störningar.

 

Fjärrkontroll

Fjärrkontrollen hör till en mikrostereo av märket Samsung, typ MM-26. På Samsungs hemsida finns en application note för deras mikrokontroller KS57 som handlar om mottagning och sändning av IR-signaler. Jag antar att de använder samma modulationsfrekvens och ett liknande protokoll i stereofjärrkontrollen som i application notet. I dokumentet är modulationsfrekvensen 38 kHz.

Jag undersökte även protokollet med ett digitalt oscilloskop. När man trycker på en knapp skickas ett pulståg på ca 60 ms och det sänds så vitt jag vet endast en gång. Fortsätter man att hålla nere knappen sänds ett kortare pulståg på 9 ms upprepade gånger. Startvillkoret består av en signal på 3.3 ms. Sedan följer långa och korta pauser med en signal på 0.4 ms emellan. En lång paus + signal är 1.8 ms och en kort paus + signal är 0.5 ms. En lång paus + signal tolkar jag som en 1:a och en kort paus + signal som en 0:a.

Jag skrev ett program (mbirrx) som tog emot pulståget för varje knapp och skrev ut det med hexkod på en LCD. Koden är E1654010XXXX02 för varje knapp och upprepningskoden är 03.

Knapp

XXXX-kod

Power

0010

Mute

0450

3Eq

6518

Sleep

5618

Function

6070

Check

5428

Timer On/Off

5268

Clock

1228

Pre/Man

1448

R. Play

0548

Stop

0328

F. Play

4430

FM-Mode

6408

Count

0548

Reverse Mode

5538

Synchro

3138

Band

6408

Rec

0768

Prog/Memo

2470

Volume+

0230

Tun/Pre Down

2648

Tun/Pre Up

2208

Volume-

0670

CD Stop

5048

CD Play/Pause

4270

Skip/Search R

7258

Skip/Search F

7638

CD Repeat

3218





Kopplingsschema

Här visas kopplingsschema för kretsen som kopplas till ett modifierat Microchip PICDEM 2-kort. På PICDEM 2-kortet måste man löda av benet på R19 närmast RC2/CCP1. Lysdioderna B0 – B7 finns på PICDEM 2-kortet, och där finns även anslutning för LCD.

Naturligtvis behöver man inte använda PICDEM 2 utan man kan bygga en stand-alone version av projektet. Komponentlista för spänningsreglering: U1=7805 (12.00 kr), C1=10 µF, C2=10 µF. Keramisk resonator med integrerade kondensatorer: XT1 inkl. 2 st. Cosc = Murata CST4,00MGW (8.70 kr). Lysdioder: B0 – B7 = 8 st. LED med passande resistornät.

  

Guide till programmet

I denna sektion förmedlas kunskaper som gör det lättare att förstå programmet.

 

Varvräknare

MainLoop tar 10 ms p.g.a. att subrutinen LoopTime väntar tills 10 ms har gått sedan den anropades senast. LoopTime utnyttjar då att ett Timer2-avbrott räknar ned en räknare, när räknaren slår över så har det gått 10 ms och subrutinen LoopTime avslutas. MainLoop anropar Blink som anropar MeasureFq var 50:e gång, d.v.s var 500:e ms. Timer0 är konfigurerad att räkna pulser på ingången RA4. I MeasureFq flyttas innehållet i Timer0 till W och RPSL, därefter nollställs timern och RPSH. RPSH räknas upp i Timer0:s overflow-avbrott, men eftersom fläkten skulle behöva rotera så snabbt som 15300 varv/min så används inte RPSH i detta system. Anledningen till att Timer0 innehåller varv/s trots att dessa endast räknas under en halv sekund är att fläkten ger två pulser per varv.

 

Power-indikering

I subrutinen Blink så togglas BLNKSTATUS på och av var 500:e ms. För att indikera läge off med 10 % duty-cycle så krävdes lite extra kod. Om BLNKSTATUS precis gått på och PWR är 0 så skall Power-indikatorn släckas redan efter 100 ms. Detta åstadkoms genom att sätta en flagga TURNOFF. Så länge TURNOFF är satt så testas i BlinkEnd om det gått 100 ms, är så fallet nollställs TURNOFF och Power-indikatorn släcks genom att skriva RPSL till PORTB.

 

PWM-reglering

I avsnittet om CPU-fläkten så förklaras PWM i allmänhet och i detta avsnitt förklaras hur det implementeras i PIC:en. PWM-regleringens utsignal på PIC:ens ben RC2/CCP1 är kopplad till effekttransistorn.

Timer2 används både för att generera avbrott för varje 2 ms och för att generera en PWM-period på 250 µs, motsvarande en frekvens på 4 kHz. Prescalern till Timer2 delar den interna oscillatorn på 1 MHz med 1 och postscalern delar komparatorsignalerna med 8. Komparatorn jämför TMR2 med PR2 som innehåller 249; vid likhet nollställs TMR2, postscalern räknas upp samt PWM-perioden börjar om. Detta innebär att komparatorn triggar var 250:e µs. För var åttonde uppräkning av prescalern så genererar den ett Timer2-avbrott, d.v.s. var 2:a ms. PWM-signalens duty-cycle beror på att signalen slås av efter en bestämd tid. Duty-cyclen har en 10-bitars upplösning eftersom de 8 bitarna från TMR2 utvidgas med 2-bitarsdelaren av den yttre klocksignalen, Q-counter. Denna 10-bitars räknare räknas upp från 0 till 999 med hastigheten 4 MHz och jämförs med 10-bitarstalet som utgörs av CCPR1L, CCP1X och CCP1Y. Detta motiverar initieringen av T2CON och den krångliga subrutinen UpdatePWM.

Duty-cyclerna lagras i en tabell, LookupPWM2. Tabellen hanteras av subrutinen LookupPWM som fyller PWMH och PWML med olika värden beroende på DutyCPtr, vilken har det aktuella varvtalsnumret: 0 – 6.

 

IR-mottagning från fjärrkontroll

I avsnittet om fjärrkontrollen så förklaras vilka pulståg den skickar. IR-sensorn är inkopplad på RC1/CCP2 på PIC:en. Timer1, vilken räknar med full hastighet, 1 MHz, används för tidtagning av händelser på CCP2. Mottagningen av ett pulståg från fjärrkontrollen befinner sig i olika tillstånd: WaitnStart (waiting for start condition), RcvStart (receiving start condition) och RcvBit (receiving bit). Först konfigureras CCP2 för att vänta på startvillkoret, d.v.s. på signal från IR-sensorn. När den kommer övergår man till att vänta på paus och tar tiden tills detta inträffar. Om tiden var kortare än 2 ms betraktas det som en störning och man börjar om från början, annars börjar man mäta tiden för första biten, vilken består av en paus (hög) följt av en signal (låg). Biten är färdigmottagen när benet CCP2 går hög nästa gång. Om mottagningstiden för biten var kortare än 1.5 ms så var biten en 0:a annars en 1:a. När pulståget är mottaget inträffar inte fler förändringar av CCP2 och efter ett tag inträffar en timeout beroende på att Timer1, vid starten av varje tidmätning, konfigureras att generera ett avbrott efter 6 ms. Befann sig pulstågsmottagningen i tillståndet RcvBit så sätts en flagga som gör att de mottagna bitarna tolkas av subrutinen UpdateLCD i nästa MainLoop-varv. Subrutinen kallas så av historiska skäl, men fortfarande skriver subrutinen ut debuginformation på en LCD om man skulle ha en sådan ansluten.

De mottagna bitarna sparas i RXBUF – 8 bitar i varje byte, och först mottagna bit är mest signifikant. När en byte är full fortsätter programmet att fylla nästa och så vidare. Detekteringen av att en byte är full sker genom att man laddar byten med 1 och när denna skiftas ut i Carry så vet man att byten är full. Indirekt adressering med hjälp av FSR (File Select Register) och INDF används.

Ett problem med denna mottagningsmetod är att de koder för fjärrkontrollen jag angett kanske inte stämmer med de som tillverkaren anger. Det beror dels på att startvillkoret inte bara består av en signal utan troligen även av den efterföljande pausen, dels på att metoden att förladda med 1 innebär att om inte pulståget består av ett helt antal bytes så kommer denna 1:a att finnas med i den sist mottagna byten. Detta spelar dock ingen roll för mottagningen och det går att översätta koder på ett entydigt sätt mellan systemen.

Vid Capture, d.v.s. vid slutet av viss signal eller paus, så sparas uppmätt tid i CCPR2H och CCPR2L, dessutom inträffar ett Capture2-avbrott. Denna tillämpning ställer inte så höga krav på tidmätning och därför används endast CCPR2H. Rent tekniskt hanteras de olika tillstånden, som pulstågsmottagningen kan befinna sig i när ett Capture-avbrott inträffar, av en hopptabell, JmpOnIRState, vilken hoppar till olika subrutiner beroende på IRState.

Detta system för att ta emot och tolka IR-pulståg kan lätt anpassas till andra fjärrkontroller genom att endast ändra tidskonstanterna och kommandokoderna. Dock måste man ta hänsyn till att vissa fjärrkontroller skickar flera identiska pulståg för en knapptryckning. Då skall man endast utföra kommandot om pulståget avkodas på samma sätt alla gångerna.

 

Programmet

;Project on "Mikrodatorteknik HT02"
;Version: 1.0beta2
;Copyright (C) 2002 by Mikael Bonnier, Lund, Sweden.
;E-mail: mikael.bonnier@gmail.com
;Web: http://www.df.lth.se.orbin.se/~mikaelb/
;
;The program recieves and outputs remote control commands on a display. 
;The commands affects a PWM output, which controls a power transistor, 
;which controls power to a CPU fan. The power causes the fan to rotate with 
;a speed proportional to the power. The fan RPS is counted and output in
;binary on LEDs.
;
;Features: 
;1. When the program is running a LED blinks with 1 Hz, 50 % duty cycle. 
;2. When a command is accepted a LED blinks briefly. A different
;   LED for each command.
;3. When "off" the running LED blinks with a 10 % duty cycle.
;
;Bugs & Limitations:
;1. The fan RPS is incorrect at low speeds due to the fan or interference
;   from PWM output frequency.
;2. Necessary to modify PICDEM-2 board by removing resistor R19 leg closest
;   to RC2.
;3. Works only with a Samsung stereo remote control.
;
;Todo:
;1. Implement regulated fan speeds i.e. choose RPS and fan speed approaches it.
;
;Commands:
;Power: toggles power to fan on or off
;Volume+: Increases speed one step
;Volume-: Decreases speed one step

	list P=16C74A, F=INHX8M, C=160, N=80, ST=OFF, MM=OFF, R=DEC
	title "mbproj1.asm"
	include <P16C74A.INC>
	__config (_CP_OFF & _PWRTE_OFF & _XT_OSC & _WDT_OFF & _BODEN_OFF)
	errorlevel -302	;Ignore "error" when storing to bank 1

;;;;;;;	Equates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Freq		equ	4		;Crystal frequency in MHz (4, 10, or 20)
Bank0RAM 	equ	H'20'		;Start of Bank 0 RAM area
MaxCount 	equ	D'50'		;50 Number of loops in half a second 
DQ		equ	0		;Display queue, bit 0 of FLAGS
PWR		equ	1		;Power on, bit 1 of FLAGS
TURNOFF		equ	2		;Turn off LED, bit 2 of FLAGS
BLNKSTATUS	equ	3		;BLNKSTATUS, bit 3 of FLAGS

;IR receiver
CapFEdge	equ	B'00000100'	;Capture falling edge
CapREdge	equ	B'00000101'	;Capture rising edge
WaitnStart	equ	0		;IRState value
RcvStart	equ	1		;IRState value
RcvBit		equ	2		;IRState value
Tmr1Z		equ	D'256'-D'23'	;Timer1 interrupt in 6 ms
MinStart	equ	7		;2 ms (start = 3.3 ms)
MaxLen0		equ	6		;1.54 ms (bit0 = 0.88 ms, bit1 = 2.2 ms)
;;;

;PWM
NODutyC 	equ	D'7'		;No of elements in PWM table
;;;

;LCD
Inner		equ	D'255'		;255 loopcount in 15ms delay
Outer		equ	D'20'		;98 loopcount in 15ms delay, 20 Mhz
RWHI		equ	B'00000100' 	;Set R/W high 
LCD8B		equ	B'00110000' 	;LCD in 8bit mode
DISPON		equ	B'00001100' 	;Display on
CL_HOME		equ	B'00000001' 	;Clear, go home
ADR_INC		equ	B'00000110' 	;Increment adress m. 
;;;

;;;;;;; Macros ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

writeLowNibble	macro
	andlw	H'0F'
        addlw	6
	btfsc   STATUS,DC
	addlw	A'A'-A'0'-H'A'
	addlw	A'0'-6	
	call	Write
	endm

wrReg	macro	register
	swapf	register,W
	writeLowNibble
	movf	register,W
	writeLowNibble
	endm

NEqGoto	macro	byte,address
	movlw	byte
	xorwf	INDF,W
	btfss	STATUS,Z
	goto	address
	endm

EqGoto	macro	byte,address
	movlw	byte
	xorwf	INDF,W
	btfsc	STATUS,Z
	goto	address
	endm

LCDData	macro
	bsf	PORTA,1		;Data RS=1, command RS=0
	endm

LCDCmd	macro
	bcf	PORTA,1		;Data RS=1, command RS=0
	endm

;;;;;;; Variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	cblock	Bank0RAM
	W_TEMP		;Temporary storage for W during interrupts
	STATUS_TEMP	;Temporary storage for STATUS during interrupts
	SCALER		;Scale-of-5 scaler used by LoopTime subroutine
	BLNKCNT
	FLAGS
	RPSH
	RPSL
	TDELAY0		;counter in delay loop
	TDELAY1		;counter in delay loop
	PWMH
	PWML
	DutyCPtr
	IRState
	RXENDBUF
	RXBUF: 10
	endc

;;;;;;;	Vectors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	org	H'000'		;Reset vector
	goto	Mainline	;Branch past tables
	org	H'004'		;Interrupt vector
IntVec
	goto	IntService	;Branch to interrupt service routine	

;;;;;;;	Tables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

JmpOnIRState
	movf	IRState,W
	addwf	PCL,F
	goto	WaitForStart
	goto	StartRcvd
	goto	BitRcvd

LookupPWM2
	addwf	PCL,F		;DutyCPtr*2 in W
				;0
	retlw	H'00'		;PWMH 0/1000 = 0 %
	retlw	H'00'		;PWML
				;1
	retlw	H'02'		;PWMH 750/1000 = 75 %
	retlw	H'EE'		;PWML
				;2
	retlw	H'03'		;PWMH 800/1000 = 80 %
	retlw	H'20'		;PWML
				;3
	retlw	H'03'		;PWMH 850/1000 = 85 %
	retlw	H'52'		;PWML
				;4
	retlw	H'03'		;PWMH 900/1000 = 90 %
	retlw	H'84'		;PWML
				;5
	retlw	H'03'		;PWMH 950/1000 = 95 %
	retlw	H'B6'		;PWML
				;6
	retlw	H'03'		;PWMH 1000/1000 = 100 %
	retlw	H'E8'		;PWML

;;;;;;; Mainline program ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Mainline
	call	Initial		;Initialize everything

MainLoop
	call	Blink
	btfsc	FLAGS,DQ
	call	UpdateLCD
	call	LoopTime	;Force loop time to be ten milliseconds	
	goto	MainLoop

;;;;;;;	Initial subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine performs all initializations of variables and registers.

Initial				;Initialize PIC and LCD.
	;;; Assert Freq=4 MHz ;;;
	;;; Assert bank 0 ;;;
	movlw	B'00111100'	;Postscaler /8, TMR2ON=1, Prescaler /1
	movwf	T2CON
	clrf	CCPR1L		;0 % duty cycle
	clrf	CCPR1H
	movlw	B'00001100'	;PWM mode
	movwf	CCP1CON
	call	FalseStart
	movlw	MaxCount	;Initialize first blink of LED
	movwf	BLNKCNT		; to last about 0.50 seconds
	clrf	FLAGS
	movlw	4		;Set up Timer2 subroutine
	movwf	SCALER
	clrf 	PORTB
	clrf	PORTC

	;;; LCD init ;;;
	clrf	PORTA	
	clrf	PORTD
	;;; -------- ;;;

	bsf	STATUS,RP0	;Set register access to bank 1
	;;; Assert bank 1 ;;;
	movlw	D'249'
	movwf	PR2		;PWM freq = 4 kHz
	bsf	TRISC,1		;Set up RC1/CCP2 as input
	bcf	TRISC,2		;Set up RC2/CCP1 as output
	clrf 	TRISB      	;Set up all bits of PORTB as outputs
	bsf	OPTION_REG,T0CS	;Use RA4/T0CKI to count CPU-fan rps
	bsf	OPTION_REG,PSA	;Presc to WDT, i.e. TMR0 without presc
	bsf	PIE1,TMR2IE	;Enable interrupt path
	bsf	PIE1,TMR1IE	;Enable interrupt path
	bsf	PIE2,CCP2IE	;Enable interrupt path

	;;; LCD init ;;;
	clrf	TRISD		;Make PORTA and PORTD outputs 
	clrf	TRISA
	movlw	B'11111111'		
	movwf	ADCON1		;If PORTA is not in ADC mode
				; power up digital outputs.
	bcf	STATUS,RP0	;Set register access back to bank 0
	;;; Assert bank 0 ;;;
	call	Time15		;Be sure LCD module ready
	movlw	RWHI		;Sets R/W high 
	movwf	PORTA
	movlw	LCD8B		;Operating 8 bit mode.
	call 	Write
	movlw	LCD8B		;Do it again
	call 	Write
	movlw	DISPON		;Display on
	call 	Write
	movlw	CL_HOME		;Clear display, goto home pos
	call 	Write
	movlw	ADR_INC		;Automatic increment of address
	call 	Write
	LCDData
	movlw	A'H'
	call	Write
	movlw	A'I'
	call	Write
	;;; -------- ;;;
	call	Power
	bcf	PIR2,CCP2IF	;Clear interrupt flag (for safety)

	bsf	INTCON,T0IE	;Enable Timer0 interrupt source
	bsf	INTCON,PEIE	;Enable Timer2 interrupt source
	bsf	INTCON,GIE	;Enable global interrupts
	return

;;;;;;; PWM subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; 

LookupPWM
	bcf	STATUS,C
	rlf	DutyCPtr,W
	call	LookupPWM2
	movwf	PWMH
	rlf	DutyCPtr,W
	addlw	1
	call	LookupPWM2
	movwf	PWML
	return

UpdatePWM
	wrReg	DutyCPtr
	wrReg	PWMH
	wrReg	PWML

	rrf	PWMH,F		;Rotate bit 8
	rrf	PWML,F		; into PWML[7]
	rrf	PWMH,F		;Rotate bit 0 into PWMH[7]
	rrf	PWML,F		; and bit 9 into PWML[7]
	rrf	PWMH,F		; and bits 1,0 into PWMH[7:6]
				;Upper 8 bits are now in PWML
				;Lower 2 bits are in PWMH[7:6]
	rrf	PWMH,F		;Move bits 1,0 to align with CCP2CON
	rrf	PWMH,W		; and move to W
	xorwf	CCP1CON,W	;Toggle if CCP2X:CCP2Y differ
	andlw	B'00110000'	;Force other bits to zero
	xorwf	CCP1CON,F	;Change bits that differ
	movf	PWML,W		;Move upper eight bits
	movwf	CCPR1L
	return			;Fourteen cycles

;;;;;;; Blink subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine toggles a LED every 0.5 second.

Blink 	
	decfsz	BLNKCNT,F	;Decrement loop counter and return if not zero
	goto	BlinkEnd
	movlw	MaxCount	;Reinitialize BLNKCNT
	movwf	BLNKCNT
	movlw	1<<BLNKSTATUS
	xorwf	FLAGS,F
	call	MeasureFq	;Called each 0.5 s
	btfsc	FLAGS,BLNKSTATUS
	iorlw	1<<7
	movwf	PORTB	
	btfsc	FLAGS,PWR
	goto	BlinkEnd
	btfsc	FLAGS,BLNKSTATUS
	bsf	FLAGS,TURNOFF
BlinkEnd	
	btfss	FLAGS,TURNOFF
	return
	movlw	MaxCount-10
	xorwf	BLNKCNT,W
	btfss	STATUS,Z
	return
	bcf	FLAGS,TURNOFF
	movf	RPSL,W
	movwf	PORTB
	return
		

;;;;;;; MeasFreq subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;

MeasureFq
	movf	TMR0,W		;Fan gives 2 pulses per rotation
	movwf	RPSL
	clrf	TMR0
	clrf	RPSH
	return

;;;;;;; LoopTime subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine first waits, for however long is necessary, for Timer2 and 
; the Timer2 subroutine in IntService to indicate that ten milliseconds have
; passed since the last time that they indicated this.

LoopTime
	btfss	SCALER,7	;Wait for 00000000 to 11111111 change
	goto	LoopTime
	movlw	5		;Add 5 to SCALER
	addwf	SCALER,F
	return

;;;;;;; LCD subroutines ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;

UpdateLCD
	bcf	FLAGS,DQ
	LCDCmd
	movlw	CL_HOME		;clear display, goto home pos
	call 	Write
	LCDData
	incf	FSR,W
	movwf	RXENDBUF
	movlw	RXBUF
	movwf	FSR
ChkPattern
	NEqGoto	H'E1',ChkRepeat
	incf	FSR,F
	NEqGoto	H'65',UnknRemote
	incf	FSR,F
	NEqGoto	H'40',UnknRemote
	incf	FSR,F
	NEqGoto	H'10',UnknRemote
	incf	FSR,F
	;Cmd byte 1 here
	incf	FSR,F
	;Cmd byte 2 here
	incf	FSR,F
	NEqGoto	H'02',UnknRemote
WhichCmd
	movlw	RXBUF+4
	movwf	FSR
	NEqGoto	H'00',Next1
	incf	FSR,F
	EqGoto	H'10',Power
Next1
	btfss	FLAGS,PWR
	goto	FalseStart	;When power is off, skip other commands.
	movlw	RXBUF+4
	movwf	FSR
	NEqGoto	H'02',Next2
	incf	FSR,F
	EqGoto	H'30',VolPlus
Next2
	movlw	RXBUF+4
	movwf	FSR
	NEqGoto	H'06',Next3
	incf	FSR,F
	EqGoto	H'70',VolMinus
Next3
UnknCmd
	movlw	A'C'
	call	Write
	bsf	PORTB,2
	goto	FalseStart
UnknRemote
	movlw	A'U'
	call	Write
	bsf	PORTB,1
	call	LookupPWM
	call	UpdatePWM	
	goto	FalseStart
ChkRepeat
	movlw	H'03'
	xorwf	INDF,W
	btfss	STATUS,Z
	goto	UnknRemote
Repeat
	movlw	A'R'
	call	Write
	bsf	PORTB,3
	call	LookupPWM
	call	UpdatePWM	
	goto	FalseStart
Power
	movlw	A'P'
	call	Write
	bsf	PORTB,6
	movlw	(NODutyC>>1) + 1
	movwf	DutyCPtr
	btfsc	FLAGS,PWR
	clrf	DutyCPtr
	movlw	1<<PWR
	xorwf	FLAGS,F
	call	LookupPWM
	call	UpdatePWM	
	goto	FalseStart
VolPlus
	movlw	A'+'
	call	Write
	bsf	PORTB,5
	movlw	NODutyC-1	;Increase DutyCPtr but within 0..NODutyC-1
	xorwf	DutyCPtr,W
	btfss	STATUS,Z
	incf	DutyCPtr,F
	call	LookupPWM
	call	UpdatePWM	
	goto	FalseStart
VolMinus
	movlw	A'-'
	call	Write
	bsf	PORTB,4
	movf	DutyCPtr,F	;Decrease DutyCPtr but within 0..NODutyC-1
	btfss	STATUS,Z
	decf	DutyCPtr,F
	call	LookupPWM
	call	UpdatePWM	
	goto	FalseStart

Write	;pulses controlling the LCD data and instruction comm.
	movwf	PORTD		;Sets the levels of d0-d7
	bcf	PORTA,2		;Set R/W low, i.e. write to LCD
	bsf	PORTA,3		;Set enable high
	bcf	PORTA,3		;Set enable low, i.e. 
				; now d0-d7 are captured by LCD.
	bsf	PORTA,2		;Set R/W hi for next write seq.
	call	Time15		;Wait until talking to lcd next time.
	return

Time15				; 15 millisecs (appr) wait 
	movlw	Outer
	movwf	TDELAY0
Time150
	movlw	Inner
	movwf	TDELAY1
Time151
	decfsz	TDELAY1, F	
	goto	Time151
	decfsz	TDELAY0, F
	goto	Time150
	return

;;;;;;; IntService interrupt service routine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This interrupt service routine fields all interrupts.  It first sets aside W
; and STATUS.  It assumes that direct addressing will not be used in the 
; mainline code to access Bank 1 addresses (once the Initial subroutine has 
; been executed and interrupts enabled).  It polls each possible interrupt
; source to determine whether it needs service.

IntService
; Set aside W and STATUS
	movwf	W_TEMP		;Copy W to RAM
	swapf	STATUS,W	;Move STATUS to W without affecting Z bit
	movwf	STATUS_TEMP	;Copy to RAM (with nibbles swapped)

; Execute polling routine
	;;; Assert bank0 ;;;
	btfsc	PIR2,CCP2IF	;Check if interrupt from IRRX
	call	Capture2
	btfsc	PIR1,TMR1IF	;Check if Timer1 timout
	call	Timer1
	btfsc	INTCON,T0IF	;Check if RPS-meter Timer0 overflow
	call	Timer0		;If more than 255 rps
	btfsc	PIR1,TMR2IF	;Check if Timer2 timeout
	call	Timer2		;Happens every 10 ms
;	btfsc	 ...		;Check another interrupt source
;	call	 ...		;If ready, service it

; Restore STATUS and W and return from interrupt
	swapf	STATUS_TEMP,W	;Restore STATUS bits (unswapping nibbles)
	movwf	STATUS		; without affecting Z bit
	swapf	W_TEMP,F	;Swap W_TEMP
	swapf	W_TEMP,W	; and swap again into W without affecting Z bit

	retfie			;Return to mainline code; reenable interrupts

;;;;;;; Timer0 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Timer0
	bcf	INTCON,T0IF	;Clear interrupt flag (Bank 0)
	incf	RPSH,F
	return
	
;;;;;;; Timer2 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine, called from within IntService, clears the Timer2 flag, 
; decrements SCALER for use by LoopTime subroutine in the mainline code, and 
; returns.

Timer2
	bcf	PIR1,TMR2IF	;Clear interrupt flag (Bank 0)
	decf	SCALER,F
	return

;;;;;;; Capture2 subroutine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This subroutine, called from within IntService, clears the Capture1 flag.

Capture2
	call	JmpOnIRState	
	bcf	PIR2,CCP2IF	;Clear interrupt flag (Bank 0)
	return

WaitForStart
	movlw	RcvStart
	movwf	IRState
TriggOnHi
	clrf	CCP2CON		;Turn CCP module off
	clrf	TMR1L
	movlw	Tmr1Z
	movwf	TMR1H
	bsf	T1CON,TMR1ON
	movlw	CapREdge
	movwf	CCP2CON
	return
StartRcvd
	bcf	T1CON,TMR1ON
	movf	CCPR2H,W
	sublw	MinStart+Tmr1Z
	btfsc	STATUS,C	;C=0 if borrow
	goto	FalseStart
TrueStart
	movlw	RcvBit		;CCPR2H > MinStart
	movwf	IRState
	call	RstQueue
	goto	TriggOnHi
FalseStart			;CCPR2H <= MinStart
	movlw	WaitnStart
	movwf	IRState
TriggOnLo
	clrf	CCP2CON		;Turn CCP module off
	movlw	CapFEdge
	movwf	CCP2CON
	return
BitRcvd
	movlw	RcvBit
	movwf	IRState
	movlw	MaxLen0+Tmr1Z
	subwf	CCPR2H,W	;Result of CCPR2H >= MaxLen0 in the Carry bit
	call	Enqueue
	goto	TriggOnHi

RstQueue
	movlw	RXBUF
	movwf	FSR
	goto	PrepByte

Enqueue				;Received bit in Carry bit
	rlf	INDF,F
	btfss	STATUS,C
	return
	incf	FSR,F
PrepByte
	movlw	B'00000001'
	movwf	INDF
	return

Timer1				;Timeout
	bcf	T1CON,TMR1ON	;Shut off Timer 1
	bcf	PIR1,TMR1IF	;Clear interrupt flag
	movf	IRState,W
	xorlw	RcvBit		;Set Z if IRState == RcvBit
	btfss	STATUS,Z
	goto	FalseStart	;If IRState != RcvBit goto FalseStart else 
	clrf	CCP2CON		;Turn CCP module off
	bcf	PIR2,CCP2IF	;Clear interrupt flag (for safety)
	bsf	FLAGS,DQ	;Display Queue in MainLoop
	return

	end

Litteratur


Copyright © 2002 by Mikael O. Bonnier, Lund, Sweden. All rights reserved.