|
En projektrapport på kursen Mikrodatorteknik, 5 p, ht 2002, vid Fysikum, Lunds universitet. Location: http://www.df.lth.se/~mikaelb/micro/mbproj.html Datum: 27/12 2002 |
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.
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.
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.
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).
Här beskriver jag vilka krav som ställs på komponenterna i detta projekt och vilka signaler de avger och tar emot.
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.
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.
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ä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.
|
|
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.
I denna sektion förmedlas kunskaper som gör det lättare att förstå programmet.
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.
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.
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.
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.
;Project on "Mikrodatorteknik HT02"
;Version: 1.0beta2
;Copyright (C) 2002 by Mikael Bonnier, Lund, Sweden.
;E-mail: mikaelb@df.lth.se
;Web: http://www.df.lth.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